# Security Implications of Using fabrica-util's RSA and ECDH for Key Exchange in Game Servers

> Explore security risks of fabrica-util RSA and ECDH for game server key exchange. Learn about Bleichenbacher oracles and man-in-the-middle vulnerabilities.

- Repository: [Pantheon/fabrica-util](https://github.com/go-pantheon/fabrica-util)
- Tags: deep-dive
- Published: 2026-03-02

---

**Using fabrica-util's RSA and ECDH primitives for game server key exchange introduces specific risks: RSA's reliance on PKCS#1 v1.5 padding creates Bleichenbacher oracle vulnerabilities, while Curve25519 ECDH lacks authentication and requires RSA signatures to prevent man-in-the-middle attacks.**

The `go-pantheon/fabrica-util` repository provides cryptographic building blocks for Go-based game servers, implementing RSA encryption in [`security/rsa/rsa.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/rsa/rsa.go) and Curve25519 ECDH in [`security/ecdh/curve25519.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/ecdh/curve25519.go). Understanding the security implications of combining these primitives is critical for establishing secure player sessions without introducing padding oracle attacks or unauthenticated key exchanges.

## Architectural Overview

The fabrica-util library separates asymmetric operations into two distinct packages that are typically combined to establish secure sessions.

### RSA Implementation

In [`security/rsa/rsa.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/rsa/rsa.go), the library exposes `Encrypt`, `Decrypt`, `ParsePublicKey`, and `ParsePrivateKey` functions. The test suite in [`security/rsa/rsa_test.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/rsa/rsa_test.go) demonstrates that these functions utilize `rsa.EncryptPKCS1v15` and `rsa.SignPKCS1v15` for padding and signature operations. This implementation is designed for protecting small payloads—such as symmetric session keys—and verifying the authenticity of exchanged data through digital signatures.

### Curve25519 ECDH Implementation

The [`security/ecdh/curve25519.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/ecdh/curve25519.go) file implements high-performance elliptic-curve Diffie-Hellman using `GenKeyPair`, `ParseKey`, `ComputeSharedKey`, and `ComputePubKey`. These functions enable forward secrecy by generating ephemeral key pairs where `GenKeyPair` reads 32 random bytes from `crypto/rand`, ensuring high-entropy private keys that are never transmitted across the network.

## Security Implications of RSA in fabrica-util

### PKCS#1 v1.5 Padding Vulnerabilities

The library's use of `rsa.EncryptPKCS1v15` and `rsa.SignPKCS1v15` (as seen in the `Encrypt` function and test implementations) relies on PKCS#1 v1.5 padding, which is **not recommended for new designs**. This padding scheme is vulnerable to Bleichenbacher-style adaptive chosen-ciphertext attacks. 

**Implication for game servers:** An attacker who can provoke the server to decrypt malformed ciphertexts—such as by sending malformed login packets—may recover plaintext session keys or mount padding-oracle attacks. In [`security/rsa/rsa.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/rsa/rsa.go), the absence of `EncryptOAEP` means the library does not expose RSA-OAEP, which provides stronger resistance to chosen-ciphertext attacks.

### Key Size and Performance Trade-offs

The test files demonstrate key generation for both 2048-bit and 4096-bit RSA keys. While 2048 bits represents the modern minimum, 4096 bits offers superior security against factorization attacks. For real-time game servers, however, 4096-bit operations introduce significant CPU overhead that may increase latency during high-concurrency connection spikes.

### Private Key Management Risks

The implementation expects callers to maintain the `*rsa.PrivateKey` in memory. Exposing this key through logging, insecure serialization, or core dumps would compromise all current and future sessions relying on that key pair. The library itself does not provide hardware security module (HSM) integration or automatic memory clearing.

## Security Implications of Curve25519 ECDH

### Forward Secrecy Strengths

Curve25519 is a well-studied elliptic curve that provides **forward secrecy**: compromise of the server's long-term RSA signing key does not reveal past session secrets derived from ephemeral ECDH exchanges. The `ComputeSharedKey` function in [`security/ecdh/curve25519.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/ecdh/curve25519.go) generates unique shared secrets for each session, ensuring that stolen session keys cannot decrypt historical traffic.

### Public Key Validation Limitations

The `ParseKey` function validates that public keys are exactly 32 bytes in length. However, it does **not** perform explicit subgroup validation checks. Because Curve25519 has a cofactor of 8, scalar multiplication inherently maps invalid points to valid curve points. While this prevents certain small-subgroup attacks, a hostile client could transmit malformed public keys that result in low-entropy shared secrets. Applications should validate derived secrets (e.g., by checking hash equality) before use.

### Unauthenticated Key Exchange Vulnerability

ECDH alone is **unauthenticated**. Without additional verification, an attacker can perform a man-in-the-middle (MITM) attack by substituting their own public key during the exchange phase. The fabrica-util implementation does not include built-in authentication mechanisms within the `ComputeSharedKey` workflow, requiring external verification—typically RSA signatures—to bind the ECDH public key to the server's identity.

## Combined Threat Model for Game Servers

When deploying both primitives together, specific attack vectors emerge that require architectural mitigation.

| Threat Vector | Mitigation Strategy using fabrica-util |
|---------------|----------------------------------------|
| **Passive eavesdropping** | Use `ecdh.GenKeyPair` to derive fresh symmetric keys per session, then encrypt traffic with AES-GCM using the shared secret. |
| **Active MITM** | Sign the ECDH public key (from `ecdh.KeyToBytes`) with the server's RSA private key using `rsa.SignPKCS1v15`; clients verify with `rsa.VerifyPKCS1v15` before computing shared secrets. |
| **Replay attacks** | Include a per-session nonce or timestamp in the RSA-signed payload; reject messages with stale nonces at the application layer. |
| **Denial-of-service** | Reserve RSA operations only for signing initial ECDH public keys; use lightweight ECDH operations for the bulk of key exchange to prevent CPU exhaustion from 4096-bit RSA decryption operations. |

## Secure Implementation Pattern

The following production-ready pattern demonstrates how to safely combine these primitives to establish authenticated, forward-secret channels.

### Server-Side: Generate and Sign ECDH Public Keys

First, generate a long-term RSA key pair for signing (typically 4096-bit for high-security game servers):

```go
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"log"
)

func generateRSAKey() *rsa.PrivateKey {
	key, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		log.Fatalf("RSA key generation failed: %v", err)
	}
	return key
}

```

During each player connection, generate an ephemeral Curve25519 key pair and sign the public key with RSA:

```go
package main

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"encoding/base64"
	"log"

	"github.com/go-pantheon/fabrica-util/security/ecdh"
	utilrsa "github.com/go-pantheon/fabrica-util/security/rsa"
)

func serverKeyExchange(rsaPriv *rsa.PrivateKey) (pubKeyBase64 string, signatureBase64 string) {
	// Generate ephemeral Curve25519 key pair
	pri, pub, err := ecdh.GenKeyPair()
	if err != nil {
		log.Fatalf("ECDH key generation failed: %v", err)
	}

	// Serialize public key (32 bytes)
	pubBytes := ecdh.KeyToBytes(&pub)

	// Sign with RSA-PKCS1v15 (as implemented in fabrica-util)
	hashed := sha256.Sum256(pubBytes)
	sig, err := utilrsa.SignPKCS1v15(rand.Reader, rsaPriv, crypto.SHA256, hashed[:])
	if err != nil {
		log.Fatalf("RSA signing failed: %v", err)
	}

	return base64.RawURLEncoding.EncodeToString(pubBytes),
		base64.RawURLEncoding.EncodeToString(sig)
}

```

### Client-Side: Verify and Compute Shared Secret

The client validates the server's identity and establishes the shared secret:

```go
package main

import (
	"crypto"
	"crypto/rsa"
	"crypto/sha256"
	"encoding/base64"
	"log"

	"github.com/go-pantheon/fabrica-util/security/ecdh"
	utilrsa "github.com/go-pantheon/fabrica-util/security/rsa"
)

func clientKeyExchange(rsaPub *rsa.PublicKey, pubB64, sigB64 string) []byte {
	// Decode received values
	pubBytes, _ := base64.RawURLEncoding.DecodeString(pubB64)
	sigBytes, _ := base64.RawURLEncoding.DecodeString(sigB64)

	// Verify RSA signature using fabrica-util's implementation
	hashed := sha256.Sum256(pubBytes)
	if err := utilrsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, hashed[:], sigBytes); err != nil {
		log.Fatalf("Signature verification failed: %v", err)
	}

	// Parse server's public key (validates 32-byte length)
	serverPub, err := ecdh.ParseKey(pubBytes)
	if err != nil {
		log.Fatalf("Invalid server public key: %v", err)
	}

	// Generate client ECDH key pair
	pri, _, err := ecdh.GenKeyPair()
	if err != nil {
		log.Fatalf("ECDH generation failed: %v", err)
	}

	// Compute shared secret
	secret, err := ecdh.ComputeSharedKey(pri, serverPub)
	if err != nil {
		log.Fatalf("Shared secret computation failed: %v", err)
	}
	return secret // Derive AES-GCM key from this secret
}

```

## Summary

- **PKCS#1 v1.5 padding in fabrica-util's RSA implementation** exposes game servers to Bleichenbacher oracle attacks; consider wrapping OAEP yourself or limiting RSA to signing operations only.
- **Curve25519 ECDH provides strong forward secrecy** but requires authentication; always sign `ecdh.GenKeyPair` outputs with RSA to prevent MITM attacks.
- **Key size selection impacts performance**; 4096-bit RSA offers better security but may degrade server performance under load compared to 2048-bit keys.
- **No built-in OAEP support** means developers must implement additional padding schemes externally if chosen-ciphertext attack resistance is required for RSA encryption operations.
- **Combined RSA+ECDH architecture** mitigates both passive eavesdropping and active interception when implemented with proper nonce handling and signature verification.

## Frequently Asked Questions

### Does fabrica-util support RSA-OAEP for improved security?

No, the [`security/rsa/rsa.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/rsa/rsa.go) file in fabrica-util only exposes PKCS#1 v1.5 padding via `EncryptPKCS1v15`. If you require OAEP padding to mitigate Bleichenbacher attacks, you must implement it separately using Go's standard `crypto/rsa` package with `EncryptOAEP`, as the library does not provide this functionality natively.

### How can I prevent man-in-the-middle attacks when using fabrica-util's ECDH?

Since `ecdh.ComputeSharedKey` performs unauthenticated Diffie-Hellman, you must bind the ephemeral public keys to verified identities. Use the `rsa.SignPKCS1v15` function in [`security/rsa/rsa.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/rsa/rsa.go) to sign the 32-byte public key output from `ecdh.KeyToBytes`, and have clients verify this signature with `rsa.VerifyPKCS1v15` before calling `ComputeSharedKey`. Alternatively, migrate to the Noise protocol or TLS 1.3 for built-in authentication.

### Is 2048-bit RSA sufficient for game server key exchange?

According to the test implementations in [`security/rsa/rsa_test.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/rsa/rsa_test.go), 2048-bit keys represent the minimum acceptable size, while 4096-bit keys provide superior security margins against future factorization attacks. However, 4096-bit operations consume significantly more CPU cycles; for high-throughput game servers, profile your authentication handshake latency before committing to larger key sizes.

### What validation does fabrica-util perform on ECDH public keys?

The `ecdh.ParseKey` function in [`security/ecdh/curve25519.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/ecdh/curve25519.go) validates that incoming public keys are exactly 32 bytes in length. It does not perform explicit subgroup validation checks because Curve25519's cofactor design inherently handles invalid points through scalar multiplication. However, you should verify the entropy of the resulting shared secret (e.g., by checking that `ComputeSharedKey` does not return all-zero bytes) to detect malicious input attempts.