crypto-audit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Crypto Audit — Cryptography Implementation Review

密码学审计——加密实现审查

Audit how cryptography is implemented in an application — algorithm choices, parameters, modes, and the implementation patterns that turn good primitives into broken systems. Deeper than
owasp-audit
A02 (which catches the obvious "MD5 password" and "VERIFY_NONE" cases). This skill is for the subtler implementation review.
Most crypto failures are not "they used MD5." Most failures are: right primitive, wrong mode (ECB instead of GCM), right algorithm, wrong parameter (PBKDF2 with 1,000 iterations in 2026), right library, wrong call order (init the cipher after the data was loaded).
Cross-references:
owasp-audit
A02 (baseline) + A07 (timing-safe comparison),
secrets-audit
(key storage),
iam-audit
(KMS / HSM patterns).
审计应用中密码学的实现方式——包括算法选择、参数、模式,以及将优秀基础算法转化为脆弱系统的实现模式。比
owasp-audit
A02(仅能发现“MD5存储密码”“VERIFY_NONE”这类明显问题)的审查更深入,此技能用于更细微的实现层面审查。
大多数密码学失败并非源于“使用了MD5”,而是:选对了基础算法但用错了模式(比如用ECB而非GCM)、选对了算法但参数错误(2026年仍使用仅1000次迭代的PBKDF2)、选对了库但调用顺序错误(加载数据后才初始化密码器)。
交叉参考:
owasp-audit
A02(基线) + A07(计时安全比较)、
secrets-audit
(密钥存储)、
iam-audit
(KMS/HSM模式)。

Don't roll your own

不要自行实现加密方案

The default audit verdict for any custom encryption scheme is "use libsodium / Tink / WebCrypto instead." There are < 50 people on Earth who can design new crypto safely, and they don't work at your company. Unless an explicit threat model says otherwise, custom crypto is a finding.
对于任何自定义加密方案,默认审计结论是“改用libsodium / Tink / WebCrypto”。全球能安全设计新加密方案的人不足50位,他们不会在你的公司任职。除非有明确的威胁模型说明,否则自定义加密方案均视为问题。

Audit Checklist

审计检查清单

Algorithm and mode

算法与模式

  • Symmetric: AES-256-GCM or ChaCha20-Poly1305 (authenticated encryption — confidentiality + integrity in one primitive)
  • Reject: AES-ECB (block-pattern leak — identical plaintext → identical ciphertext), AES-CBC without HMAC (unauthenticated; padding oracle attacks), AES-CTR without HMAC (malleable; bit-flip = plaintext-flip), DES / 3DES, RC4, Blowfish (use Twofish or skip altogether)
  • Asymmetric: Ed25519 / X25519 for signatures and key exchange; RSA-OAEP / RSA-PSS at 3072+ bits if compatibility forces RSA; never RSA with PKCS#1 v1.5 padding for encryption (Bleichenbacher); never raw RSA
  • Hashing (general purpose): SHA-256, SHA-3, BLAKE2 / BLAKE3
  • Hashing (passwords) — categorically different problem: Argon2id, scrypt, bcrypt (with cost ≥ 12 for bcrypt; OWASP 2024 floor)
  • MAC: HMAC-SHA256 minimum; never CBC-MAC; never homemade
    hash(key + message)
  • Grep for:
    MD5
    ,
    SHA1
    (outside of HMAC-SHA1 in legacy compat),
    DES
    ,
    RC4
    ,
    Blowfish
    ,
    AES.*ECB
    ,
    pkcs1_v1_5
    (Python),
    RSA.encrypt
    without OAEP
  • 对称加密: 使用AES-256-GCM或ChaCha20-Poly1305(认证加密——同一基础算法同时提供保密性与完整性)
  • 禁用: AES-ECB(块模式泄露——相同明文生成相同密文)、不带HMAC的AES-CBC(无认证;存在填充 oracle 攻击风险)、不带HMAC的AES-CTR(可篡改;位翻转会导致明文翻转)、DES/3DES、RC4、Blowfish(改用Twofish或直接弃用)
  • 非对称加密: 签名与密钥交换使用Ed25519/X25519;若兼容性要求必须使用RSA,则采用3072位以上的RSA-OAEP/RSA-PSS;绝不要使用带PKCS#1 v1.5填充的RSA进行加密(存在Bleichenbacher攻击风险);绝不要使用原始RSA
  • 通用哈希: SHA-256、SHA-3、BLAKE2/BLAKE3
  • 密码哈希——完全不同的场景: Argon2id、scrypt、bcrypt(bcrypt的cost值需≥12;OWASP 2024最低要求)
  • 消息认证码(MAC): 最低要求HMAC-SHA256;绝不要使用CBC-MAC;绝不要自行实现
    hash(key + message)
  • 需搜索的关键词:
    MD5
    SHA1
    (遗留兼容性场景下的HMAC-SHA1除外)、
    DES
    RC4
    Blowfish
    AES.*ECB
    pkcs1_v1_5
    (Python)、未使用OAEP的
    RSA.encrypt

