Перейти к содержимому

OWASP Top 10 в Go: secure coding practices

Зачем знать: Security — это не “feature”, это base requirement. Middle 2 разработчик должен знать OWASP Top 10 наизусть и понимать, как каждая категория применяется в Go-коде. Один баг с SQL injection может стоить компании миллионы (GDPR fines, утечка данных, brand damage). При этом большинство уязвимостей легко избежать, если знаешь стандартные паттерны: parameterized queries, html/template, validation, crypto/rand. В 2026 году security review — обязательная стадия любого production code review.

  1. Концепция: OWASP Top 10 2021 в Go
  2. Production-практики: validation, crypto, secrets
  3. Gotchas: subtle bugs, race conditions, timing attacks
  4. Real cases: Equifax, log4shell, supply chain
  5. Вопросы для собеседования
  6. Practice
  7. Источники

Текущая версия (2021) от Open Web Application Security Project:

  1. A01: Broken Access Control — нарушение прав доступа.
  2. A02: Cryptographic Failures — слабая криптография.
  3. A03: Injection — SQL, command, LDAP injection.
  4. A04: Insecure Design — отсутствие threat modeling.
  5. A05: Security Misconfiguration — небезопасные defaults.
  6. A06: Vulnerable and Outdated Components — устаревшие зависимости.
  7. A07: Identification and Authentication Failures — слабая auth.
  8. A08: Software and Data Integrity Failures — supply chain.
  9. A09: Security Logging and Monitoring Failures — отсутствие audit.
  10. A10: Server-Side Request Forgery (SSRF) — SSRF атаки.

Изменения с 2017: добавили A04 (Insecure Design), A10 (SSRF), убрали XSS из топа (переместили в A03 Injection).

Категория: пользователь делает что-то, на что не имеет прав.

Примеры в Go:

// УЯЗВИМО: IDOR (Insecure Direct Object Reference)
func GetOrder(w http.ResponseWriter, r *http.Request) {
orderID := r.URL.Query().Get("id")
order, _ := db.GetOrder(orderID) // не проверяем owner
json.NewEncoder(w).Encode(order)
}
// ПРАВИЛЬНО
func GetOrder(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r.Context())
orderID := r.URL.Query().Get("id")
order, err := db.GetOrder(orderID)
if err != nil { ... }
if order.UserID != userID {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
json.NewEncoder(w).Encode(order)
}

Defense:

  • ВСЕГДА проверять authorization (не только authentication).
  • Deny by default — explicit allow.
  • RBAC / ABAC / OPA / Casbin (см. файл 38).
  • Audit logs для access decisions.

Категория: неправильное использование crypto.

Anti-patterns:

// УЯЗВИМО: MD5 для passwords
hash := md5.Sum([]byte(password))
// УЯЗВИМО: math/rand для security
n := rand.Intn(1000000) // predictable
// УЯЗВИМО: own crypto
func encrypt(data, key []byte) []byte {
// never roll your own crypto
}

Правила:

NeedUseDon’t use
Password hashingargon2 / bcryptMD5, SHA1, SHA256
Random for securitycrypto/randmath/rand
Encryptioncrypto/aes-gcm, chacha20poly1305DIY
TLScrypto/tlsPlain TCP
Hashsha256, sha512, blake2MD5, SHA1
HMACcrypto/hmacDIY

Пример password hashing:

import "golang.org/x/crypto/bcrypt"
// Hash
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
// Verify
err := bcrypt.CompareHashAndPassword(hash, []byte(input))

SQL Injection:

// УЯЗВИМО
db.Query("SELECT * FROM users WHERE email = '" + email + "'")
// ПРАВИЛЬНО — параметризованный
db.Query("SELECT * FROM users WHERE email = $1", email)

database/sql всегда использует параметры безопасно — драйвер делает escape на низком уровне.

Command Injection:

