如何计算Laravel加密方式输出的最大长度?

How to calculate the maximum length of the output of the Laravel encryption method?

设置

鉴于以下情况:

$s = Crypt::encryptString('a');

是否可以知道,对于长度为1的字符串,$s的可能长度范围?

上下文

数据库存储 - 需要存储加密值,并希望设置输入字符串的验证,以便最长长度的输入字符串在加密时插入到数据库中而不被截断。

基本测试

运行 一些 非常 本地粗略测试,使用以下代码段:

Route::get('/test', function() {
    echo '<table>';
    for ($i=0; $i < 100; $i++) { 
        $s = str_repeat('a', $i);
        $l1 = strlen($s);
        $l2 = strlen(Crypt::encryptString($s));
        echo "<tr><td>$l1</td><td>$l2</td></tr>";
    }
    echo '</table>';
});

我可以看到以下内容,但它在运行之间有所不同,例如,'a' 的字符串的长度为 188 或 192(更长的值似乎在 244 和 248 之间)。

所以一定有公式。我见过 output_size = input_size + (16 - (input_size % 16)) 但没有说明差异。

输出

0   192
1   188
2   188
3   192
4   188
5   188
6   188
7   192
8   192
9   188
10  188
11  192
12  192
13  192
14  192
15  192
16  220
17  220
18  216
19  216
20  220

编辑

好的,所以在下面和@Luke Joshua Park聊过后,长度的变化来自laravel加密函数和$iv的创建方式,是随机字节,可以包含/

$value里面的加密方式也可以包含一个/

当包含 / 的值被 JSON 编码时,/ 被转义为 \\/,每次出现时额外添加 3 个字符。

真正的问题 - $iv$value 可以包含多个“/”吗?

通过 the source code 查找 Crypt::encryptString,我们可以看到最终结果将是一个 base64 编码的 JSON 对象,它具有以下结构:

{ "iv": "<128 bits in base64>", "value": "<x bits in base64>", "mac": "<256 bits in hex>" }

其中x的值为ceil(n / 128) * 128 其中n为原始明文的位数

这意味着,对于长度为 1 的输入明文,输出的大小应为:

  • IV (base64) 的 24 个字符。
  • 24 个字符的密文 (base64)。
  • 64 个字符用于 SHA256 mac(十六进制)。
  • JSON 字段名称的 10 个字符。
  • 19 个额外的 JSON 个字符,例如{, ", :.
  • 整个事情的最后一轮base64编码...(ceil(141 / 3) * 4

一共给出了188个。高达 192 的波动是奇怪的 - 您的输入根本没有改变大小(因为明文应始终为 0 - 15 长度之间的 16 个字节)。

The real problem - can $iv and $value contain more than a single '/'?

当然可以。 IV 的最坏情况是 IV FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF(十六进制),其 Base64 值为 /////////////////////w==.

21 个正斜杠 * 每个额外的 3 个字节 = 63 个额外的字节。

对于 HMAC-SHA-2-256,您可以获得 32 个字节的 0xFF(最坏情况),即 base64 中的 //////////////////////////////////////////8=

42 个正斜杠 => 126 个额外字节。

对于密文,同样,整个输出可能是(但很可能不是)FF FF ... FF。所有一个字母的输入(无论什么编码)都是一个密文块,使输出再次成为 /////////////////////w== (+63).

最大值的通用公式似乎是

  • 四:24 + 63 = 87
  • HMAC: 24 + 63 = 87
  • JSON 属性 姓名:10
  • JSON结构:19
  • 密文:ceil(ceil((n+1) / 16) * 16 / 3) * 4 * 4(我用n作为字节。填充后的密文是ceil((n+1) / blocksize) * blocksize,base64是4 * ceil(data / 3),额外的 *4 是 "everything is slashes")
  • 再次使用 Base64:4 * ceil(sum / 3)

= 4 * ceil((4 * 4 * ceil(16 * ceil((n + 1) / 16) / 3) + 203) / 3)

n=1 产生 400 字节。实际最大值是(我认为)388,因为密文公式将 24 个斜杠计算为最坏情况,而 21 是最坏情况。所以真正的至高无上者需要将密文称为更复杂的东西,包括下限、上限和减法。

注意我要把赏金奖励给@Luke Joshua Park,因为他让我最接近最终成为(最接近)解决方案的东西,即跟随。

(不是)解决方案

答案是,没有具体答案,并非没有未知数和方差。在撰写本文时,三个关注此问题的人(我自己、Luke 和 bartonjs)对 100% 准确的解决方案仍然存在一些疑问。

提出这个问题是为了找出一种可靠的类型和大小来存储加密数据,最好是以独立于数据库的方式(我不想指定特定的数据库,因为我想知道并理解如何计算一个长度,不管它的持久化方式如何)。

然而,即使是最小长度的字符串在最坏的情况下也变得相当长(其中创建了一个包含许多斜线的随机 $iv - 不太可能或不可能,这是可能的)。 n=1 的可能加密字符串可能有 400 个字节长,这意味着 varchar 永远不会是正确答案。

那么……应该怎么办?

因此,无论原始字符串的长度如何,将加密数据存储为文本字段而不是 varchar(在 mysql 领域)似乎是最好、最一致和最可靠的。这是一个令人失望的无聊答案,没有涉及花哨的数学。这不是我愿意接受的答案,但最有道理。

但是,密码呢?

在一瞬间的愚蠢中,我想,但是密码字段呢?那是一个varchar。但当然这是一个 hashed 值,而不是 encrypted 值(当我脑子里突然冒出这个想法时,我没有喝足够的咖啡,好吧?)