密码学是一个如此广泛的学科,即使是经验丰富的编码人员也几乎总是会在最初几次犯错。然而加密是一个如此重要的话题,我们常常不能承受这些错误。
这个问题的目的是识别和列出什么不能与给定的算法或 API 做。通过这种方式,我们可以从他人的经验中学习并防止不良做法的传播。
为了使这个问题保持建设性,请
- 包括一个“错误”的例子
- 解释这个例子有什么问题
- 提供正确的实现(如果适用)。
- 尽您所能,提供有关上述#2 和#3 的参考资料。
密码学是一个如此广泛的学科,即使是经验丰富的编码人员也几乎总是会在最初几次犯错。然而加密是一个如此重要的话题,我们常常不能承受这些错误。
这个问题的目的是识别和列出什么不能与给定的算法或 API 做。通过这种方式,我们可以从他人的经验中学习并防止不良做法的传播。
为了使这个问题保持建设性,请
不要推出自己的加密货币。
不要发明自己的加密算法或协议;这是非常容易出错的。正如 Bruce Schneier 喜欢说的那样,
“任何人都可以发明一种他们自己无法破解的加密算法;要发明一种别人无法破解的加密算法要难得多”。
加密算法非常复杂,需要严格审查以确保它们是安全的;如果你自己发明,你就不会得到它,而且很容易在没有意识到的情况下得到一些不安全的东西。
相反,请使用标准加密算法和协议。很可能其他人以前遇到过您的问题并为此目的设计了适当的算法。
最好的情况是使用经过严格审查的高级方案:为了通信安全,使用 TLS(或 SSL);对于静态数据,请使用 GPG(或 PGP)。如果您不能这样做,请使用高级加密库,如cryptlib、GPGME、Keyczar或NaCL,而不是低级库,如 OpenSSL、CryptoAPI、JCE 等。感谢 Nate Lawson建议。
不要在没有消息身份验证的情况下使用加密
加密数据而不对其进行身份验证是一个非常常见的错误。
示例:开发人员希望对消息保密,因此使用 AES-CBC 模式对消息进行加密。错误:在存在主动攻击、重放攻击、反应攻击等情况下,这不足以保证安全性。已知存在不经消息认证的加密攻击,攻击可能相当严重。解决方法是添加消息身份验证。
这个错误导致在部署系统中使用未经身份验证的加密,包括ASP.NET、XML 加密、Amazon EC2、JavaServer Faces、Ruby on Rails、OWASP ESAPI、IPSEC、WEP、ASP.NET和SSH2。您不想成为此列表中的下一个。
为避免这些问题,您需要在每次应用加密时都使用消息身份验证。你有两种选择如何做到这一点:
可能最简单的解决方案是使用提供经过身份验证的加密的加密方案,例如 GCM、CWC、EAX、CCM、OCB。(另请参阅:1。)经过身份验证的加密方案会为您处理此问题,因此您不必考虑它。
或者,您可以应用自己的消息身份验证,如下所示。首先,使用适当的对称密钥加密方案(例如,AES-CBC)对消息进行加密。然后,获取整个密文(包括任何 IV、nonce 或解密所需的其他值),应用消息验证码(例如,AES-CMAC、SHA1-HMAC、SHA256-HMAC),并将生成的 MAC 摘要附加到传输前的密文。在接收端,在解密前检查 MAC 摘要是否有效。这被称为先加密后认证结构。(另请参阅:1、2 。)这也可以正常工作,但需要您多加注意。
在散列之前连接多个字符串时要小心。
我有时会看到一个错误:人们想要字符串 S 和 T 的散列。他们将它们连接起来得到一个字符串 S||T,然后对它进行散列得到 H(S||T)。这是有缺陷的。
问题:连接使两个字符串之间的边界不明确。示例:builtin
|| securely
= built
|| insecurely
. 换句话说,哈希 H(S||T) 不能唯一标识字符串 S 和 T。因此,攻击者可能能够在不改变哈希的情况下改变两个字符串之间的边界。例如,如果 Alice 想要发送两个字符串builtin
和securely
,攻击者可以将它们更改为这两个字符串built
并且insecurely
不会使哈希无效。
将数字签名或消息验证码应用于字符串连接时,也会出现类似的问题。
解决方法:使用一些明确可解码的编码,而不是简单的串联。例如,您可以计算 H(length(S)||S||T),而不是计算 H(S||T),其中 length(S) 是一个 32 位值,表示 S 的长度(以字节为单位)。或者,另一种可能性是使用 H(H(S)||H(T)),甚至 H(H(S)||T)。
有关此缺陷的真实示例,请参阅Amazon Web Services 中的此缺陷或Flickr 中的此缺陷[pdf]。
确保为具有足够熵的随机数生成器提供种子。
确保使用加密强度的伪随机数生成器来生成密钥、选择 IV/nonce 等。不要使用rand()
, random()
,drand48()
等。
确保为伪随机数生成器提供足够的熵。不要在一天中的时间播种;这是可以猜测的。
例子:srand(time(NULL))
很糟糕。播种 PRNG 的一个好方法是获取 128 位或真随机数,例如 from /dev/urandom
、CryptGenRandom 或类似的。在 Java 中,使用 SecureRandom,而不是 Random。在 .NET 中,使用 System.Security.Cryptography.RandomNumberGenerator,而不是 System.Random。在 Python 中,使用 random.SystemRandom,而不是随机的。感谢 Nate Lawson 提供的一些示例。
真实世界的例子:在早期版本的 Netscape 浏览器中看到这个缺陷,它允许攻击者破坏 SSL。