我意识到这个问题到现在已经大约一个月了,但我在搜索有关做类似事情的信息时遇到了这个问题。鉴于这里缺乏答案,我去源头寻找答案。
TL;DR(直接回答)
PKCS5_PBKDF2_HMAC() 同时生成密钥和 IV。虽然它被连接到一个字符串。您可以将字符串拆分为所需的部分。
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
int iklen = EVP_CIPHER_key_length(cipher);
int ivlen = EVP_CIPHER_iv_length(cipher);
PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair);
memcpy(key, keyivpair, iklen);
memcpy(iv, keyivpair + iklen, ivlen);
详细说明
在详细说明之前,我觉得我应该提到我使用的是 C 而不是 C++。不过,我确实希望所提供的信息即使对 C++ 也有帮助。
首先需要在应用程序中从 base64 解码字符串。之后,我们可以继续进行密钥和 IV 生成。
openssl 工具通过以字符串 'Salted__' 后跟 8 个字节的 salt(至少对于 aes-256-cbc 而言)开头的加密字符串来指示正在使用 salt。除了盐,我们还需要知道密钥和 IV 的长度。幸运的是,对此有 API 调用。
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
int iklen = EVP_CIPHER_key_length(cipher);
int ivlen = EVP_CIPHER_iv_length(cipher);
我们还需要知道迭代次数(openssl 1.1.1 中使用-pbkdf2 时的默认值为10000),以及在这种情况下为EVP_sha512() 的消息摘要函数(如指定通过选项-md sha512)。
当我们具备以上所有条件后,就可以致电 PKCS5_PBKDF2_HMAC()。
PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair);
关于参数的简短信息
- pass 的类型是 (const char *)
- 密码长度(int),如果设置为-1,长度将由strlen(pass)决定
- salt 的类型为 (const unsigned char *)
- 盐长(整数)
- 迭代次数(整数)
- 消息摘要(const EVP_MD *),在这种情况下由
EVP_sha512()返回
- key + iv (int)的总长度
- keyivpair (unsigned char *),这是存储密钥和 IV 的地方
现在我们需要将密钥和 IV 分开并将它们存储在单独的变量中。
unsigned char key[EVP_MAX_KEY_LENGTH];
unsigned char iv[EVP_MAX_IV_LENGTH];
memcpy(key, keyivpair, iklen);
memcpy(iv, keyivpair + iklen, ivlen);
现在我们有了一个密钥和 IV,可以用来解密由 openssl 工具加密的数据。
概念验证
为了进一步澄清,我编写了以下概念证明(针对 Linux 编写)。
/*
* PoC written by zoke
* Compiled with gcc decrypt-poc.c -o decrypt-poc -lcrypto -ggdb3 -Wall -Wextra
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
void bail() {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
if(argc < 3)
bail();
unsigned char key[EVP_MAX_KEY_LENGTH];
unsigned char iv[EVP_MAX_IV_LENGTH];
unsigned char salt[8]; // openssl tool uses 8 bytes for salt
unsigned char decodeddata[256];
unsigned char ciphertext[256];
unsigned char plaintext[256];
const char *pass = argv[1]; // use first argument as password (PoC only)
unsigned char *encodeddata = (unsigned char *)argv[2]; // use second argument
int decodeddata_len, ciphertext_len, plaintext_len, len;
// Decode base64 string provided as second option
EVP_ENCODE_CTX *ctx;
if(!(ctx = EVP_ENCODE_CTX_new()))
bail();
EVP_DecodeInit(ctx);
EVP_DecodeUpdate(ctx, decodeddata, &len, encodeddata, strlen((const char*)encodeddata));
decodeddata_len = len;
if(!EVP_DecodeFinal(ctx, decodeddata, &len))
bail();
EVP_ENCODE_CTX_free(ctx);
// openssl tool format seems to be 'Salted__' + salt + encrypted data
// take it apart
memcpy(salt, decodeddata + 8, 8); // 8 bytes starting at 8th byte
memcpy(ciphertext, decodeddata + 16, decodeddata_len - 16); // all but the 16 first bytes
ciphertext_len = decodeddata_len - 16;
// Get some needed information
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
int iklen = EVP_CIPHER_key_length(cipher);
int ivlen = EVP_CIPHER_iv_length(cipher);
int iter = 10000; // default in openssl 1.1.1
unsigned char keyivpair[iklen + ivlen];
// Generate the actual key IV pair
if(!PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair))
bail();
memcpy(key, keyivpair, iklen);
memcpy(iv, keyivpair + iklen, ivlen);
// Decrypt data
EVP_CIPHER_CTX *cipherctx;
if(!(cipherctx = EVP_CIPHER_CTX_new()))
bail();
if(!EVP_DecryptInit_ex(cipherctx, cipher, NULL, key, iv))
bail();
if(!EVP_DecryptUpdate(cipherctx, plaintext, &len, ciphertext, ciphertext_len))
bail();
plaintext_len = len;
if(!EVP_DecryptFinal_ex(cipherctx, plaintext + len, &len))
bail();
plaintext_len += len;
EVP_CIPHER_CTX_free(cipherctx);
plaintext[plaintext_len] = '\0'; // add null termination
printf("%s", plaintext);
exit(EXIT_SUCCESS);
}
通过运行测试的应用程序
$ openssl aes-256-cbc -e -a -md sha512 -pbkdf2 -pass pass:test321 <<< "Some secret data"
U2FsdGVkX19ZNjDQXX/aACg7d4OopxqvpjclkaSuybeAxOhVRIONXoCmCQaG/Vg9
$ ./decrypt-poc test321 U2FsdGVkX19ZNjDQXX/aACg7d4OopxqvpjclkaSuybeAxOhVRIONXoCmCQaG/Vg9
Some secret data
命令行工具使用的密钥/IV 生成位于 apps/enc.c 中,在解决这一问题时非常有帮助。