我刚刚遇到了一个有趣的小问题。
使用 Laravel 4,我在将一些条目添加到数据库之前对其进行加密,包括电子邮件地址。
数据库设置为默认varchar
长度 255。
我刚刚有一个加密到 309 个字符的条目,通过切断数据库中的最后 50 多个字符来破坏加密。
我已经(暂时)通过简单地将varchar
长度增加到 500 来解决这个问题,理论上应该可以覆盖我,但我想确定一下。
我不确定加密是如何工作的,但是有没有办法为了设置我的数据库而告诉加密输出中预期的最大字符长度?
我是否应该将字段类型从varchar
更改为其他字段,以确保不会再次发生这种情况?
结论
首先,请注意,4.0.0 和 4.2.16(似乎是最新版本)之间有很多变化。
该方案以 4.2 的 188 个字符和 4.0 的大约 244 个字符的惊人开销开始(鉴于我没有忘记任何换行符等)。因此,为了安全起见,如果明文中的字符编码为单个字节,则可能需要 4.2 的 200 个字符和 4.0 的 256 个字符加上纯文本大小的 1.8 倍。
分析
我刚刚查看了 Laravel 4.0 和 Laravel 4.2 关于这个函数的源代码。让我们先进入尺寸:
- 数据是序列化的,因此加密大小取决于值类型(可能是字符串)的大小;
- 序列化数据是使用 Rijndael 256 或 AES 填充的 PKCS#7,因此这意味着添加 1 到 32 字节或 1 到 16 字节 - 具体取决于 4.0 或 4.2 的使用;
- 此数据使用密钥和 IV 进行加密;
- 密文和 IV 都分别转换为 base64;
- 计算在base64编码密文上使用SHA-256的HMAC,返回64字节的小写十六进制字符串
- 然后密文由
base64_encode(json_encode(compact('iv', 'value', 'mac')))
组成(当然,该值是 Base 64 密文,Mac 是 HMAC 值)。
中的字符串被序列化为 s:<i>:"<s>";
其中<i>
是字符串的大小,<s>
是字符串(我假设这里的 PHP 平台编码的大小)。请注意,我不是 100% 确定 Laravel 没有使用任何环绕字符串值的包装,也许有人可以为我清除它。
计算
总而言之,一切都在很大程度上取决于字符编码,对我来说,做出一个好的估计是相当危险的。现在让我们假设字节和字符之间的关系是 1:1(例如 US-ASCII):
- 序列化最多添加 9 个字符,最多 999 个字符的字符串
- 填充加起来最多 16 或 32 个字节,我们假设它们也是字符
- 加密使数据保持相同的大小
- PHP 中的 base64 创建
ceil(len / 3) * 4
个字符 - 但让我们将其简化为(len * 4) / 3 + 4
,base 64 编码的 IV 是 44 个字符 - 完整的 HMAC 为 64 个字符
- JSON 编码为引号和冒号添加了 3*5 个字符,加上 4 个字符用于它们周围的大括号和逗号,总共 19 个字符(我假设
json_encode
在这里没有以空格结尾,base 64 再次增加了相同的开销
好的,所以我在这里有点累了,但你可以看到它至少两次使用 base64 编码扩展明文。最后,这是一个增加了相当多开销的方案;他们本可以使用base64(IV|ciphertext|mac)
来严重减少开销。
笔记
- 如果您现在不在 4.2 上,我会认真考虑升级到最新版本,因为 4.2 修复了很多安全问题
- 示例代码使用字符串作为键,不清楚使用字节是否容易 ;
- 该文档确实警告了 Rijndael 默认值以外的密钥大小,但忘记提及字符串编码问题;
- 即使使用CTR模式,也始终执行填充,这违背了目的;
- 使用 PKCS#7 填充的 Laravel垫,但由于序列化似乎总是以
;
结尾,这实际上没有必要; - 很高兴看到经过身份验证的加密被用于数据库加密(IV未使用,在4.2中修复)。
@MaartenBodewes' 在解释实际字符串可能有多长方面做得很好。但是,您永远无法确定,因此有两种选择来处理这种情况。
1. 让你的田野text
将字段从有限的varchar
更改为"自我扩展"text
。这可能是更简单的,特别是如果你期望相当长的输入,我肯定会推荐这个。
2.让你的varchar
更长
正如您已经做的那样,根据您期望/允许的输入长度使varchar
更长。我会乘以 5 倍。
但不要止步于此!在代码中添加检查以确保数据不会被截断:
$encrypted = Crypt::encrypt($input);
if(strlen($encrypted) > 500){
// do something about it
}
你能做些什么呢?
您可以将错误写入日志并添加加密数据(以便在扩展数据库字段长度后手动重新插入它)
Log::error('An encrypted value was too long for the DB field xy. Length: '.strlen($encrypted).' Data: '.$encrypted);
显然,这意味着您必须经常检查日志(或通过邮件将它们发送给您),并且由于数据库中的数据不正确,用户在使用应用程序时可能会遇到错误。
另一种方法是抛出异常(并向用户显示错误),当然也将其写入日志,以便您可以修复它......
无论如何
无论选择选项 1 还是 2,都应始终限制输入字段的可接受长度。服务器端和客户端。