【问题标题】:openssl encypt/decrypt inconsistently working/failingopenssl 加密/解密不一致地工作/失败
【发布时间】:2016-09-05 21:43:18
【问题描述】:

我看到 PHP 中的 openssl_* 方法有一些奇怪的行为。 50% 的时间,它会失败,抛出Unknown cipher algorithm,而另外 50% 的时间,它会正确编码我的数据。这是我的代码中相关的 sn-p:

$iv = openssl_random_pseudo_bytes(16);
$hash = openssl_encrypt($raw, "AES-128-CBC", $hashing_secret, OPENSSL_RAW_DATA, $iv);
// send $iv.$hash

使用openssl_get_cipher_methods 给我:

[0] => AES-128-CBC
...
[81] => aes-128-cbc

所以我知道密码可用。此外,$ openssl ciphers 将 AES-128-CBC 列为系统级别的可用密码(但是,我被告知 PHP 捆绑的 openssl 是独立的)

我正在运行 Ubuntu 14.04、php5.5.9-1ubuntu4.14、openssl 1.0.1f 2014 年 1 月 6 日(phpinfo 中列出的版本相同)。如果相关,所有这些代码都通过 nginx/php-fpm 在 Silex 框架下运行。

更新:更多信息...

我做了更多的测试。我写了一个小脚本,它只循环 x 次,对一些数据进行编码。

set_error_handler(function() use (&$errorCount) {
    $errorCount++;
});

for ($i = 0; $i < $numTests; $i++) {
    $hash = openssl_encrypt($data, "AES-128-CBC", $hashing_secret, OPENSSL_RAW_DATA, $iv);    
}

如果我在同一台服务器上运行它(通过php test.php),它会始终如一地运行——即每次都运行$errorCount == 0。这让我相信它是:a) silex 或 b) 阻碍该功能的 fastcgi 进程 - 我已经添加了这些标签。

虽然现在还不确定从这里去哪里......

第二次更新

我做了更多的测试。我将测试脚本卡在 nginx 后面,运行 php-fpm。奇怪的是,要么 a) 它失败了 100% 的时间,要么 b) 它失败了 0 次,而不是两个结果的一点点。这让我相信罪魁祸首是 nginx 或 php-fpm。

【问题讨论】:

  • 密钥和 IV 是否完全正确,密钥是 128 位、192 位或 256 位之一,而 iv 是 16 字节?
  • 您可以看到我的 IV 有多长 - 它正好是 16 个字节。我使用的密钥是 128 位(16 个字符)
  • 您是否检查openssl_random_pseudo_bytes(16); 不返回FALSE,来自文档:成功时返回生成的字节字符串,失败时返回FALSE。 有时这样的例程因为这些没有足够的熵来返回请求的字节。添加这样的检查并记录任何失败可能是值得的。
  • 另外:每次都使用相同的密钥吗? “(16个字符)”这句话让我担心,这在不同的语言中意味着不同的东西。加密适用于 8 位字节,而字符很多时候指的是特定的字符编码,例如 UTF-8、UTF-16 等。我个人只相信在所有输入和输出的加密调用之前和之后的十六进制显示。
  • 我将密钥设置为“abce...”16 个字符。

标签: php symfony ubuntu openssl silex


【解决方案1】:

我可以向您展示我如何使用 openssl_encrypt 和解密我正在使用 Silex 框架,我现在没有任何问题。希望对你有帮助。

我知道这不是最好的解决方案,但也许可以帮助你

$encrypt_method = "aes128";
$secret_key = 'd2ae49e3b63ed418b9fc25105cd964d4';
$secret_iv = 'fb68e879fab1db2a2ce30dbf6f9b3743';
$key = hash('sha256', $secret_key);
$iv = substr(hash('sha256', $secret_iv), 0, 16);

$output = openssl_encrypt($str, $encrypt_method, $key, 0, $iv);
return base64_encode($output);


$output = openssl_decrypt(base64_decode($str), $encrypt_method,   $key, 0, $iv);
return $output;

【讨论】:

  • 对不起 - 没有骰子。我几乎逐行复制了加密部分(包括示例密钥/iv),但仍然出现错误。
  • 你在什么平台上运行?
  • 在 Windows 10 上工作,apache xampp。 php 5.6。这就是你需要的?
  • 非常感谢。对我没有帮助,但我只是想知道重叠。
【解决方案2】:

这看起来可能是 OpenSSL 错误锁定错误。您应该确保在同一进程空间中的任何时候都只使用一个 OpenSSL 对象。

要验证,请运行测试脚本,使其成为唯一使用 OpenSSL 的脚本。它仍然有 50% 的时间失败吗?还是只有对脚本的多个并发访问才会发生故障?

如果它仍然发生,它几乎必须是 php-fpm 中的一个错误——它正在实例化函数并且没有正确清除其数据区域,直到发生错误。在这种情况下,我希望它每两次调用失败一次,而不是“平均 50%”,而是每个偶数调用一次。在 的情况下,我会尝试使用不同版本的 OpenSSL。

