Laravel AES-256 加密 &MySQL
我正在尝试通过 Laravel (5.3) 使用 AES-256-CBC 的加密方法加密敏感的用户信息.加密数据后,我想将其存储在我的 MySQL 数据库中,但我不知道应该将其保存为什么类型,也不知道它的长度.
感谢任何帮助.
解决方案更新
PR 31721 已合并到 Laravel 7.0.8,修复了转义json 编码中的正斜杠.在此之前,加密相同的数据会给您带来不同大小的结果.现在,从 7.0.8 开始,每次加密相同的数据都会得到相同大小的结果.
TL;DR:
Laravel 的 encrypt 方法将返回一个字符串,因此数据类型应该是 varchar 或 text 变体,具体取决于被加密的数据的大小.
要确定大致尺寸,可以使用以下一系列计算:
Laravel >= 7.0.8
令 a
= 序列化未加密数据的大小 (strlen(serialize($data))
)
让 b
= a + 16 - (a MOD 16)
(计算加密数据的大小)
让 c
= (b + 2 - ((b + 2) MOD 3))/3 * 4
(计算 base64 编码数据的大小)
让 d
= c + 117
(添加 MAC、IV 和 json 编码的大小)
让 e
= (d + 2 - ((d + 2) MOD 3))/3 * 4
(计算 base64 编码数据的大小)
即使值不是确定性的,结果的大小也是.例如,如果您要加密一个 9 位数的社会保险号,结果将始终是 216 个字符.
Laravel <7.0.8
令 a
= 序列化未加密数据的大小 (strlen(serialize($data))
)
让 b
= a + 16 - (a MOD 16)
(计算加密数据的大小)
让 c
= (b + 2 - ((b + 2) MOD 3))/3 * 4
(计算 base64 编码数据的大小)
让 d
= c + 117 + 8 + ((c + 2 - ((c + 2) MOD 3))/3)
(添加 MAC、IV、和 json 编码,以及用于潜在转义斜杠的额外缓冲区)
让 e
= (d + 2 - ((d + 2) MOD 3))/3 * 4
(计算 base64 编码数据的大小)
例如,如果您要加密一个 9 位数的社会保险号,则结果将至少为 216 个字符,最多为 308 个字符(尽管这在统计上可能是不可能的).如果您运行 100000 多个加密的循环,您会看到大小通常在 216 - 224 范围内.上面提供的公式会告诉您将字段设置为 248 个字符,这是超出预期范围的健康缓冲区,但在统计上并非不可能达到.
详情:
encrypt 方法返回的值不仅仅是加密文本,而是 json 编码的有效负载数组的 base64 编码表示,其中包含 (1) 序列化数据的 base64 编码加密值,(2) base64 编码初始化向量 (IV),以及 (3) 消息认证码 (MAC).因此,要确定所需字段的大小,您需要知道将被编码的数据的最大大小,然后为这些填充在返回字符串中的额外信息添加一些额外的空间.>
首先,让我们计算加密值的最大大小.由于您的加密算法 (AES-256-CBC) 是分组密码,因此使用公式很容易完成.AES 使用 16 字节块,并且需要至少填充一个字节,因此加密值的大小将是 16 的下一个倍数.因此,如果您的原始数据是 30 字节,那么您的加密数据将是 32 字节.如果您的原始数据是 32 字节,那么您的加密数据将是 48 字节(因为 AES 需要至少填充一个字节,所以您的 32 字节变为 33,然后上升到下一个 16 到 48 的倍数).计算公式为 x + 16 - (x MOD 16)
.因此,对于 30 个字节,您会得到 30 + 16 - (30 MOD 16) = 32
.
在计算加密值的大小时,请记住被加密的数据首先被序列化.因此,例如,如果您正在加密社会保险号,那么明文值只有 9 个字符,但序列化值实际上是 16 个字符(s:9:"xxxxxxxxx";
).由于序列化值是实际加密的,并且是 16 字节,因此加密值的大小将为 32 字节 (16 + 16 - (16 MOD 16) = 32
).
除此之外,openssl_encrypt
函数返回已经 base64 编码的加密数据.Base64 编码将值的大小增加了大约 4/3.对于原始数据中的每 3 个字节,base64 编码将生成一个 4 字节(字符)表示.因此,对于 SSN 示例,加密结果为 32 个字节.转换为 base64 时,32 字节为我们提供 (32/3) = 10.6
3 字节段.由于 base64 填充到下一个字节,取上限,然后乘以 4,得到 11 * 4 = 44
个字节.因此,我们原来的 32 字节加密值变成了 44 个字符的字符串.如果你需要一个公式,你可以使用 (x + 2 - ((x + 2) MOD 3))/3 * 4
.所以,(32 + 2 - ((32 + 2) MOD 3))/3 * 4 = 44
.
下一条信息是 MAC.MAC 是一个 SHA256 哈希值,所以我们知道它将是 64 个字符.
最后一条信息是 IV.普通 IV 是 16 个随机字节.存储在有效负载数组中的 IV 是普通 IV 的 base64 编码值.所以,我们可以用上面的公式来计算base64编码的IV的大小:(16 + 2 - ((16 + 2) MOD 3))/3 * 4 = 24
.
将这三条信息压缩成一个数组,然后进行json_encoded.由于 json 表示和数组中值的名称,这又增加了 29 个字节.
此外,在 Laravel <在 7.0.8 中,base64 编码数据中的任何正斜杠都在 json 字符串中使用反斜杠进行转义,因此这会根据存在的正斜杠数量添加可变数量的字节.对于 SSN 示例,base64 编码数据有 68 个字符(加密数据为 44 个,IV 为 24 个).让我们假设正斜杠的最大数量大约是结果的 1/3,或者大约 23 个额外字节.在 Laravel >= 7.0.8 中,这些正斜杠没有被转义,所以没有额外的字节.
最后,这个 json_encoded 值是 base64_encoded,这将再次将大小增加约 4/3.
所以,总而言之,让我们再次假设您正在加密一个社会安全号码.openssl_encrypt
结果将是 44 个字符,MAC 是 64 个字符,IV 是 24 个字符,并且 json 表示添加另外 29 个字符.
在 Laravel 中7.0.8,还有一个额外的23个字符的缓冲区.这给了我们 (44 + 64 + 24 + 29 + 23 = 184
) 个字符.这个结果被 base64 编码,这给了我们 ((184 + 2 - ((184 + 2) MOD 3))/3 * 4 = 248
) 个字符.
在 Laravel >= 7.0.8 中,没有额外的缓冲区.这给了我们 (44 + 64 + 24 + 29 = 161
) 个字符.这个结果被 base64 编码,这给了我们 ((161 + 2 - ((161 + 2) MOD 3))/3 * 4 = 216
) 个字符.
I'm trying to encrypt sensitive user information via Laravel's (5.3) encrypt method which uses AES-256-CBC. After encrypting the data I want to store it in my MySQL database but I don't know what type should I save it as nor the length of it.
Any help is appreciated.
解决方案Update
PR 31721 has been merged into Laravel 7.0.8, which fixes the escaped forward slashes in the json encoding. Before this, encrypting the same data would give you variable size results. Now, as of 7.0.8, encrypting the same data will give you the same size result every time.
TL;DR:
Laravel's encrypt method will return a string, so the datatype should be a varchar or text variation, depending on the size of the data being encrypted.
To determine the approximate size, you can use the following series of calculations:
Laravel >= 7.0.8
Let a
= the size of the serialized unencrypted data (strlen(serialize($data))
)
Let b
= a + 16 - (a MOD 16)
(calculate size of encrypted data)
Let c
= (b + 2 - ((b + 2) MOD 3)) / 3 * 4
(calculate size of base64 encoded data)
Let d
= c + 117
(add size of MAC, IV, and json encoding)
Let e
= (d + 2 - ((d + 2) MOD 3)) / 3 * 4
(calculate size of base64 encoded data)
Even though the value is not deterministic, the size of the result is. For example, if you were to encrypt a 9 digit social security number, the result will always be 216 characters.
Laravel < 7.0.8
Let a
= the size of the serialized unencrypted data (strlen(serialize($data))
)
Let b
= a + 16 - (a MOD 16)
(calculate size of encrypted data)
Let c
= (b + 2 - ((b + 2) MOD 3)) / 3 * 4
(calculate size of base64 encoded data)
Let d
= c + 117 + 8 + ((c + 2 - ((c + 2) MOD 3)) / 3)
(add size of MAC, IV, and json encoding, plus extra buffer for potentially escaped slashes)
Let e
= (d + 2 - ((d + 2) MOD 3)) / 3 * 4
(calculate size of base64 encoded data)
For example, if you were to encrypt a 9 digit social security number, the result would be at minimum 216 characters, and at maximum 308 characters (though this is probably a statistical impossibility). If you run a loop of 100000+ encryptions, you'll see the size is usually in the 216 - 224 range. The formula provided above would tell you to set your field to 248 characters, which is a healthy buffer above the expected range, but not statistically impossible to hit.
Details:
The value returned from the encrypt method is not just the encrypted text, but is a base64 encoded representation of a json encoded payload array that contains (1) the base64 encoded encrypted value of the serialized data, (2) the base64 encoded initialization vector (IV), and (3) the message authentication code (MAC). So, to determine the size of the field needed, you will need to know the max size of the data that will be encoded, and then add on some extra room for these extra pieces of information that are stuffed in the returned string.
First, let's calculate the max size of your encrypted value. Since your encryption algorithm (AES-256-CBC) is a block cipher, this is pretty easily done with a formula. AES uses 16 byte blocks and requires at least one byte of padding, so the size of the encrypted value will be the next multiple of 16. So, if your original data is 30 bytes, your encrypted data will be 32 bytes. If your original data is 32 bytes, your encrypted data will be 48 bytes (since AES requires at least one byte of padding, your 32 bytes becomes 33, and then that goes up to the next multiple of 16 to 48). The formula for this would be x + 16 - (x MOD 16)
. So, for 30 bytes you get 30 + 16 - (30 MOD 16) = 32
.
When calculating the size of the encrypted value, keep in mind that the data being encrypted is first serialized. So, for example, if you are encrypting a social security number, the plain value is only 9 characters, but the serialized value is actually 16 characters (s:9:"xxxxxxxxx";
). Since the serialized value is what is actually encrypted, and it is 16 bytes, the size of the encrypted value will be 32 bytes (16 + 16 - (16 MOD 16) = 32
).
In addition to this, the openssl_encrypt
function returns the encrypted data already base64 encoded. Base64 encoding increases the size of the value by about 4/3. For every 3 bytes in the original data, base64 encoding will generate a 4 byte (character) representation. So, for the SSN example, the encrypted result is 32 bytes. When translating to base64, 32 bytes gives us (32 / 3) = 10.6
3 byte segments. Since base64 pads to the next byte, take the ceiling, and multiply by 4, which gives 11 * 4 = 44
bytes. So, our original 32 byte encrypted value becomes a 44 character string. If you need a formula for this, you can use (x + 2 - ((x + 2) MOD 3)) / 3 * 4
. So, (32 + 2 - ((32 + 2) MOD 3)) / 3 * 4 = 44
.
The next piece of information is the MAC. The MAC is a SHA256 hashed value, so we know that it will be 64 characters.
The final piece of information is the IV. The plain IV is 16 random bytes. The IV stored in the payload array is the base64 encoded value of the plain IV. So, we can use the formula above to calculate the size of the base64 encoded IV: (16 + 2 - ((16 + 2) MOD 3)) / 3 * 4 = 24
.
These three pieces of information are compacted into an array, and then json_encoded. Because of the json representation and the name of the values in the array, this adds another 29 bytes.
Additionally, in Laravel < 7.0.8, any forward slashes in the base64 encoded data are escaped with backslashes in the json string, so this adds a variable number of bytes depending on how many forward slashes are present. For the SSN example, there are 68 characters of base64 encoded data (44 for the encrypted data, 24 for the IV). Let's assume the maximum number of forward slashes is probably about 1/3 of the results, or about 23 extra bytes. In Laravel >= 7.0.8, these forward slashes are not escaped, so there are no extra bytes.
Finally, this json_encoded value is base64_encoded, which will again increase the size by a factor of about 4/3.
So, to put this all together, lets again imagine you're encrypting a social security number. The openssl_encrypt
result will be 44 characters, the MAC is 64 characters, the IV is 24 characters, and the json representation adds another 29 characters.
In Laravel < 7.0.8, there is also the buffer of an extra 23 characters. This gives us (44 + 64 + 24 + 29 + 23 = 184
) characters. This result gets base64 encoded, which gives us ((184 + 2 - ((184 + 2) MOD 3)) / 3 * 4 = 248
) characters.
In Laravel >= 7.0.8, there is no extra buffer. This gives us (44 + 64 + 24 + 29 = 161
) characters. This result gets base64 encoded, which gives us ((161 + 2 - ((161 + 2) MOD 3)) / 3 * 4 = 216
) characters.
相关文章