// УЯЗВИМО
exec.Command("sh", "-c", "ping " + userInput).Run()
// ПРАВИЛЬНО
exec.Command("ping", "-c", "1", userInput).Run() // args separately
// + validate userInput через regex (only IPs/domains)

Никогда не передавай user input в sh -c, и валидируй args.

Path Traversal:

// УЯЗВИМО
file := r.URL.Query().Get("file") // "../../etc/passwd"
os.Open("/uploads/" + file)
// ПРАВИЛЬНО
file := r.URL.Query().Get("file")
file = filepath.Clean(file)
fullPath := filepath.Join("/uploads", file)
// Verify результат внутри base directory
if !strings.HasPrefix(fullPath, "/uploads/") {
http.Error(w, "invalid path", http.StatusBadRequest)
return
}
os.Open(fullPath)

В Go 1.20+ есть os.Root API (Go 1.24):

root, _ := os.OpenRoot("/uploads")
defer root.Close()
f, _ := root.Open(userFile) // automatically prevents traversal

Template Injection / XSS:

// УЯЗВИМО
tmpl := template.New("page")
tmpl.Parse("<h1>" + userInput + "</h1>") // text/template
// ПРАВИЛЬНО
import "html/template"
tmpl := template.New("page")
tmpl.Parse("<h1>{{.}}</h1>") // auto-escape
tmpl.Execute(w, userInput)

LDAP Injection — фильтр sanitize:

import "github.com/go-ldap/ldap/v3"
filter := fmt.Sprintf("(uid=%s)", ldap.EscapeFilter(username))

Категория: архитектурные проблемы (не bugs).

Examples:

  • Нет rate limiting → DoS возможен.
  • Нет audit logs → нельзя расследовать.
  • Нет separation of concerns → один баг компрометирует всё.

Defense:

  • Threat modeling до coding (STRIDE, DREAD).
  • Security requirements в spec.
  • Defense in depth (multiple layers).
  • Principle of least privilege.

Examples:

# УЯЗВИМО
spec:
containers:
- image: app:latest
ports: [80, 443, 22, 3306] # экспозиция лишних портов
securityContext:
privileged: true # root в container

Defense:

  • Distroless / minimal base images.
  • runAsNonRoot: true.
  • readOnlyRootFilesystem: true.
  • Network policies — deny by default.
  • Disable unused features (admin panels, debug endpoints in prod).

В Go контексте:

Окно терминала
# Check vulnerabilities
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

Defense:

  • govulncheck в CI (см. файл 36).
  • Dependabot/Renovate для updates.
  • Минимальные dependencies (less attack surface).
  • Прикладывай go.sum (cryptographic verification).

См. файл 38 (auth & RBAC).

Common issues:

  • Weak passwords (no policy).
  • Brute force allowed (no rate limit).
  • Session hijacking (no HttpOnly, Secure cookies).
  • Predictable session IDs (math/rand).

Категория: supply chain.

Examples:

  • Unsigned packages.
  • Auto-update без verify signature.
  • Insecure deserialization (gob, JSON с unknown types).

Defense:

  • cosign для signing (файл 36).
  • SBOM tracking.
  • Verify checksums в go.sum.
// УЯЗВИМО: нет audit log
func DeleteUser(userID string) {
db.Delete(userID)
}
// ПРАВИЛЬНО
func DeleteUser(ctx context.Context, userID string) {
actor := getActor(ctx)
slog.InfoContext(ctx, "user delete",
"actor", actor.ID,
"target", userID,
"ip", getIP(ctx),
)
db.Delete(userID)
}

What to log:

  • Authentication attempts (success + fail).
  • Authorization decisions.
  • Critical data modifications.
  • API calls с PII.

Don’t log:

  • Passwords (plain or hashed).
  • Tokens, API keys.
  • Credit card numbers.
  • Personal data без masking.

Атака: сервер делает HTTP request на user-controlled URL → может dosrar internal services.

