Crypto #

Cryptography is the foundation of modern application security — data integrity verification, password hashing, unpredictable tokens, and digital signatures all depend on correctly implemented cryptographic primitives. Go provides a complete crypto ecosystem in the standard library: crypto/sha256 and crypto/md5 for hashing, crypto/rand for cryptographically secure random numbers, crypto/hmac for message authentication, and crypto/subtle for operations safe against timing attacks. What makes Go’s crypto packages special is a design that encourages correct usage — for example crypto/rand is explicitly distinct from math/rand, which isn’t secure for cryptography. This article covers everything you need to know to implement correct cryptography in production Go applications.

An Overview of the crypto Packages #

flowchart TD
    Crypto["package crypto\n(and sub-packages)"] --> Hash["Hashing"]
    Crypto --> Rand["Random"]
    Crypto --> Auth["Authentication"]
    Crypto --> Sym["Symmetric Encryption"]
    Crypto --> Asym["Asymmetric Encryption"]

    Hash --> SHA256["crypto/sha256\nSHA-256, SHA-224"]
    Hash --> SHA512["crypto/sha512\nSHA-512, SHA-384"]
    Hash --> MD5["crypto/md5\nMD5 (DON'T for security)"]
    Hash --> SHA1["crypto/sha1\nSHA-1 (deprecated for security)"]

    Rand --> CR["crypto/rand\nCryptographically secure random\nuse this, not math/rand"]

    Auth --> HMAC["crypto/hmac\nHMAC for integrity verification"]
    Auth --> BCrypt["golang.org/x/crypto/bcrypt\npassword hashing"]
    Auth --> Subtle["crypto/subtle\nconstant-time comparison"]

    Sym --> AES["crypto/aes\nAES cipher"]
    Sym --> Cipher["crypto/cipher\nGCM, CBC, CTR modes"]

    Asym --> RSA["crypto/rsa\nRSA encryption & signatures"]
    Asym --> ECDSA["crypto/ecdsa\nElliptic Curve DSA"]
    Asym --> ED["crypto/ed25519\nEdDSA (modern, fast)"]

    style Crypto fill:#4f86c6,color:#fff
    style Hash fill:#e8f5e9
    style Rand fill:#e3f2fd
    style Auth fill:#fff3e0
    style Sym fill:#f3e5f5
    style Asym fill:#fce4ec

crypto/sha256 — Secure Hashing #

SHA-256 is the most commonly used cryptographic hash function today — it produces a deterministic, irreversible 32-byte (256-bit) digest. It’s used for file integrity verification, data fingerprints, and as a component in larger cryptographic systems.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "os"
)

func main() {
    // Way 1: hash a short string all at once
    data := []byte("Hello, World!")
    hash := sha256.Sum256(data)
    // hash is a [32]byte — an array, not a slice
    fmt.Printf("SHA-256: %x\n", hash)
    // SHA-256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

    // Convert to a hex string
    hashHex := hex.EncodeToString(hash[:])
    fmt.Println(hashHex)

    // Way 2: hash large data streaming (memory-efficient)
    f, err := os.Open("large-file.bin")
    if err == nil {
        defer f.Close()

        h := sha256.New() // create a new hasher
        if _, err := io.Copy(h, f); err != nil {
            fmt.Println("error hashing file:", err)
        }
        hashFile := h.Sum(nil) // Sum(nil) returns the hash as []byte
        fmt.Printf("File hash: %x\n", hashFile)
    }

    // Way 3: incremental hashing — add data piece by piece
    h := sha256.New()
    h.Write([]byte("first part"))
    h.Write([]byte(" "))
    h.Write([]byte("second part"))
    result := h.Sum(nil)
    fmt.Printf("Incremental hash: %x\n", result)

    // The result is the same as:
    combined := sha256.Sum256([]byte("first part second part"))
    fmt.Printf("Combined hash:    %x\n", combined)
    fmt.Println("Same?", result != nil &&
        hex.EncodeToString(result) == hex.EncodeToString(combined[:]))
}

SHA-224, SHA-384, SHA-512 #

import (
    "crypto/sha256"
    "crypto/sha512"
)

// SHA-224 (28-byte output)
hash224 := sha256.New224()
hash224.Write([]byte("data"))
fmt.Printf("SHA-224: %x\n", hash224.Sum(nil))

// SHA-384 (48-byte output)
hash384 := sha512.New384()
hash384.Write([]byte("data"))
fmt.Printf("SHA-384: %x\n", hash384.Sum(nil))