要锁定 openssl,您可以尝试使用 flock 并实例化一个锁定文件以供 SSL 函数使用(首先检查锁定是否可用,然后运行该函数并解锁)。试试这个,看看它是否有效。如果是这样,您可以研究一种更有效的方法 - 例如,您可以使用 MySQL LOCK() 或 semaphore(如果可用)。

洞穴探险

5.5.9 中的异常功能可在ext/openssl/openssl.c 中找到,引发的错误是初步检查之一。还没有惊喜:

/* {{{ proto string openssl_encrypt(string data, string method, string password [, long options=0 [, string $iv='']])
   Encrypts given data with given method and key, returns raw or base64 encoded string */
PHP_FUNCTION(openssl_encrypt)
{
    long options = 0;
    char *data, *method, *password, *iv = "";
    int data_len, method_len, password_len, iv_len = 0, max_iv_len;
    const EVP_CIPHER *cipher_type;
    EVP_CIPHER_CTX cipher_ctx;
    int i=0, outlen, keylen;
    unsigned char *outbuf, *key;
    zend_bool free_iv;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|ls", &data, &data_len, &method, &method_len, &password, &password_len, &options, &iv, &iv_len) == FAILURE) {
        return;
    }
    cipher_type = EVP_get_cipherbyname(method);
    if (!cipher_type) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown cipher algorithm");
        RETURN_FALSE;
    }

所以我们可以假设EVP_get_cipherbyname(method) 正在返回一个虚假信息。

除了它是一个标准的 SSL 功能。我发现这个简洁(而且很可能已经过时)reply 这似乎表明某处的食谱中有一些面部果汁。但这并不能解释为什么函数应该每两次失败一次。

函数是here on github。它初始化 OpenSSL,并通过 ancillary function 获取方法名称,该名称将返回指向非空内存的指针。

我有一个牵强的假设,即该函数随机返回类似于 0 或 81 的内容(因为两个字符串都在您的密码列表输出中,索引为 0 和 81)并且 0 等于 NULL,因此失败。它似乎不能那样工作,它也应该在 CLI 中这样做。 但为了确定,请验证是否只有特定密码失败(例如 AES-256-CBC 有效)。

另一种可能性是 OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS, NULL) 调用失败。如果此测试(在 Ubuntu 上;其他平台的行为不同)失败,则可能会发生这种情况:

int CRYPTO_THREAD_run_once(CRYPTO_ONCE *once, void (*init)(void))
{
    if (pthread_once(once, init) != 0)
        return 0;

    return 1;
}

这再次表明 libcrypto 内部存在一些共享资源冲突。

作为另一个测试,我建议你不要调用随机字节IV初始化并尝试使用固定IV;那是因为我还偶然发现了this note,它指向的资源与我想象的略有不同,但足够接近让我做出反应:

似乎调用 openssl 的 openssl_random_pseudo_bytes(), 导致底层 libcrypto 调用一个回调,该回调是 先前由 PostgreSQL 库作为锁的一部分建立 openssl 多线程的可移植性回调。

可以在此处找到有关该主题的一些信息 http://wiki.openssl.org/index.php/Manual:Threads(3)

如果 HHVM openssl 扩展没有建立这些相同的回调,可能会导致调用错误的回调。

如果时间允许,我将要执行的下一个测试是在上述故障点中放置警报(以静态系统日志调用的形式),以准确查明哪个测试失败了...前提是我可以在 VM 上安装与您相同的设置,并且可以重现相同的怪异行为。

【讨论】:

  • 测试脚本在通过 PHP CLI 运行时永远不会失败。但是,我想我会尝试通过 fastcgi 运行它(我只是将它卡在 nginx 后面),并且我遇到了与以前相同的行为 - 50% 的时间都失败了。
  • 你能验证它是每两次调用失败一次,还是更随机?明天我可能可以看一下PHP源代码。
  • 我的“解决方案”见下文 - 由于您对此的深入了解,我将奖励您赏金。
【解决方案3】:

我们通过在我们的服务器上“更新”openssl 解决了这个问题 - OpenSSL 1.0.1f 6 Jan 2014 - 是的,这就是我上面列出的确切版本,我也不明白。不过,这很奇怪——因为在本地(在 linux VM 上——我将其配置为与我们的生产环境相同)它运行良好。对于在您最黑暗的时刻偶然发现此问题的任何人,我深表歉意-我的解决方案对我有用,但我对此一无所知。

【讨论】:

    猜你喜欢
    • 2022-11-24
    • 2020-10-14
    • 2021-01-30
    • 2019-04-22
    • 1970-01-01
    • 2023-01-30
    • 1970-01-01
    • 2021-07-21
    • 2018-02-23
    相关资源
    最近更新 更多