openssl3.2 - 官方demo学习 - cipher - aesccm.c

📅 2026/7/14 11:20:48
openssl3.2 - 官方demo学习 - cipher - aesccm.c
1. 初识AES-CCM模式加密第一次看到OpenSSL的AES-CCM示例代码时我和大多数开发者一样感到困惑——为什么除了常见的key、iv和data之外还需要设置nonce、tag和认证数据这和我们熟悉的AES-CBC模式差别太大了。后来在实际项目中踩过几次坑才明白CCM是一种特殊的认证加密模式AEAD它不仅能加密数据还能验证数据的完整性。AES-CCM的全称是AES Counter with CBC-MAC它结合了CTR加密模式和CBC-MAC认证机制。这种模式在物联网设备通信中特别常见比如智能家居设备与云端的安全通信。举个例子当你用手机APP控制智能灯泡时APP发送的开灯指令就需要同时保证机密性不能被窃听和完整性不能被篡改。2. 解密官方demo代码结构打开openssl3.2的demos/cipher/aesccm.c文件首先映入眼帘的是几组测试数据/* AES key */ static const unsigned char ccm_key[] { 0xce, 0xb0, 0x09, 0xae, 0xa4, 0x45, 0x44, 0x51, /* 省略其他字节... */ }; /* Unique nonce */ static const unsigned char ccm_nonce[] { 0x76, 0x40, 0x43, 0xc4, 0x94, 0x60, 0xb7 }; /* Additional Authenticated Data (AAD) */ static const unsigned char ccm_adata[] { 0x6e, 0x80, 0xdd, 0x7f, 0x1b, 0xad, 0xf3, 0xa1, /* 省略其他字节... */ };这些数据都是从NIST官方测试向量中提取的保证了示例的权威性。特别要注意的是AAD附加认证数据这部分数据不会被加密但会参与完整性校验。比如在视频会议系统中视频帧的元数据时间戳、分辨率等可以作为AAD传输既保持可读性又能防篡改。3. 加密流程深度解析加密函数aes_ccm_encrypt()的完整流程可以分为六个关键步骤3.1 上下文初始化EVP_CIPHER_CTX *ctx EVP_CIPHER_CTX_new(); EVP_CIPHER *cipher EVP_CIPHER_fetch(libctx, AES-192-CCM, propq);这里创建了加密上下文并获取算法实现。OpenSSL 3.2的新API推荐使用EVP_CIPHER_fetch而不是直接使用EVP_aes_192_ccm()这样可以更好地支持Provider机制。3.2 参数设置OSSL_PARAM params[3] { OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_AEAD_IVLEN, ccm_nonce_len), OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, NULL, ccm_tag_len), OSSL_PARAM_END }; if (!EVP_EncryptInit_ex2(ctx, cipher, NULL, NULL, params)) goto err;这里设置了两个关键参数nonce长度默认7字节tag长度16字节我在实际项目中遇到过坑如果tag长度设置不对解密时会直接失败。比如设置成12字节但实际生成16字节tag会导致解密时验证失败。3.3 密钥和nonce设置if (!EVP_EncryptInit_ex(ctx, NULL, NULL, ccm_key, ccm_nonce)) goto err;注意这里使用的是传统的EVP_EncryptInit_ex而不是带_ex2后缀的新API。这种混用在新旧版本过渡期很常见。3.4 明文长度设置if (!EVP_EncryptUpdate(ctx, NULL, outlen, NULL, sizeof(ccm_pt))) goto err;这个看似奇怪的操作其实是CCM模式特有的——需要提前告知加密数据的长度。我在第一次使用时完全不明白为什么要对NULL数据做Update后来发现这是CCM的内部要求。3.5 AAD数据处理if (!EVP_EncryptUpdate(ctx, NULL, outlen, ccm_adata, sizeof(ccm_adata))) goto err;AAD数据通过特殊的Update调用传入——输出缓冲区为NULL。这里有个限制AAD必须在加密数据之前设置且只能设置一次。3.6 实际加密操作if (!EVP_EncryptUpdate(ctx, outbuf, outlen, ccm_pt, sizeof(ccm_pt))) goto err;终于到了真正的加密步骤注意CCM模式有个重要限制加密操作只能调用一次不能像CBC那样分块处理。这也是为什么示例代码中直接传入了完整的明文数据。4. 解密流程关键点解密流程与加密基本对称但有几点需要特别注意/* 设置期望的tag值 */ params[1] OSSL_PARAM_construct_octet_string( OSSL_CIPHER_PARAM_AEAD_TAG, (unsigned char *)ccm_tag, sizeof(ccm_tag)); /* 解密验证 */ rv EVP_DecryptUpdate(ctx, outbuf, outlen, ccm_ct, sizeof(ccm_ct)); if (rv 0) { printf(Tag verify successful!\n); } else { printf(Tag verify failed!\n); }解密时必须在初始化阶段传入预期的tag值。如果验证失败EVP_DecryptUpdate会返回0或负数且不会输出任何明文数据。这种设计保证了验证失败时不处理数据的安全原则。5. 常见问题与解决方案在实际项目中使用AES-CCM时我遇到过几个典型问题问题1tag验证失败可能原因加密解密使用的tag长度不一致nonce值不同AAD数据被修改 解决方案确保两端参数完全一致建议封装成统一函数。问题2多块数据加密CCM模式不支持分块加密必须一次性传入完整数据。如果数据很大可以考虑使用更大的缓冲区改用支持分块的GCM模式对每块数据单独CCM加密需要管理多个tag问题3性能问题CCM模式由于需要同时处理加密和认证性能会比单纯CBC模式慢约30%。在树莓派等嵌入式设备上测试AES-192-CCM的吞吐量约为50MB/sCBC模式可达70MB/s。6. 最佳实践建议经过多个项目的实践我总结出以下使用建议参数管理封装成结构体确保加解密参数一致typedef struct { unsigned char key[24]; // AES-192 unsigned char nonce[7]; // 默认nonce长度 size_t tag_len; // 通常16字节 unsigned char *aad; size_t aad_len; } ccm_params_t;错误处理检查所有OpenSSL调用返回值if (!EVP_EncryptUpdate(ctx, ...)) { ERR_print_errors_fp(stderr); // 打印详细错误 goto cleanup; }资源释放使用goto统一处理错误err: EVP_CIPHER_free(cipher); EVP_CIPHER_CTX_free(ctx);版本适配OpenSSL 3.x的Provider机制OSSL_LIB_CTX *libctx OSSL_LIB_CTX_new(); EVP_CIPHER *cipher EVP_CIPHER_fetch(libctx, AES-192-CCM, NULL);7. 深入理解CCM模式CCM模式的核心优势在于它将加密和认证合二为一。从实现角度看它实际上是两种模式的组合CTR模式用于数据加密CBC-MAC用于生成认证标签这种组合带来的安全特性包括机密性数据内容被加密完整性任何篡改都会被检测到真实性确保数据来自预期发送方在TLS 1.3中CCM模式被列为可选套件TLS_AES_128_CCM_SHA256特别适合资源受限的物联网设备。