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(orx/crypto/chacha20poly1305); thecipher.AEADinterface (Seal/Open) bundles confidentiality and integrity so unauthenticated CBC never appears in new code. crypto/randalways: crypto randomness comes fromcrypto/rand.Reader, full stop. Since Go 1.22 the math/rand global is auto-seeded (andmath/rand/v2exists), which removed the classic "predictable seed" bug class — butmath/randremains non-cryptographic.- Signatures:
crypto/ed25519is 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/cryptoextras:argon2,bcrypt,scryptfor 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.0at build time and/orfips140=onin GODEBUG selects it, no BoringCrypto fork or cgo required. - Secrets management:
filippo.io/agefor encrypting files/config at rest,hashicorp/vault/apifor 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 discipline — cipher.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
- Security in Go applications — the map — parent map of concern → library.
- Cryptography for engineers — the principles behind these API choices.
- TLS, HTTPS and certificates — the protocol and PKI side of crypto/tls.
- Secrets management — Vault, KMS and secret-rotation patterns in general.
- Cryptography in Java — JCA/JSSE — the provider-architecture contrast to Go's curated stdlib.