9.7 KiB
Cryptography Security Rules
Cryptography vulnerabilities threaten confidentiality and integrity of sensitive data.
Rules:
- TLS MUST use 1.2+.
- NEVER use DES, RC4, MD5, or SHA1 for security purposes.
- SSH host keys MUST be verified — NEVER use
InsecureIgnoreHostKey. - Passwords MUST be hashed with Argon2id (preferred) or bcrypt.
- Security-critical randomness MUST use
crypto/rand.
Algorithm Selection Guide
Choose the right algorithm for the job — using the wrong primitive (e.g. SHA256 for passwords) is as dangerous as using a broken one:
| Use Case | Recommended | Avoid | Why |
|---|---|---|---|
| Symmetric encryption | AES-256-GCM, ChaCha20-Poly1305 | DES, 3DES, AES-ECB, RC4 | ECB reveals patterns; DES/RC4 are broken |
| Password hashing | Argon2id (preferred), bcrypt, scrypt | MD5, SHA-1, plain SHA-256 | Fast hashes enable brute-force; memory-hard functions resist GPU attacks |
| Message authentication | HMAC-SHA256, Poly1305 | HMAC-MD5, HMAC-SHA1 | MD5/SHA1 have known collision weaknesses |
| Digital signatures | Ed25519, ECDSA P-256 | RSA-PKCS1v1.5 | PKCS1v1.5 has padding oracle vulnerabilities |
| Key exchange | X25519, ECDH P-256 | Static RSA key transport | Forward secrecy requires ephemeral keys |
| Random generation | crypto/rand |
math/rand |
math/rand output is predictable |
| TLS | TLS 1.2+ (prefer 1.3) | TLS 1.0, 1.1, SSL | Known attacks (BEAST, POODLE) on older versions |
Key Size Requirements
| Algorithm | Minimum Key Size | Recommended |
|---|---|---|
| RSA | 2048 bits | 4096 bits |
| AES | 128 bits | 256 bits |
| ECDSA | P-256 (128-bit security) | P-256 or Ed25519 |
Key Rotation Pattern
Keys should be rotated periodically. Use envelope encryption so rotating the Key Encryption Key (KEK) doesn't require re-encrypting all data:
// Envelope encryption: encrypt data with a DEK, encrypt DEK with KEK
func EnvelopeEncrypt(kek, plaintext []byte) (encryptedDEK, ciphertext []byte, err error) {
// 1. Generate random Data Encryption Key
dek := make([]byte, 32)
if _, err := rand.Read(dek); err != nil {
return nil, nil, err
}
// 2. Encrypt data with DEK
ciphertext, err = EncryptAESGCM(dek, plaintext)
if err != nil {
return nil, nil, err
}
// 3. Encrypt DEK with KEK
encryptedDEK, err = EncryptAESGCM(kek, dek)
if err != nil {
return nil, nil, err
}
return encryptedDEK, ciphertext, nil
}
func EnvelopeDecrypt(kek, encryptedDEK, ciphertext []byte) ([]byte, error) {
dek, err := DecryptAESGCM(kek, encryptedDEK)
if err != nil {
return nil, err
}
return DecryptAESGCM(dek, ciphertext)
}
func EncryptAESGCM(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil { return nil, err }
aead, err := cipher.NewGCM(block)
if err != nil { return nil, err }
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil { return nil, err }
return aead.Seal(nonce, nonce, plaintext, nil), nil
}
func DecryptAESGCM(key, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil { return nil, err }
aead, err := cipher.NewGCM(block)
if err != nil { return nil, err }
nonceSize := aead.NonceSize()
if len(ciphertext) < nonceSize {
return nil, errors.New("ciphertext too short")
}
return aead.Open(nil, ciphertext[:nonceSize], ciphertext[nonceSize:], nil)
}
When the KEK is rotated, only re-encrypt the DEKs (small), not the data (potentially large).
Common Cryptographic Mistakes
Mistake 1: AES-ECB reveals patterns — High
ECB encrypts each block independently — identical plaintext blocks produce identical ciphertext blocks, revealing data structure:
// Bad — ECB mode reveals patterns in structured data
block, _ := aes.NewCipher(key)
// Using block.Encrypt directly = ECB mode
// Good — GCM provides authenticated encryption
aead, err := cipher.NewGCM(block) // randomized, authenticated
if err != nil {
return nil, err
}
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
ciphertext := aead.Seal(nonce, nonce, plaintext, nil)
Mistake 2: Reusing nonces — Critical
A nonce reuse with AES-GCM completely breaks confidentiality and authentication:
// Bad — static or reused nonce
nonce := []byte("fixed_nonce!") // catastrophic with GCM
// Good — random nonce per encryption
nonce := make([]byte, 12) // 96-bit for GCM
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
Mistake 3: Non-constant-time comparison for secrets — Medium
Comparing secrets with == short-circuits on the first differing byte, leaking timing information. See Network/Web Security — Observable Timing for constant-time comparison patterns using crypto/subtle.
Insecure TLS Configuration — High
Using insecure TLS configurations can expose your application to man-in-the-middle attacks.
Bad:
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // DON'T: verify certificates
},
}
Good:
import "crypto/tls"
func secureConfig() *tls.Config {
return &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
}
}
DES Encryption — High
DES is cryptographically broken.
Bad:
import "crypto/des"
block, _ := des.NewCipher(key) // DON'T: broken
Good:
import "crypto/aes"
block, _ := aes.NewCipher(key) // OK: AES
cipher.NewGCM(block) // OK: GCM for auth
Insecure SSH Host Key Verification — High
Bad:
import "golang.org/x/crypto/ssh"
&ssh.ClientConfig{
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // DON'T
}
Good:
import "golang.org/x/crypto/ssh"
&ssh.ClientConfig{
HostKeyCallback: ssh.FixedHostKey(publicKey),
}
MD5 Hash — High
MD5 is collision-prone and weak for security.
Bad:
import "crypto/md5"
hash := md5.Sum([]byte(data)) // DON'T: weak
Good:
// For password hashing:
import "golang.org/x/crypto/argon2"
hash := argon2.IDKey([]byte(pw), salt, 3, 64*1024, 4, 32)
// Or bcrypt (simpler API, no salt management):
import "golang.org/x/crypto/bcrypt"
hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
// For general-purpose hashing (not passwords):
import "crypto/sha256"
digest := sha256.Sum256(data)
RC4 Cipher — High
RC4 is cryptographically broken.
Bad:
import "crypto/rc4"
cipher, _ := rc4.NewCipher(key) // DON'T: broken
Good:
import "crypto/cipher"
import "crypto/aes"
aead, _ := cipher.NewGCM(block) // OK: AES-GCM
// Or ChaCha20:
import "golang.org/x/crypto/chacha20poly1305"
aead, _ := chacha20poly1305.New(key)
SHA1 Hash — Medium
SHA1 provides insufficient collision resistance.
Bad:
import "crypto/sha1"
hash := sha1.Sum(data) // DON'T: weak
Good:
import "crypto/sha256"
hash := sha256.Sum256(data)
Weak Cryptographic Algorithms — Medium
Bad:
import "crypto/hmac"
import "crypto/md5"
mac := hmac.New(md5.New, key) // DON'T: HMAC-MD5
Good:
import "crypto/sha256"
mac := hmac.New(sha256.New, key)
Insufficient Key Strength — Medium
RSA keys smaller than 2048 bits are insufficient.
Bad:
import "crypto/rsa"
key, _ := rsa.GenerateKey(rand.Reader, 1024) // DON'T: too weak
Good:
key, _ := rsa.GenerateKey(rand.Reader, 4096) // OK: 2048+ bits
Weak Random Number Generators — High
math/rand is predictable, never use for security.
Bad:
import "math/rand"
bytes := make([]byte, 16)
rand.Read(bytes) // DON'T: predictable
Good:
import "crypto/rand"
_, err := rand.Read(bytes) // OK: cryptographically secure
Weak TLS Versions — High
TLS 1.0 and 1.1 have known vulnerabilities.
Bad:
import "crypto/tls"
&tls.Config{MinVersion: tls.VersionTLS10} // DON'T
Good:
&tls.Config{MinVersion: tls.VersionTLS12} // OK
Password Hashing — High
Don't use MD5, SHA1, or single-iteration hashes for passwords.
Bad:
import "crypto/sha256"
hash := sha256.Sum256([]byte(password)) // DON'T: too fast
Good:
// Argon2id (preferred) — memory-hard, resists GPU attacks:
import "golang.org/x/crypto/argon2"
key := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32)
// Or bcrypt (simpler API, widely supported):
import "golang.org/x/crypto/bcrypt"
hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
// Or PBKDF2 with 600,000+ iterations (Go 1.24+ stdlib):
import "crypto/pbkdf2"
key, err := pbkdf2.Key(sha512.New, password, salt, 600_000, 32)
if err != nil {
return err
}
// Or scrypt:
import "golang.org/x/crypto/scrypt"
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil {
return err
}
For Go 1.24+, prefer stdlib crypto/hkdf, crypto/pbkdf2, and crypto/sha3. Use golang.org/x/crypto/... fallbacks only for modules targeting older Go versions or for algorithms still outside the standard library.
CWE References
- CWE-327: Use of a Broken or Risky Cryptographic Algorithm
- CWE-331: Insufficient Entropy
- CWE-326: Inadequate Encryption Strength
- CWE-295: Improper Certificate Validation
- CWE-330: Use of Insufficiently Random Values
- CWE-916: Use of Password Hash With Insufficient Computational Effort