PHP:警告 mcrypt_generic_init():IV 大小不正确;提供长度:12,需要:8
Posted
技术标签:
【中文标题】PHP:警告 mcrypt_generic_init():IV 大小不正确;提供长度:12,需要:8【英文标题】:PHP: Warning mcrypt_generic_init(): Iv size is incorrect; supplied length: 12, needed: 8 【发布时间】:2016-10-15 23:13:59 【问题描述】:基本事实:
$algorithm = MCRYPT_BLOWFISH;
$mode = MCRYPT_MODE_CBC;
$randSource = MCRYPT_DEV_URANDOM;
注意 这不是一个严格的编码问题。
上下文:
CentOS 7、Apache 2.4.12 和 php 5.6.20。
我正在制作一封带有“验证您的电子邮件地址”链接的 html 电子邮件,以便完成注册过程。我的虚拟专用服务器上的所有内容都是 UTF-8,所有表单和查询字符串输入都使用多字节 (mb) 函数进行处理。
背景
作为一个实验(我知道 mcrypt 库的年龄和状态),我正在尝试解密 Blowfish 加密的查询字符串参数。假设在上升过程中,加密序列完美运行,并且我收到了带有链接的电子邮件。
在下降过程中,hmac_hash()
签名(SHA-512,仅用于本实验)正在工作,我能够将每条独立消息(32 个字符)与其哈希校验和(128 个字符)分开。分离的消息部分的 Base64 解码正在工作。对于每个参数,我留下了复合密文,其中复合密文等于IV + 基本密文。假设我使用substr()
的一个版本 来独立地获取 IV 和基本密文(这是课程的标准)。
问题
PHP: Warning mcrypt_generic_init(): Iv size is incorrect; supplied length: 12, needed: 8
假设我已经梳理了 PHP 手册和 ***。假设我看过其他类似的问题,但不完全像这个。假设我搜索了互联网无济于事。假设我有足够的经验来正确设置mb_string
。假设当我解决当前的问题时,我会处理 mcrypt 填充。
多字节问题会干扰解密吗?
base64 编码 IV + base cipher text
会损坏 IV 吗?
base64 填充会是个问题吗?
我应该指定更具体的MCRYPT_BLOWFISH_*
吗?
为什么河豚 IV 大小报告 8 字节,但很少产生 8 字节 IV?
我应该使用哪个 substr(),substr()
或 mb_substr()
,用于将所有内容都设为 UTF-8 并将所有其他输入作为多字节 UTF-8 处理的设置。我知道这是一个奇怪的问题,但所有 PHP 手册 mycrypt 解密序列示例都使用 substr(),而没有使用 mb_substr()
。如果可能,我网站上的所有内容都可以使用 mb_functions,如果它解决了我的问题,我不介意使用 substr()
,但它并没有解决它。当我使用mb_substr()
时,我收到以下警告。
PHP: Warning mcrypt_generic_init(): Iv size is incorrect; supplied length: 11, needed: 8
有人对这个确切的问题有任何经验吗?建设性的答案将得到奖励!
最新
上面是我尝试从数组中重建的示例 Blowfish 哈希,通过 SHA512 HMACed、对称 Blowfish 加密 (CBC)、url 安全 Base64 编码、urlencoded、查询字符串接收(唷!)。
下面是查询字符串的字符串(已经切掉了上面的河豚哈希)在加密、签名和 base64 编码之后但在被 urlencoded 之前的样子。每一个都是 128 个字符长(当你做更多的事情时,每个字符串会变长)。
上面是从查询字符串派生的 Base64 解码和 Blowfish 解密数组(显然,在此结果之间存在安全步骤,但我只是想显示最新状态。)有些不对劲。加密似乎没有任何错误。解密也不会产生任何错误。纯文本是错误的。如果我加入/分解这些元素,它们将不会像上面的 Blowfish 散列那样。
【问题讨论】:
在我使用 ryndael 128 时遇到了类似的问题。花了几个小时寻找原因,但发现 mcrypt 没有得到积极维护,并切换到 openssl 加密,到目前为止运行良好。这是一个猜测,但我认为问题在于密钥的派生/重新创建方式。 @Nitin 我尝试确保用于密钥的字符串长度至少为 56 个字符。此外,我还集成了 base64_encode 的 url 安全版本。我能想到的唯一另一件事是将密钥作为二进制数据返回并以这种方式使用。 @Nitin 显然,返回的 IV 是 ISO-8859-1。当我将它转换为 UTF-8 时,IV 总是报告它是 8 个字节。我可能已经解决了这个问题。 @Nitin 也许我应该在加密期间强制使用 ISO-8859-1 IV 并将传入的 IV (mb, UTF8,) 转换为 ISO-8859-1 IV。那可能是票。 这 12 个 字节 可能是因为您使用了 mb_substr(),它接受字符,而不是字节。使用 substr(),8 将占用 8 个字节。编码永远不会成为问题,因为输出要么是二进制的(使用 base64),要么是 ascii 安全的(mb 与否都不会产生影响)。 【参考方案1】:我猜想问题会隐藏在 UTF-8 编码的某个地方,因为您在不正确的上下文中使用它。也可能是您的框架对所有用例都有一些魔力。这可能太多了,而且通常会导致安全漏洞或类似的错误,因为当真正需要做的时候,你没有做真正需要做的事情。
PHP 中的字符串只是字节的集合。您可以将文本存储在您选择的编码中,或者您可以只将二进制数据存储在那里,例如图像。 PHP 既不知道什么类型的数据在什么字符串中,也不知道那里使用了什么编码。这取决于开发人员来跟踪此信息。
使用加密时,您会在生成随机字符串或加密某些有效负载时获得二进制数据。它保存在字符串中,但它没有 UTF-8 编码,因为它只是字节。我什至不会说它的编码是 ISO-8859-1,因为这意味着字节 77 (0x4D) 代表字母“M”。但实际上,它只是数字 - 77 根本不代表任何字母。
还要添加一件事 - 对于 ASCII 符号(拉丁字母、数字等 - 0-127 字节值),它需要一个字节来表示 UTF-8 编码中的符号(与 ISO-8859 相同)。因此,只要您传递base64_encode
d 数据,您就不必太担心它。 mb_substr
也将以与substr
相同的方式工作。 但是! 对于二进制数据,您不能使用 mb_*
函数,因为它适用于字符。例如,如果加密数据是两个字节0xC5 0xA1
,那么它只是 UTF-8 中的单个符号。加密使用字节(直到最终结果,可以是任何东西 - 甚至是二进制文件),而不是字符。
由于您没有提供任何代码,我已经为您提供了一些代码 - 我希望它对您的问题有所帮助(如果它仍然相关的话)。
为了在 URL 中显示传递参数,有两个文件:encrypt.php
和 decrypt.php
。保存到一个目录,在里面运行php -S localhost:8000
,然后转到http://localhost:8000/encrypt.php
encrypt.php
:
<?php
// mcrypt_enc_get_key_size($td) gives 56, so it's longest that this key can be
$key = 'LedsoilgarvEwAbDavVenpirabUfjaiktavKekjeajUmshamEsyenvoa';
$data = 'This is very important data, with some š UTF-8 ĘĖ symbols';
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
// create random IV - it's just random 8 bytes. You should use random_bytes() instead if available
$ivSize = mcrypt_enc_get_iv_size($td);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $data);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
// payload that you want to send - binary. It's neither UTF-8 nor ISO-8859-1 - it's just bytes
$payload = $iv . $encrypted;
// base64 to pass safely
$base64EncodedPayload = base64_encode($payload);
// URL encode for URL. No need to do both URL-safe base64 *and* base64 + urlencode
$link = 'http://localhost:8000/decrypt.php?encryptedBase64=' . urlencode($base64EncodedPayload);
// in fact, just for the reference, you don't even need base64_encode - urlencode also works at byte level
// base64_encode takes about 1.33 more space, but urlencode takes 3 times more than original for non-safe symbols, so base_64 will probably be shorter
$link2 = 'http://localhost:8000/decrypt.php?encrypted=' . urlencode($payload);
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<pre><?php
var_dump('Data:', $data);
var_dump('Data size in bytes:', strlen($data));
var_dump('Data size in characters - smaller, as 3 of the characters take 2 bytes:', mb_strlen($data, 'UTF-8'));
var_dump('Encrypted data size in bytes - same as original:', strlen($encrypted));
var_dump('Encrypted data size in characters - will be pseudo-random each time:', mb_strlen($encrypted, 'UTF-8'));
var_dump('IV base64 encoded:', base64_encode($iv));
var_dump('Encrypted string base64 encoded:', base64_encode($encrypted));
?></pre>
<!-- Link will not contain any special characters, so htmlentities should not make any difference -->
<!-- In any case, I would still recommend to use right encoding at the right context to avoid any issues if something changes -->
<a href="<?php echo htmlentities($link, ENT_QUOTES, 'UTF-8');?>">Link to decrypt</a><br/>
<a href="<?php echo htmlentities($link2, ENT_QUOTES, 'UTF-8');?>">Link to decrypt2</a>
</body>
</html>
decrypt.php
:
<?php
$key = 'LedsoilgarvEwAbDavVenpirabUfjaiktavKekjeajUmshamEsyenvoa';
if (isset($_GET['encryptedBase64']))
// just get base64_encoded symbols (will be ASCII - same in UTF-8 or other encodings)
$base64EncodedPayload = $_GET['encryptedBase64'];
$payload = base64_decode($base64EncodedPayload);
else
// just get binary string from URL
$payload = $_GET['encrypted'];
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
$ivSize = mcrypt_enc_get_iv_size($td);
$iv = substr($payload, 0, $ivSize);
$encrypted = substr($payload, $ivSize);
mcrypt_generic_init($td, $key, $iv);
/* Decrypt encrypted string */
$decrypted = mdecrypt_generic($td, $encrypted);
/* Terminate decryption handle and close module */
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<pre><?php
var_dump('IV base64 encoded:', base64_encode($iv));
var_dump('Encrypted string base64 encoded:', base64_encode($encrypted));
var_dump('Result:', $decrypted);
?></pre>
</body>
</html>
【讨论】:
因此,您描述的解密序列将用于 link2。我会看看是否可以将您的一些 UTF-8 建议应用于加密代码 (class Blowfish extends Cipher
)。在加密或解密过程中,我会尝试在任何时候省略任何使用 mb_* 函数。我确实相信在签名后对密文进行 url-safe base64 编码仍然是明智的 (hash_hmac('sha512', $string, $this->hmacKey, false)
),因为这是一个廉价的模糊步骤,可以将下划线和其他两个字符排除在查询字符串之外。但是,如果这引起了问题,那就必须放弃。
link
和 link2
都可以工作 - 它只需要在解密端执行相同的步骤(反向)(参见 decrypt.php
中的 if
)。您可以使用 URL 安全的 base64 编码,如果您使用urlencode
,这并不是真正必要的。如果它更清楚 - 你可以做到,只需在解密时进行反向操作。关于框架 - 我从您的评论 (where all inputs are assured to be UTF-8 via mb string functions in my filter framework
) 中误解了这一点
好的,我会告诉你进展如何。我现在实际上正在配置一个 VirtualHost,但几个小时后,我可能想玩。我会联系你。让我们希望签名不会导致问题。
Marius,嘿,在我深入研究之前,也许你可以看一下我对编码这个问题的回答中的代码。它只是一个类的两个方法,但它可能有一个轴承。 ***.com/questions/7979567/…
我一直在想。通过GET
请求接收输入时,您的答案中描述的事件序列与事件序列不同。具体来说,它缺乏任何验证输入编码的尝试。也就是说,当我重新组织输入清理程序时,我会告诉你进展如何。以上是关于PHP:警告 mcrypt_generic_init():IV 大小不正确;提供长度:12,需要:8的主要内容,如果未能解决你的问题,请参考以下文章