OpenSSL AES对称加密

一、对称加密
对称加密也被称为共享密钥加密,是一种将相同密钥用于加密和解密的加密方式。在对称加密中,加密和解密使用的是相同的密钥,因此需要确保密钥的安全性。

1.工作原理
对称加密使用一个密钥来加密和解密数据。发送方使用该密钥对数据进行加密,接收方使用相同的密钥对数据进行解密。这种加密方式非常快速,因为它只需要一个密钥来加密和解密数据。
以下是对称加密的基本过程:
发送方使用密钥对明文进行加密。
发送方将加密后的数据发送给接收方。
接收方使用相同的密钥对加密后的数据进行解密。
2. 优缺点
优点:
加密和解密速度快。
加密强度高,因为使用的是相同的密钥。
适合用于大量数据的加密。
缺点:
密钥管理困难,需要确保密钥的安全性。
不适合用于跨网络的通信,因为需要将密钥传输给接收方。
3. 应用场景
对称加密广泛应用于保护本地数据的安全,例如:
文件和文件夹加密
数据库加密
本地存储设备加密

EVP_CipherInit_ex()、EVP_CipherUpdate() 和 EVP_CipherFinal_ex() 是可用于解密或加密的函数。执行的操作取决于enc参数的值。加密时应设置为 1,解密时设置为 0,保持值不变为 -1。

// 创建密码上下文
EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);
// 清除密码上下文中的所有信息并释放与其关联的任何已分配内存,包括ctx本身。
// 应在使用密码的所有操作完成后调用此函数,以便敏感信息不会保留在内存中。
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx)
/**
函数作用:初始化密码上下文ctx
ctx  : 由 EVP_CIPHER_CTX_new() 创建
type : 使用的算法类型,例如:EVP_aes_256_cbc()、EVP_aes_128_cbc()
impl :密码类型,如果impl为 NULL,则使用默认实现。一般都设置为NULL
key  : 加密密钥
iv   : 偏移量
enc  : 1 - 加密;0 - 解密
**/
int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
                       ENGINE *impl, const unsigned char *key, const unsigned char *iv, int enc);
/**
输入 in 缓冲区中的 inl 字节的数据并将加/解密数据写入 out。可以多次调用此函数来加/解密连续的数据块。
**/
int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
                      int *outl, const unsigned char *in, int inl);
/**
输出 缓冲区中剩余的数据。必须在 EVP_CipherUpdate() 之后调用。
outm : 为输出缓冲区中剩余部分
**/
int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);

/**
启用或禁用填充。在使用 EVP_EncryptInit_ex()、EVP_DecryptInit_ex() 或 EVP_CipherInit_ex() 为加密或解密设置上下文后,应调用此函数。
默认情况下,加密操作使用标准块填充进行填充,并且在解密时检查并删除填充。
如果填充参数 padding 设置为零,则不执行填充,此时加密或解密的数据总量必须是块大小的倍数,否则将发生错误。
默认情况下填充是启用的。padding 为0 则禁用填充,否则启用填充
**/
int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *x, int padding);

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/bn.h>

unsigned char key[] = "0123456789abcdeF";
unsigned char iv[] = "1234567887654321";

/**
inBuf : 输入数据
inl   : 输入数据长度
out   : 输出缓冲区,由调用者确定其大小
cipher:算法类型。例如:EVP_aes_128_cbc()、EVP_aes_256_cbc()
enc: operator type , 1 is enc and 0 is dec
**/
int do_crypt(const char *inBuf, int inl, char *out,  const EVP_CIPHER *cipher, int enc)
{
    if(NULL == inBuf || NULL == out)
    {
        return -1;
    }
    int templ, total;
    // 创建上下文
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    // 初始化.设置算法类型和加解密类型以及加密密钥和偏移量
    EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, enc);
    // 设置是否启用填充.默认是启用的
    // 如果填充参数为零,则不执行填充,此时加密或解密的数据总量必须是块大小的倍数,否则将发生错误。
    EVP_CIPHER_CTX_set_padding(ctx, 1);
    // key 和 iv 长度断言检查。断言长度随着 cipher 的不同而不同
	OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
    OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);
    templ= 0;
    total = 0;
    if (!EVP_CipherUpdate(ctx, out, &templ, inBuf, inl))
    {
        printf("EVP_CipherUpdate fail...");
        goto err;
    }
    total += templ;

    if (!EVP_CipherFinal_ex(ctx, out + total, &templ))
    {
        printf("EVP_CipherFinal_ex fail...");
        goto err;
    }
    total += templ;
    EVP_CIPHER_CTX_free(ctx);
    return 0;
    err:
    EVP_CIPHER_CTX_free(ctx);
    return -1;
}