// SHA-512 (64-byte output)
hash512 := sha512.New()
hash512.Write([]byte("data"))
fmt.Printf("SHA-512: %x\n", hash512.Sum(nil))

// SHA-512/256 — SHA-512 truncated to 256 bits (faster on 64-bit CPUs)
hash512_256 := sha512.New512_256()
hash512_256.Write([]byte("data"))
fmt.Printf("SHA-512/256: %x\n", hash512_256.Sum(nil))

crypto/md5 — For Non-Security Use Only #

MD5 produces a 16-byte (128-bit) hash. It’s not secure for cryptography because it’s vulnerable to collision attacks — two different inputs can produce the same hash. Use MD5 only for non-security checksums like change detection or cache keys:

flowchart LR
    subgraph Safe["✓ Use SHA-256"]
        S1["Integrity verification of\nimportant data"]
        S2["Fingerprints for\nsecurity"]
        S3["Components in\ncryptographic systems"]
        S4["Tokens and IDs\nthat must be unique and secure"]
    end

    subgraph NotSafe["⚠ MD5 — only for this"]
        T1["Cache keys\n(not for security)"]
        T2["HTTP ETags\n(resource identification)"]
        T3["Simple checksums\n(not for security)"]
        T4["Deduplication IDs\n(not for security)"]
    end

    subgraph Never["✗ NEVER use MD5 for"]
        J1["Password hashing"]
        J2["Identity verification"]
        J3["Digital signatures"]
        J4["Authentication tokens"]
    end

    style Safe fill:#e8f5e9
    style NotSafe fill:#fff3e0
    style Never fill:#fce4ec
import "crypto/md5"

// MD5 for cache keys — not for security!
func makeCacheKey(data []byte) string {
    hash := md5.Sum(data)
    return hex.EncodeToString(hash[:])
}

// MD5 for HTTP ETags
func computeETag(content []byte) string {
    hash := md5.Sum(content)
    return fmt.Sprintf(`"%x"`, hash)
}

// Streaming MD5 for large files
func md5File(path string) (string, error) {
    f, err := os.Open(path)
    if err != nil {
        return "", err
    }
    defer f.Close()

    h := md5.New()
    if _, err := io.Copy(h, f); err != nil {
        return "", err
    }
    return hex.EncodeToString(h.Sum(nil)), nil
}

crypto/rand — Secure Randomness #

crypto/rand generates cryptographically secure random data — using the operating system’s entropy source (/dev/urandom on Linux, CryptGenRandom on Windows). This is different from math/rand, which uses a PRNG (Pseudo-Random Number Generator) whose output can be predicted:

flowchart LR
    subgraph MathRand["math/rand — DON'T use for crypto"]
        M1["Seeded from a fixed value\nor time.Now()"]
        M2["Output is predictable\nif the seed is known"]
        M3["Fast — suitable for\nsimulation, games, tests"]
        M4["NOT SECURE for\ntokens, passwords, keys"]
    end

    subgraph CryptoRand["crypto/rand — USE this"]
        C1["From OS entropy\n(/dev/urandom, CryptGenRandom)"]
        C2["Output can't be predicted\neven with access to previous state"]
        C3["Slightly slower\nbut secure"]
        C4["SECURE for tokens,\npasswords, cryptographic keys"]
    end

    style MathRand fill:#fce4ec
    style CryptoRand fill:#e8f5e9
import (
    "crypto/rand"
    "math/big"
)

// Read random bytes directly
func randomBytes(n int) ([]byte, error) {
    b := make([]byte, n)
    if _, err := rand.Read(b); err != nil {
        return nil, fmt.Errorf("randomBytes: %w", err)
    }
    return b, nil
}

// Generate a hex token — for session IDs, API keys, password resets
func generateToken(length int) (string, error) {
    b, err := randomBytes(length)
    if err != nil {
        return "", err
    }
    return hex.EncodeToString(b), nil // string length = length*2
}

// Generate a URL-safe base64 token
func generateTokenBase64(length int) (string, error) {
    b, err := randomBytes(length)
    if err != nil {
        return "", err
    }
    return base64.URLEncoding.EncodeToString(b), nil
}

// Random integer in the range [0, max)
func randomInt(max int64) (int64, error) {
    n, err := rand.Int(rand.Reader, big.NewInt(max))
    if err != nil {
        return 0, fmt.Errorf("randomInt: %w", err)
    }
    return n.Int64(), nil
}

