Ответ 1
Похоже, что это может быть ошибка блокировки OpenSSL. Вы должны убедиться, что только один объект OpenSSL когда-либо используется в одно время в одном и том же пространстве процесса.
Чтобы проверить, запустите тест script, чтобы он был единственным, использующим OpenSSL. Он по-прежнему не работает в 50% случаев? Или это происходит только при одновременном доступе к script?
Если это все еще случается, оно почти должно быть ошибкой в php-fpm - оно создает экземпляр функции и не очищает ее области данных до тех пор, пока не произойдет ошибка. В этом случае я ожидаю, что он будет терпеть неудачу один раз каждые два вызова, а не "50% в среднем", но ровно один раз каждый четный вызов. В этом случае я попробую использовать другую версию OpenSSL.
Чтобы заблокировать openssl, вы можете попробовать flock и создать экземпляр файла блокировки для использования функцией SSL (сначала вы проверите блокировку доступно, затем запустите функцию и разблокируйте). Попробуйте это и посмотрите, работает ли он. Если это так, вы можете изучить более эффективный способ сделать это - например, вы можете использовать семафор MySQL LOCK() или , если он доступен.
Спелеология
Неисправную функцию в 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. Я нашел этот краткий (и, возможно, устаревший) ответ, который, кажется, указывает там какой-то сок facepalm в рецепте где-то. Но это не объясняет, почему функция должна терпеть неудачу один раз в два.
Функция здесь, на github. Он инициализирует OpenSSL и получает имя метода с помощью вспомогательной функции, которая вернет указатель на не-нулевую память.
У меня была надуманная гипотеза о том, что функция возвращала что-то похожее на 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; потому что я также наткнулся на эту заметку, которая указывает на немного другой ресурс, чем я думал, но достаточно близко, чтобы меня реагировали
Похоже, что openssl_random_pseudo_bytes(), который вызывает openssl, заставляет базовый libcrypto вызывать обратный вызов, который был установленный ранее библиотекой PostgreSQL как часть блокировки обратные вызовы переносимости для многопоточности openssl.
Некоторые сведения по этой теме можно найти здесь http://wiki.openssl.org/index.php/Manual:Threads(3)
Если расширение HHVM openssl не устанавливает эти же обратные вызовы, это может вызывать неправильные обратные вызовы.
Следующие тесты, которые я собираюсь выполнить, если позволяют время, размещать предупреждения (в виде статических вызовов syslog) в вышеупомянутых точках отказа, чтобы точно определить, какой тест терпит неудачу... при условии, что я могу установить та же самая настройка, что и на виртуальной машине, и я могу воспроизвести одно и то же странное поведение.