本文译自stackexchange

密码学是一个广泛复杂的主题,即使经验丰富的工程师在初步应用密码技术时也会犯错,而加密又是一个重要的行为,在这件事上犯错的代价往往是巨大的。因此本文旨在记录常见的密码应用误区、经验教训以及对于算法或API的谨慎选择。

不要创造算法

不要发明自己的加密算法或协议,因为容易出错。加密算法很复杂,需要大量的严谨的审查以确保其安全性,通常自己发明的算法或协议,无法提供合理的数学推导以证明其安全。

最佳选择是,使用标准加密算法和协议。通常一个标准算法的诞生,是为了解决曾经的某些问题,而这些问题很可能就是你所面临的,比如:

  • 使用 TLS 或 SSL 解决通信安全
  • 使用 GPG 或 PGP 解决静态数据安全

这些属于经过充分验证的密码方案,如果需要使用密码接口实现某些功能,优先考虑使用一些高级加密库,例如 cryptlib、GPGME、Keyczar等,而不是诸如 OpenSSL、CryptoAPI、JCE 等易于出错且难以应用的低级别库。

不要使用非认证的加密

这是一种非常常见的错误——对数据进行加密而不进行认证

例如:某开发者想保护敏感信息,使用 AES-CBC 模式对该消息进行加密。

该行为仅实现了消息的机密性,但却无法保证在面临主动攻击(如篡改)、重返攻击和反射攻击等威胁时的安全性。

而增加消息认证即可解决该问题。

许多线上系统出现过该问题导致的严重漏洞,如 ASP.NET, XML encryption, Amazon EC2, JavaServer Faces, Ruby on Rails, OWASP ESAPI, IPSEC, WEP, ASP.NET again, 以及 SSH2

为了避免该问题,应该在每次加密时都使用消息认证,主流做法有以下两种:

  • 最简便的方式是,使用现成的加密认证方案,如 GCM、CWC、EAX、CCM、OCB,这些加密方法实现了加密的同时并对数据做认证,使用者无需再关心该问题;
  • 另一种方式是,自己实现消息认证。首先,使用适当的对称加密方法加密消息(例如AES-CBC),然后对密文数据生成消息认证码(如AES-CMAC, SHA1-HMAC, SHA256-HMAC),并在传输数据前,将生成的 MAC 摘要附在密文后。对于接收端,在解密数据前先验证 MAC 摘要的有效性。

不要将一个密钥同时用于加密、认证或签名

一个密钥不用该重用于多种目的,这种行为会扩大风险。

例如,如果你有一对 RSA 公私钥对,不应该将其既用于加密又用于签名。如果你需要加密和签名这两种行为,那么就生成两对密钥。

相应的,对于对称加密而言,不应该将用于加密的密钥,用于生成消息认证码。总而言之,不要将密钥重用与不同的目的。

对拼接字符串做哈希要慎重

例如,某开发者想获取字符串 S 和 T 的哈希值。于是他将 S 与 T 做拼接后进行哈希运算,获得H(S||T)。这是有缺陷的。

问题在于,拼接使得两个字符串的边界不明确。例如 builtin||securely = built||insecurely,哈希运算无法识别字符串 S 和 T。因此攻击者或许可以改变两个字符串的边界而不改变哈希值。例如,如果 Alice 想发送两个字符串 builtin securely,攻击者可以在不使哈希值失效的情况下将字符串改为builtinsecurely

在对拼接字符串进行数字签名或消息认证码运算是也会有类似的问题。

解决办法是,不进行明文拼接,而是使用可以明确解码的编码。例如,当想计算H(S||T)时,不妨改为H(length(S)||S||T),这里length(S)是一个32位值,用以表示字符串 S 的长度;亦或者采取 H(H(S)||H(T))、H(H(S)||T) 等方式。

有关该问题的相关案例参见 this flaw in Amazon Web Servicesthis flaw in Flickr

不要重用nonces或IVs

许多加密模式需要 IV(初始化向量)。永远不要重复使用同一个 IV,这样会导致严重的漏洞。

  • 对于流密码而言,例如 CTR 或 OFB 模式,会导致加密数据被轻松恢复为明文
  • 对于 CBC 等其它模式而言,重用 IV 同样会造成明文恢复攻击

因此不管使用什么加密模式,都不应该重用 IV。

确保随机数生成器具备足够的熵

确保使用密码伪随机数生成器来产生key、IV、nonce等,而不是使用rand(), random(), drand48()等。

确保给随机数生成器注入足够的熵,不要使用日期作为种子,这是可预测的。

例如:srand(time(NULL))并不好,比较好的方式是给随机数生成器注入128位数据或真随机数,例如通过/dev/urandom, CryptGenRandom或其它类似工具。在 Java 中,使用 SecureRandom 而不是 Random。在 .NET 中,使用 System.Security.Cryptography.RandomNumberGenerator,而不是 System.Random。在 Python 中,使用 random.SystemRandom,而不是 random。

对于对称加密,不要使用ECB模式

例如,一图片数据的明文显示如下:

明文

该数据经ECB模式加密后的效果:

该数据经CBC模式加密后的效果:

避免使用密码作为密钥

一个常见的错误做法是,使用密码或密码的哈希值作为加密密钥。密钥需要有足够的熵,而大多数密码/口令并不具备,因此容易遭到字典攻击。

如果一定要基于密码来生成密钥,那么可以通过迭代哈希的方式,来增加字典攻击的难度,例如H(H(H(…H(password)…)))。

不要使用不安全的密钥长度

确保使用足够长的密钥,比如1024-bit 的 RSA 密钥在2010年左右已被证实不安全,建议使用至少 2048 长度的 RSA 密钥。

对于对称密码而言,应使用 128-bit 以上的密钥。