始终解密为纯文本的加密功能,即使使用不正确的密钥也是如此

Crypto function that always decrypts to plain text, even with the incorrect key

我正在研究 javascript 功能,使用 6 位 PIN 在浏览器上加密和存储密码。虽然这很容易被暴力破解,但服务器端代码通过在 3 次错误尝试后锁定帐户来防止这种情况发生。

下面的示例使用 AES,仅当 pin/key 正确时才解密为纯文本。这允许攻击者尝试 99,9999 种组合并挑选出唯一的纯文本结果,从而绕过服务器端限制。

有人可以推荐一个 javascript 加密 function/library,即使使用不正确的密钥也能始终解密为纯文本吗?

var encrypted = CryptoJS.AES.encrypt("password:abcdefg", "pin:123456");
$('#1').text(encrypted);


var decryptedCorrect = CryptoJS.AES.decrypt($('#1').text(), "pin:123456")
$('#3').text(decryptedCorrect.toString(CryptoJS.enc.Utf8));

var decryptedInCorrect = CryptoJS.AES.decrypt($('#1').text(), "pin:112233")
$('#4').text(decryptedInCorrect.toString(CryptoJS.enc.Utf8));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>

<div>
  Encrypted text: <span id="1"></span>
</div>
<br />
<div>
  Decrypted with correct pin: <span id="3"></span>
</div>
<br />
<div>
  Decrypted with incorrect pin: <span id="4"></span>
</div>
(Ideally the above should be some plain text value)

有保格式加密,直接回答你的问题。你可以找一个库函数,但是FPE并不常见。相反,您应该在开始 decrypt/verify 之前减少 PIN 重试计数器,然后在 PIN 解密且正确时再次增加它。

请注意,您的随意方案不能替代 TLS,并且可能 - 并且可能具有 - 其他漏洞。

一般来说,这是行不通的。

为了防止像这样的暴力猜测攻击,您不仅需要每个密钥(或至少相当大一部分密钥)来将密文解密为有效的明文,而且您还必须以某种方式安排对于将密文解密为 似是而非的 明文的每个密钥,至少在粗略检查时足以令人信服地通过真实明文。

特别是,"fake" 错误加密产生的明文至少需要在句法上有效,这样您自己的代码才能接受它,并且它还需要具有相同的一般结构和与您的真实明文具有相同的统计属性,因此攻击者不能仅使用一些正则表达式或字母频率分析来猜测哪个明文最有可能是正确的。

基本上,如果您要加密密码,您的解密方法必须为任何密钥生成一个看似合理的密码。如果您要加密 JSON 数据,则必须生成有效的 JSON。如果您要加密诗歌,则必须生成一首诗歌。

而且它们必须是 诗,因为谁会为糟糕的诗加密?

显然,没有通用的加密算法可以做到这一点。


那么,如何才能 实现您想要的目标呢?基本上,您有两个选择:

  1. 您可以增加键的长度,使它们实际上无法被枚举。如果您使用的是随机十进制数(实际上是随机选择的,而不是由用户选择的!),那么 25 到 30 位数字应该是最小安全长度。

    您可以使用 key stretching 缩短长度。例如,如果您在使用结果解密数据之前对每个密钥进行 100,000 次哈希处理,那么您可以将密钥空间大小减少 100,000 倍,即从 25 位数减少到 20 位数。

    您还可以将密钥编码为短语,从而使它们更易于记忆。例如,您可以编制一个包含 1000 个简短的常用英语单词(或 use an existing list)的列表,并将密码中每组三位数字替换为列表中的相应单词。对于大多数人来说,记住五个随机单词的序列比记住一个随机的 25 位数字要容易得多。

  2. 当然,另一种选择是利用您已有的服务器端速率限制。为此,您需要确保客户端必须先与服务器检查密钥,然后它才能用它做任何其他事情。

    也就是说,您应该在服务器上存储实际的加密密钥(可以是随机的 128 位二进制字符串),并让服务器仅在客户端成功验证后才将其发送给客户端有自己的 "short key".

    您还应该确保短密钥和长密钥都不能被窃听者捕获,例如通过使用类似 SRP for the authentication, or simply by doing the authentication over TLS/SSL.

这两种解决方案都不是完美的:第一种可能需要不方便的长密钥,即使使用密钥拉伸也是如此,而第二种方法在客户端离线时不起作用,并且如果服务器被黑,可能会出现灾难性的失败。不过,总的来说,这些几乎是您能做到的最好的了。

(有 种方法可以将这两种方法结合起来以提高安全性,这样攻击者就必须两者 破坏服务器 通过猜测客户端的密码来破解系统。但是你也有这两种方法的缺点。)