如何计算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 值(当我脑子里突然冒出这个想法时,我没有喝足够的咖啡,好吧?)
设置
鉴于以下情况:
$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 值(当我脑子里突然冒出这个想法时,我没有喝足够的咖啡,好吧?)