Key derivation

密钥推导

  • From a password: Argon2id (memory-hard) or PBKDF2-HMAC-SHA256 with ≥ 600,000 iterations (OWASP 2024) or scrypt with N=2^17, r=8, p=1
  • From a high-entropy secret: HKDF-SHA256 — the right primitive when you have key material and need to derive sub-keys
  • From a low-entropy secret to encryption key: PBKDF2 / Argon2 (treat it as a password)
  • Grep for:
    PBKDF2
    (check iteration count),
    HKDF
    ,
    Argon2
    ,
    scrypt
    ,
    pbkdf2_hmac
    (Python; check
    iterations
    arg)
  • 从密码推导: 使用Argon2id(内存密集型)或迭代次数≥600000的PBKDF2-HMAC-SHA256(OWASP 2024标准),或参数为N=2^17、r=8、p=1的scrypt
  • 从高熵密钥推导: HKDF-SHA256——当已有密钥材料并需要派生子密钥时的正确选择
  • 从低熵密钥推导加密密钥: PBKDF2/Argon2(将其视为密码处理)
  • 需搜索的关键词:
    PBKDF2
    (检查迭代次数)、
    HKDF
    Argon2
    scrypt
    pbkdf2_hmac
    (Python;检查
    iterations
    参数)

IV / nonce handling

IV/随机数处理

