实施 Php open_ssl_decrypt AES 256 CBC 作为 CryptoJS
Implement Php open_ssl_decrypt AES 256 CBC as CryptoJS
我尝试在 ReactJs(不是 NodeJs)中编写以下代码,但这在 JS 中不起作用。
PHP中的原始代码工作正常:
function decryptOpensslDigestSHA256Sum($data)
{
$key = hash('sha256', 'Nootric2703202'); //My password has 14 characters
$method = 'AES-256-CBC';
$data = base64_decode($data);
$iv_size = openssl_cipher_iv_length($method);
$salt_header = substr($data, 0, $iv_size);
if (substr($salt_header, 0, 8) != "Salted__") {
return "";
}
$salt = substr($salt_header, 8);
$creds = extractOpenSSLCreds($key, $salt, $iv_size);
$data = openssl_decrypt(substr($data, $iv_size), $method, $creds['password'], OPENSSL_RAW_DATA, $creds['iv']);
return $data;
}
function extractOpenSSLCreds($key, $salt, $iv_size)
{
$m = "";
while (strlen($m) < 48) {
$m .= hashCryptoDigestSHA256Sum($m, $key, $salt);
}
$result = array(
'password' => substr($m, 0, 32),
'iv' => substr($m, 32, $iv_size)
);
return $result;
}
function hashCryptoDigestSHA256Sum($hash, $key, $salt)
{
$hash.= $key.$salt;
$prev = openssl_digest($hash, "sha256", true);
return $prev;
}
如果我在 php 中调用这个函数:
$data = "U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==";
echo "Data urlEncoded: $data<br>";
$decryption = decryptOpensslDigestSHA256Sum($data);
echo "Data decrypted: $decryption<br><br>";
这表明:
数据解密:email=abc@xyz.abc&name=&gpw_id=gpwID
但是当我尝试在 JS 中使用这个函数时它不起作用(我在这个 Whosebug 条目上找到的源代码
function CryptoJSAesDecrypt(encrypted){
// 1. Separate ciphertext and salt
var encryptedWA = CryptoJS.enc.Base64.parse(encrypted);
var prefixWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(0, 8/4)); // Salted__ prefix
var saltWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(8/4, 16/4)); // 8 bytes salt: 0x0123456789ABCDEF
var ciphertextWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(16/4, encryptedWA.words.length)); // ciphertext
// 2. Determine key and IV using PBKDF2
var password = 'Nootric2703202'
var keyIvWA = CryptoJS.PBKDF2(
password,
saltWA,
{
keySize: (32+16)/4, // key and IV
iterations: 10000,
hasher: CryptoJS.algo.SHA256
}
);
var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, 32/4));
var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(32/4, (32+16)/4));
// 3. Decrypt
var decryptedWA = CryptoJS.AES.decrypt(
{ciphertext: ciphertextWA},
keyWA,
{iv: ivWA}
);
var decrypted = decryptedWA.toString(CryptoJS.enc.Utf8)
return decrypted;
}
如果我在 JS 中调用最后一个函数:
const dec = CryptoJSAesDecrypt("U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==");
console.log("Data Decrypted: " + dec);
这显示为空:
已解密数据:
如果我用这个数据调用:
const dec = CryptoJSAesDecrypt("U2FsdGVkX18BI0VniavN78vlhR6fryIan0VvUrdIr+YeLkDYhO2xyA+/oVXJj/c35swVVkCqHPh9VdRbNQG6NQ==");
console.log("Data Decrypted: " + dec);
并且在 Javascript 函数中我替换了这一行:
var password = 'Nootric2703202'
这个:
var password = 'mypassword'
这很好用!但是使用我自己的密码和我的加密数据,这个 JS 解密代码不起作用。
请帮忙?
const dec = CryptoJSAesDecrypt();
console.log("Decrypted: " + dec);
function CryptoJSAesDecrypt(encrypted){
// 1. Separate ciphertext and salt
var encrypted = "U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==";
console.log("Encrypted:", encrypted);
var encryptedWA = CryptoJS.enc.Base64.parse(encrypted);
var prefixWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(0, 8/4)); // Salted__ prefix
var saltWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(8/4, 16/4)); // 8 bytes salt: 0x0123456789ABCDEF
var ciphertextWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(16/4, encryptedWA.words.length)); // ciphertext
// 2. Determine key and IV using PBKDF2
var password = 'Nootric2703202'
var keyIvWA = CryptoJS.PBKDF2(
password,
saltWA,
{
keySize: (32+16)/4, // key and IV
iterations: 10000,
hasher: CryptoJS.algo.SHA256
}
);
var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, 32/4));
var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(32/4, (32+16)/4));
// 3. Decrypt
var decryptedWA = CryptoJS.AES.decrypt(
{ciphertext: ciphertextWA},
keyWA,
{iv: ivWA}
);
var decrypted = decryptedWA.toString(CryptoJS.enc.Utf8)
return decrypted;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
这是 PHP openssl-decrypt 的源代码(这是一个 openssl.c 文件)PHP openssl.c:
PHP_OPENSSL_API zend_string* php_openssl_decrypt(
const char *data, size_t data_len,
const char *method, size_t method_len,
const char *password, size_t password_len,
zend_long options,
const char *iv, size_t iv_len,
const char *tag, zend_long tag_len,
const char *aad, size_t aad_len)
{
const EVP_CIPHER *cipher_type;
EVP_CIPHER_CTX *cipher_ctx;
struct php_openssl_cipher_mode mode;
int i = 0, outlen;
zend_string *base64_str = NULL;
bool free_iv = 0, free_password = 0;
zend_string *outbuf = NULL;
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(data_len, data);
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(password_len, password);
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(aad_len, aad);
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(tag_len, tag);
cipher_type = EVP_get_cipherbyname(method);
if (!cipher_type) {
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
return NULL;
}
cipher_ctx = EVP_CIPHER_CTX_new();
if (!cipher_ctx) {
php_error_docref(NULL, E_WARNING, "Failed to create cipher context");
return NULL;
}
php_openssl_load_cipher_mode(&mode, cipher_type);
if (!(options & OPENSSL_RAW_DATA)) {
base64_str = php_base64_decode((unsigned char*)data, data_len);
if (!base64_str) {
php_error_docref(NULL, E_WARNING, "Failed to base64 decode the input");
EVP_CIPHER_CTX_free(cipher_ctx);
return NULL;
}
data_len = ZSTR_LEN(base64_str);
data = ZSTR_VAL(base64_str);
}
if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode,
&password, &password_len, &free_password,
&iv, &iv_len, &free_iv, tag, tag_len, options, 0) == FAILURE ||
php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen,
data, data_len, aad, aad_len, 0) == FAILURE) {
outbuf = NULL;
} else if (mode.is_single_run_aead ||
EVP_DecryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) {
outlen += i;
ZSTR_VAL(outbuf)[outlen] = '[=21=]';
ZSTR_LEN(outbuf) = outlen;
} else {
php_openssl_store_errors();
zend_string_release_ex(outbuf, 0);
outbuf = NULL;
}
if (free_password) {
efree((void *) password);
}
if (free_iv) {
efree((void *) iv);
}
if (base64_str) {
zend_string_release_ex(base64_str, 0);
}
EVP_CIPHER_CTX_reset(cipher_ctx);
EVP_CIPHER_CTX_free(cipher_ctx);
return outbuf;
}
/* {{{ Takes raw or base64 encoded string and decrypts it using given method and key */
PHP_FUNCTION(openssl_decrypt)
{
zend_long options = 0;
char *data, *method, *password, *iv = "", *tag = NULL, *aad = "";
size_t data_len, method_len, password_len, iv_len = 0, tag_len = 0, aad_len = 0;
zend_string *ret;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lsss", &data, &data_len, &method, &method_len,
&password, &password_len, &options, &iv, &iv_len, &tag, &tag_len, &aad, &aad_len) == FAILURE) {
RETURN_THROWS();
}
if (!method_len) {
zend_argument_value_error(2, "cannot be empty");
RETURN_THROWS();
}
if ((ret = php_openssl_decrypt(data, data_len, method, method_len, password, password_len, options, iv, iv_len, tag, tag_len, aad, aad_len))) {
RETVAL_STR(ret);
} else {
RETVAL_FALSE;
}
}
PHP openssl-digest 的源代码是这样的:
/* {{{ Computes digest hash value for given data using given method, returns raw or binhex encoded string */
PHP_FUNCTION(openssl_digest)
{
bool raw_output = 0;
char *data, *method;
size_t data_len, method_len;
const EVP_MD *mdtype;
EVP_MD_CTX *md_ctx;
unsigned int siglen;
zend_string *sigbuf;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|b", &data, &data_len, &method, &method_len, &raw_output) == FAILURE) {
RETURN_THROWS();
}
mdtype = EVP_get_digestbyname(method);
if (!mdtype) {
php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
RETURN_FALSE;
}
siglen = EVP_MD_size(mdtype);
sigbuf = zend_string_alloc(siglen, 0);
md_ctx = EVP_MD_CTX_create();
if (EVP_DigestInit(md_ctx, mdtype) &&
EVP_DigestUpdate(md_ctx, (unsigned char *)data, data_len) &&
EVP_DigestFinal (md_ctx, (unsigned char *)ZSTR_VAL(sigbuf), &siglen)) {
if (raw_output) {
ZSTR_VAL(sigbuf)[siglen] = '[=22=]';
ZSTR_LEN(sigbuf) = siglen;
RETVAL_STR(sigbuf);
} else {
int digest_str_len = siglen * 2;
zend_string *digest_str = zend_string_alloc(digest_str_len, 0);
make_digest_ex(ZSTR_VAL(digest_str), (unsigned char*)ZSTR_VAL(sigbuf), siglen);
ZSTR_VAL(digest_str)[digest_str_len] = '[=22=]';
zend_string_release_ex(sigbuf, 0);
RETVAL_NEW_STR(digest_str);
}
} else {
php_openssl_store_errors();
zend_string_release_ex(sigbuf, 0);
RETVAL_FALSE;
}
EVP_MD_CTX_destroy(md_ctx);
}
PHP 实现使用 EVP_BytesToKey()
作为密钥派生函数,因此与 CryptoJS 密钥派生兼容。
然而,CryptoJS 默认使用 MD5 作为摘要,而 PHP 代码使用 SHA256(请注意,从 v1.1.0 版本开始,OpenSSL 已将默认摘要从 MD5 更改为 SHA256)。
此外,用于密钥推导函数的密码不是密码本身(即 Nootric2703202),而是密码的十六进制编码 SHA256 哈希。
如果考虑到这一点,用 CryptoJS 解密是:
var password = 'Nootric2703202';
var passwordHashWA = CryptoJS.SHA256(password);
var passwordHashHex = passwordHashWA.toString(CryptoJS.enc.Hex);
var ciphertext = 'U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==';
CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.SHA256.create();
var data = CryptoJS.AES.decrypt(ciphertext, passwordHashHex);
console.log(data.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
产生预期的明文:
email=abc@xyz.abc&name=&gpw_id=gpwID
由于OpenSSL兼容性,密文也可以用以下OpenSSL表达式解密:
openssl enc -aes-256-cbc -d -md sha256 -in <ciphertextFile> -k d0f95d5e54a7aa25934a5d4915c9e2a06dadac20d16551693be1d21d4d8e8798 -A -a -p
其中 <ciphertextFile>
是包含 Base64 编码密文(无换行符)的文件的路径:U2FsdGVkX1...
,密码 d0f95d...
是密码的十六进制编码 SHA256 哈希Nootric2703202.
请记住 EVP_BytesToKey()
被认为是不安全的,s。例如here。相反,应该使用像 PBKDF2 这样可靠的密钥派生函数。
我尝试在 ReactJs(不是 NodeJs)中编写以下代码,但这在 JS 中不起作用。
PHP中的原始代码工作正常:
function decryptOpensslDigestSHA256Sum($data)
{
$key = hash('sha256', 'Nootric2703202'); //My password has 14 characters
$method = 'AES-256-CBC';
$data = base64_decode($data);
$iv_size = openssl_cipher_iv_length($method);
$salt_header = substr($data, 0, $iv_size);
if (substr($salt_header, 0, 8) != "Salted__") {
return "";
}
$salt = substr($salt_header, 8);
$creds = extractOpenSSLCreds($key, $salt, $iv_size);
$data = openssl_decrypt(substr($data, $iv_size), $method, $creds['password'], OPENSSL_RAW_DATA, $creds['iv']);
return $data;
}
function extractOpenSSLCreds($key, $salt, $iv_size)
{
$m = "";
while (strlen($m) < 48) {
$m .= hashCryptoDigestSHA256Sum($m, $key, $salt);
}
$result = array(
'password' => substr($m, 0, 32),
'iv' => substr($m, 32, $iv_size)
);
return $result;
}
function hashCryptoDigestSHA256Sum($hash, $key, $salt)
{
$hash.= $key.$salt;
$prev = openssl_digest($hash, "sha256", true);
return $prev;
}
如果我在 php 中调用这个函数:
$data = "U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==";
echo "Data urlEncoded: $data<br>";
$decryption = decryptOpensslDigestSHA256Sum($data);
echo "Data decrypted: $decryption<br><br>";
这表明: 数据解密:email=abc@xyz.abc&name=&gpw_id=gpwID
但是当我尝试在 JS 中使用这个函数时它不起作用(我在这个 Whosebug 条目上找到的源代码
function CryptoJSAesDecrypt(encrypted){
// 1. Separate ciphertext and salt
var encryptedWA = CryptoJS.enc.Base64.parse(encrypted);
var prefixWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(0, 8/4)); // Salted__ prefix
var saltWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(8/4, 16/4)); // 8 bytes salt: 0x0123456789ABCDEF
var ciphertextWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(16/4, encryptedWA.words.length)); // ciphertext
// 2. Determine key and IV using PBKDF2
var password = 'Nootric2703202'
var keyIvWA = CryptoJS.PBKDF2(
password,
saltWA,
{
keySize: (32+16)/4, // key and IV
iterations: 10000,
hasher: CryptoJS.algo.SHA256
}
);
var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, 32/4));
var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(32/4, (32+16)/4));
// 3. Decrypt
var decryptedWA = CryptoJS.AES.decrypt(
{ciphertext: ciphertextWA},
keyWA,
{iv: ivWA}
);
var decrypted = decryptedWA.toString(CryptoJS.enc.Utf8)
return decrypted;
}
如果我在 JS 中调用最后一个函数:
const dec = CryptoJSAesDecrypt("U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==");
console.log("Data Decrypted: " + dec);
这显示为空: 已解密数据:
如果我用这个数据调用:
const dec = CryptoJSAesDecrypt("U2FsdGVkX18BI0VniavN78vlhR6fryIan0VvUrdIr+YeLkDYhO2xyA+/oVXJj/c35swVVkCqHPh9VdRbNQG6NQ==");
console.log("Data Decrypted: " + dec);
并且在 Javascript 函数中我替换了这一行:
var password = 'Nootric2703202'
这个:
var password = 'mypassword'
这很好用!但是使用我自己的密码和我的加密数据,这个 JS 解密代码不起作用。 请帮忙?
const dec = CryptoJSAesDecrypt();
console.log("Decrypted: " + dec);
function CryptoJSAesDecrypt(encrypted){
// 1. Separate ciphertext and salt
var encrypted = "U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==";
console.log("Encrypted:", encrypted);
var encryptedWA = CryptoJS.enc.Base64.parse(encrypted);
var prefixWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(0, 8/4)); // Salted__ prefix
var saltWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(8/4, 16/4)); // 8 bytes salt: 0x0123456789ABCDEF
var ciphertextWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(16/4, encryptedWA.words.length)); // ciphertext
// 2. Determine key and IV using PBKDF2
var password = 'Nootric2703202'
var keyIvWA = CryptoJS.PBKDF2(
password,
saltWA,
{
keySize: (32+16)/4, // key and IV
iterations: 10000,
hasher: CryptoJS.algo.SHA256
}
);
var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, 32/4));
var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(32/4, (32+16)/4));
// 3. Decrypt
var decryptedWA = CryptoJS.AES.decrypt(
{ciphertext: ciphertextWA},
keyWA,
{iv: ivWA}
);
var decrypted = decryptedWA.toString(CryptoJS.enc.Utf8)
return decrypted;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
这是 PHP openssl-decrypt 的源代码(这是一个 openssl.c 文件)PHP openssl.c:
PHP_OPENSSL_API zend_string* php_openssl_decrypt(
const char *data, size_t data_len,
const char *method, size_t method_len,
const char *password, size_t password_len,
zend_long options,
const char *iv, size_t iv_len,
const char *tag, zend_long tag_len,
const char *aad, size_t aad_len)
{
const EVP_CIPHER *cipher_type;
EVP_CIPHER_CTX *cipher_ctx;
struct php_openssl_cipher_mode mode;
int i = 0, outlen;
zend_string *base64_str = NULL;
bool free_iv = 0, free_password = 0;
zend_string *outbuf = NULL;
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(data_len, data);
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(password_len, password);
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(aad_len, aad);
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(tag_len, tag);
cipher_type = EVP_get_cipherbyname(method);
if (!cipher_type) {
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
return NULL;
}
cipher_ctx = EVP_CIPHER_CTX_new();
if (!cipher_ctx) {
php_error_docref(NULL, E_WARNING, "Failed to create cipher context");
return NULL;
}
php_openssl_load_cipher_mode(&mode, cipher_type);
if (!(options & OPENSSL_RAW_DATA)) {
base64_str = php_base64_decode((unsigned char*)data, data_len);
if (!base64_str) {
php_error_docref(NULL, E_WARNING, "Failed to base64 decode the input");
EVP_CIPHER_CTX_free(cipher_ctx);
return NULL;
}
data_len = ZSTR_LEN(base64_str);
data = ZSTR_VAL(base64_str);
}
if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode,
&password, &password_len, &free_password,
&iv, &iv_len, &free_iv, tag, tag_len, options, 0) == FAILURE ||
php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen,
data, data_len, aad, aad_len, 0) == FAILURE) {
outbuf = NULL;
} else if (mode.is_single_run_aead ||
EVP_DecryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) {
outlen += i;
ZSTR_VAL(outbuf)[outlen] = '[=21=]';
ZSTR_LEN(outbuf) = outlen;
} else {
php_openssl_store_errors();
zend_string_release_ex(outbuf, 0);
outbuf = NULL;
}
if (free_password) {
efree((void *) password);
}
if (free_iv) {
efree((void *) iv);
}
if (base64_str) {
zend_string_release_ex(base64_str, 0);
}
EVP_CIPHER_CTX_reset(cipher_ctx);
EVP_CIPHER_CTX_free(cipher_ctx);
return outbuf;
}
/* {{{ Takes raw or base64 encoded string and decrypts it using given method and key */
PHP_FUNCTION(openssl_decrypt)
{
zend_long options = 0;
char *data, *method, *password, *iv = "", *tag = NULL, *aad = "";
size_t data_len, method_len, password_len, iv_len = 0, tag_len = 0, aad_len = 0;
zend_string *ret;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lsss", &data, &data_len, &method, &method_len,
&password, &password_len, &options, &iv, &iv_len, &tag, &tag_len, &aad, &aad_len) == FAILURE) {
RETURN_THROWS();
}
if (!method_len) {
zend_argument_value_error(2, "cannot be empty");
RETURN_THROWS();
}
if ((ret = php_openssl_decrypt(data, data_len, method, method_len, password, password_len, options, iv, iv_len, tag, tag_len, aad, aad_len))) {
RETVAL_STR(ret);
} else {
RETVAL_FALSE;
}
}
PHP openssl-digest 的源代码是这样的:
/* {{{ Computes digest hash value for given data using given method, returns raw or binhex encoded string */
PHP_FUNCTION(openssl_digest)
{
bool raw_output = 0;
char *data, *method;
size_t data_len, method_len;
const EVP_MD *mdtype;
EVP_MD_CTX *md_ctx;
unsigned int siglen;
zend_string *sigbuf;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|b", &data, &data_len, &method, &method_len, &raw_output) == FAILURE) {
RETURN_THROWS();
}
mdtype = EVP_get_digestbyname(method);
if (!mdtype) {
php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
RETURN_FALSE;
}
siglen = EVP_MD_size(mdtype);
sigbuf = zend_string_alloc(siglen, 0);
md_ctx = EVP_MD_CTX_create();
if (EVP_DigestInit(md_ctx, mdtype) &&
EVP_DigestUpdate(md_ctx, (unsigned char *)data, data_len) &&
EVP_DigestFinal (md_ctx, (unsigned char *)ZSTR_VAL(sigbuf), &siglen)) {
if (raw_output) {
ZSTR_VAL(sigbuf)[siglen] = '[=22=]';
ZSTR_LEN(sigbuf) = siglen;
RETVAL_STR(sigbuf);
} else {
int digest_str_len = siglen * 2;
zend_string *digest_str = zend_string_alloc(digest_str_len, 0);
make_digest_ex(ZSTR_VAL(digest_str), (unsigned char*)ZSTR_VAL(sigbuf), siglen);
ZSTR_VAL(digest_str)[digest_str_len] = '[=22=]';
zend_string_release_ex(sigbuf, 0);
RETVAL_NEW_STR(digest_str);
}
} else {
php_openssl_store_errors();
zend_string_release_ex(sigbuf, 0);
RETVAL_FALSE;
}
EVP_MD_CTX_destroy(md_ctx);
}
PHP 实现使用 EVP_BytesToKey()
作为密钥派生函数,因此与 CryptoJS 密钥派生兼容。
然而,CryptoJS 默认使用 MD5 作为摘要,而 PHP 代码使用 SHA256(请注意,从 v1.1.0 版本开始,OpenSSL 已将默认摘要从 MD5 更改为 SHA256)。
此外,用于密钥推导函数的密码不是密码本身(即 Nootric2703202),而是密码的十六进制编码 SHA256 哈希。
如果考虑到这一点,用 CryptoJS 解密是:
var password = 'Nootric2703202';
var passwordHashWA = CryptoJS.SHA256(password);
var passwordHashHex = passwordHashWA.toString(CryptoJS.enc.Hex);
var ciphertext = 'U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==';
CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.SHA256.create();
var data = CryptoJS.AES.decrypt(ciphertext, passwordHashHex);
console.log(data.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
产生预期的明文:
email=abc@xyz.abc&name=&gpw_id=gpwID
由于OpenSSL兼容性,密文也可以用以下OpenSSL表达式解密:
openssl enc -aes-256-cbc -d -md sha256 -in <ciphertextFile> -k d0f95d5e54a7aa25934a5d4915c9e2a06dadac20d16551693be1d21d4d8e8798 -A -a -p
其中 <ciphertextFile>
是包含 Base64 编码密文(无换行符)的文件的路径:U2FsdGVkX1...
,密码 d0f95d...
是密码的十六进制编码 SHA256 哈希Nootric2703202.
请记住 EVP_BytesToKey()
被认为是不安全的,s。例如here。相反,应该使用像 PBKDF2 这样可靠的密钥派生函数。