int main(){
    const char plaintext[] = "Hello World!";
    char ciphertext[128];
    char decryptedtext[128];
    printf("plain text: %s\n",plaintext);
    do_crypt(plaintext, strlen((const char*)plaintext),ciphertext,EVP_aes_128_cbc(),1);
    printf("enc text: %s\n",ciphertext);
    do_crypt(ciphertext, strlen((const char*)ciphertext),decryptedtext,EVP_aes_128_cbc(),0);
    printf("dec text: %s\n",decryptedtext);
}

EVP_Encryp 和 EVP_Decryp 系列:

/**
设置密码上下文ctx以使用来自 ENGINE impl 的密码类型进行加密。ctx必须在调用此函数之前创建。类型通常由诸如 EVP_aes_256_cbc() 之类的函数提供。如果impl为 NULL,则使用默认实现。key是要使用的对称密钥,iv是要使用的 IV(如有必要),用于密钥和 IV 的实际字节数取决于密码。可以在初始调用中将除type之外的所有参数设置为 NULL ,并在后续调用中提供其余参数.
**/
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
                        ENGINE *impl, const unsigned char *key, const unsigned char *iv);
/**
加密inl缓冲区中的inl字节in并将加密版本写入out。可以多次调用此函数来加密连续的数据块。
写入的数据量取决于加密数据的块对齐。对于大多数密码和模式,写入的数据量可以是从零字节到 (inl + cipher_block_size - 1) 字节的任何内容。对于包装密码模式,写入的数据量可以是从零字节到 (inl + cipher_block_size) 字节的任何内容。
对于流密码,写入的数据量可以是从零字节到 inl 字节的任何内容。因此,out应该为正在执行的操作包含足够的空间。实际写入的字节数放在outl中
**/
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
                       int *outl, const unsigned char *in, int inl);
/**
必须在 EVP_EncryptUpdate 之后调用,用来加密原文剩余部分。
**/
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

EVP_DecryptInit_ex()、EVP_DecryptUpdate()和EVP_DecryptFinal_ex()是对应的解密操作。如果启用了填充并且最终块的格式不正确,则 EVP_DecryptFinal() 将返回错误代码。

#include <iostream>
#include <cstring>
#include <openssl/evp.h>
using namespace std;

void symmetric_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,
                       unsigned char *iv, unsigned char *ciphertext) {
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertext_len;
    ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len);
    ciphertext_len = len;
    EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
    ciphertext_len += len;
    EVP_CIPHER_CTX_free(ctx);
}

void symmetric_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
                       unsigned char *iv, unsigned char *plaintext) {
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;
    ctx = EVP_CIPHER_CTX_new();
    EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len);
    plaintext_len = len;
    EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
    plaintext_len += len;
    EVP_CIPHER_CTX_free(ctx);
}

