Files
teamai-test/.teamai/skills/common/golang-security/references/cryptography.md
T

9.7 KiB

Cryptography Security Rules

Cryptography vulnerabilities threaten confidentiality and integrity of sensitive data.

Rules:

  1. TLS MUST use 1.2+.
  2. NEVER use DES, RC4, MD5, or SHA1 for security purposes.
  3. SSH host keys MUST be verified — NEVER use InsecureIgnoreHostKey.
  4. Passwords MUST be hashed with Argon2id (preferred) or bcrypt.
  5. 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