AES-GCM 同时做到加密与认证,但前提是同一把密钥下 nonce 绝不重复。JDK 帮你挡住了什么,又挡不住什么?重复之后到底会泄露什么?这篇用自己生成的密钥和数据逐项真实运行,包括随机 nonce 的生日碰撞实验。
明文 23 字节 → 密文+标签 39 字节(多出 16 字节 = 16 字节认证标签);IV 12 字节 正确解密:转账 100 元给 alice 改动密文 1 个 bit → AEADBadTagException: Tag mismatch 改动标签 1 个 bit → AEADBadTagException: Tag mismatch AAD(头部)不一致 → AEADBadTagException: Tag mismatch IV 不一致 → AEADBadTagException: Tag mismatch 密钥不对 → AEADBadTagException: Tag mismatch 不给 IV 的 init:JDK 自己生成随机 IV,getIV().length = 12,getParameters().getParameterSpec(GCMParameterSpec).getTLen() = 128 位
同一个 Cipher 对象不重新 init 连续加密两次 → IllegalStateException: Must use either different key or iv for GCM encryption 同一个 Cipher 对象用同一个密钥和 IV 重新 init → InvalidAlgorithmParameterException: Cannot reuse iv for GCM encryption 两个不同的 Cipher 对象用同一个密钥和同一个 IV 各自 init → 都成功,JDK 无法跨对象阻止 nonce 重用 同一把密钥、同一个 IV 加密两条消息:密文 XOR = 0000000003030b4345000908…,明文 XOR = 0000000003030b4345000908…,两者相同 = true(密文泄露了两条明文的异或) 已知第一条明文,由密文 XOR 恢复第二条明文:「PAY bob 9999 CNY; token=BBBB」(只靠两个密文与已知的第一条明文,没有用密钥) 同一个 (密钥, IV) 加密同一条明文两次,密文是否完全相同 = true(确定性,也泄露「两条消息相同」)
随机 24 位 nonce,每个密钥发 3000 条消息:2000 次实验里出现重复的有 466 次(23.3%);生日公式 1−exp(−n²/2N) 预测 23.5% 随机 32 位 nonce,每个密钥发 65536 条消息:2000 次实验里出现重复的有 785 次(39.3%);生日公式 1−exp(−n²/2N) 预测 39.3% 12 字节(96 位)随机 nonce:发 2^32 条消息时重复概率约 1.16e-10(NIST 对随机 nonce 的 GCM 要求不超过 2^32 条/密钥) 计数器 nonce:1000 条消息 1000 个不同值 = true(计数器不会重复,但要保证重启后不回绕、多实例不共用同一个密钥与计数区间)
GCM 解密:喂入 16 MB 密文分 16 次 update,update 返回的明文累计 0 字节(标签验证通过之前不释放明文) doFinal 之后一次性返回 16 MB 明文 GCM 加密:喂入 16 MB 明文分 16 次 update,update 返回的密文累计 16 MB(加密可以流式输出) AES/GCM/NoPadding 加密 1 MB:最好 10.34 ms(97 MB/s) ChaCha20-Poly1305 加密 1 MB:最好 1.85 ms(541 MB/s)