C - tiny-aes-c 和 Javascript CryptoJS 互操作性

C - tiny-aes-c and Javascript CryptoJS interoperability

使用tiny-aes-c。考虑以下 C 代码:

int main(int argc, char const *argv[])
{
    uint8_t key[6] = { 's','e','c','r','e','t' };
    uint8_t iv[16]  = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };

    uint8_t in[6]  = { 'm','e','s','a','g','e'};

    uint8_t out[6] = {0x17, 0x8d, 0xc3, 0xa1, 0x56, 0x34};
    struct AES_ctx ctx;

    AES_init_ctx_iv(&ctx, key, iv);
    AES_CTR_xcrypt_buffer(&ctx, in, 6);    

    printf("idx\t encrypted\t expected");
    for(int i=0 ; i<6 ; i++){
        printf("\n[%i]\t %.2x\t\t %.2x" , i , in[i], out[i]);
    }

    return 0;
}

代码加密消息并将结果与​​预期输出进行比较。代码运行正常,输出如下:

idx      encrypted       expected
[0]      17              17
[1]      8d              8d
[2]      c3              c3
[3]      a1              a1
[4]      56              56
[5]      34              34

我有另一个服务,一个使用 CryptoJS.
的 NodeJS 服务器 我的问题是:如何转换 C 结果 ({0x17, 0x8d, 0xc3, 0xa1, 0x56, 0x34}) 以便它匹配 CryptoJS 可以处理的内容?


编辑: 详细说明一下。出于本次讨论的目的,C 结果通过网络传输,因此应将其转换为 String。据我所知,CryptoJS 使用 base64 作为其 AES 方法的输入,解密为稍后可以转换为纯文本的字节:

var bytes  = CryptoJS.AES.decrypt(BASE_64_STRING, SECRET);
var plaintext = bytes.toString(CryptoJS.enc.Utf8);

使用 CryptoJS 对同一消息 + secret 的加密结果是:U2FsdGVkX1/TAYUIFnXzC76zb+sd8ol+2DfKCkwITfY= (JS Fiddle) 并且在每个 运行.

上发生变化

更新二:
感谢@MDTech.us_MAN 的回答,我对 JS 和 C 代码都做了一些更改,但我仍然缺少拼图。

C:

int main(int argc, char const *argv[])
{
    uint8_t key[16] = { 's','e','c','r','e','t','s','e','c','r','e','t','1','2','3','4' };
    uint8_t iv[16]  = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };
    uint8_t in[7]  = { 'm','e','s','s','a','g','e'};

    struct AES_ctx ctx;

    AES_init_ctx_iv(&ctx, key, iv);
    AES_CTR_xcrypt_buffer(&ctx, in, 7);

    printf("Encrypted: ");
    for(int i=0 ; i<7 ; i++){
        printf("%.2x" , in[i]);
    }

    return 0;
}

加密的HEX字符串C输出:cba9d5bc84113c,转换成Base64结果为:y6nVvIQRPA==

在 JS 方面,我明确地使用没有填充的 CTR 模式,并像这样启动(希望)相同的 iv:

const CryptoJS = require("crypto-js");
let iv = CryptoJS.enc.Hex.parse('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); // 16 Bytes (same as the C code)
let message = CryptoJS.AES.decrypt("y6nVvIQRPA==", "secretsecret1234", { iv: iv, mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.NoPadding });
console.log(message.toString());

解密结果:a47172dfe151c7,不是预期结果"message"。

我错过了什么?

你在做两件事

  1. 加密
  2. 转换为 base64

接收后,必须按照发送前应用的相反顺序执行这两个操作的相反操作

  1. 从 base64 转换
  2. 解密

你也可以交换步骤的顺序,但是必须在发送端和接收端交换顺序。

还要确保双方的密钥格式相同。

您应该更仔细地阅读 CryptoJS 文档。默认情况下,它使用 CBC 模式进行加密,因此您应该更改您的微型 AES 实现以使用它。

CryptoJS supports the following modes:

  • CBC (the default)

另请注意,CryptoJS 默认启用填充,而 tiny-AES 根本没有。所以messages必须是16的倍数。(或者你可以手动使用自己的padding实现)

No padding is provided so for CBC and ECB all buffers should be mutiples of 16 bytes. For padding PKCS7 is recommendable.

然后,请注意 CryptoJS 通过密钥大小自动选择 AES 变体:

CryptoJS supports AES-128, AES-192, and AES-256. It will pick the variant by the size of the key you pass in. If you use a passphrase, then it will generate a 256-bit key.

因此,您必须在微型 AES 代码中考虑所有这些因素。

感谢@MDTech.us_MAN ,我找到了一个解决方案,在修复模式和填充之后,不同之处在于我在 JS 端解析秘密的方式。在以下示例中,机密被解析为 HEX 字符串:

const CryptoJS = require("crypto-js");
let iv = CryptoJS.enc.Hex.parse('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); // 16 Bytes
let secret = CryptoJS.enc.Hex.parse('73656372657473656372657431323334'); // 16 Bytes == "secretsecret1234"
let message = CryptoJS.AES.decrypt("y6nVvIQRPA==", secret, { iv: iv, mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.NoPadding });
console.log(message.toString(CryptoJS.enc.Utf8)); // -> message