// УЯЗВИМО
func Proxy(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
resp, _ := http.Get(url) // attacker passes http://169.254.169.254/latest/meta-data
io.Copy(w, resp.Body)
}

В AWS — это metadata endpoint, может выдать IAM credentials.

Defense:

import (
"net"
"net/http"
"net/url"
)
var allowedHosts = map[string]bool{"api.example.com": true}
func safeFetch(rawURL string) (*http.Response, error) {
u, err := url.Parse(rawURL)
if err != nil { return nil, err }
// 1. Only allowed schemes
if u.Scheme != "https" {
return nil, fmt.Errorf("only https allowed")
}
// 2. Allowed hosts
if !allowedHosts[u.Hostname()] {
return nil, fmt.Errorf("host not allowed")
}
// 3. Resolve and check IP не internal
ips, err := net.LookupIP(u.Hostname())
if err != nil { return nil, err }
for _, ip := range ips {
if isPrivate(ip) || isLoopback(ip) || isLinkLocal(ip) {
return nil, fmt.Errorf("blocked IP")
}
}
return http.Get(rawURL)
}
func isPrivate(ip net.IP) bool {
private := []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16"}
for _, cidr := range private {
_, block, _ := net.ParseCIDR(cidr)
if block.Contains(ip) { return true }
}
return false
}

Дополнительные защиты:

  • Use HTTP proxy с allowlist.
  • AWS IMDSv2 (требует token).
  • Network policies — block egress на internal.

