【问题标题】:How to do encryption using AES in Openssl如何在 Openssl 中使用 AES 进行加密
【发布时间】:2012-04-10 23:33:37
【问题描述】:

我正在尝试编写一个示例程序来使用 Openssl 进行 AES 加密。我尝试浏览 Openssl 文档(这很痛苦),但想不通。我浏览了代码并找到了我使用的 API,我编写了一个如下的小程序(请省略行号)。我没有看到任何加密发生...我错过了什么吗?

PS:我在编译时没有收到任何错误。

  1 #include <stdio.h> 
  2 #include <openssl/aes.h>   
  3 
  4 static const unsigned char key[] = {
  5   0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
  6     0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
  7       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  8         0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
  9         };
 10 
 11 void main()
 12 {
 13     unsigned char text[]="virident";
 14     unsigned char out[10]; 
 15     unsigned char decout[10];
 16 
 17     AES_KEY wctx;
 18 
 19     AES_set_encrypt_key(key, 128, &wctx);
 20     AES_encrypt(text, out, &wctx);  
 21 
 22     printf("encryp data = %s\n", out);
 23     
 24     AES_decrypt(out, decout, &wctx);
 25     printf(" Decrypted o/p: %s \n", decout);
 26 
 27 
 28 }

请帮我解决这个问题...

【问题讨论】:

  • 您需要将 text[] 填充到 16 个字节。 out[] 遭受缓冲区溢出,因为 AES 需要 16 字节块。
  • 你应该使用AES_encrypt和朋友。您应该使用EVP_* 函数。请参阅 OpenSSL wiki 上的 EVP Symmetric Encryption and Decryption。事实上,您可能应该使用经过身份验证的加密,因为它提供 机密性和真实性。请参阅 OpenSSL wiki 上的 EVP Authenticated Encryption and Decryption

标签: c openssl aes


【解决方案1】:

我的建议是跑

openssl enc -aes-256-cbc -in plain.txt -out encrypted.bin

在调试器下,看看它到底在做什么。 openssl.c 是 OpenSSL 唯一真正的教程/入门/参考指南。所有其他文档只是 API 参考。

U1:我猜你没有设置一些其他必需的选项,比如操作模式(填充)。

U2:这可能是这个问题的重复: AES CTR 256 Encryption Mode of operation on OpenSSL 并且那里的答案可能会有所帮助。

