OpenSSL 3中ChaCha20-Poly1305不验证标签问题排查及用法咨询
OpenSSL ChaCha20-Poly1305 加密/解密与标签验证问题排查及正确用法
问题描述
使用OpenSSL的EVP_chacha20_poly1305()进行加密和MAC操作时遇到异常:
- 加密完成后,认证标签未自动附加到密文末尾
- 解密时返回成功状态码1,但未执行标签验证逻辑,也无法获取或验证标签
问题代码:
int main( ) { unsigned char key[] = "01234567890123456789012345678912"; unsigned char iv[] = "0123456789012345"; unsigned char pt[23] = "plaintext of length 23"; unsigned char ct[23 + 16]; unsigned char dt[23]; memset( ct, '\0', sizeof( ct ) ); memset( dt, '\0', sizeof( dt ) ); int ct_bw{ 0 }; int dt_bw{ 0 }; int status{ -19 }; { // Encrypt EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex2( ectx, EVP_chacha20_poly1305(), key, iv, nullptr ); EVP_EncryptUpdate( ectx, ct, &ct_bw, pt, 23 ); int bw = ct_bw; EVP_EncryptFinal_ex( ectx, ct + ct_bw, &ct_bw ); ct_bw += bw; EVP_CIPHER_CTX_free( ectx ); for(int i=24;i<34;i++){ //tag id not stored at the end of the ciphertext!!! ct[i]='\0'; } } { // Decrypt EVP_CIPHER_CTX* dctx = EVP_CIPHER_CTX_new(); EVP_DecryptInit_ex( dctx, EVP_chacha20_poly1305(), nullptr, key, iv ); EVP_DecryptUpdate( dctx, dt, &dt_bw, ct, ct_bw ); status = EVP_DecryptFinal_ex( dctx, dt + dt_bw, &dt_bw ); //decrypts sucsessfully, with sucsess status code of 1 //does not verify tag, or write it anywhere printf("\nstatus:%i decrypted_test:%s\n",status,dt); EVP_CIPHER_CTX_free( dctx ); } }
补充:EVP_chacha20与EVP_chacha20_poly1305()不具备互操作性(本就不应互操作)。
错误点拆解
加密阶段未主动获取认证标签
ChaCha20-Poly1305的认证标签不会自动附加到密文后,必须手动调用EVP_CIPHER_CTX_ctrl()配合EVP_CTRL_AEAD_GET_TAG参数获取标签,当前代码跳过了这一步,导致无法拿到标签。解密阶段未传入待验证标签
解密时必须明确传入加密生成的标签,OpenSSL才会执行标签验证逻辑。当前解密流程未提供标签,EVP_DecryptFinal_ex()仅完成解密,未做认证校验,因此返回成功。解密初始化参数顺序不规范
EVP_DecryptInit_ex()的正确参数顺序应为ctx, cipher, nullptr, key, iv,当前代码将key和nullptr位置写反,虽OpenSSL可能兼容,但需修正为规范写法。
正确实现代码
#include <stdio.h> #include <string.h> #include <openssl/evp.h> int main() { // 实际场景中密钥和IV需用安全随机数生成,此处仅作示例 unsigned char key[32] = "01234567890123456789012345678912"; unsigned char iv[12] = "012345678901"; // ChaCha20-Poly1305标准IV长度为12字节 unsigned char pt[] = "plaintext of length 23"; size_t pt_len = strlen((char*)pt); // 密文缓冲区:明文长度 + 标签长度(16字节) unsigned char ct[pt_len + 16]; unsigned char dt[pt_len]; unsigned char tag[16]; // 存储认证标签 memset(ct, 0, sizeof(ct)); memset(dt, 0, sizeof(dt)); memset(tag, 0, sizeof(tag)); int ct_bw = 0; int dt_bw = 0; int status = -1; // -------------------------- 加密流程 -------------------------- { EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new(); if (!ectx) { printf("Failed to create encrypt context\n"); return -1; } if (EVP_EncryptInit_ex2(ectx, EVP_chacha20_poly1305(), key, iv, nullptr) != 1) { printf("EncryptInit failed\n"); EVP_CIPHER_CTX_free(ectx); return -1; } if (EVP_EncryptUpdate(ectx, ct, &ct_bw, pt, pt_len) != 1) { printf("EncryptUpdate failed\n"); EVP_CIPHER_CTX_free(ectx); return -1; } int final_bw = 0; if (EVP_EncryptFinal_ex(ectx, ct + ct_bw, &final_bw) != 1) { printf("EncryptFinal failed\n"); EVP_CIPHER_CTX_free(ectx); return -1; } ct_bw += final_bw; // 此时ct_bw等于明文长度 // 获取认证标签 if (EVP_CIPHER_CTX_ctrl(ectx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1) { printf("Failed to get tag\n"); EVP_CIPHER_CTX_free(ectx); return -1; } // 将标签附加到密文末尾(也可单独存储) memcpy(ct + ct_bw, tag, 16); ct_bw += 16; EVP_CIPHER_CTX_free(ectx); printf("Encrypted successfully, ciphertext + tag length: %d\n", ct_bw); } // -------------------------- 解密流程 -------------------------- { EVP_CIPHER_CTX* dctx = EVP_CIPHER_CTX_new(); if (!dctx) { printf("Failed to create decrypt context\n"); return -1; } if (EVP_DecryptInit_ex2(dctx, EVP_chacha20_poly1305(), key, iv, nullptr) != 1) { printf("DecryptInit failed\n"); EVP_CIPHER_CTX_free(dctx); return -1; } // 解密密文部分(去掉末尾16字节的标签) int ciphertext_len = ct_bw - 16; if (EVP_DecryptUpdate(dctx, dt, &dt_bw, ct, ciphertext_len) != 1) { printf("DecryptUpdate failed\n"); EVP_CIPHER_CTX_free(dctx); return -1; } // 设置待验证的标签 if (EVP_CIPHER_CTX_ctrl(dctx, EVP_CTRL_AEAD_SET_TAG, 16, ct + ciphertext_len) != 1) { printf("Failed to set tag for verification\n"); EVP_CIPHER_CTX_free(dctx); return -1; } // 完成解密并验证标签 int final_bw = 0; status = EVP_DecryptFinal_ex(dctx, dt + dt_bw, &final_bw); dt_bw += final_bw; if (status == 1) { printf("Decrypt and tag verification successful!\n"); printf("Decrypted text: %s\n", dt); } else { printf("Tag verification failed! Status code: %d\n", status); } EVP_CIPHER_CTX_free(dctx); } return 0; }
关键注意事项
- IV长度规范:ChaCha20-Poly1305标准IV长度为12字节(96位),遵循规范可避免兼容性问题。
- 标签处理逻辑:
- 加密后必须主动调用
EVP_CTRL_AEAD_GET_TAG获取标签,可选择附加到密文末尾或单独存储。 - 解密前必须调用
EVP_CTRL_AEAD_SET_TAG传入标签,否则不会执行标签验证。
- 加密后必须主动调用
- 上下文安全:每个加密/解密流程需创建独立的
EVP_CIPHER_CTX,使用完毕后调用EVP_CIPHER_CTX_free()释放资源。 - 错误检查:所有OpenSSL API返回值都要检查,返回1表示成功,非1表示失败,避免忽略潜在问题。
内容的提问来源于stack exchange,提问作者xzijoq
相关产品推荐
相关产品推荐