// UUID v4 — a secure unique identifier
func generateUUID() (string, error) {
    b, err := randomBytes(16)
    if err != nil {
        return "", err
    }
    // Set version 4
    b[6] = (b[6] & 0x0f) | 0x40
    // Set variant bits
    b[8] = (b[8] & 0x3f) | 0x80

    return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
        b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil
}

// Usage examples
func main() {
    // A 32-byte token = 64 hex characters
    token, _ := generateToken(32)
    fmt.Println("Token:", token)
    // Token: a3f2c1d8e9b7a6f5...

    // A random number for games or sampling
    n, _ := randomInt(100)
    fmt.Println("Random 0-99:", n)

    // UUID
    uuid, _ := generateUUID()
    fmt.Println("UUID:", uuid)
    // UUID: 550e8400-e29b-41d4-a716-446655440000
}

A Secure Random Password #

const (
    charLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    charDigits  = "0123456789"
    charSymbols = "!@#$%^&*()_+-=[]{}|;':\",./<>?"
    charAll     = charLetters + charDigits + charSymbols
)

func generatePassword(length int, useSymbols bool) (string, error) {
    charset := charLetters + charDigits
    if useSymbols {
        charset = charAll
    }

    password := make([]byte, length)
    for i := range password {
        n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
        if err != nil {
            return "", fmt.Errorf("generatePassword: %w", err)
        }
        password[i] = charset[n.Int64()]
    }
    return string(password), nil
}

// A 6-digit OTP (One-Time Password)
func generateOTP() (string, error) {
    n, err := rand.Int(rand.Reader, big.NewInt(1000000))
    if err != nil {
        return "", err
    }
    return fmt.Sprintf("%06d", n.Int64()), nil
}

crypto/hmac — Integrity Verification #

HMAC (Hash-based Message Authentication Code) uses a secret key to produce an authentication tag — verifying that the message wasn’t modified AND comes from a party knowing the secret key:

sequenceDiagram
    participant Sender as Sender (knows the key)
    participant Msg as Message
    participant Receiver as Receiver (knows the key)

    Sender->>Msg: HMAC(key, data) → tag
    Sender->>Receiver: send (data + tag)
    Receiver->>Receiver: recompute HMAC(key, data) → tag2
    Receiver->>Receiver: compare tag vs tag2\n(constant-time!)

    alt tag == tag2
        Receiver-->>Receiver: ✓ data valid & authentic
    else tag != tag2
        Receiver-->>Receiver: ✗ data modified or\nnot from the legitimate sender
    end
import (
    "crypto/hmac"
    "crypto/sha256"
)

// Create an HMAC tag
func makeHMAC(key, data []byte) []byte {
    mac := hmac.New(sha256.New, key)
    mac.Write(data)
    return mac.Sum(nil)
}

// Verify an HMAC — MUST use hmac.Equal, not bytes.Equal!
func verifyHMAC(key, data, tag []byte) bool {
    mac := hmac.New(sha256.New, key)
    mac.Write(data)
    expectedTag := mac.Sum(nil)

    // hmac.Equal uses constant-time comparison
    // to prevent timing attacks
    return hmac.Equal(expectedTag, tag)
}

// Pattern: a signed webhook token
func makeWebhookToken(payload []byte, secret string) string {
    tag := makeHMAC([]byte(secret), payload)
    return "sha256=" + hex.EncodeToString(tag)
}

func verifyWebhook(payload []byte, secret, signature string) bool {
    expected := makeWebhookToken(payload, secret)
    // constant-time comparison for the whole string
    return hmac.Equal([]byte(expected), []byte(signature))
}

// Usage in an HTTP handler for GitHub/Stripe webhooks
func webhookHandler(w http.ResponseWriter, r *http.Request) {
    payload, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // max 1MB
    if err != nil {
        http.Error(w, "failed to read body", 400)
        return
    }

    signature := r.Header.Get("X-Hub-Signature-256")
    webhookSecret := os.Getenv("WEBHOOK_SECRET")

    if !verifyWebhook(payload, webhookSecret, signature) {
        http.Error(w, "invalid signature", 401)
        return
    }

    // Process the payload...
    w.WriteHeader(http.StatusOK)
}

bcrypt — Password Hashing #

Passwords must not be stored as plain SHA-256 hashes — this is vulnerable to rainbow table attacks and fast brute force. Use bcrypt, which is designed specifically for password hashing: deliberately slow and containing automatic salt.

import "golang.org/x/crypto/bcrypt"