int main() {
    unsigned char key[] = "01234567890123456789012345678901";
    unsigned char iv[] = "0123456789012345";
    unsigned char plaintext[] = "Hello World!";
    unsigned char ciphertext[128];
    unsigned char decryptedtext[128];
    symmetric_encrypt(plaintext, strlen((char *) plaintext), key, iv, ciphertext);
    symmetric_decrypt(ciphertext, strlen((char *) ciphertext), key, iv, decryptedtext);
    cout << "Plaintext: " << plaintext << endl;
    cout << "Ciphertext: " << ciphertext << endl;
    cout << "Decrypted text: " << decryptedtext << endl;
    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
0、此例程调试环境 运行uname -a的结果如下: Linux freescale 3.0.35-2666-gbdde708-g6f31253 #1 SMP PREEMPT Thu Nov 30 15:45:33 CST 2017 armv7l GNU/Linux 简称2017 armv7l GNU/Linux 1、openssl 直接处理AES的API 在openssl/aes.h定义。是基本的AES库函数接口,可以直接调用,但是那个接口是没有填充的。而如果要与Java通信,必须要有填充模式。所以看第2条。 2、利用openssl EVP接口 在openssl/evp.h中定义。在这个接口中提供的AES是默认是pkcs5padding方式填充方案。 3、注意openssl新老版本的区别 看如下这段 One of the primary differences between master (OpenSSL 1.1.0) and the 1.0.2 version is that many types have been made opaque, i.e. applications are no longer allowed to look inside the internals of the structures. The biggest impact on applications is that: 1)You cannot instantiate these structures directly on the stack. So instead of: EVP_CIPHER_CTX ctx; you must instead do: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); .... EVP_CIPHER_CTX_free(ctx); 2)You must only use the provided accessor functions to access the internals of the structure. 4、注意加密的内容是数据不限制是否为字符串 openssl接口加密的是数据,不限制是否为字符串,我看到有些人在加密时使用strlen(),来获取要加密的长度,如果是对字符串加密的话没有问题,如果不是字符串的话,用它获取的长度是到第一个0处,因为这个函数获取的是字符串长度,字符串是以零为终止的。 5、在调用EVP_EncryptFinal_ex时不要画蛇添足 正常加解密处理过程,引用网友的代码如下,经测试正确。 int kk_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext) { EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; ctx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); //EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_EncryptUpdate(ctx, ciphertext, &len;, plaintext, plaintext_len); ciphertext_len = len; EVP_EncryptFinal_ex(ctx, ciphertext + len, &len;); ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); return ciphertext_len; } int kk_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv, unsigned char *plaintext) { EVP_CIPHER_CTX *ctx; int len; int plaintext_len; ctx = EVP_CIPHER_CTX_new(); EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); //EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_DecryptUpdate(ctx, plaintext, &len;, ciphertext, ciphertext_len); plaintext_len = len; EVP_DecryptFinal_ex(ctx, plaintext + len, &len;); plaintext_len += len; EVP_CIPHER_CTX_free(ctx); return plaintext_len; } 我看到有人提供的代码在加密长度正好是16字节的整数倍时特意不去调用EVP_EncryptFinal_ex,这实在是画蛇添足啊,不论什么情况下,最后一定要调用EVP_EncryptFinal_ex一次,然后结束加密过程。 6、Base64陷阱 如果用到了base64,要注意如下: 1)base64算法是将3个字节变成4个可显示字符。所以在如果数据长度不是3字节对齐时,会补0凑齐。 2)在解密时先要解base64,再解AES。在解base64后,要减掉补上的0。算法就去查看base64后的字符串尾处有几个=号,最多是2个,如果正好要加密的数据是3的倍数,不需要补0,那么base64后的数据尾处就没有=,如果补了1个0,就有一个=号。 算法如下: int encode_str_size = EVP_EncodeBlock(base64, en, el); int length = EVP_DecodeBlock(base64_out, base64, encode_str_size ); //EVP_DecodeBlock内部同样调用EVP_DecodeInit + EVP_DecodeUpdate + Evp_DecodeFinal实现,但是并未处理尾部的'='字符,因此结果字符串长度总是为3的倍数 while(base64[--encode_str_size] == '=') length--; 算法网友提供,测试正确。
OpenSSL提供了AES加密算法,可以使用AES_cbc_encrypt函数进行加密操作。这个函数需要传入明文数据、密钥、初始向量(ivec)等参数进行加密。其中,初始向量(ivec)的内容可以任意指定,但加密和解密操作必须使用相同的数据。在AES_cbc_encrypt的底层实现中,每16个字节进行一次处理,先与初始向量进行异或运算,然后调用AES_encrypt函数进行加密。在加密过程中,AES_cbc_encrypt会修改初始向量的内容,因此初始向量参数不能是一个常量,也不能在传递给加密函数后立即传递给解密函数,必须重新赋值后再传递给解密函数。 需要注意的是,对称加密算法的优势是算法公开、计算量小、加密速度快、加密效率高,而安全性主要取决于密钥的保护。对称加密的缺点主要体现在多用户通信场景中,密钥分发和管理比较困难。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [基于openssl库实现AES加密(C语言)](https://blog.csdn.net/ylgcgbd/article/details/117931518)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [OpenSSLAES加密的用法](https://blog.csdn.net/m0_46577050/article/details/121302115)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值