【问题标题】:C++ OpenSSL API : How to compute CLI default IV from PBKDF2 derivationC++ OpenSSL API:如何从 PBKDF2 派生计算 CLI 默认 IV
【发布时间】:2020-08-21 14:02:30
【问题描述】:

我正在尝试在我的一个 C++ 程序中实现 AES 解密。想法是使用以下 openSSL 命令行来生成密文(但使用 C++ API 来解密):

openssl enc -aes-256-cbc -in plaintext.txt -base64 -md sha512 -pbkdf2 -pass pass:<passwd>

由于官方文档有点太复杂,我根据本教程的实现来实现解密:https://eclipsesource.com/blogs/2017/01/17/tutorial-aes-encryption-and-decryption-with-openssl/

它运行良好,但使用了一种已弃用的密钥派生算法,我想用 PBKDF2 替换它。

据我了解,我应该使用 PKCS5_PBKDF2_HMAC() 而不是教程中建议的 EVP_BytesToKey()。我的问题是 EVP_BytesToKey 能够从盐和密码中派生 keyIV,而 PKCS5_PBKDF2_HMAC 似乎一次只能派生一个。

我找不到更多关于如何获取 keyIV 的信息/教程,并尝试了几种实现,但找不到 openSSL CLI 如何生成 IV。 我真的很想避免在 CLI 或有效负载中编写 IV,教程的实现非常方便。

有人可以帮我吗?

谢谢,问候

【问题讨论】:

    标签: c++ encryption openssl aes pbkdf2


    【解决方案1】:

    我意识到这个问题到现在已经大约一个月了,但我在搜索有关做类似事情的信息时遇到了这个问题。鉴于这里缺乏答案,我去源头寻找答案。

    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);
    

    关于参数的简短信息

    1. pass 的类型是 (const char *)
    2. 密码长度(int),如果设置为-1,长度将由strlen(pass)决定
    3. salt 的类型为 (const unsigned char *)
    4. 盐长(整数)
    5. 迭代次数(整数)
    6. 消息摘要(const EVP_MD *),在这种情况下由EVP_sha512()返回
    7. key + iv (int)的总长度
    8. 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 中,在解决这一问题时非常有帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-06
      • 1970-01-01
      • 2011-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多