// Hash a password at registration
func hashPassword(password string) (string, error) {
    // Cost factor: 10 is the minimum recommendation (2^10 = 1024 iterations)
    // Higher = more secure but slower
    // Use 12-14 for production if the hardware allows it
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
    if err != nil {
        return "", fmt.Errorf("hashPassword: %w", err)
    }
    return string(bytes), nil
}

// Verify a password at login
func verifyPassword(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

// Check whether a hash needs updating (cost factor lower than the current standard)
func needsRehash(hash string, minCost int) bool {
    cost, err := bcrypt.Cost([]byte(hash))
    if err != nil {
        return true // assume a rehash is needed on error
    }
    return cost < minCost
}

// A full flow example
func registerUser(db *sql.DB, email, password string) error {
    // Validate the password
    if len(password) < 8 {
        return fmt.Errorf("password must be at least 8 characters")
    }

    // Hash the password
    hash, err := hashPassword(password)
    if err != nil {
        return fmt.Errorf("failed to hash password: %w", err)
    }

    // Store in the database — store the HASH, not the real password!
    _, err = db.Exec(
        "INSERT INTO users (email, password_hash) VALUES ($1, $2)",
        email, hash)
    return err
}

func loginUser(db *sql.DB, email, password string) (*User, error) {
    var user User
    var hash string

    err := db.QueryRow(
        "SELECT id, email, name, password_hash FROM users WHERE email = $1",
        email,
    ).Scan(&user.ID, &user.Email, &user.Name, &hash)

    if err == sql.ErrNoRows {
        // Don't reveal whether the email is registered or not
        // Still run the verification to prevent timing attacks
        bcrypt.CompareHashAndPassword([]byte("$2a$12$dummy"), []byte(password))
        return nil, fmt.Errorf("wrong email or password")
    }
    if err != nil {
        return nil, fmt.Errorf("user query: %w", err)
    }

    if !verifyPassword(password, hash) {
        return nil, fmt.Errorf("wrong email or password")
    }

    // Rehash if the cost factor is too low
    if needsRehash(hash, 12) {
        newHash, _ := hashPassword(password)
        db.Exec("UPDATE users SET password_hash=$1 WHERE id=$2",
            newHash, user.ID)
    }

    return &user, nil
}

crypto/subtle — Constant-Time Comparison #

Ordinary string comparison (== or bytes.Equal) is vulnerable to timing attacks — an attacker can measure the time needed to get hints about the string’s content. crypto/subtle provides constant-time operations that take the same time regardless of the input:

import "crypto/subtle"

// ANTI-PATTERN: ordinary comparison — vulnerable to timing attacks
func verifyTokenBad(receivedToken, validToken string) bool {
    return receivedToken == validToken
    // The time varies based on the length of the matching prefix
    // An attacker can guess the token character by character!
}

// CORRECT: constant-time comparison
func verifyToken(receivedToken, validToken string) bool {
    // ConstantTimeCompare returns 1 if equal, 0 if not
    return subtle.ConstantTimeCompare(
        []byte(receivedToken),
        []byte(validToken),
    ) == 1
}

// XOR — a constant-time operation on byte slices
func xorSlices(a, b []byte) []byte {
    if len(a) != len(b) {
        panic("slices must have the same length")
    }
    result := make([]byte, len(a))
    subtle.XORBytes(result, a, b) // Go 1.20+
    return result
}

// ConstantTimeByteEq — compare two bytes in constant time
fmt.Println(subtle.ConstantTimeByteEq(0x41, 0x41)) // 1 (equal)
fmt.Println(subtle.ConstantTimeByteEq(0x41, 0x42)) // 0 (different)

Production Security Patterns #

Signed URLs with HMAC #

// Create a signature-protected URL for temporary downloads
type SignedURL struct {
    URL       string
    ExpiresAt time.Time
    Signature string
}

func makeSignedURL(baseURL, path, secret string, duration time.Duration) SignedURL {
    expiresAt := time.Now().Add(duration)
    payload := fmt.Sprintf("%s|%d", path, expiresAt.Unix())
    sig := makeHMAC([]byte(secret), []byte(payload))

    return SignedURL{
        URL:       baseURL + path,
        ExpiresAt: expiresAt,
        Signature: hex.EncodeToString(sig),
    }
}

func verifySignedURL(path, signatureHex, secret string, expiresAt time.Time) bool {
    // Check the expiry
    if time.Now().After(expiresAt) {
        return false
    }

    payload := fmt.Sprintf("%s|%d", path, expiresAt.Unix())
    sigBytes, err := hex.DecodeString(signatureHex)
    if err != nil {
        return false
    }

    expected := makeHMAC([]byte(secret), []byte(payload))
    return hmac.Equal(expected, sigBytes)
}

File Fingerprints for Change Detection #

// Calculate and store a file fingerprint for change detection
type Fingerprint struct {
    Path    string
    SHA256  string
    Size    int64
    ModTime time.Time
}

func computeFingerprint(path string) (*Fingerprint, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("open file: %w", err)
    }
    defer f.Close()

    info, err := f.Stat()
    if err != nil {
        return nil, fmt.Errorf("stat file: %w", err)
    }

    h := sha256.New()
    if _, err := io.Copy(h, f); err != nil {
        return nil, fmt.Errorf("hashing: %w", err)
    }

    return &Fingerprint{
        Path:    path,
        SHA256:  hex.EncodeToString(h.Sum(nil)),
        Size:    info.Size(),
        ModTime: info.ModTime(),
    }, nil
}

