Linux C语言调用OpenSSL: 对称算法(AES高级加密标准)

概述

本文主要介绍AES:高级加密标准(Advanced Encryption Standard)。

对称算法种类及特点:

  1. AES:高级加密标准(Advanced Encryption Standard),是目前应用最广泛的对称加密算法之一。AES 加密强度高、运算速度快,已被广泛使用。
  2. DES:数据加密标准(Data Encryption Standard),是早期应用广泛的对称加密算法。DES 加密强度相对较低,已经被认为不够安全,已经逐步被淘汰。
  3. 3DES:三重数据加密标准(Triple Data Encryption Standard),是对 DES 加密算法的改进版。通过迭代使用 DES 算法加密三次,提高了加密强度,但是运算速度缓慢,已被 AES 取代。
  4. Blowfish:布洛芬鱼算法,是一种比较新的对称加密算法。它的优点是加密速度快,安全性好,密钥长度可变,但也因此导致了算法的不稳定,已经被 AES 取代。
  5. RC4:是一种流式加密算法,加密速度很快,但由于其密钥管理的不当,已经被认为不够安全,已经逐步被淘汰。

AES算法模式种类及特点:

  1. ECB 模式(Electronic Codebook):这是最简单的加密模式,它将明文分成块,并对每个块进行独立的加密。由于每个块之间没有关联,因此 ECB 模式不适合处理重复出现的数据,容易受到攻击。该模式的优点是加解密速度快,适用于短数据的加密。
  2. CBC 模式(Cipher Block Chaining):CBC 模式在 ECB 模式的基础上增加了一个初始化向量(IV),使得每个块的加密依赖于前一个块的密文。这样可以打破 ECB 模式的块独立性,提高安全性。但是由于每个块的加密都依赖于前一个块的密文,因此 CBC 模式不能并行处理数据,可能影响加密效率。
  3. CFB 模式(Cipher Feedback):CFB 模式将加密变换的输出作为密钥流,并将其与明文异或得到密文。这样可以实现流加密,适用于长数据的加密。但是由于 CF 模式需要保证密钥流的可预测性,因此其安全性相对较弱。
  4. OFB 模式(Output Feedback):OFB 模式也将加密变换的输出作为密钥流,但是与 CFB 模式不同的是,OFB 模式不需要依赖上一轮的密文。这种模式下,密钥流是固定的,而且可以预先生成,因此适用于长数据的加密。但是由于 OFB 模式不会改变密文的长度,因此对于某些攻击来说比较容易分析。
  5. CTR 模式(Counter):CTR 模式将一个计数器作为密钥流,每次加密时计数器会自增。这样可以实现流加密,适用于长数据的加密。与 OFB 模式类似,CTR 模式也可以预先生成密钥流,但是它还可以并行处理数据,因此加密效率更高。其缺点是需要保证计数器的唯一性,否则可能导致密钥流重复,从而降低安全性。

调用函数介绍

EVP_CIPHER_CTX_new

用于创建一个新的 EVP_CIPHER_CTX 结构体实例,该结构体是 OpenSSL 中用于加密和解密操作的上下文,如算法、密钥、初始化向量(IV)等。

#include <openssl/evp.h>
EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);

返回值:
成功时返回一个地址,失败返回NULL;

EVP_CIPHER_CTX_free

用于释放由 EVP_CIPHER_CTX_new 函数分配的 EVP_CIPHER_CTX 结构体实例所占用的内存。

#include <openssl/evp.h>
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx);

参数:
ctx:指向要释放的 EVP_CIPHER_CTX 结构体的指针。这个指针必须是之前通过 EVP_CIPHER_CTX_new 或其他方式(如某些函数的返回值)获取的。

EVP_EncryptInit_ex

用于初始化加密操作的上下文。它设置加密算法、密钥和(如果需要的话)初始化向量。一旦上下文被初始化,就可以使用 EVP_EncryptUpdate 和 EVP_EncryptFinal_ex 函数来加密数据。

#include <openssl/evp.h>
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, const unsigned char *key, const unsigned char *iv);

参数:
ctx:指向 EVP_CIPHER_CTX 结构体的指针,这个结构体将被初始化为加密操作的上下文。
type:指向 EVP_CIPHER 结构的指针,该结构定义了加密算法的类型。例如,EVP_aes_256_cbc() 表示 AES-256-CBC 加密算法。
impl:指向 ENGINE 结构的指针,它允许在 OpenSSL 中使用特定的加密引擎(通常是硬件加速)。如果不需要特定的引擎,可以设置为 NULL。
key:指向加密密钥的指针。密钥的长度取决于所选的加密算法。
iv:指向初始化向量(IV)的指针。不是所有的加密算法都需要 IV,但对于需要它的算法(如 CBC 模式的块加密算法),这个参数是必须的。如果算法不需要 IV,可以设置为 NULL。

