如何使用具有低级DES功能的openssl EVP库?

时间:2018-03-15 22:08:02

标签: c++ encryption openssl cryptography

因此,我花了几个小时挖掘谷歌搜索结果和一些与openssl函数相关的联机帮助页,试图找出如何使用3DES加密的EVP函数。现在我的代码正在使用<openssl/des.h>中的这些函数:

  • DES_ede2_cbc_encrypt()
  • DES_set_odd_parity()
  • DES_set_key_checked()
  • DES_ecb2_encrypt()

我的理解是,通过使用EVP功能,我可以统一处理3DES的ECB和CBC模式之间的加密/解密逻辑(上面列表中的第一个和最后一个函数)。管理输入/输出数据的方式在两者之间是不同的。

我无法通过高级EVP功能找到任何DES加密示例。在evp.h标题中,我看到EVP_des_ede3_ecbEVP_des_ede3_cbc之类的内容,但我不确定如何使用它们。由于你必须设置DES密钥的奇怪方式(使用上面列表中间的2个函数),我不确定在EVP方式中加载密钥会是什么样的。

如何在EVP中使用DES?如果有人可以提供真正有用的加密,解密和密钥设置示例。我试图用C ++包装所有类似C的代码,并尽我所能使用STL对象/算法。我想要我的&#34; DES&#34;包装器在DES的不同密码模式之间在语义上工作相同。我不确定这是否可行,但这就是我尝试使用EVP的原因。

1 个答案:

答案 0 :(得分:2)

我认为你想做的事情应该是可能的。 EVP API非常关注使用所有参数初始化上下文对象,然后使用上下文对象而不知道其中的内容。您甚至可能会受益于OpenSSL将允许您提供不需要的参数...例如在初始化ECB模式的上下文时提供IV(将被忽略)。

在进入EVP版本之前,我们先谈谈你发布的功能。