func verifyIntegrity(path, expectedHash string) (bool, error) {
    fp, err := computeFingerprint(path)
    if err != nil {
        return false, err
    }
    return subtle.ConstantTimeCompare(
        []byte(fp.SHA256),
        []byte(expectedHash),
    ) == 1, nil
}

Password Reset Tokens #

type PasswordResetToken struct {
    Token     string
    UserID    int
    ExpiresAt time.Time
}

// Create a secure password reset token
func createResetToken(db *sql.DB, email string) (*PasswordResetToken, error) {
    // Find the user
    var userID int
    err := db.QueryRow(
        "SELECT id FROM users WHERE email = $1", email).Scan(&userID)
    if err != nil {
        // Don't reveal whether the email is registered
        return nil, fmt.Errorf("if the email is registered, a reset link was sent")
    }

    // Generate a secure random token (32 bytes = 64 hex chars)
    tokenBytes, err := randomBytes(32)
    if err != nil {
        return nil, fmt.Errorf("generate token: %w", err)
    }
    token := hex.EncodeToString(tokenBytes)

    // Hash the token before storing it in the database
    // So if the database leaks, the tokens can't be used
    tokenHash := sha256.Sum256([]byte(token))
    tokenHashHex := hex.EncodeToString(tokenHash[:])

    expiresAt := time.Now().Add(1 * time.Hour)

    // Store the token hash, not the actual token
    _, err = db.Exec(
        "INSERT INTO reset_tokens (user_id, token_hash, expires_at) VALUES ($1, $2, $3) "+
        "ON CONFLICT (user_id) DO UPDATE SET token_hash=$2, expires_at=$3",
        userID, tokenHashHex, expiresAt)
    if err != nil {
        return nil, fmt.Errorf("store token: %w", err)
    }

    return &PasswordResetToken{
        Token:     token, // send the actual token via email
        UserID:    userID,
        ExpiresAt: expiresAt,
    }, nil
}

// Verify a reset token
func verifyResetToken(db *sql.DB, token string) (int, error) {
    // Hash the received token
    tokenHash := sha256.Sum256([]byte(token))
    tokenHashHex := hex.EncodeToString(tokenHash[:])

    var userID int
    var expiresAt time.Time

    err := db.QueryRow(
        "SELECT user_id, expires_at FROM reset_tokens WHERE token_hash = $1",
        tokenHashHex,
    ).Scan(&userID, &expiresAt)

    if err == sql.ErrNoRows {
        return 0, fmt.Errorf("invalid or already used token")
    }
    if err != nil {
        return 0, fmt.Errorf("token verification: %w", err)
    }

    if time.Now().After(expiresAt) {
        // Delete the expired token
        db.Exec("DELETE FROM reset_tokens WHERE token_hash = $1", tokenHashHex)
        return 0, fmt.Errorf("token expired")
    }

    // Delete the token after use (one-time use)
    db.Exec("DELETE FROM reset_tokens WHERE token_hash = $1", tokenHashHex)

    return userID, nil
}

Cryptographic Mistakes to Avoid #

