求基于OpenSSL库的Retail MAC算法C语言实现示例
Hey there! 我懂你现在的困扰——想用C语言结合OpenSSL实现Retail MAC的校验和计算,却找不到现成的示例代码对吧?别着急,我给你整理了一份完整的实现,注释都写得很明白,你跟着一步步来就行~
Retail MAC 校验和计算的OpenSSL实现(C语言)
首先得说下,Retail MAC(也就是ANSI X9.19 MAC)是一种基于DES算法的消息认证码,核心逻辑是对数据分块后做加密+异或的迭代处理。下面的代码用OpenSSL的EVP通用加密接口实现,兼容性更好,也更符合OpenSSL的最佳实践。
完整代码示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <openssl/evp.h> #include <openssl/err.h> // 处理Retail MAC的核心函数 // 参数说明: // key: DES密钥(8字节,含奇偶校验位) // key_len: 密钥长度(必须为8) // data: 待计算MAC的原始数据 // data_len: 原始数据长度 // mac: 输出MAC值的缓冲区 // mac_len: 输入为缓冲区最大长度,输出为实际生成的MAC长度(通常取4或8字节) int compute_retail_mac(const unsigned char *key, size_t key_len, const unsigned char *data, size_t data_len, unsigned char *mac, size_t *mac_len) { EVP_CIPHER_CTX *ctx = NULL; int ret = -1; size_t block_size = EVP_CIPHER_block_size(EVP_des_ecb()); unsigned char current_block[8] = {0}; unsigned char encrypted_block[8] = {0}; int out_len; // 校验输入参数合法性 if (!key || key_len != 8 || !data || !mac || !mac_len || *mac_len < 4) { fprintf(stderr, "Invalid input parameters\n"); goto cleanup; } // 初始化OpenSSL错误处理(可选,但建议加上) OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); ERR_load_crypto_strings(); // 创建并初始化加密上下文 ctx = EVP_CIPHER_CTX_new(); if (!ctx) { fprintf(stderr, "Failed to create EVP_CIPHER_CTX\n"); goto cleanup; } // 初始化DES-ECB加密模式(Retail MAC用ECB模式) if (EVP_EncryptInit_ex(ctx, EVP_des_ecb(), NULL, key, NULL) != 1) { fprintf(stderr, "Failed to init encryption context\n"); ERR_print_errors_fp(stderr); goto cleanup; } // 关闭填充(我们自己处理Retail MAC的填充规则) EVP_CIPHER_CTX_set_padding(ctx, 0); // 第一步:处理数据分块和填充 size_t total_blocks = (data_len + block_size - 1) / block_size; // 如果数据长度刚好是块大小的整数倍,需要额外加一个全0块 if (data_len % block_size == 0) { total_blocks++; } // 初始化当前块为全0 memset(current_block, 0, sizeof(current_block)); // 迭代处理每个数据块 for (size_t i = 0; i < total_blocks; i++) { unsigned char input_block[8] = {0}; size_t copy_len = (i < total_blocks - 1) ? block_size : (data_len % block_size); if (copy_len > 0) { memcpy(input_block, data + i * block_size, copy_len); } // 当前块和输入块异或 for (int j = 0; j < block_size; j++) { current_block[j] ^= input_block[j]; } // 加密异或后的块 if (EVP_EncryptUpdate(ctx, encrypted_block, &out_len, current_block, block_size) != 1) { fprintf(stderr, "Encryption failed\n"); ERR_print_errors_fp(stderr); goto cleanup; } // ECB模式下out_len应该等于block_size if (out_len != block_size) { fprintf(stderr, "Unexpected encryption output length\n"); goto cleanup; } // 将加密结果作为下一轮的当前块 memcpy(current_block, encrypted_block, block_size); } // 最终的MAC取前N字节(通常是4或8字节,这里默认取4字节,可按需调整) size_t output_len = *mac_len > 8 ? 8 : *mac_len; memcpy(mac, current_block, output_len); *mac_len = output_len; ret = 0; cleanup: // 释放资源 if (ctx) { EVP_CIPHER_CTX_free(ctx); } ERR_free_strings(); return ret; } // 测试主函数 int main() { // 示例DES密钥(8字节,注意DES密钥的奇偶校验位,这里用测试值) unsigned char key[8] = {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF}; // 示例待计算MAC的数据 unsigned char data[] = "Hello Retail MAC Test"; size_t data_len = strlen((char *)data); // 存储MAC的缓冲区 unsigned char mac[8] = {0}; size_t mac_len = 4; // 取4字节MAC if (compute_retail_mac(key, sizeof(key), data, data_len, mac, &mac_len) == 0) { printf("Computed Retail MAC (4 bytes): "); for (size_t i = 0; i < mac_len; i++) { printf("%02X ", mac[i]); } printf("\n"); } else { fprintf(stderr, "Failed to compute Retail MAC\n"); return 1; } return 0; }
关键说明和注意事项
- 编译命令:编译时需要链接OpenSSL的crypto库,命令如下:
gcc -o retail_mac retail_mac.c -lcrypto - 密钥要求:Retail MAC通常使用单DES,密钥必须是8字节(包含1字节的奇偶校验位,OpenSSL会自动处理校验)。如果需要3DES版本,只需把
EVP_des_ecb()换成EVP_des_ede3_ecb(),同时密钥长度改为24字节。 - 填充规则:严格遵循ANSI X9.19标准:
- 如果数据长度不是8字节的整数倍,用
0x00填充到8字节; - 如果数据长度刚好是8字节的整数倍,额外添加一个全
0x00的8字节块。
- 如果数据长度不是8字节的整数倍,用
- MAC长度:标准的Retail MAC可以是4字节或8字节,代码里默认取4字节,你可以通过修改
mac_len参数调整。
运行测试
编译后运行程序,会输出示例数据的4字节Retail MAC值,你可以用其他工具验证结果是否正确。
内容的提问来源于stack exchange,提问作者GI PP
相关产品推荐
相关产品推荐