import "github.com/go-playground/validator/v10"
type CreateUserReq struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=12,containsany=!@#$%^&*"`
Age int `json:"age" validate:"gte=18,lte=120"`
Username string `json:"username" validate:"required,alphanum,min=3,max=30"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req CreateUserReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 400)
return
}
v := validator.New()
if err := v.Struct(req); err != nil {
http.Error(w, err.Error(), 400)
return
}
// ... process
}
import "crypto/rand"
// Generate token
func generateToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// Generate int in range
func randomInt(max int64) (int64, error) {
n, err := rand.Int(rand.Reader, big.NewInt(max))
if err != nil { return 0, err }
return n.Int64(), nil
}
import "github.com/gorilla/csrf"
csrfMiddleware := csrf.Protect(
[]byte("32-byte-long-auth-key-rotated"),
csrf.Secure(true), // only HTTPS
csrf.SameSite(csrf.SameSiteStrictMode),
csrf.HttpOnly(true),
)
http.ListenAndServe(":8080", csrfMiddleware(handler))

Modern alternative — SameSite cookies:

http.SetCookie(w, &http.Cookie{
Name: "session",
Value: sessionID,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
})
import "golang.org/x/time/rate"
type Limiter struct {
mu sync.Mutex
limiters map[string]*rate.Limiter
}
func (l *Limiter) Get(key string) *rate.Limiter {
l.mu.Lock()
defer l.mu.Unlock()
if r, ok := l.limiters[key]; ok {
return r
}
r := rate.NewLimiter(rate.Every(time.Second), 10) // 10 req/s
l.limiters[key] = r
return r
}
func middleware(l *Limiter) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := getIP(r)
if !l.Get(ip).Allow() {
http.Error(w, "rate limited", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}

Distributed: Redis-based (go-redis-rate или DIY).

func securityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h := w.Header()
h.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
h.Set("X-Content-Type-Options", "nosniff")
h.Set("X-Frame-Options", "DENY")
h.Set("Content-Security-Policy", "default-src 'self'")
h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
h.Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
next.ServeHTTP(w, r)
})
}
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS13,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
},
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
}
srv := &http.Server{
Addr: ":443",
TLSConfig: tlsConfig,
// ...
}

В 2026 — TLS 1.3 как baseline. TLS 1.2 — minimum для legacy.

Layered approach:

1. Local development:
- .env files (НЕ commit)
- direnv для project-scoped
2. CI/CD:
- GitHub Secrets / GitLab Variables (encrypted)
- OIDC для cloud (no static keys)
3. Production:
- Vault / AWS Secrets Manager / GCP Secret Manager
- SOPS для encrypted YAML в Git
- External Secrets Operator (k8s)
- CSI Secrets Store

Vault example:

import "github.com/hashicorp/vault/api"
config := api.DefaultConfig()
client, _ := api.NewClient(config)
client.SetToken(os.Getenv("VAULT_TOKEN"))
secret, _ := client.KVv2("secret").Get(ctx, "myapp/db")
password := secret.Data["password"].(string)

File: AES-GCM via crypto/cipher:

func encrypt(plaintext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil { return nil, err }
gcm, err := cipher.NewGCM(block)
if err != nil { return nil, err }
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil { return nil, err }
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
func decrypt(ciphertext, key []byte) ([]byte, error) {
block, _ := aes.NewCipher(key)
gcm, _ := cipher.NewGCM(block)
if len(ciphertext) < gcm.NonceSize() {
return nil, errors.New("ciphertext too short")
}
nonce, ct := ciphertext[:gcm.NonceSize()], ciphertext[gcm.NonceSize():]
return gcm.Open(nil, nonce, ct, nil)
}
// УЯЗВИМО: timing attack
if storedToken == providedToken {
// ...
}
// ПРАВИЛЬНО
import "crypto/subtle"
if subtle.ConstantTimeCompare(
[]byte(storedToken),
[]byte(providedToken),
) == 1 {
// ...
}
type User struct {
ID string
Email string
Password string // sensitive
APIKey string // sensitive
}
func (u User) LogValue() slog.Value {
return slog.GroupValue(
slog.String("id", u.ID),
slog.String("email", redactEmail(u.Email)),
// НЕ Password, НЕ APIKey
)
}
slog.Info("user logged in", "user", user)
// Output: user={id=123 email=a***@example.com}
.github/workflows/security.yml
- name: gosec
uses: securego/gosec@master
with:
args: ./...
- name: govulncheck
run: govulncheck ./...
- name: gitleaks
uses: gitleaks/gitleaks-action@v2
- name: trivy
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
severity: HIGH,CRITICAL
  • Static analysis — каждый PR (CI).
  • Dependency scan — каждый PR + weekly.
  • DAST (dynamic) — eachly per environment.
  • Pen test — annual + после major release.
  • Bug bounty — continuous.

DoS attack — send huge JSON:

// УЯЗВИМО
json.NewDecoder(r.Body).Decode(&data)
// ПРАВИЛЬНО
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1MB
json.NewDecoder(r.Body).Decode(&data)

encoding/gob с unknown types может triggered allocation bomb. Avoid gob с untrusted data.

При decoding ZIP from user — check decompressed size.

// УЯЗВИМО
re := regexp.MustCompile(`^(a+)+$`)
re.MatchString("aaaaaaaaaaaaaaaaaaaaaa!") // exponential time

Go’s regexp package использует RE2 (linear time), но не все regex libraries.

Go 1.21 имел уязвимость. Update Go!

filepath.Clean не resolve symlinks. Используй filepath.EvalSymlinks или os.Root API (Go 1.24).

// УЯЗВИМО: TOCTOU
if isAdmin(user) {
// user может потерять admin между check и action
doAdminAction(user)
}
// ПРАВИЛЬНО: atomic check + action
return db.Transaction(func(tx) {
if !isAdmin(tx, user) { return err }
return doAdminAction(tx, user)
})
// УЯЗВИМО
http.Redirect(w, r, r.URL.Query().Get("next"), 302)
// ПРАВИЛЬНО
next := r.URL.Query().Get("next")
if !strings.HasPrefix(next, "/") || strings.HasPrefix(next, "//") {
next = "/"
}
http.Redirect(w, r, next, 302)

Go’s encoding/xml не resolve external entities by default — safe.

// УЯЗВИМО
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
sendToClient(buf) // отправляем 1024 байта, включая uninitialized
// ПРАВИЛЬНО
sendToClient(buf[:n])

Если slice content sensitive (password buffer), capacity growth копирует данные → old memory не cleared. Use sync.Pool или runtime.GC после use.

Tokens на основе timestamp predictable. Use crypto/rand.

// УЯЗВИМО
http.HandleFunc("/start", func(w, r) {
go longTask() // никогда не cancelled
})

Attacker spam’ит /start → out of memory.

Если cookies Secure: false — отправлены через HTTP. MITM перехватит. Always Secure: true в prod.


  • Apache Struts vulnerability (CVE-2017-5638).
  • Не patched 2 месяца.
  • 147 million records leaked.
  • $1.4 billion settlement.

Lesson: dependency updates критичны.

  • log4j vulnerability (Java, не Go, но lesson generic).
  • JNDI lookup в log statements → RCE.
  • Тысячи компаний affected.

Lesson: даже logging library может быть attack vector.

  • Supply chain attack.
  • Backdoor inserted в build process.
  • 18,000 customers affected.

Lesson: secure CI/CD critical.

  • Поломаны через path traversal в job artifacts.
  • Compromised CI runners.

Lesson: validate всё, даже из internal source.

Periodically database vulnerabilities. Govulncheck ловит зависимости.

В 2022 некий exchange имел SSRF в проксях. Attacker fetch’ил AWS metadata → IAM creds → drained accounts.

Через OAuth redirect_uri открытые перенаправления — phishing. Whitelist redirect_uris.

Кейс из 2023: компания использовала text/template для HTML emails. User-controlled поле без escape → XSS в email клиентах.


Q1: Что такое OWASP Top 10? A: Список 10 наиболее критичных security рисков для web applications от Open Web Application Security Project. Версия 2021: Broken Access Control, Crypto Failures, Injection, Insecure Design, Misconfiguration, Vulnerable Components, Auth Failures, Integrity Failures, Logging Failures, SSRF.

Q2: Как защититься от SQL injection в Go? A: Использовать параметризованные запросы через database/sql: db.Query("SELECT ... WHERE id = $1", id). Никогда не concat’енировать user input в SQL. ORM (gorm, sqlx) делают это автоматически.

Q3: Чем html/template отличается от text/template? A: html/template auto-escape user data в HTML context (предотвращает XSS). text/template для plain text, не escape. Для генерации HTML всегда html/template.

Q4: math/rand vs crypto/rand? A: math/rand — pseudo-random, predictable, для simulations. crypto/rand — cryptographically secure, для tokens, keys, secrets. ВСЕГДА crypto/rand для security.

Q5: Какой алгоритм для password hashing? A: bcrypt (классика), argon2 (победитель Password Hashing Competition 2015), scrypt. НИКОГДА MD5, SHA1, SHA256 (слишком быстрые — brute force легче).

Q6: Что такое SSRF и как защититься? A: Server-Side Request Forgery — атакующий заставляет сервер делать HTTP запрос на внутренний ресурс (AWS metadata, internal admin panel). Защита: allowlist хостов, блокировка приватных IPs после DNS resolve, отдельный egress proxy.

Q7: Path traversal в Go — как защититься? A: filepath.Clean + verify, что результат внутри base directory (strings.HasPrefix). В Go 1.24 — os.Root API, который автоматически предотвращает escape.

Q8: Что такое CSRF и как защититься? A: Cross-Site Request Forgery — атака, где зловредный сайт делает request от имени пользователя к target сайту. Защита: CSRF tokens (gorilla/csrf), SameSite=Strict cookies, проверка Origin/Referer header.

Q9: Что такое timing attack? A: Атакующий измеряет время response для extract информации. Например, == сравнение byte-by-byte → можно угадать password char-by-char. Защита: crypto/subtle.ConstantTimeCompare.

Q10: Что такое govulncheck? A: Official Go tool, scan’ит зависимости vs Go vulnerability database. Уникальность: показывает только vulnerabilities, которые реально вызываются в коде (statically analyzed). Reduces false positives.

Q11: Какие HTTP security headers важны? A: Strict-Transport-Security (HSTS), Content-Security-Policy (CSP), X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy, Permissions-Policy. Defense in depth.

Q12: Что такое HSTS? A: HTTP Strict Transport Security — header, заставляющий browser использовать только HTTPS для domain. Преволочает SSL stripping атаки. max-age=31536000; includeSubDomains; preload.

Q13: Как rate limit’ить в Go? A: golang.org/x/time/rate для in-process. Distributed — Redis-based (например go-redis-rate). Granularity: per IP, per user, per API key, per endpoint. Возвращай 429 Too Many Requests с Retry-After.

Q14: Что такое SOPS? A: Mozilla SOPS — encrypted YAML/JSON в Git. Шифрует только values (keys видны). Поддерживает AWS KMS, GCP KMS, Vault, age. Стандарт для storing secrets в Git репо.

Q15: Где хранить secrets в production? A: Vault / AWS Secrets Manager / GCP Secret Manager. В k8s — External Secrets Operator или CSI Secrets Store sync’ит из Vault в Secret. SOPS для GitOps. Никогда в коде или env vars в plain.

Q16: Что такое TLS 1.3 и зачем? A: Latest TLS version (RFC 8446). Меньше handshake (1-RTT), better security (removed weak ciphers), forward secrecy by default. В 2026 — baseline для new services. TLS 1.2 — minimum для legacy compatibility.

Q17: Что такое JWT и его уязвимости? A: JSON Web Token. Уязвимости: alg=none (sign verification skip), weak HS256 key (brute force), token in URL (logs leak), no expiration. См. файл 38.

Q18: Что такое insecure deserialization? A: Десериализация untrusted data → RCE / DoS. В Go: gob с unknown types может trigger alloc bomb. JSON относительно safe но check sizes. Validate input после Unmarshal.

Q19: Как защититься от open redirect? A: Validate redirect URL: starts with /, not с //, не absolute URL. Whitelist allowed redirect domains. Не verbatim pass user input в Redirect.

Q20: Что НЕ должно быть в логах? A: Passwords (даже хэшированные), tokens, API keys, credit card numbers, PII без masking. Используй custom LogValue() для sanitize объектов. Redact в Collector.


  1. OWASP audit: возьми существующий Go проект, найди по 1 проблеме на каждую категорию Top 10.

  2. SSRF defense: реализуй safeFetch() с allowlist + блокировкой private IPs.

  3. Path traversal: создай file server. Сделай vulnerable version. Эксплуатируй (через ../). Исправь через os.Root.

  4. Password hashing: сравни performance bcrypt vs argon2id. Выбери cost factor для 250ms на hash.

  5. Rate limit middleware: реализуй per-IP rate limit с golang.org/x/time/rate. Test через k6.

  6. Validator setup: добавь go-playground/validator во все API requests. Покрытие 100% входов.

  7. TLS config audit: настрой TLS 1.3-only с правильным cipher suite. Test через testssl.sh.

  8. Govulncheck integration: добавь в CI, fix all findings.


  1. OWASP Top 10 2021: https://owasp.org/Top10/.
  2. OWASP Go-SCP: https://github.com/OWASP/Go-SCP — secure coding practices Go.
  3. Go Security Best Practices: https://go.dev/security/best-practices.
  4. govulncheck: https://go.dev/security/vuln/.
  5. gosec: https://github.com/securego/gosec.
  6. Vault: https://www.vaultproject.io/docs.
  7. “Web Application Hacker’s Handbook” — Stuttard, Pinto.
  8. OWASP Cheat Sheet Series: https://cheatsheetseries.owasp.org/.
  9. “Real-World Cryptography” — David Wong, Manning.
  10. NIST Crypto Standards: https://csrc.nist.gov/.