You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于tiny-AES-c实现与CryptoJS AES等效的C加解密代码

等效CryptoJS AES加解密的C实现方案(基于tiny-AES-c)

首先得明确:你当前的问题大概率是没匹配上CryptoJS的默认行为逻辑——比如你用了ECB模式,但CryptoJS默认是CBC;没处理PKCS#7填充;也没把密文字节转成Base64可读字符串。下面我分场景给出和CryptoJS完全等效的C代码实现,解决你提到的两个核心问题。

先理清CryptoJS AES的关键默认规则

  • 默认模式是CBC(不是ECB,除非你JS代码显式指定了mode: CryptoJS.mode.ECB)
  • 默认填充是PKCS#7(自动补全到16字节块的整数倍)
  • 若用字符串密码而非原始字节密钥,会通过OpenSSL EVP_BytesToKey算法(MD5哈希、1次迭代)生成实际AES密钥和IV
  • 加密输出默认是包含salt+IV+密文的Base64编码字符串(直接用密钥时则是密文的Base64)

场景1:JS显式使用ECB模式+直接密钥

假设你的JS代码是这样的:

const key = CryptoJS.enc.Utf8.parse('your-16-byte-key-here'); // 16/24/32字节对应AES-128/192/256
const plaintext = 'Hello, this is a long message that needs encryption';
const ciphertext = CryptoJS.AES.encrypt(plaintext, key, {
  mode: CryptoJS.mode.ECB,
  padding: CryptoJS.pad.Pkcs7
}).toString();

对应的C实现代码

tiny-AES-c本身不带填充和Base64转换,所以我们需要自己补充这部分逻辑:

1. PKCS#7填充/去填充函数

#include "aes.h"
#include <string.h>
#include <stdlib.h>

// PKCS#7填充:将数据补全到AES块大小(16字节)的整数倍
void pkcs7_pad(uint8_t *data, size_t data_len, size_t *padded_len) {
    size_t block_size = AES_BLOCKLEN;
    size_t pad_len = block_size - (data_len % block_size);
    *padded_len = data_len + pad_len;
    memset(data + data_len, pad_len, pad_len);
}

// PKCS#7去填充:移除末尾的填充字节
void pkcs7_unpad(uint8_t *data, size_t *data_len) {
    uint8_t pad_len = data[*data_len - 1];
    *data_len -= pad_len;
}

2. AES-ECB加密(输出Base64可读字符串)

// 你需要实现或引入一个标准Base64编码函数,这里省略具体实现
char* base64_encode(const uint8_t* data, size_t len);

// AES-ECB加密,等效CryptoJS的ECB+PKCS7逻辑
char* aes_ecb_encrypt(const char* plaintext, const uint8_t* key, size_t key_len) {
    // 1. 把明文转成UTF-8字节数组
    size_t plain_len = strlen(plaintext);
    uint8_t* plain_data = (uint8_t*)malloc(plain_len + AES_BLOCKLEN); // 预留填充空间
    memcpy(plain_data, plaintext, plain_len);

    // 2. 执行PKCS#7填充(解决长消息块不匹配问题)
    size_t padded_len;
    pkcs7_pad(plain_data, plain_len, &padded_len);

    // 3. 初始化AES上下文,执行分块加密
    AES_ctx ctx;
    AES_init_ctx(&ctx, key);
    for (size_t i = 0; i < padded_len; i += AES_BLOCKLEN) {
        AES_ECB_encrypt(&ctx, plain_data + i);
    }

    // 4. 密文字节转Base64,得到可读字符串
    char* cipher_b64 = base64_encode(plain_data, padded_len);

    free(plain_data);
    return cipher_b64;
}

3. AES-ECB解密函数

// 标准Base64解码函数,需自行实现或引入
uint8_t* base64_decode(const char* b64_str, size_t* out_len);

char* aes_ecb_decrypt(const char* cipher_b64, const uint8_t* key, size_t key_len) {
    // 1. Base64解码得到密文字节数组
    size_t cipher_len;
    uint8_t* cipher_data = base64_decode(cipher_b64, &cipher_len);

    // 2. 初始化AES上下文,分块解密
    AES_ctx ctx;
    AES_init_ctx(&ctx, key);
    for (size_t i = 0; i < cipher_len; i += AES_BLOCKLEN) {
        AES_ECB_decrypt(&ctx, cipher_data + i);
    }

    // 3. 移除PKCS#7填充
    size_t plain_len = cipher_len;
    pkcs7_unpad(cipher_data, &plain_len);

    // 4. 转成UTF-8字符串
    char* plaintext = (char*)malloc(plain_len + 1);
    memcpy(plaintext, cipher_data, plain_len);
    plaintext[plain_len] = '\0';

    free(cipher_data);
    return plaintext;
}

