rgoussu@goussu: ~/library/go/go-security
~/library/go/go-security cat cryptography.md

Cryptography in Go — the stdlib way

# Go's curated, misuse-resistant crypto stdlib, x/crypto extras, TLS configuration, FIPS 140-3 mode, and key and secret management.

Conceptsaved 2026-08-09 #go#security#cryptography#tls#secrets

Overview

Go treats cryptography as a curated standard-library concern, not a provider marketplace: crypto/* ships a deliberately small set of modern primitives with safe defaults, the TLS stack is a first-party pure-Go implementation with good out-of-the-box configuration, and the semi-official golang.org/x/crypto holds everything else worth using. The design philosophy is misuse-resistance by omission — ECB mode simply is not provided, crypto/tls refuses known-bad configurations — which contrasts with the JCA's everything-pluggable approach (Cryptography in Java). Principles live in Cryptography for engineers; this note is the Go API surface and its judgement calls.

Key points

  • AEAD first: symmetric encryption means crypto/aes + cipher.NewGCM (or x/crypto/chacha20poly1305); the cipher.AEAD interface (Seal/Open) bundles confidentiality and integrity so unauthenticated CBC never appears in new code.
  • crypto/rand always: crypto randomness comes from crypto/rand.Reader, full stop. Since Go 1.22 the math/rand global is auto-seeded (and math/rand/v2 exists), which removed the classic "predictable seed" bug class — but math/rand remains non-cryptographic.
  • Signatures: crypto/ed25519 is the default for new designs (no nonce to misuse, fast, small keys); crypto/ecdsa (P-256) where ecosystem compatibility demands it — Go's ECDSA is hedged/deterministic-nonce internally, defusing the nonce-reuse disaster.
  • Key agreement: crypto/ecdh (Go 1.20+) replaced error-prone raw scalar multiplication with an API that validates peer public keys.
  • x/crypto extras: argon2, bcrypt, scrypt for passwords; chacha20poly1305; ssh (client, server, agent); acme/autocert — Let's Encrypt certificates with automatic renewal in a few lines.
  • Password hashing: argon2id (x/crypto/argon2, tune memory/time to your hardware) or bcrypt (x/crypto/bcrypt, cost ≥ 12) — never a bare hash; both are fine, argon2id is the modern recommendation.
  • FIPS 140-3: Go 1.24 ships a native validated crypto module — GOFIPS140=v1.0.0 at build time and/or fips140=on in GODEBUG selects it, no BoringCrypto fork or cgo required.
  • Secrets management: filippo.io/age for encrypting files/config at rest, hashicorp/vault/api for dynamic secrets, cloud KMS SDKs for envelope encryption — patterns in Secrets management.

Details

crypto/tls: what to touch and what to leave

The zero-value posture is good: TLS 1.2 minimum, 1.3 preferred, secure cipher-suite selection that the team deliberately does not expose for 1.3 at all. Knobs worth setting:

Knob Guidance
MinVersion tls.VersionTLS12 explicitly (or 1.3 for internal-only services)
ClientAuth + ClientCAs tls.RequireAndVerifyClientCert for mTLS servers
Certificates / GetCertificate static cert or dynamic (SNI, autocert)
RootCAs pin an internal CA pool for client-side mTLS
NextProtos h2, http/1.1 when not using net/http's automatic setup

Leave alone: CipherSuites (1.3 suites are not configurable by design; 1.2 defaults are curated), CurvePreferences (defaults now include post-quantum hybrid X25519+ML-KEM), session tickets and renegotiation settings. InsecureSkipVerify is for tests behind a build tag, never a "fix" for certificate errors — use ServerName or a custom RootCAs pool instead. Operational side: TLS, HTTPS and certificates.

What Go designs away — and what remains yours

Designed away: ECB is absent; GCM nonce length and tag handling are fixed; ecdsa.Sign hedges nonces; constant time comparisons exist as crypto/subtle.ConstantTimeCompare (use it for MACs/tokens, not == or bytes.Equal); certificate verification is on by default everywhere. Still yours: nonce disciplinecipher.AEAD will happily let you reuse a nonce, which is catastrophic for GCM, so generate 12 random bytes per message from crypto/rand (or use XChaCha20-Poly1305's 24-byte nonces for high-volume keys) and rotate keys before birthday bounds; key storage — the stdlib gives x509/pem encoding but no keystore, so keys live in files with tight permissions, Vault, or KMS; protocol design — composing primitives into a protocol is still where amateurs lose, prefer age/NaCl-style boxes (x/crypto/nacl) over hand-rolled schemes.

Secrets and keys in practice

  • age (filippo.io/age): modern file encryption — X25519 recipients, no config; the right answer for "encrypt this backup/config", also usable as a library.
  • Vault (hashicorp/vault/api): dynamic DB credentials, transit encryption-as-a- service (Vault holds the key, app sends plaintext/ciphertext), PKI for internal mTLS.
  • Cloud KMS (AWS/GCP/Azure SDKs): envelope encryption — KMS wraps a locally-generated data key; never ship the data through KMS itself.
  • In-process: zeroing memory is best-effort in a GC'd language; treat process memory as sensitive at the infrastructure level rather than pretending to scrub it.

Examples

// AEAD encryption, the canonical shape.
key := make([]byte, 32) // from KMS/Vault/argon2 KDF — never hardcoded
block, _ := aes.NewCipher(key)
aead, _ := cipher.NewGCM(block)

nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil { panic(err) } // crypto/rand
ciphertext := aead.Seal(nonce, nonce, plaintext, additionalData) // nonce prepended

// Password hashing with argon2id (RFC 9106 moderate parameters).
salt := make([]byte, 16)
rand.Read(salt)
hash := argon2.IDKey(password, salt, 1, 64*1024, 4, 32)
// HTTPS with automatic Let's Encrypt certificates.
m := &autocert.Manager{
    Prompt:     autocert.AcceptTOS,
    HostPolicy: autocert.HostWhitelist("example.com"),
    Cache:      autocert.DirCache("/var/cache/certs"),
}
srv := &http.Server{Addr: ":443", TLSConfig: m.TLSConfig(), Handler: mux}
log.Fatal(srv.ListenAndServeTLS("", ""))

Related