源码文件:/evp/evp_enc.c
EVP_CIPHER_CTX ctx;
加密函数说明:
1、EVP_CIPHER_CTX_init(&ctx)
把ctx清0
2、EVP_EncryptInit_ex(&ctx, EVP_rc4(), NULL, key, NULL);
初始化ctx
2.1、ctx->encrypt = enc;设置ctx为加密或者解密
2.2、ctx->cipher = cipher;设置ctx的加密算法为EVP_rc4()
2.3、ctx->cipher_data = OPENSSL_malloc(ctx->cipher->ctx_size);开辟算法的私有数据空间,并赋值给ctx,这个私有数据一般是秘钥,在这里是EVP_RC4_KEY。
2.4、根据算法设置ctx的其它一些成员
ctx->key_len = cipher->key_len;
ctx->flags = 0;
2.5、ctx->cipher->init(ctx, key, iv, enc) 把key设置到2.3所分配的cipher_data中
2.6、最后设置一些ctx成员
ctx->buf_len = 0;
ctx->final_used = 0;
ctx->block_mask = ctx->cipher->block_size - 1; 加密块掩码用例检测要加密的数据是否是各算法块大小的整数倍
3、EVP_EncryptUpdate()
3.1、如果要加密的数据大小是算法块大小的整数倍执行如下的if分支。直接调用EVP算法加密函数进行加密
if (ctx->buf_len == 0 && (inl & (ctx->block_mask)) == 0) {
*outl = 0;
if (ctx->cipher->do_cipher(ctx, out, in, inl)) {
*outl = inl;
return 1;
} else {
}*outl = 0;
return 0;
}
3.2、如果要加密的数据大小不是算法块大小的整数倍,先只加密块大小最大整数倍大小的数据。
i = inl & (bl - 1); //计算要加密的数据大小除以块大小的余数
inl -= i;//要加密的数据最后不够块大小的数据暂时不加密
if (inl > 0) {
if (!ctx->cipher->do_cipher(ctx, out, in, inl))
return 0;
*outl += inl;
}
3.3、memcpy(ctx->buf, &(in[inl]), i);把剩余不够块大小没加密完的数据存到ctx里面
ctx->buf_len = i; 没加密完的数据大小也记录下来,在EVP_EncryptFinal_ex会用
3.4、在执行完EVP_EncryptUpdate()后,outl大小分三种情况:
3.4.1、outl = 0 要加密的数据大小不足块大小
3.4.2、outl = inl 要加密的数据大小大于块大小且是块大小的整数倍,这种情况下outl就是要加密的数据长度
3.4.3、outl = inl - (inl % block_size) 要加密的数据大小大于块大小但不是块大小的整数倍,这种情况下outl是要加密的数据长度减去最后不够块大小的数据长度
4、EVP_EncryptFinal_ex(&ctx, zz + i, &j)
一般用来处理最后没加密完的数据,这里的zz是要加密的数据,i是EVP_EncryptUpdate()处理完后的outl。
4.1、bl = ctx->buf_len;这个长度是0或者是没加密的数据长度
4.2、如果bl不是0,并且加密模式被设置过EVP_CIPH_NO_PADDING,返回0(表示加密失败)。
如果明文大小是块大小的整数倍,outl设置为0,就返回1(表示最后执行成功),这种情况下没必要再执行EVP_EncryptFinal_ex函数
if (ctx->flags & EVP_CIPH_NO_PADDING) {
if (bl) {
EVPerr(EVP_F_EVP_ENCRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
return 0;
}
*outl = 0;
return 1;//EVP_EncryptFinal_ex()只对需要填充的模式起效
}
4.3、对最后的数据填充至算法block_size大小
n = b - bl; //b= ctx->cipher->block_size;算法的块大小。bl大小见4.1的说明
for (i = bl; i < b; i++)
ctx->buf[i] = n;
buf[EVP_MAX_BLOCK_LENGTH]这里的buf是固定32字节的一个数组,
也就是说openssl只支持最多32字节的块大小,如需要支持更大块大小,需要修改代码。
可以看出openssl采用的是pkcs7的填充规则(如果有n个字节需要填充,那么每个字节就填入n,十六进制填充)。
4.4、对最后一个填充过的块进行加密
ret = ctx->cipher->do_cipher(ctx, out, ctx->buf, b);//加密成功后,密文的大小一定是算法块大小的整数倍。
解密函数说明:
5、EVP_DecryptInit_ex和EVP_EncryptInit_ex是一样的,都是掉用的EVP_CipherInit_ex()函数,EVP_CipherInit_ex的最后一个参数是1还是0来设置ctx是加密还是解密
6、EVP_DecryptUpdate
6.1、if (ctx->flags & EVP_CIPH_NO_PADDING)
return EVP_EncryptUpdate(ctx, out, outl, in, inl);
不填充,直接解密。这里加解密都是调用的EVP_EncryptUpdate(),具体是加密还是解密是根据ctx->encrypt的值来判断的,在2.1中ctx初始化时设置这个成员
这个时候执行EVP_EncryptUpdate()中如下代码:
if (ctx->buf_len == 0 && (inl & (ctx->block_mask)) == 0) {
if (ctx->cipher->do_cipher(ctx, out, in, inl)) {
//解密成功
*outl = inl;
return 1;
}
*outl = 0;
return 0;
}
不填充,密文长度是块大小的整数倍,解密才成功。如果填充,不执行这段代码。
6.2、EVP_EncryptUpdate(ctx, out, outl, in, inl)执行解密
6.2.1、如果要解密的数据大小是块大小的整数倍,执行6.1中执行的EVP_EncryptUpdate()代码
6.2.2、如果要解密的数据大小不是块大小的整数倍,执行EVP_EncryptUpdate(),这时调的是具体算法的解密函数。
和加密类似,解密时最后一个不够块大小的密文块不会被解密,存放到ctx->buf里面,同时设置ctx->buf_len = i,XXX_final函数会用到
正常情况下,密文是不会出现大小不是块大小的整数倍的。
6.3、处理最后一个密文块,也就是明文填充后加密出的那个密文块
if (b > 1 && !ctx->buf_len) {
//正常情况下会走这个分支,正常情况是指,密文是由相应的加密算法成功加密的结果,不是随意的一个字符串。
*outl -= b;//需要单独处理最后一个填充过的块,除最后一个填充过的块,其它数据块跟原来的明文是一样的。
ctx->final_used = 1;
memcpy(ctx->final, &out[*outl], b); //把解密后最后一个带填充值的数据块,复制到ctx->final_used里面
}
7、EVP_DecryptFinal,这个函数直接调用的EVP_DecryptFinal_ex(),并无其它操作
8、EVP_DecryptFinal_ex
8.1、如果是不填充的,并且还有没解密完的数据,报错,返回解密失败0。
如果是不填充的,并且密文长度是块大小的整数倍,什么都没做就返回了,这种情况下就没必要再调EVP_DecryptFinal了。
if (ctx->flags & EVP_CIPH_NO_PADDING) {
if (ctx->buf_len) {
EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
return 0;
}
*outl = 0;
return 1;
}
8.2、如何还有没解密完的数据块,直接返回,报错。密文不正确、不是块大小的整数倍才会出现没解密完的数据块。
if (ctx->buf_len || !ctx->final_used) {
}EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_WRONG_FINAL_BLOCK_LENGTH);
return (0);
8.3、从填充数据的最后一个字节获取填充的值,并检测获取到的值是否合法
n = ctx->final[b - 1];
if (n == 0 || n > (int)b) {
//填充的值是不能为0的,也不能大于算法的块大小
EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_BAD_DECRYPT);
return (0);
}
for (i = 0; i < n; i++) {
//循环检查填充的值是否正确,检测规则是:根据最后一个字节的值n确定最后的几个字节需要检查,并且每个字节的值等于n。
if (ctx->final[--b] != n) {
EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_BAD_DECRYPT);
return (0);
}
}
8.4、剔除填充的值
//计算填充块中,明文的长度
n = ctx->cipher->block_size - n;
for (i = 0; i < n; i++)
*outl = n;out[i] = ctx->final[i]; //把明文放到out里面,这个out是EVP_DecryptUpdate()执行完后的out+outl1
这样在应用程序调用EVP_DecryptFinal()这个函数后,这个outl的值就是最后一个填充过的数据块中原来明文的长度。这个outl(假设为outl2)和EVP_DecryptUpdate()处理完成后的outl(假设为outl1)合起来就是原来明文的长度,
其实完整的明文在调用EVP_DecryptUpdate()后已经全部解密出来了(只是需要确定最后一个填充块中明文的长度)
最后掉用EVP_DecryptFinal()用来确定填充块中的明文的长度。
EVP_DecryptUpdate()执行完成后的out就是解密后明文的起始地址,outl1+outl2就是解密后的明文长度
密文一定要是对应算法块大小的整数倍,在调用解密的EVP_XXX函数之前可以做个检查,不满足这个条件可以不用解密了
不填充,只能加密块大小整数倍的明文,否则,加密结果是不完整的
9、关于设置是否填充的接口
EVP_CIPHER_CTX_set_padding(ctx, flag)
flag是0表示没有padding,flag是1表示有padding。默认是有padding的结果
EVP加密步骤:
OpenSSL_add_all_ciphers();
EVP_get_cipherbyname(xxx);
EVP_CIPHER_CTX_init(&ctx);
EVP_EncryptInit_ex(&ctx, ec, NULL, key, NULL);
//padding为0表示不填充,1表示填充,这里会根据padding的值来设置ctx->flags的值。不调此函数跟padding是1的效果一样
EVP_CIPHER_CTX_set_padding(&ctx, padding);
EVP_EncryptUpdate(&ctx, out, &out_len, input, input_len);
input是明文,input_len是明文长度;out是密文,out_len是密文长度
如果明文不是算法块大小的整数倍,这里的out_len是不包含最后一个填充块加密出的密文长度。
EVP_EncryptFinal_ex(&ctx, out + out_len, &out_len_final); //加密最后一个填充的数据块,out+out_len作用是确定加密后的密文放置位置。最后一个需要填充的明文块被放置在ctx->buf里面
本函数在填充后会调用算法的加密函数进行加密,并把加密出的密文放到out+out_len处。
如果明文大小是块大小的整数倍,可以不调本函数。这种情况下ctx->buf是空的,这个函数不会有下一步的加密操作
如果明文大小是块大小的整数倍,调用了本函数,并且是填充模式,那么就会填充整个块大小(也就是整个数据块都是填充的值,并不包含明文)的数据来进行加密,这其实是不需要的
从out开始的out_len+out_len_final长的字符就是完整的密文
加密完成后的密文不能直接在网络上发送。直接写文件也是乱码,也不合适。需要做BASE64转换后在发往网络或者写文件。以下是用lcrypto库提供的BASE64转换接口实现的BASE64转换。
int base64_encode(char *in_str, int in_len, char *out_str)
{
BIO *b64, *bio;
BUF_MEM *bptr = NULL;
size_t size = 0;
if (in_str == NULL || out_str == NULL)
return -1;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_write(bio, in_str, in_len);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bptr);
memcpy(out_str, bptr->data, bptr->length);
out_str[bptr->length] = '\0';
size = bptr->length;
BIO_free_all(bio);
return size;
}
int base64_decode(char *in_str, int in_len, char *out_str)
{
BIO *b64, *bio;
BUF_MEM *bptr = NULL;
int counts;
int size = 0;
if (in_str == NULL || out_str == NULL)
return -1;
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bio = BIO_new_mem_buf(in_str, in_len);
bio = BIO_push(b64, bio);
size = BIO_read(bio, out_str, in_len);
out_str[size] = '\0';
BIO_free_all(bio);
return size;
}
EVP解密步骤
OpenSSL_add_all_ciphers();
EVP_get_cipherbyname(xxx);
EVP_CIPHER_CTX_init(&ctx)
EVP_DecryptInit_ex(&ctx, ec, NULL, key, NULL);
EVP_CIPHER_CTX_set_padding(&ctx, padding);
//input是密文,input_len是密文长度;out,是明文,out_len是明文长度
EVP_DecryptUpdate(&ctx, out, &out_len, input, input_len);
如果密文是没填充过的,那么out_len就是完整明文的长度,否则是明文的部分长度
这一步里面明文已经完全解密出来放到out里面了,只是在有填充时,还不确定最后一个块里面有几个字节是明文EVP_DecryptFinal_ex(&ctx, out + out_len, &out_len_final);从最后一个包含填充值的明文块里计算剩余的明文长度,并把剩余的明文长度赋值给out_len_final
从out开始的out_len+out_len_final长的字符就是完整的明文
上面两个例子中的out空间要够存放加密或解密的值。这个空间大小在加密时,可设置成明文长度+算法的块大小;解密时,可设置成密文长度。
如果是不填充的
加密时,只能加密长度是块大小整数倍的明文,否则加密的结果是不完整的。
解密时,EVP_DecryptUpdate()执行后的out包含的字符就是解密后的内容,这个时候out_len始终是0,不能用来确定解密后的明文长度。不需要再调用EVP_DecryptFinal_ex(),调的话也是执行失败
如果是填充的
不管明文长度是否是块大小的整数倍,加解密都要调用EVP_EncryptFinal_ex()
本文详细介绍了OpenSSL中EVP_CIPHER_CTX的加密和解密过程,包括EVP_EncryptInit_ex、EVP_EncryptUpdate、EVP_EncryptFinal_ex等函数的使用。内容涵盖加密模式、填充规则以及PKCS7填充的处理。同时,讨论了加密解密时对数据块大小的要求和填充选项的设置方法。
7388

被折叠的 条评论
为什么被折叠?