返回值:
如果函数成功,返回 1。如果发生错误,返回 0。

EVP_EncryptUpdate

在已经初始化的 EVP_CIPHER_CTX 上下文中执行加密操作。它处理输入数据(in),并将加密后的数据写入输出缓冲区(out)。

#include <openssl/evp.h>
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);

参数:
ctx:指向 EVP_CIPHER_CTX 结构体的指针,这个结构体必须已经被初始化(通常使用 EVP_EncryptInit_ex 或 EVP_DecryptInit_ex 函数)。
out:指向输出缓冲区的指针,加密后的数据将被写入这个缓冲区。
outl:一个指向整数的指针,这个整数在函数返回时将被设置为输出缓冲区的实际长度(以字节为单位)。
in:指向输入数据的指针,即要加密的数据。
inl:输入数据的长度(以字节为单位)。

返回值:
如果函数成功,返回 1。如果发生错误,返回 0。

注意:
对于块加密算法(如 AES),EVP_EncryptUpdate 可能不会处理完整的输入数据块,因为它可能等待足够的数据来形成一个完整的块。在这种情况下,任何剩余的未处理数据将在调用 EVP_EncryptFinal_ex 时被处理。  

EVP_EncryptFinal_ex

完成加密操作,并处理在 EVP_EncryptUpdate 调用中可能遗留的未加密数据(如块加密算法中的填充)。它还将任何必要的填充添加到加密数据中,并生成最终的加密块。

#include <openssl/evp.h>
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

参数:
ctx:指向 EVP_CIPHER_CTX 结构体的指针,这个结构体必须已经被初始化(通常使用 EVP_EncryptInit_ex 函数)并用于加密操作。
out:指向输出缓冲区的指针,用于存放 EVP_EncryptFinal_ex 处理的最后一批加密数据(如果有的话)。
outl:一个指向整数的指针,这个整数在函数返回时将被设置为输出缓冲区的实际长度(以字节为单位)。如果 out 为 NULL,则 *outl 将被设置为需要的输出缓冲区大小。

返回值:
如果函数成功,返回 1。如果发生错误,返回 0。

注意:
如果 out 参数不为 NULL,则加密的填充数据将被写入 out 指向的缓冲区。如果 out 为 NULL,则 *outl 将被设置为需要的输出缓冲区大小。

EVP_DecryptInit_ex

用于初始化一个解密操作的上下文。这个函数为后续的解密操作(如 EVP_DecryptUpdate 和 EVP_DecryptFinal_ex)设置必要的参数。

#include <openssl/evp.h>
int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, const unsigned char *key, const unsigned char *iv);

参数:
ctx:指向 EVP_CIPHER_CTX 结构体的指针,该结构体将被初始化为解密操作的上下文。
type:指向 EVP_CIPHER 结构的指针,定义了要使用的加密算法。例如,对于 AES-256-CBC,你可以使用 EVP_aes_256_cbc()。
impl:指向加密引擎的指针,通常设置为 NULL,除非你正在使用特定的硬件加速或自定义加密引擎。
key:指向解密密钥的指针。密钥的长度取决于所选的加密算法。
iv:指向初始化向量(IV)的指针,用于某些需要它的块加密算法(如 CBC 模式)。IV 的长度也取决于加密算法。对于不需要 IV 的算法,此参数可以设置为 NULL。

返回值:
如果函数成功,返回 1。如果发生错误(如无效的加密算法或密钥长度),返回 0。

EVP_DecryptUpdate

用于处理加密数据的输入块,并将解密后的数据写入输出缓冲区。这个函数可以被多次调用,以处理不同长度的输入数据块。

#include <openssl/evp.h>
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);

参数:
ctx:指向 EVP_CIPHER_CTX 结构体的指针,该结构体必须已经被 EVP_DecryptInit_ex 初始化。
out:指向输出缓冲区的指针,用于存放解密后的数据。
outl:一个指向整数的指针,该整数在函数返回时将被设置为输出缓冲区的实际长度(以字节为单位)。
in:指向输入缓冲区的指针,包含待解密的加密数据。
inl:输入缓冲区的长度(以字节为单位)。

返回值:
如果函数成功,返回 1。如果发生错误(如输入数据损坏),返回 0。

注意:
每次调用时,它都会处理尽可能多的输入数据,并将解密后的数据写入输出缓冲区,直到输入数据被完全处理或输出缓冲区已满。

EVP_DecryptFinal_ex

完成解密操作,并处理任何在 EVP_DecryptUpdate 调用后剩余的数据。这通常包括填充的处理(例如,对于块加密算法中的块填充),以及任何在解密过程中产生的错误检查。