OpenSSL将DES密钥的生成分为三个步骤:

  1. 制作随机字节
  2. 设置/更正随机字节中的奇偶校验位
  3. 展开主要时间表
  4. 第一步是使用任何合适的(N / D)RBG(OpenSSL提供RAND_DRBG_bytesRAND_bytes)。第二步产生是因为DES密钥的某些位对密码强度没有贡献;因此,按照惯例,它们被设置为给出密钥奇校验的字节。这是DES_set_odd_parity的功能(有关详细说明,请参阅https://crypto.stackexchange.com/questions/34199/purpose-of-des-parity-bits。)最后一步在OpenSSL中有几个选项:(a)信任用户给出一个好的密钥,然后展开它;或者(b)检查用户提供的内容以确保它不是弱密钥,并在展开密钥时间表之前检查它是否具有奇数奇偶校验。后者由后面列出的函数DES_set_key_checked执行。前者,更值得信赖的版本是DES_set_key_unchecked

    好的,为什么这一切都有用?嗯,它在API的EVP版本中都有直接模拟。使用DES功能,您将执行以下操作:

    1. 致电RAND_bytes(或同等)以制作随机密钥
    2. 调用DES_set_odd_parity设置奇偶校验位
    3. 致电DES_set_key_checked以展开主要时间表
    4. 使用展开的密钥计划调用DES_<mode>_encrypt加密
    5. 使用EVP,您可以执行以下(大多数)等效步骤:

      1. 调用EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_RAND_KEY, 0, dest_buf)以生成适合已初始化EVP_CIPHER_CTX *ctx的密码的随机密钥。此步骤还通过调用DES_set_odd_parity在引擎盖下设置奇数奇偶校验。请注意,假定dest_buf足够大,可以容纳ctx配置的密钥类型。见https://github.com/openssl/openssl/blob/master/crypto/evp/e_des3.c

      2. 中的第280行
      3. 调用EVP_EncryptInit传递密钥,该密钥在引擎盖下调用DES_set_key_unchecked以展开密钥计划并将其存储在上下文中。请注意,这是set-key的未选中变体; EVP API假设您提供了一个好密钥。参见https://github.com/openssl/openssl/blob/master/crypto/evp/e_des3.c中的第226行(对于2键ede)

      4. 注意小鸡蛋问题:你必须使用算法类型初始化上下文,以便它知道要制作什么样的键;并且您必须提供用于初始化上下文的键。 EVP_ * Init正常处理NULL参数,因此它允许您部分初始化。如,

        uint8_t twokey[16];
        EVP_EncryptInit(ctx, EVP_des_ede_cbc(), NULL, NULL);
        EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_RAND_KEY, 0, twokey);
        EVP_EncryptInit(ctx, NULL, twokey, iv);
        

        另请注意,如果您已经拥有密钥,则不需要OpenSSL来随机生成密钥。 EVP_EncryptInit非常乐意相信您提供的任何密钥。

        这是一个使用硬编码密钥的相当长的例子,但使用openssl函数来纠正其奇偶校验。

        #include <openssl/evp.h>
        #include <openssl/des.h>
        #include <stdint.h>
        
        int main(int argc, char *argv[])
        {
          int res;
          uint8_t key[16] = {
            0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
            0x21, 0x32, 0x43, 0x54, 0x65, 0x76, 0x87, 0x98
          };
        
          uint8_t iv[8] = {
            0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0
          };
        
          uint8_t message[16] = {0,1,2,3,4,5,6,7,8,9,0xa,0xb,0xc,0xd,0xe,0xf};
          uint8_t ciphertext[24] = {0}; //leave room for padding!
          int ciphertext_total = 0;
          uint8_t decrypted [16] = {0};
          int decrypted_total = 0;
        
          //Select our cipher of choice: 2-key 3DES in CBC mode (hence the IV)
          const EVP_CIPHER *tdea_ede = EVP_des_ede_cbc();
        
          printf("hardcoded key:       ");
          {
            int i;
            for(i=0; i< (int)sizeof(key); i++)
            {
              printf("%02x ", key[i]);
            }
            printf("\n");
          }
        
          //Note I have to set parity on each of the keys, and I'm doing 2-key 3DES
          //DES_cblock is an annoying typdef of uchar[8]
          DES_set_odd_parity((DES_cblock *)key);
          DES_set_odd_parity((DES_cblock *)(key+8));
        
          printf("key with odd parity: ");
          {
            int i;
            for(i=0; i< (int)sizeof(key); i++)
            {
              printf("%02x ", key[i]);
            }
            printf("\n");
          }
        
          printf("Message:             ");
          {
            int i;
            for(i=0; i< (int)sizeof(message); i++)
            {
              printf("%02x ", message[i]);
            }
            printf("\n");
          }
        
          /* encrypt */
          {
            int outl = 0;
            EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
            EVP_EncryptInit(ctx, tdea_ede, key, iv);
        
            res = EVP_EncryptUpdate(ctx, ciphertext, &outl, message, (int)sizeof(message));
            printf("Update wrote %d bytes\n", outl);
            ciphertext_total += outl;
        
            EVP_EncryptFinal(ctx, ciphertext + ciphertext_total, &outl);
            printf("Final wrote %d bytes\n", outl);
            ciphertext_total += outl;
          }
        
          printf("Ciphertext:          ");
          {
            int i;
            for(i=0; i<ciphertext_total; i++)
            {
              printf("%02x ", ciphertext[i]);
            }
            printf("\n");
          }
        
          /* decrypt */
          {
            int outl = 0;
            EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
            EVP_DecryptInit(ctx, tdea_ede, key, iv);
        
            res = EVP_DecryptUpdate(ctx, decrypted, &outl, ciphertext, ciphertext_total );
            printf("Update wrote %d bytes\n", outl);
            decrypted_total += outl;
        
            EVP_DecryptFinal(ctx, decrypted + decrypted_total, &outl);
            printf("Final wrote %d bytes\n", outl);
            decrypted_total += outl;
          }
        
          printf("Decrypted:           ");
          {
            int i;
            for(i=0; i<decrypted_total; i++)
            {
              printf("%02x ", decrypted[i]);
            }
            printf("\n");
          }
        }
        

        它的输出是:

        $ ./a.out
        hardcoded key:       11 22 33 44 55 66 77 88 21 32 43 54 65 76 87 98
        key with odd parity: 10 23 32 45 54 67 76 89 20 32 43 54 64 76 86 98
        Message:             00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
        Update wrote 16 bytes
        Final wrote 8 bytes
        Ciphertext:          5d f9 44 ff 82 0b c3 47 90 be 11 fb 62 01 15 f0 65 45 f6 05 3f fa 81 96
        Update wrote 16 bytes
        Final wrote 0 bytes
        Decrypted:           00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
        

        我承认,我很惊讶地看到密文大于消息,但后来我记得OpenSSL默认应用了填充。

        如果您不熟悉EVP API的init / update / final模型,请注意它是专为流而设计的。您可以根据需要多次调用update,只有获得足够的数据来处理块后才能看到它写入任何输出。这就是“最终”的原因......如果一个人在飞行中,填写任何剩余数据并处理最后一个块。

        另请注意,该示例泄漏了EVP_CIPHER_CTX对象......应该在某些时候释放这些对象。