OpenSSL调用EVP_DecryptUpdate使用AES_wrap_pad时触发段错误
问题原因
使用EVP_aes_*_wrap_pad(即RFC 5649定义的AES密钥封装算法)时触发段错误,核心是这类算法和ECB等普通分组加密模式的EVP调用逻辑存在差异,未做对应适配,主要有3个必须处理的点:
- 必须设置Wrap模式允许标志:AES-Wrap系列算法默认不支持分块调用
EVP_DecryptUpdate/EVP_EncryptUpdate,必须在上下文初始化后立刻调用EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW)开启分块调用支持,否则内部逻辑会走一次性处理分支,传入分块数据时很容易触发内存越界导致段错误。 - IV长度要求不同:普通AES分组模式(ECB/CBC等)的块大小是16字节,对应IV长度也是16字节,其中ECB模式本身不使用IV,传入的IV参数会被直接忽略,所以之前传16字节IV不会报错。但AES-Wrap系列算法使用的是64位(8字节)的默认IV(RFC标准定义的固定值
0xA6A6A6A6A6A6A6A6),传入16字节的IV会导致内部读取IV时内存越界。如果不需要自定义IV,直接在Init时给iv参数传NULL即可使用标准默认IV;如果要自定义IV,必须传入长度为8字节的IV缓冲区。 - 密钥长度必须严格匹配算法:
EVP_aes_128_wrap_pad要求密钥长度为16字节,EVP_aes_192_wrap_pad要求24字节,EVP_aes_256_wrap_pad要求32字节,长度不匹配会导致内部读取密钥时越界触发段错误。测试128版本时密钥刚好是16字节所以没触发这个问题,换256版本时如果还沿用16字节密钥会直接崩溃。
另外要注意:测试用的indata是普通字符串,不是合法的AES-Wrap密文(合法密文是经过Wrap加密输出的、符合RFC格式的数据,带pad版本的密文长度至少16字节,且包含校验信息),就算解决了段错误,EVP_DecryptFinal_ex也会因为校验失败返回解密错误,这是测试数据的问题,和接口逻辑无关。
修正后的可运行示例(以EVP_aes_128_wrap_pad为例)
#include <openssl/evp.h> #include <stdio.h> #include <string.h> void dump_head(unsigned char *buf, size_t len) { unsigned end, i; for (end = len; end > 0; end--) if (buf[end-1] != 0) break; printf("buf = {"); for (i = 0; i < end; i++) printf(" %02hhx,", buf[i]); printf(" }\n"); } int main(void) { // 注意:换EVP_aes_256_wrap_pad时,key必须改成32字节长度 unsigned char key[] = "0123456789abcdef"; unsigned char indata[32] = "0123456789abcdeffedcba9876543210"; unsigned char buf[4096]; int out_len; EVP_CIPHER_CTX *ctx; ctx = EVP_CIPHER_CTX_new(); // 初始化解密上下文,iv传NULL使用默认标准IV EVP_DecryptInit_ex(ctx, EVP_aes_128_wrap_pad(), NULL, key, NULL); // 必须设置这个flag,允许分块调用Update接口 EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); // 注意:这里的indata如果不是合法Wrap密文,后续Final会报错 if(!EVP_DecryptUpdate(ctx, buf, &out_len, indata, 32)) { printf("DecryptUpdate failed\n"); EVP_CIPHER_CTX_free(ctx); return 1; } printf("Got %d from Update\n", out_len); dump_head(buf, sizeof(buf)); printf("Final!\n"); memset(buf, 0, sizeof(buf)); if(!EVP_DecryptFinal_ex(ctx, buf, &out_len)) { printf("DecryptFinal failed, input is not valid wrap ciphertext\n"); EVP_CIPHER_CTX_free(ctx); return 1; } printf("Got %d from Final\n", out_len); dump_head(buf, sizeof(buf)); EVP_CIPHER_CTX_free(ctx); return 0; }
切换到EVP_aes_256_wrap_pad的注意点
- 将
EVP_aes_128_wrap_pad()替换为EVP_aes_256_wrap_pad() - 将key缓冲区长度改为32字节,填入对应256位密钥
- 其余逻辑(设置flag、IV传NULL或传8字节自定义IV)保持不变即可。
内容的提问来源于stack exchange,提问作者Kivuos
相关产品推荐
相关产品推荐