#include <openssl/evp.h>
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

参数:
ctx:指向 EVP_CIPHER_CTX 结构体的指针,该结构体必须已经被 EVP_DecryptInit_ex 初始化,并且已经通过 EVP_DecryptUpdate 处理了加密数据。
out:指向输出缓冲区的指针,用于存放解密后剩余的数据。如果不需要这部分数据,可以设置为 NULL。
outl:一个指向整数的指针,该整数在函数返回时将被设置为输出缓冲区的实际长度(以字节为单位)。如果 out 参数为 NULL,则这个值将被忽略。

返回值:
如果函数成功,返回 1。如果发生错误(如解密过程中的数据损坏),返回 0。

示例

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/err.h>
#include <openssl/evp.h>

#define WK_OK            0
#define WK_ERR           (-1)

#define ANSI_COLOR_RESET   "\x1b[0m"
#define ANSI_COLOR_RED     "\x1b[31m"
#define log_err(...)        (printf("[" ANSI_COLOR_RED "%s-%s-%d" ANSI_COLOR_RESET "]: ", __FILE__, __FUNCTION__, __LINE__), printf(__VA_ARGS__))

//打印
void my_printf(char* str, unsigned char* data, unsigned int len)
{
    if(NULL != str)
    {
        printf("%s\n", str);
    }
    for(int i=0; i<len; i++)
    {
        printf("0x%02x, ", data[i]);
        if(0 == (i+1)%32)
        {
            printf("\n");
        }
    }
    printf("\n");
}

//加密封装
int my_en_AES( 
    const EVP_CIPHER *en_mode,    //算法模式
    unsigned char* key,     //密钥
    unsigned char* iv,      //iv
    unsigned char* src,     //原文
    unsigned int srclen,    //原文长度
    unsigned char* dst,     //结果
    unsigned int* dstlen    //结果长度
)
{
    int ret = 0;
    EVP_CIPHER_CTX *ctx_enc = NULL;
    int len = 0;
    int ciphertext_len = 0;

    // 创建一个新的上下文  
    ctx_enc = EVP_CIPHER_CTX_new(); 
    if (NULL == ctx_enc)
    {
        log_err("err: EVP_CIPHER_CTX_new\n");
        return WK_ERR;
    }

    // 初始化加密操作  
    ret = EVP_EncryptInit_ex(ctx_enc, en_mode, NULL, key, iv);
    if (1 != ret)
    {
        log_err("err: EVP_EncryptInit_ex\n");
        goto err_handle;
    }

    // 加密  
    ret = EVP_EncryptUpdate(ctx_enc, dst, &len, src, srclen);
    if (1 != ret)
    {
        log_err("err: EVP_EncryptUpdate\n");
        goto err_handle;
    }
    ciphertext_len = len;

    // 完成加密(如果有需要填充的额外字节)
    ret = EVP_EncryptFinal_ex(ctx_enc, dst + ciphertext_len, &len);
    if (1 != ret)  
    {
        log_err("err: EVP_EncryptFinal_ex\n");
        goto err_handle;
    }
    ciphertext_len += len;

    *dstlen = ciphertext_len;
    EVP_CIPHER_CTX_free(ctx_enc);
    return WK_OK;

err_handle:
    if(NULL != ctx_enc)
    {
        EVP_CIPHER_CTX_free(ctx_enc);
    }
    return WK_ERR;
}

//解密封装
int my_de_AES( 
    const EVP_CIPHER *mode,    //算法模式
    unsigned char* key,     //密钥
    unsigned char* iv,      //iv
    unsigned char* src,     //原文
    unsigned int srclen,    //原文长度
    unsigned char* dst,     //结果
    unsigned int* dstlen    //结果长度
)
{
    int ret = 0;
    EVP_CIPHER_CTX *ctx_dec = NULL;
    int decryptedtext_len = 0; 
    int len = 0;  

    // 创建一个新的上下文  
    ctx_dec = EVP_CIPHER_CTX_new();
    if (NULL == ctx_dec)
    {
        log_err("err: EVP_CIPHER_CTX_new\n");
        return WK_ERR;
    }

    // 初始化加密操作  
    ret = EVP_DecryptInit_ex(ctx_dec, mode, NULL, key, iv);
    if (1 != ret)
    {
        log_err("err: EVP_DecryptInit_ex\n");
        goto err_handle;
    }

    // 加密  
    ret = EVP_DecryptUpdate(ctx_dec, dst, &len, src, srclen);
    if (1 != ret)
    {
        log_err("err: EVP_EncryptUpdate\n");
        goto err_handle;
    }
    decryptedtext_len = len;

    // 完成加密(如果有需要填充的额外字节)
    ret = EVP_DecryptFinal_ex(ctx_dec, dst + decryptedtext_len, &len);
    if (1 != ret)  
    {
        log_err("err: EVP_EncryptFinal_ex\n");
        goto err_handle;
    }
    decryptedtext_len += len;

    *dstlen = decryptedtext_len;
    EVP_CIPHER_CTX_free(ctx_dec);
    return WK_OK;

err_handle:
    if(NULL != ctx_dec)
    {
        EVP_CIPHER_CTX_free(ctx_dec);
    }
    return WK_ERR;
}