【讨论】:

    【解决方案2】:

    我不知道你有什么问题,但可以肯定的是,在解密消息之前,你需要致电 AES_set_decrypt_key()。也不要尝试打印为 %s,因为加密的消息不再由 ascii 字符组成。例如:

    static const unsigned char key[] = {
        0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
        0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
    };
    
    int main()
    {
        unsigned char text[]="hello world!";
        unsigned char enc_out[80];
        unsigned char dec_out[80];
    
        AES_KEY enc_key, dec_key;
    
        AES_set_encrypt_key(key, 128, &enc_key);
        AES_encrypt(text, enc_out, &enc_key);      
    
        AES_set_decrypt_key(key,128,&dec_key);
        AES_decrypt(enc_out, dec_out, &dec_key);
    
        int i;
    
        printf("original:\t");
        for(i=0;*(text+i)!=0x00;i++)
            printf("%X ",*(text+i));
        printf("\nencrypted:\t");
        for(i=0;*(enc_out+i)!=0x00;i++)
            printf("%X ",*(enc_out+i));
        printf("\ndecrypted:\t");
        for(i=0;*(dec_out+i)!=0x00;i++)
            printf("%X ",*(dec_out+i));
        printf("\n");
    
        return 0;
    } 
    

    U1:你的密钥是 192 位的,不是吗...

    【讨论】:

    • 是的,它正在工作,但是:它仅限于只有 16 个字符的文本(AES_BLOCK_SIZE),它使用不安全的 ECB 默认模式,enc_out 像带有 0x00 分隔符的字符串一样处理,但基本上它是长度为 16 字节的二进制代码,包括 0x00 作为普通字节
    【解决方案3】:

    查看此链接,其中包含使用 EVP API 使用 AES256CBC 加密/解密数据的示例代码。

    https://github.com/saju/misc/blob/master/misc/openssl_aes.c

    您还可以在我开发的详细开源项目中查看 AES256 CBC 的使用https://github.com/llubu/mpro

    使用 cmets 的代码已经足够详细了,如果您仍然需要更多关于 API 本身的解释,我建议您查看这本书 Viega/Messier/Chandra 的 OpenSSL 网络安全(谷歌它你会很容易找到这个的 pdf ..)阅读第 6 章,该章专门针对使用 EVP API 的对称密码。这有助于我真正理解使用 EVP 的各种功能和结构背后的原因。

    如果您想深入了解 Openssl 加密库,我建议您从 openssl 网站(您机器上安装的版本)下载代码,然后查看 EVP 和 aeh 的实现api实现。

    您在上面发布的代码中的另一个建议我看到您正在使用来自 aes.h 的 api 而是使用 EVP。在这里查看这样做的原因OpenSSL using EVP vs. algorithm API for symmetric crypto Daniel 在我提出的一个问题中很好地解释了......

    【讨论】:

    • 第一个例子不加“#include ”是无法编译的。
    • @Étienne 是的,因为那个人在不使用 EVP 的情况下直接使用了 openssl aes api,所以你需要使用 openssl/aes 标头
    • 这就是为什么不赞成在答案中发布链接的原因。它不再起作用了
    【解决方案4】:

    我正在尝试编写一个示例程序来使用 Openssl 进行 AES 加密。

    这个答案有点受欢迎,所以我将提供一些更新的东西,因为 OpenSSL 添加了一些可能会对您有所帮助的操作模式。

    首先,不要使用AES_encryptAES_decrypt。它们级别低,更难使用。此外,它是一个纯软件例程,它从不使用硬件加速,如 AES-NI。最后,它在一些不知名的平台上会遇到字节序问题。

    改为使用EVP_* 接口。 EVP_* 函数使用硬件加速,如 AES-NI(如果可用)。而且它不会在晦涩的平台上遇到字节序问题。

    其次,您可以使用像CBC这样的模式,但密文将缺乏完整性和真实性保证。所以你通常需要像 EAX、CCM 或 GCM 这样的模式。 (或者您必须在使用单独的密钥加密后手动应用 HMAC。)

    第三,OpenSSL 有一个您可能会感兴趣的 wiki 页面:EVP Authenticated Encryption and Decryption。它使用 GCM 模式。

    最后,这是使用 AES/GCM 进行加密的程序。 OpenSSL wiki 示例就是基于它。

    #include <openssl/evp.h>
    #include <openssl/aes.h>
    #include <openssl/err.h>
    #include <string.h>   
    
    int main(int arc, char *argv[])
    {
        OpenSSL_add_all_algorithms();
        ERR_load_crypto_strings();     
    
        /* Set up the key and iv. Do I need to say to not hard code these in a real application? :-) */
    
        /* A 256 bit key */
        static const unsigned char key[] = "01234567890123456789012345678901";
    
        /* A 128 bit IV */
        static const unsigned char iv[] = "0123456789012345";
    
        /* Message to be encrypted */
        unsigned char plaintext[] = "The quick brown fox jumps over the lazy dog";
    
        /* Some additional data to be authenticated */
        static const unsigned char aad[] = "Some AAD data";
    
        /* Buffer for ciphertext. Ensure the buffer is long enough for the
         * ciphertext which may be longer than the plaintext, dependant on the
         * algorithm and mode
         */
        unsigned char ciphertext[128];
    
        /* Buffer for the decrypted text */
        unsigned char decryptedtext[128];
    
        /* Buffer for the tag */
        unsigned char tag[16];
    
        int decryptedtext_len = 0, ciphertext_len = 0;
    
        /* Encrypt the plaintext */
        ciphertext_len = encrypt(plaintext, strlen(plaintext), aad, strlen(aad), key, iv, ciphertext, tag);
    
        /* Do something useful with the ciphertext here */
        printf("Ciphertext is:\n");
        BIO_dump_fp(stdout, ciphertext, ciphertext_len);
        printf("Tag is:\n");
        BIO_dump_fp(stdout, tag, 14);
    
        /* Mess with stuff */
        /* ciphertext[0] ^= 1; */
        /* tag[0] ^= 1; */
    
        /* Decrypt the ciphertext */
        decryptedtext_len = decrypt(ciphertext, ciphertext_len, aad, strlen(aad), tag, key, iv, decryptedtext);
    
        if(decryptedtext_len < 0)
        {
            /* Verify error */
            printf("Decrypted text failed to verify\n");
        }
        else
        {
            /* Add a NULL terminator. We are expecting printable text */
            decryptedtext[decryptedtext_len] = '\0';
    
            /* Show the decrypted text */
            printf("Decrypted text is:\n");
            printf("%s\n", decryptedtext);
        }
    
        /* Remove error strings */
        ERR_free_strings();
    
        return 0;
    }
    
    void handleErrors(void)
    {
        unsigned long errCode;
    
        printf("An error occurred\n");
        while(errCode = ERR_get_error())
        {
            char *err = ERR_error_string(errCode, NULL);
            printf("%s\n", err);
        }
        abort();
    }
    
    int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *aad,
                int aad_len, unsigned char *key, unsigned char *iv,
                unsigned char *ciphertext, unsigned char *tag)
    {
        EVP_CIPHER_CTX *ctx = NULL;
        int len = 0, ciphertext_len = 0;
    
        /* Create and initialise the context */
        if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
    
        /* Initialise the encryption operation. */
        if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
            handleErrors();
    
        /* Set IV length if default 12 bytes (96 bits) is not appropriate */
        if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
            handleErrors();
    
        /* Initialise key and IV */
        if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();
    
        /* Provide any AAD data. This can be called zero or more times as
         * required
         */
        if(aad && aad_len > 0)
        {
            if(1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len))
                handleErrors();
        }
    
        /* Provide the message to be encrypted, and obtain the encrypted output.
         * EVP_EncryptUpdate can be called multiple times if necessary
         */
        if(plaintext)
        {
            if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
                handleErrors();
    
            ciphertext_len = len;
        }
    
        /* Finalise the encryption. Normally ciphertext bytes may be written at
         * this stage, but this does not occur in GCM mode
         */
        if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();
        ciphertext_len += len;
    
        /* Get the tag */
        if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
            handleErrors();
    
        /* Clean up */
        EVP_CIPHER_CTX_free(ctx);
    
        return ciphertext_len;
    }
    
    int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *aad,
                int aad_len, unsigned char *tag, unsigned char *key, unsigned char *iv,
                unsigned char *plaintext)
    {
        EVP_CIPHER_CTX *ctx = NULL;
        int len = 0, plaintext_len = 0, ret;
    
        /* Create and initialise the context */
        if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
    
        /* Initialise the decryption operation. */
        if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
            handleErrors();
    
        /* Set IV length. Not necessary if this is 12 bytes (96 bits) */
        if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
            handleErrors();
    
        /* Initialise key and IV */
        if(!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();
    
        /* Provide any AAD data. This can be called zero or more times as
         * required
         */
        if(aad && aad_len > 0)
        {
            if(!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
                handleErrors();
        }
    
        /* Provide the message to be decrypted, and obtain the plaintext output.
         * EVP_DecryptUpdate can be called multiple times if necessary
         */
        if(ciphertext)
        {
            if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
                handleErrors();
    
            plaintext_len = len;
        }
    
        /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
        if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag))
            handleErrors();
    
        /* Finalise the decryption. A positive return value indicates success,
         * anything else is a failure - the plaintext is not trustworthy.
         */
        ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
    
        /* Clean up */
        EVP_CIPHER_CTX_free(ctx);
    
        if(ret > 0)
        {
            /* Success */
            plaintext_len += len;
            return plaintext_len;
        }
        else
        {
            /* Verify failed */
            return -1;
        }
    }
    

    【讨论】:

    • 您的标签缓冲区是 16,但您将它作为 14 传递给 BIO_dump_fp,这是故意的还是错字?如果不是错字,你能解释一下为什么吗?谢谢。
    • 可能有错误,提供的代码永远不会确定地工作。 if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();在这里,您应该将指向数组的指针作为第二个参数传递。为此,将写入数据。您可以查找文档。正确的是 if(1 != EVP_EncryptFinal_ex(ctx, ciphertext, &len)) handleErrors();或者,如果您确定 len 始终为零,则可以。但是,没有必要在那里添加它。令人惊讶的是,即使 len 不为零,也不会发生内存泄漏或 valgrind 尖叫。但它不会正常工作。
    • @JanGlaser AFAICS 该示例正确引用了OpenSSL docs。第二个参数必须指向密文之后,所以这里必须使用ciphertext+ciphertext_len(使用cipertext+len有点令人费解,但仍然正确,因为len == ciphertext_len在那观点)。 EVP_EncryptFinal_ex 接收的第三个参数没有输入,它是写入第二个参数的字节数的返回值,所以这个返回的长度也必须添加到 ciphertext_len 中,如示例所示。
    猜你喜欢
    • 2011-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    相关资源
    最近更新 更多