# How `serverPubKey` Eliminates MITM Vulnerabilities in MySQL RSA Authentication

> Learn how go-sql-driver/mysql serverPubKey parameter prevents MITM attacks by using a pre-registered RSA public key for secure password encryption.

- Repository: [Go SQL Drivers/mysql](https://github.com/go-sql-driver/mysql)
- Tags: security-best-practices
- Published: 2026-03-02

---

**The `serverPubKey` DSN parameter in go-sql-driver/mysql prevents man-in-the-middle attacks during password encryption by allowing the client to use a pre-registered RSA public key instead of requesting one from the server.**

The go-sql-driver/mysql package encrypts passwords using RSA public key cryptography when authenticating with `sha256_password` or `caching_sha2_password` plugins. By default, the driver requests the server's public key over the network, creating a vulnerability window where an attacker could substitute a malicious key. The `serverPubKey` parameter closes this gap by enabling out-of-band key verification that bypasses the network request entirely.

## The MITM Risk of Default RSA Key Exchange

When the driver does not know the server's public key in advance, it follows a default RSA key exchange flow that exposes a man-in-the-middle (MITM) attack vector. In [`auth.go`](https://github.com/go-sql-driver/mysql/blob/main/auth.go), the encryption process follows three steps:

1. The client sends a request for the server's public key.
2. The server replies with a PEM-encoded RSA key.
3. The driver encrypts the password using `rsa.EncryptOAEP` (via the `encryptPassword` helper).

This round-trip is functional but creates a critical vulnerability: an attacker who can intercept the packet could supply a malicious RSA key. If the client encrypts the password using the attacker's key, the attacker can decrypt it and compromise the credentials.

## How `serverPubKey` Secures the Authentication Channel

The driver provides a safer alternative through the **`serverPubKey`** DSN parameter, which eliminates the MITM window by removing the network request entirely. Instead of fetching the key from the server, the driver uses a key that the client has verified and registered out-of-band.

### Registering Trusted Keys in the Driver

Before connecting, applications register their trusted RSA public keys using `RegisterServerPubKey`. According to the implementation in [`auth.go`](https://github.com/go-sql-driver/mysql/blob/main/auth.go) (lines 31-39), this function stores the key in the `serverPubKeyRegistry`:

```go
func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
    serverPubKeyRegistry.Lock()
    defer serverPubKeyRegistry.Unlock()
    serverPubKeyRegistry.keys[name] = pubKey
}

```

The registry is protected by a `sync.RWMutex`, making it safe for concurrent access across multiple goroutines. To retrieve a key, the driver uses `getServerPubKey` (lines 78-85 in [`auth.go`](https://github.com/go-sql-driver/mysql/blob/main/auth.go)), which acquires a read-lock before accessing the map.

### DSN Parsing and Key Resolution

When a DSN contains `serverPubKey=<name>`, the driver resolves this name during configuration initialization. In [`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go) (lines 23-30), the parser extracts the value and stores it in `cfg.ServerPubKey`. During the `normalize()` method (lines 23-28), the driver calls `getServerPubKey(cfg.ServerPubKey)` to fetch the actual RSA key and assigns it to `cfg.pubKey`:

```go
if cfg.ServerPubKey != "" {
    cfg.pubKey = getServerPubKey(cfg.ServerPubKey)
}

```

This resolution happens before any network connection is established, ensuring the key is available immediately during authentication.

### Bypassing the Network Request in Authentication

During authentication, both supported plugins check for the presence of `cfg.pubKey` before requesting a key from the server. For the `sha256_password` path in [`auth.go`](https://github.com/go-sql-driver/mysql/blob/main/auth.go) (lines 24-30), the driver uses the pre-cached key directly:

```go
pubKey := mc.cfg.pubKey
if pubKey == nil {
    // Only request from server if not pre-registered
    pubKey, err = mc.requestServerPubKey()
}

```

Similarly, in the `caching_sha2_password` authentication block (lines 23-31), the driver checks `cfg.pubKey` first. If the key is present, it encrypts the password immediately using `rsa.EncryptOAEP`; otherwise, it falls back to the vulnerable request flow. By pre-registering the key, the driver never executes the request path, eliminating the MITM attack surface.

## Complete Implementation Example

The following example demonstrates loading a PEM-encoded public key, registering it with the driver, and configuring the DSN to use `serverPubKey`:

```go
package main

import (
	"crypto/rsa"
	"crypto/x509"
	"database/sql"
	"encoding/pem"
	"log"
	"os"

	"github.com/go-sql-driver/mysql"
)

func main() {
	// 1. Load the server's RSA public key from a trusted source (e.g., verified PEM file)
	data, err := os.ReadFile("myserver_pub.pem")
	if err != nil {
		log.Fatal(err)
	}
	block, _ := pem.Decode(data)
	if block == nil || block.Type != "PUBLIC KEY" {
		log.Fatal("invalid PEM block")
	}
	pubIfc, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		log.Fatal(err)
	}
	pubKey, ok := pubIfc.(*rsa.PublicKey)
	if !ok {
		log.Fatal("not an RSA public key")
	}

	// 2. Register the key under a logical name
	mysql.RegisterServerPubKey("myserver", pubKey)

	// 3. Configure DSN to use the pre-registered key
	//    The driver will use the verified key and never request one from the server
	dsn := "user:pass@tcp(localhost:3306)/dbname?serverPubKey=myserver"

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Connection established with MITM-resistant password encryption
}

```

This pattern guarantees that only a key verified out-of-band is used for encryption, protecting against malicious key substitution during the handshake.

## Summary

- The default RSA key exchange mechanism requests the server's public key over the network, creating a MITM vulnerability where an attacker can substitute a malicious key.
- The `serverPubKey` parameter enables pre-registration of trusted RSA keys via `RegisterServerPubKey`, storing them in a thread-safe registry (`serverPubKeyRegistry`) in [`auth.go`](https://github.com/go-sql-driver/mysql/blob/main/auth.go).
- During DSN parsing in [`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go), the driver resolves the key name to an actual RSA key and stores it in `cfg.pubKey` before connection.
- Both `sha256_password` and `caching_sha2_password` authentication paths use the pre-cached key from `cfg.pubKey`, bypassing the network request and eliminating the MITM attack window.

## Frequently Asked Questions

### How does `serverPubKey` prevent man-in-the-middle attacks compared to the default mechanism?

The default mechanism requests the RSA public key from the server during authentication, allowing an attacker to intercept the request and substitute a malicious key that would decrypt the password. The `serverPubKey` parameter uses a key registered out-of-band via `RegisterServerPubKey`, eliminating the network request and ensuring the client only encrypts data with a verified key.

### Which MySQL authentication plugins utilize the `serverPubKey` parameter?

According to the implementation in [`auth.go`](https://github.com/go-sql-driver/mysql/blob/main/auth.go), both the `sha256_password` and `caching_sha2_password` authentication paths check for the presence of `cfg.pubKey`. If the key is present (resolved from the `serverPubKey` DSN parameter), both plugins use it directly via `rsa.EncryptOAEP` instead of requesting a key from the server.

### Is the public key registry thread-safe for concurrent access?

Yes. The `serverPubKeyRegistry` accessed via `RegisterServerPubKey` and `getServerPubKey` in [`auth.go`](https://github.com/go-sql-driver/mysql/blob/main/auth.go) is protected by a `sync.RWMutex`. The registry uses `Lock()` for writes during registration and `RLock()` for reads during key retrieval, making it safe for concurrent use across multiple goroutines.

### When does the driver resolve the `serverPubKey` DSN name to an actual RSA key?

During the configuration normalization phase in [`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go) (specifically within the `normalize()` method at lines 23-28), the driver calls `getServerPubKey()` to resolve the name specified in the DSN to the registered RSA key. It stores the result in `cfg.pubKey` before any network connection is attempted, ensuring the key is available at authentication time.