//测试AES加解密
int test_AES_ende(int choice)
{
    int ret = 0;
    unsigned char aes_key[] = "mysecretpassword=========";//注意小于EVP_MAX_KEY_LENGTH
    unsigned char* aes_iv = "mysecretiv=";  //注意小于EVP_MAX_IV_LENGTH
    // unsigned char plaintext[277] = "Hello, OpenSSL!";  
    unsigned char plaintext[1024] = {0};  
    unsigned int plainlen = 177;
    int cipher_len = 0;  
    int decrypt_len = 0;
    unsigned char *cipher = NULL;  
    unsigned char *decrypt = NULL;  

    for(int i=0; i<plainlen; i++)
    {
        plaintext[i] = i%256;
    }
    printf("plainlen %d\n", plainlen);
    my_printf("plain", plaintext, plainlen);

    // 分配内存用于存放密文和解密后的文本(考虑到可能的填充
    cipher = (unsigned char *)malloc(plainlen + EVP_MAX_BLOCK_LENGTH);  
    decrypt = (unsigned char *)malloc(plainlen + EVP_MAX_BLOCK_LENGTH);  


    if(1 == choice)
    {
        ret = my_en_AES(EVP_aes_128_ecb(), aes_key, NULL, plaintext, plainlen, cipher, &cipher_len);
        if(WK_OK != ret)
        {
            log_err("err: en EVP_aes_128_ecb\n");
            return WK_ERR;
        }
        printf("cipher_len %d\n", cipher_len);
        my_printf("cipher", cipher, cipher_len);
        ret = my_de_AES(EVP_aes_128_ecb(), aes_key, NULL, cipher, cipher_len, decrypt, &decrypt_len);
        if(WK_OK != ret)
        {
            log_err("err: en EVP_aes_128_ecb\n");
            return WK_ERR;
        }
    }
    else
    {
        ret = my_en_AES(EVP_aes_128_cbc(), aes_key, aes_iv, plaintext, plainlen, cipher, &cipher_len);
        if(WK_OK != ret)
        {
            log_err("err: en EVP_aes_128_cbc\n");
            return WK_ERR;
        }
        printf("cipher_len %d\n", cipher_len);
        my_printf("cipher", cipher, cipher_len);
        ret = my_de_AES(EVP_aes_128_cbc(), aes_key, aes_iv, cipher, cipher_len, decrypt, &decrypt_len);
        if(WK_OK != ret)
        {
            log_err("err: en EVP_aes_128_cbc\n");
            return WK_ERR;
        }
    }

    if((plainlen != decrypt_len) || (0 != memcmp(decrypt, plaintext, decrypt_len)))//对比长度 + 内容
    {
        free(cipher);
        free(decrypt);
        log_err("err: test_AES_ende\n");
        return WK_ERR;
    }
    free(cipher);
    free(decrypt);
    return WK_OK;
}

int main()
{
    int ret = 0;
    int choice = 0;
    unsigned char md_result[EVP_MAX_MD_SIZE] = {0};//hash结果
    unsigned int md_result_len = 0;

    while(1)
    {
        int ret = 0;
        int choice = 0;
        printf("\n调用的openssl库版本: num [%lx], text [%s]\r\n", OpenSSL_version_num(), OpenSSL_version(OPENSSL_VERSION));
        printf("选择算法模式: 1:ECB模式   2:CBC模式\n");
        scanf("%d", &choice);
        ret = test_AES_ende(choice);
        if(WK_OK != ret)
        {
            printf("err: test_AES_ende\n");
        }
        else
        {
            printf("succ: test_AES_ende\n");
        }
    }

    return WK_OK;
}

makefile

SRC := $(wildcard ./*.c)

#paramter
CC := gcc
target := app_openssl

#头文件和库路径(修改成安装openssl的路径)
DIR_LIB := -L /xxxxxxxxxxxxxx/openssl/lib64
DIR_INCLUDE := -I /xxxxxxxxxxxxxx/openssl/include/

$(target):$(SRC)
	$(CC) $(SRC) $(DIR_INCLUDE) $(DIR_LIB) -lssl -lcrypto -o $@

clean:
	rm -rf $(target)

执行结果

在这里插入图片描述

参考链接:https://www.openssl.org/source/old/index.html

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值