场景2:JS使用默认CBC模式(带自动salt/IV)

如果你的JS代码是最常见的默认调用(用密码而非直接密钥):

const password = 'your-password';
const plaintext = 'Long message to encrypt';
const ciphertext = CryptoJS.AES.encrypt(plaintext, password).toString();

这种情况需要先实现EVP_BytesToKey算法,再处理CBC模式加密。

补充EVP_BytesToKey实现(MD5版本)

#include <md5.h> // 需引入MD5库,可自行实现或用开源版本

// 从密码和salt生成AES密钥和IV,完全匹配CryptoJS默认逻辑
void evp_bytes_to_key(const char* password, const uint8_t* salt, uint8_t* key, uint8_t* iv, size_t key_len, size_t iv_len) {
    uint8_t md5_buf[MD5_DIGEST_LENGTH];
    uint8_t* temp = (uint8_t*)malloc(strlen(password) + 8); // 密码 + 8字节salt
    size_t offset = 0;

    memcpy(temp, password, strlen(password));
    memcpy(temp + strlen(password), salt, 8);

    // 第一次MD5哈希
    MD5(temp, strlen(password) + 8, md5_buf);
    memcpy(key + offset, md5_buf, MD5_DIGEST_LENGTH);
    offset += MD5_DIGEST_LENGTH;

    // 若密钥+IV长度不足,补充迭代(CryptoJS默认1次,这里简化处理)
    if (offset < key_len + iv_len) {
        memset(temp, 0, strlen(password) + 8);
        memcpy(temp, md5_buf, MD5_DIGEST_LENGTH);
        memcpy(temp + MD5_DIGEST_LENGTH, password, strlen(password));
        memcpy(temp + MD5_DIGEST_LENGTH + strlen(password), salt, 8);
        MD5(temp, MD5_DIGEST_LENGTH + strlen(password) + 8, md5_buf);
        memcpy(key + offset, md5_buf, key_len + iv_len - offset);
    }

    // 拆分密钥和IV
    memcpy(iv, key + key_len, iv_len);
    key[key_len] = '\0';

    free(temp);
}

CBC模式加密函数

char* aes_cbc_encrypt(const char* plaintext, const char* password) {
    // 1. 生成随机8字节salt(CryptoJS默认行为)
    uint8_t salt[8];
    // 生产环境请用安全随机源(如/dev/urandom),这里用rand()做示例
    for (int i = 0; i < 8; i++) salt[i] = rand() % 256;

    // 2. 生成AES-128密钥(16字节)和IV(16字节)
    uint8_t key[16], iv[16];
    evp_bytes_to_key(password, salt, key, iv, 16, 16);

    // 3. 明文转UTF-8并PKCS#7填充
    size_t plain_len = strlen(plaintext);
    uint8_t* plain_data = (uint8_t*)malloc(plain_len + AES_BLOCKLEN);
    memcpy(plain_data, plaintext, plain_len);
    size_t padded_len;
    pkcs7_pad(plain_data, plain_len, &padded_len);

    // 4. CBC模式加密
    AES_ctx ctx;
    AES_init_ctx_iv(&ctx, key, iv);
    AES_CBC_encrypt_buffer(&ctx, plain_data, padded_len);

    // 5. 按CryptoJS格式组装:Salted__ + salt + 密文,再转Base64
    size_t total_len = 8 + 8 + padded_len; // "Salted__"占8字节
    uint8_t* output = (uint8_t*)malloc(total_len);
    memcpy(output, "Salted__", 8);
    memcpy(output + 8, salt, 8);
    memcpy(output + 16, plain_data, padded_len);

    char* cipher_b64 = base64_encode(output, total_len);

    free(plain_data);
    free(output);
    return cipher_b64;
}

解决你现有问题的关键总结

  1. 加密结果转可读字符串:CryptoJS默认输出Base64,你需要把C代码里的密文字节数组转成Base64编码,而不是直接输出原始字节。
  2. 长消息异常:tiny-AES-c的加密是按16字节块处理的,必须先对长消息做PKCS#7填充,确保长度是块大小的整数倍,否则会出现块不匹配导致的异常。

额外注意事项

  • 若使用AES-256,需要在tiny-AES-c的头文件中定义AES256宏。
  • Base64编码/解码要使用标准实现,确保和CryptoJS的Base64兼容。
  • 生产环境生成salt/IV时,一定要用安全的随机数源,不要用rand()。

内容的提问来源于stack exchange,提问作者Roni Gadot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:44:12