Wrong IV / nonce handling is one of the top three sources of "the crypto looks right but actually leaks plaintext."
  • AES-GCM: unique nonce per encryption under the same key. NEVER reuse. If you reuse a GCM nonce with the same key, you give the attacker the XOR of two plaintexts and the ability to forge messages. Use 96-bit random nonces (RFC 5116). For high-volume systems, switch to AES-GCM-SIV (nonce-misuse-resistant)
  • AES-CBC: IV must be unpredictable AND unique. Random 16-byte IV per encryption
  • AES-CTR: counter must never repeat for a (key, counter) pair within the lifetime of the key
  • ChaCha20-Poly1305: unique nonce per message; XChaCha20-Poly1305 has 192-bit nonce so random nonces are safe at scale
  • Grep for: hardcoded IVs (
    iv = "0000000000000000"
    ,
    iv = bytes(16)
    ), zero-IV constructors, counter resets
  • Specifically grep for:
    Cipher.getInstance("AES")
    (Java default is ECB),
    AES.new(key)
    (PyCryptodome default is ECB),
    crypto.createCipher
    (Node, deprecated, derives IV from key — DON'T)
IV/随机数处理错误是导致“加密看似正确但实际泄露明文”的三大原因之一。
  • AES-GCM: 同一密钥下每次加密需使用唯一的随机数。绝不能重复使用。若同一密钥重复使用GCM随机数,攻击者将获得两个明文的异或结果,还能伪造消息。使用96位随机随机数(RFC 5116)。高流量系统应切换为AES-GCM-SIV(抗随机数误用)
  • AES-CBC: IV必须不可预测且唯一。每次加密使用16字节随机IV
  • AES-CTR: 同一密钥的计数器在密钥生命周期内绝不能重复
  • ChaCha20-Poly1305: 每条消息使用唯一随机数;XChaCha20-Poly1305的随机数为192位,因此随机随机数在大规模场景下是安全的
  • 需搜索的关键词:硬编码IV(
    iv = "0000000000000000"
    iv = bytes(16)
    )、零IV构造函数、计数器重置
  • 重点搜索:
    Cipher.getInstance("AES")
    (Java默认是ECB)、
    AES.new(key)
    (PyCryptodome默认是ECB)、
    crypto.createCipher
    (Node已废弃,从密钥派生IV——禁用)

Authenticated encryption

认证加密

  • Always use authenticated modes — AES-GCM, ChaCha20-Poly1305, or Encrypt-then-MAC (HMAC-SHA256 over ciphertext)
  • Never decrypt → check MAC; always check MAC → then decrypt (otherwise: padding oracle)
  • If using Encrypt-then-MAC, use separate keys for encryption and authentication (or HKDF-derive both from one master key)
  • Verify the MAC with a constant-time compare (
    crypto.timingSafeEqual
    in Node,
    hmac.compare_digest
    in Python,
    subtle.ConstantTimeCompare
    in Go) — see
    owasp-audit
    A07
  • 始终使用认证模式——AES-GCM、ChaCha20-Poly1305,或Encrypt-then-MAC(对密文使用HMAC-SHA256)
  • 绝不要先解密再检查MAC;始终先检查MAC再解密(否则会存在填充oracle风险)
  • 若使用Encrypt-then-MAC,加密与认证需使用独立密钥(或从一个主密钥通过HKDF派生两个密钥)
  • 使用常数时间比较验证MAC(Node中的
    crypto.timingSafeEqual
    、Python中的
    hmac.compare_digest
    、Go中的
    subtle.ConstantTimeCompare
    )——参考
    owasp-audit
    A07

Signature verification

签名验证

  • The most common signature-verification bug isn't a broken algorithm — it's not checking the signature at all, or checking the algorithm from the message itself
  • JWT: verify
    alg
    is exactly what your code expects. Never call
    jwt.decode
    and use the claims without
    jwt.verify
    . Many libraries accept
    alg: none
    (un-signed) by default — verify the library version doesn't have this
  • Webhook signatures: verify the signature BEFORE doing anything else with the body. Many implementations parse-then-verify, leaving a JSON-parsing attack surface
  • Constant-time comparison for the signature byte string
  • Timestamp tolerance window — accept signatures from within ± 5 minutes (Stripe, GitHub, Slack all do this), not "forever" (replay attack)
  • See
    owasp-audit
    A02 type-coercion in signature paths (
    parseInt → NaN
    )
  • Grep for:
    jwt.decode
    (without subsequent
    verify
    ),
    alg: 'none'
    ,
    verify.*sig
    without
    timingSafeEqual
    nearby
  • 最常见的签名验证漏洞并非算法问题,而是完全未验证签名,或从消息本身获取算法进行验证
  • JWT: 验证
    alg
    完全符合代码预期。绝不要调用
    jwt.decode
    后直接使用声明而不调用
    jwt.verify
    。许多库默认接受
    alg: none
    (无签名)——需验证库版本不存在此问题
  • Webhook签名: 在处理请求体之前先验证签名。许多实现先解析再验证,留下JSON解析攻击面
  • 签名字节串需使用常数时间比较
  • 时间戳容忍窗口——接受±5分钟内的签名(Stripe、GitHub、Slack均采用此策略),而非“永久有效”(避免重放攻击)
  • 参考
    owasp-audit
    A02中签名路径的类型转换问题(
    parseInt → NaN
  • 需搜索的关键词:
    jwt.decode
    (未后续调用
    verify
    )、
    alg: 'none'
    、未搭配
    timingSafeEqual
    verify.*sig

Randomness

随机性

  • Use:
    crypto.randomBytes
    (Node),
    secrets.token_bytes
    (Python ≥ 3.6),
    crypto/rand
    (Go),
    SecRandomCopyBytes
    (iOS),
    SecureRandom
    (Java)
  • Never use:
    Math.random()
    (Node — Mersenne Twister, predictable),
    random.random()
    (Python — same),
    rand()
    (C — terrible),
    arc4random_uniform
    for crypto (BSD — historical name only, but verify the runtime)
  • For UUIDs, prefer UUID v4 from a CSPRNG (most language stdlibs do this correctly; verify by reading the implementation if it matters)
  • Token / session ID minimum entropy — 128 bits (16 random bytes, base64 → 22 chars) is the floor; 256 bits is the sane default
  • Grep for:
    Math.random
    ,
    random.random
    ,
    rand(
    ,
    mt_rand
    (PHP),
    Random.new
    (Ruby)
  • 推荐使用:
    crypto.randomBytes
    (Node)、
    secrets.token_bytes
    (Python ≥3.6)、
    crypto/rand
    (Go)、
    SecRandomCopyBytes
    (iOS)、
    SecureRandom
    (Java)
  • 禁止使用:
    Math.random()
    (Node——基于Mersenne Twister,可预测)、
    random.random()
    (Python——同理)、
    rand()
    (C——安全性极差)、用于加密的
    arc4random_uniform
    (BSD——仅为历史名称,需验证运行时)
  • UUID优先使用CSPRNG生成的UUID v4(大多数语言标准库已正确实现;若重要可查阅实现代码验证)
  • Token/会话ID最小熵——下限为128位(16个随机字节,base64编码后为22字符);合理默认值为256位
  • 需搜索的关键词:
    Math.random
    random.random
    rand(
    mt_rand
    (PHP)、
    Random.new
    (Ruby)

TLS configuration

TLS配置

  • Versions: TLS 1.3 preferred, TLS 1.2 minimum, refuse TLS 1.0 / 1.1 / SSLv3 / SSLv2
  • Cipher suites (TLS 1.2): ECDHE only, AEAD ciphers only (AES-GCM, ChaCha20-Poly1305). Reject CBC, RC4, NULL, EXPORT, anonymous
  • Certificate validation:
    VERIFY_PEER
    , full chain, hostname check enabled (see
    owasp-audit
    A02 for managed-service caveat)
  • Certificate pinning: for high-trust connections (mobile apps to your backend, sensitive internal services); pair with backup pin (rotation)
  • HSTS preload: verified every subdomain serves HTTPS first; preload submission is sticky (months to remove)
  • OCSP stapling for performance and privacy
  • Test with:
    testssl.sh https://target
    or
    sslyze --regular target
  • 版本: 优先使用TLS 1.3,最低要求TLS 1.2,拒绝TLS 1.0/1.1/SSLv3/SSLv2
  • 密码套件(TLS 1.2): 仅使用ECDHE,仅使用AEAD密码套件(AES-GCM、ChaCha20-Poly1305)。拒绝CBC、RC4、NULL、EXPORT、匿名套件
  • 证书验证: 启用
    VERIFY_PEER
    、完整证书链检查、主机名检查(托管服务场景参考
    owasp-audit
    A02的注意事项)
  • 证书固定: 适用于高信任连接(移动应用到后端、敏感内部服务);需搭配备用固定项(用于轮换)
  • HSTS预加载: 验证所有子域名均优先提供HTTPS;预加载提交后难以撤销(需数月时间)
  • OCSP stapling 提升性能与隐私性
  • 测试工具:
    testssl.sh https://target
    sslyze --regular target

Key lifecycle

密钥生命周期

  • Where keys live: HSM / cloud KMS (AWS KMS, GCP Cloud KMS, Azure Key Vault, HashiCorp Vault Transit) — applications request encrypt / decrypt without ever seeing the key material
  • Envelope encryption: per-record data key wrapped by a customer master key — limits blast radius if any single data key is exposed
  • Rotation: master keys annually (or per provider default); data keys per record (no rotation needed — re-encrypt only if compromise suspected). KMS providers handle this if configured
  • Key versioning: every ciphertext records the key ID that encrypted it, so decryption can find the right key after rotation
  • Revocation: how do you stop a compromised key from being used to decrypt? Plan exists, documented, tested
  • 密钥存储位置: HSM/云KMS(AWS KMS、GCP Cloud KMS、Azure Key Vault、HashiCorp Vault Transit)——应用仅请求加密/解密操作,永远不会接触密钥材料
  • 信封加密: 每条记录的数据密钥由客户主密钥包裹——若单个数据密钥泄露,影响范围可控
  • 轮换: 主密钥每年轮换一次(或遵循服务商默认);数据密钥按记录生成(无需轮换——仅在怀疑泄露时重新加密)。若配置正确,KMS服务商会自动处理轮换
  • 密钥版本控制: 每个密文记录加密它的密钥ID,以便轮换后解密时能找到正确密钥
  • 吊销: 如何阻止泄露的密钥用于解密?需有计划、文档记录并经过测试

Specific framework patterns

特定框架模式

  • Rails:
    MessageVerifier
    /
    MessageEncryptor
    use modern primitives by default; verify they're configured with a strong key (32 bytes / 256 bits)
  • Django:
    cryptography.fernet
    is AES-128-CBC + HMAC-SHA256 (acceptable but not GCM);
    django.core.signing
    for short signed values
  • Node/Express: prefer
    iron-session
    /
    cookie-signature
    over rolling your own
  • iOS: CryptoKit for modern Swift code;
    CommonCrypto
    works but has more footguns
  • Android: Tink (Google) is the recommended high-level library; raw JCA has historical AES-ECB defaults
  • Rails:
    MessageVerifier
    /
    MessageEncryptor
    默认使用现代基础算法;需验证其配置了强密钥(32字节/256位)
  • Django:
    cryptography.fernet
    采用AES-128-CBC + HMAC-SHA256(可接受但不如GCM);
    django.core.signing
    用于短签名值
  • Node/Express: 优先使用
    iron-session
    /
    cookie-signature
    而非自行实现
  • iOS: 现代Swift代码使用CryptoKit;
    CommonCrypto
    可用但易出错
  • Android: 推荐使用Tink(Google)作为高级库;原生JCA存在历史默认AES-ECB的问题

Verify Fixes at Runtime

运行时验证修复效果

  • Test encryption / decryption round-trip after every change — silent data corruption is the failure mode of crypto changes
  • For algorithm changes (e.g., bcrypt → Argon2id): plan migration on next user login (rehash from plaintext during auth flow); old hashes need to remain readable until migrated
  • For TLS changes: verify with
    testssl.sh
    and
    curl --tlsv1.3 --tls-max 1.3 https://target
    (lower-bound TLS version enforcement)
  • For KMS changes: verify the IAM permissions cover both encrypt AND decrypt (common rollout bug: encrypted data, can't decrypt it back)
  • 每次修改后测试加密/解密往返流程——加密修改的失败模式通常是静默数据损坏
  • 算法变更(如bcrypt→Argon2id):计划在用户下次登录时迁移(认证流程中从明文重新哈希);旧哈希需保持可读直到完成迁移
  • TLS变更:使用
    testssl.sh
    curl --tlsv1.3 --tls-max 1.3 https://target
    验证(强制最低TLS版本)
  • KMS变更:验证IAM权限同时覆盖加密与解密(常见发布漏洞:加密了数据但无法解密)

Output Format

输出格式

markdown
undefined
markdown
undefined

Cryptography Implementation Audit

密码学实现审计

Project: [name]

项目:[名称]

Scope: [components covered]

范围:[覆盖组件]

Date: [date]

日期:[日期]

Summary

摘要

[2-3 paragraphs]
[2-3段文字]

Findings

问题发现

IDSeverityComponentIssueCWE
ID严重程度组件问题CWE

Per-finding detail

问题详情

[Title, severity, file:line, description, vulnerable snippet, remediation, verification]
[标题、严重程度、文件:行号、描述、漏洞代码片段、修复方案、验证方式]

TLS posture (if applicable)

TLS状态(如适用)

[Output of testssl.sh / sslyze]
[testssl.sh/sslyze的输出结果]

Key inventory

密钥清单

KeyPurposeLocationAlgorithmRotation
密钥用途位置算法轮换策略

Recommendations

建议

[Prioritized]

Disposition rule (Fixed / Deferred / Accepted Risk) per `owasp-audit`.
[按优先级排序]

问题处理规则(已修复/延迟处理/接受风险)遵循`owasp-audit`标准。

Boundaries

边界说明

  • Audit code and configurations the user provides
  • Refuse to help break, weaken, or build backdoors into cryptography
  • For TLS testing — only test endpoints the user has authorization for
  • If the audit surfaces a fundamentally broken design (custom crypto, ROT13-as-protection), the recommendation is "replace, don't patch" — don't try to incrementally improve broken designs
  • Quantum-resistant migration: track NIST PQC standardization but don't recommend specific PQC primitives until they're standardized and library-supported; the field is changing
  • 仅审计用户提供的代码与配置
  • 拒绝协助破解、弱化加密或为密码学实现添加后门
  • TLS测试仅针对用户有权测试的端点
  • 若审计发现存在根本性设计缺陷(自定义加密、用ROT13做保护),建议为“替换而非修补”——不要尝试逐步改进已损坏的设计
  • 抗量子迁移:跟踪NIST PQC标准化进程,但在PQC原语标准化并获得库支持前,不推荐具体的PQC原语;该领域仍在变化中

References

参考资料

  • NIST SP 800-57 (Key Management)
  • NIST SP 800-131A (Algorithm Transitions)
  • NIST SP 800-175B (Cryptographic Standards Guidelines)
  • NIST FIPS 140-3 (Cryptographic Module Standards)
  • IETF RFC 7525 (TLS Recommendations)
  • OWASP Cryptographic Storage Cheat Sheet
  • OWASP Transport Layer Protection Cheat Sheet
  • "Cryptography Engineering" — Ferguson, Schneier, Kohno (the book to read)
  • "Real-World Cryptography" — David Wong
  • libsodium / Tink / BoringSSL documentation
  • NIST SP 800-57(密钥管理)
  • NIST SP 800-131A(算法过渡)
  • NIST SP 800-175B(密码学标准指南)
  • NIST FIPS 140-3(密码模块标准)
  • IETF RFC 7525(TLS建议)
  • OWASP密码存储 cheat sheet
  • OWASP传输层保护 cheat sheet
  • 《Cryptography Engineering》——Ferguson、Schneier、Kohno(必读书籍)
  • 《Real-World Cryptography》——David Wong
  • libsodium / Tink / BoringSSL 文档