flowchart TD
    subgraph Wrong["✗ Common Mistakes"]
        E1["Use math/rand\nfor tokens/keys"]
        E2["Store passwords\nas plain SHA-256"]
        E3["Compare tokens\nwith == or bytes.Equal"]
        E4["Use MD5\nfor security"]
        E5["Store the actual token\nin the database"]
        E6["Reveal whether the\nemail is registered\nat login/reset"]
    end

    subgraph Right["✓ What You Should Do"]
        C1["Use crypto/rand\nfor all cryptographic randomness"]
        C2["Use bcrypt\nfor password hashing"]
        C3["Use subtle.ConstantTimeCompare\nor hmac.Equal"]
        C4["Use SHA-256 or SHA-512\nfor security"]
        C5["Store the token hash\nin the database"]
        C6["Return the same response\nfor valid/invalid emails"]
    end

    E1 -.->|"fix"| C1
    E2 -.->|"fix"| C2
    E3 -.->|"fix"| C3
    E4 -.->|"fix"| C4
    E5 -.->|"fix"| C5
    E6 -.->|"fix"| C6

    style Wrong fill:#fce4ec
    style Right fill:#e8f5e9
// ✗ ANTI-PATTERN: math/rand for tokens
import "math/rand"
token := fmt.Sprintf("%d", rand.Int63()) // NOT SECURE!

// ✓ CORRECT: crypto/rand
tokenBytes, _ := randomBytes(32)
token := hex.EncodeToString(tokenBytes)

// ✗ ANTI-PATTERN: SHA-256 for passwords
hash := sha256.Sum256([]byte(password)) // easy to brute force!

// ✓ CORRECT: bcrypt
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 12)

// ✗ ANTI-PATTERN: ordinary comparison for tokens
if receivedToken == storedToken { // timing attack!

// ✓ CORRECT: constant-time
if subtle.ConstantTimeCompare([]byte(receivedToken), []byte(storedToken)) == 1 {

// ✗ ANTI-PATTERN: store the actual token in the DB
db.Exec("INSERT INTO tokens (token) VALUES ($1)", token)
// If the DB leaks, all tokens can be used!

// ✓ CORRECT: store the token hash
h := sha256.Sum256([]byte(token))
db.Exec("INSERT INTO tokens (token_hash) VALUES ($1)", hex.EncodeToString(h[:]))

When to Switch to Alternatives #

Keep using the crypto standard library if:
  ✓ SHA-256 / SHA-512 for data hashing and integrity
  ✓ crypto/rand for all cryptographic randomness needs
  ✓ crypto/hmac for keyed integrity verification
  ✓ crypto/subtle for constant-time comparison

Use golang.org/x/crypto (the extended library) for:
  ✓ bcrypt — password hashing
  ✓ argon2 — modern password hashing (stronger than bcrypt)
  ✓ scrypt — password hashing with a memory-hard function
  ✓ chacha20poly1305 — modern symmetric encryption
  ✓ ssh — SSH clients and servers
  ✓ tls — more advanced TLS configuration

Consider external libraries if:
  ✗ JWT (JSON Web Token) → golang-jwt/jwt
  ✗ OAuth 2.0 → golang.org/x/oauth2
  ✗ OpenPGP → golang.org/x/crypto/openpgp
  ✗ Hardware Security Modules (HSM) → vendor-specific libraries

NEVER implement your own cryptographic algorithms —
always use battle-tested implementations from the standard library
or trusted libraries.

Summary #

  • crypto/rand, not math/rand for all cryptographic needs — math/rand is a PRNG whose output can be predicted; crypto/rand uses operating system entropy that can’t be predicted.
  • SHA-256 for data hashing, not SHA-1 or MD5 — SHA-1 and MD5 have proven cryptographic weaknesses and must not be used for security.
  • bcrypt (or argon2) for password hashing — SHA-256 isn’t suitable for passwords because it’s too fast, making brute force easy; bcrypt is deliberately slow and contains automatic salt.
  • hmac.Equal or subtle.ConstantTimeCompare for comparing cryptographic values — ordinary comparison (==) is vulnerable to timing attacks that allow an attacker to guess values character by character.
  • Store token hashes, not the actual tokens in the database — if the database leaks, an attacker can’t use the tokens directly because they’d need to know the original values.
  • io.Copy into a hasher for hashing large files — avoid reading the entire file into memory; use streaming for efficiency.
  • HMAC for integrity verification with a secret key — proving that the message wasn’t modified AND comes from the party holding the key.
  • Don’t reveal excessive information on login/reset endpoints — return the same response for valid and invalid emails to prevent user enumeration attacks.
  • Never implement your own cryptography — always use battle-tested implementations from the standard library or golang.org/x/crypto.

← Previous: Encoding Csv   Next: Testing →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact