加密函数,即使使用错误的密钥,也总是解密为纯文本

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

本文关键字:解密 文本 函数 错误 加密 密钥      更新时间:2023-09-26

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

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

有人可以推荐一个javascript加密函数/库,总是解密为纯文本,即使有不正确的密钥?

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)

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

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

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

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

它们必须是好的诗歌,因为谁会费心去加密糟糕的诗歌呢?

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


那么,如何才能实现你想要的东西呢?基本上,您有两个选项:
  1. 您可以增加密钥的长度,这样就不能实际枚举它们。如果您使用随机的十进制数(实际上是随机选择的,而不是由用户选择的!),那么25到30位应该是最小的安全长度。

    你可以使用键拉伸来缩短长度。例如,如果在使用结果解密数据之前将每个密钥散列100,000次,那么您可以将密钥空间大小减少100,000倍,即从25位减少到20位。

    您还可以通过将键编码为短语来使它们更容易被记住。例如,您可以编译一个包含1000个简短的常用英语单词的列表(或使用现有的列表),并用列表中相应的单词替换密码中的每组三位数。对大多数人来说,记住五个随机单词的序列比记住一个随机的25位数字要容易得多。

  2. 当然,另一种选择是利用您已经拥有的服务器端速率限制。要做到这一点,您需要确保客户端在对密钥执行任何其他操作之前必须与服务器检查密钥。

    也就是说,您应该在服务器上存储实际的加密密钥(例如,它可能是一个随机的128位二进制字符串),并让服务器仅在客户端使用自己的"短密钥"成功进行身份验证后才将其发送给客户端。

    您还应该确保短密钥和长密钥都不能被窃听者捕获,例如,通过使用类似SRP的身份验证,或者简单地通过TLS/SSL进行身份验证。

这两种解决方案都不是完美的:第一种方法可能需要不方便的长密钥,即使有密钥拉伸,而第二种方法在客户端离线时不起作用,并且可能在服务器被黑客攻击时灾难性地失败。一般来说,这些都是你能做到的最好的了。

(方法可以将这两种方法结合起来以获得额外的安全性,因此攻击者必须同时危及服务器猜测客户端的密码才能破坏系统。但是这两种方法都有缺点

有格式保留加密,它直接回答了您的问题。您可以查找库函数,但FPE并不常见。相反,您应该在开始解密/验证之前减少您的PIN重试计数器,然后在PIN解密并且正确的情况下再次增加它。

请注意,您的随机方案不能替代TLS,并且可能(而且可能有)其他漏洞。