Golang 中的 AES 256 CTR 加密 使用 CryptoJS 在 Node JS 中解密,密钥是字符串(不是 WordArray)

发布于 2025-01-17 06:17:08 字数 5232 浏览 0 评论 0原文

我必须使用 golang 将数据发送到具有 Nodejs 加密的现有(旧)服务,该服务将使用 AES CTR 模式和 Crypto JS 库来解密数据。我编写了一些代码如下(密钥加密是这个问题中的随机密钥)。

Golang 加密:

func main() {
    rawKey := "46ca2a49c8074dadb99843f6b86c5975"
    data := "the quick brown fox jumps over the lazy dog"
    
    encryptedData := encrypt(rawKey, data);
    fmt.Println("encrypted data: ", encryptedData)
}

func encrypt(rawKey string, data string) string {
    key := []byte(rawKey)
    plainText := []byte(data)

    // Create new AES cipher block
    block, err := aes.NewCipher(key)
    if err != nil {
        return err.Error()
    }

    // The IV (Initialization Vector) need to be unique, but not secure.
    // Therefore, it's common to include it at the beginning of the cipher text.
    cipherText := make([]byte, aes.BlockSize+len(plainText))

    // Creates IV.
    iv := cipherText[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return err.Error()
    }

    // Encrypt.
    encryptStream := cipher.NewCTR(block, iv)
    encryptStream.XORKeyStream(cipherText[aes.BlockSize:], plainText)

    ivHex := hex.EncodeToString(iv)
    encryptedDataHex := hex.EncodeToString(cipherText)
    return encryptedDataHex[0:len(ivHex)] + ":" + encryptedDataHex[len(ivHex):]
}

GO Playground:https://play.golang.com/p/2I-BTyvUBKJ< /a> 结果如下

加密数据: c7fa927db8d5e95d7a56eaa74ccdbd6c:5c739daa892ca101c0d0f9b122721a1ccda0de473ce1a1d81b12fafa2e63965022c10036fc991d1650e900

并使用带有 CryptoJS 库的 NodeJS 成功解码以下代码

const decrypt = function (rawKey, encryptedData) {
    const split = encryptedData.split(':');
    if (split.length < 2) return '';

    const reb64 = CryptoJS.enc.Hex.parse(split[1]);
    const bytes = reb64.toString(CryptoJS.enc.Base64);
    
    const hexKey = rawKey.split("")
     .map(c => c.charCodeAt(0).toString(16).padStart(2, "0"))
     .join("");
    const hash = CryptoJS.AES.decrypt(bytes, CryptoJS.enc.Hex.parse(hexKey), {
        iv: CryptoJS.enc.Hex.parse(split[0]),
        mode: CryptoJS.mode.CTR,
        padding: CryptoJS.pad.NoPadding
    });
    const plain = hash.toString(CryptoJS.enc.Utf8);
    return plain;
}

const rawKey = '46ca2a49c8074dadb99843f6b86c5975';
const encryptedData = 'c7fa927db8d5e95d7a56eaa74ccdbd6c:5c739daa892ca101c0d0f9b122721a1ccda0de473ce1a1d81b12fafa2e63965022c10036fc991d1650e900';

const decyptedData = decrypt(rawKey, encryptedData);
document.write("decrypted data: ", decyptedData)

JS Fiddle: https://jsfiddle.net/cnq7g0vp/ 结果如下

解密数据:敏捷的棕色狐狸跳过了懒狗

但是nodejs中现有的(遗留)服务是解密代码直接使用字符串密钥作为密钥参数(不是WordArray)并且没有NoPadding参数,如下所示:

const decrypt = function (rawKey, encryptedData) {
    const split = encryptedData.split(':');
    if (split.length < 2) return '';

    const reb64 = CryptoJS.enc.Hex.parse(split[1]);
    const bytes = reb64.toString(CryptoJS.enc.Base64);
    
    const hash = CryptoJS.AES.decrypt(bytes, rawKey, {
        iv: split[0],
        mode: CryptoJS.mode.CTR
    });
    const plain = hash.toString(CryptoJS.enc.Utf8);
    return plain;
}

const rawKey = '46ca2a49c8074dadb99843f6b86c5975';
const encryptedData = '3a010df5e7985f2d8b0c00e3a096347f:6036327f61cf3050fddd6ea76325148c81e170a63b514b8818afbbb894c874c87cc4c865300c7b2d0e0fd8';

const decyptedData = decrypt(rawKey, encryptedData);
document.write("decrypted data: ", decyptedData);

JS Fiddle:https://jsfiddle.net/pyntruLj/ 并且失败,没有结果(空字符串)如下:

解密数据:

这是我在 golang 加密代码中编写的内容,以匹配 nodejs 解密代码,通过使用 BytesToKeyAES256CBCMD5 来获取密钥和基于我上一个问题

func main() {
    rawKey := "46ca2a49c8074dadb99843f6b86c5975"
    data := "the quick brown fox jumps over the lazy dog"
    
    encryptedData := encrypt(rawKey, data);
    fmt.Println("encrypted data: ", encryptedData)
}

func encrypt(rawKey string, data string) string {
    salt := []byte("ABCDEFGH") // hardcoded at the moment

    // Gets key and IV from raw key.
    key, iv := evp.BytesToKeyAES256CBCMD5([]byte(salt), []byte(rawKey))

    plainText := []byte(data)

    // Create new AES cipher block
    block, err := aes.NewCipher(key)
    if err != nil {
        return err.Error()
    }

    cipherText := make([]byte, len(plainText))

    // Encrypt.
    encryptStream := cipher.NewCTR(block, iv)
    encryptStream.XORKeyStream(cipherText, plainText)

    ivHex := hex.EncodeToString(iv)
    encryptedDataHex := hex.EncodeToString(cipherText)
    return ivHex + ":" + encryptedDataHex
}

GO Playground 的提示的 iv: https://play.golang.com/p/luyTVhvtyOn 和输出如下:

加密数据: 3a010df5e7985f2d8b0c00e3a096347f:6036327f61cf3050fddd6ea76325148c81e170a63b514b8818afbbb894c874c87cc4c865300c7b2d0e0fd8

任何人都可以帮忙吗我的 golang 代码有什么问题吗?能够通过直接在 CryptoJS 解密代码上使用字符串密钥来解密(而且我也无法更改 nodejs 实现,因为它是遗留代码)?

I have to send data by using golang to existing (legacy) service with nodejs encryption that will decrypt data using AES CTR mode with Crypto JS libray. I have made some code as follow (the key encryption is a random key in this question).

Golang Encryption:

func main() {
    rawKey := "46ca2a49c8074dadb99843f6b86c5975"
    data := "the quick brown fox jumps over the lazy dog"
    
    encryptedData := encrypt(rawKey, data);
    fmt.Println("encrypted data: ", encryptedData)
}

func encrypt(rawKey string, data string) string {
    key := []byte(rawKey)
    plainText := []byte(data)

    // Create new AES cipher block
    block, err := aes.NewCipher(key)
    if err != nil {
        return err.Error()
    }

    // The IV (Initialization Vector) need to be unique, but not secure.
    // Therefore, it's common to include it at the beginning of the cipher text.
    cipherText := make([]byte, aes.BlockSize+len(plainText))

    // Creates IV.
    iv := cipherText[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return err.Error()
    }

    // Encrypt.
    encryptStream := cipher.NewCTR(block, iv)
    encryptStream.XORKeyStream(cipherText[aes.BlockSize:], plainText)

    ivHex := hex.EncodeToString(iv)
    encryptedDataHex := hex.EncodeToString(cipherText)
    return encryptedDataHex[0:len(ivHex)] + ":" + encryptedDataHex[len(ivHex):]
}

GO Playground: https://play.golang.com/p/2I-BTyvUBKJ
and the result is as follows

encrypted data:
c7fa927db8d5e95d7a56eaa74ccdbd6c:5c739daa892ca101c0d0f9b122721a1ccda0de473ce1a1d81b12fafa2e63965022c10036fc991d1650e900

and successfully decode with nodejs with CryptoJS library with following code

const decrypt = function (rawKey, encryptedData) {
    const split = encryptedData.split(':');
    if (split.length < 2) return '';

    const reb64 = CryptoJS.enc.Hex.parse(split[1]);
    const bytes = reb64.toString(CryptoJS.enc.Base64);
    
    const hexKey = rawKey.split("")
     .map(c => c.charCodeAt(0).toString(16).padStart(2, "0"))
     .join("");
    const hash = CryptoJS.AES.decrypt(bytes, CryptoJS.enc.Hex.parse(hexKey), {
        iv: CryptoJS.enc.Hex.parse(split[0]),
        mode: CryptoJS.mode.CTR,
        padding: CryptoJS.pad.NoPadding
    });
    const plain = hash.toString(CryptoJS.enc.Utf8);
    return plain;
}

const rawKey = '46ca2a49c8074dadb99843f6b86c5975';
const encryptedData = 'c7fa927db8d5e95d7a56eaa74ccdbd6c:5c739daa892ca101c0d0f9b122721a1ccda0de473ce1a1d81b12fafa2e63965022c10036fc991d1650e900';

const decyptedData = decrypt(rawKey, encryptedData);
document.write("decrypted data: ", decyptedData)

JS Fiddle: https://jsfiddle.net/cnq7g0vp/ and the result is as follows

decrypted data: the quick brown fox jumps over the lazy dog

But the existing (legacy) service in nodejs is decryption code is using the string key directly as key parameter (not WordArray) and without NoPadding parameter as follows:

const decrypt = function (rawKey, encryptedData) {
    const split = encryptedData.split(':');
    if (split.length < 2) return '';

    const reb64 = CryptoJS.enc.Hex.parse(split[1]);
    const bytes = reb64.toString(CryptoJS.enc.Base64);
    
    const hash = CryptoJS.AES.decrypt(bytes, rawKey, {
        iv: split[0],
        mode: CryptoJS.mode.CTR
    });
    const plain = hash.toString(CryptoJS.enc.Utf8);
    return plain;
}

const rawKey = '46ca2a49c8074dadb99843f6b86c5975';
const encryptedData = '3a010df5e7985f2d8b0c00e3a096347f:6036327f61cf3050fddd6ea76325148c81e170a63b514b8818afbbb894c874c87cc4c865300c7b2d0e0fd8';

const decyptedData = decrypt(rawKey, encryptedData);
document.write("decrypted data: ", decyptedData);

JS Fiddle: https://jsfiddle.net/pyntruLj/ and it failed with no result (empty string) as follows:

decrypted data:

and here is what I write in golang encryption code to match the nodejs decryption code by using BytesToKeyAES256CBCMD5 to get the key and the iv based on the hint on my previous question

func main() {
    rawKey := "46ca2a49c8074dadb99843f6b86c5975"
    data := "the quick brown fox jumps over the lazy dog"
    
    encryptedData := encrypt(rawKey, data);
    fmt.Println("encrypted data: ", encryptedData)
}

func encrypt(rawKey string, data string) string {
    salt := []byte("ABCDEFGH") // hardcoded at the moment

    // Gets key and IV from raw key.
    key, iv := evp.BytesToKeyAES256CBCMD5([]byte(salt), []byte(rawKey))

    plainText := []byte(data)

    // Create new AES cipher block
    block, err := aes.NewCipher(key)
    if err != nil {
        return err.Error()
    }

    cipherText := make([]byte, len(plainText))

    // Encrypt.
    encryptStream := cipher.NewCTR(block, iv)
    encryptStream.XORKeyStream(cipherText, plainText)

    ivHex := hex.EncodeToString(iv)
    encryptedDataHex := hex.EncodeToString(cipherText)
    return ivHex + ":" + encryptedDataHex
}

GO Playground: https://play.golang.com/p/luyTVhvtyOn and the output is as follows:

encrypted data:
3a010df5e7985f2d8b0c00e3a096347f:6036327f61cf3050fddd6ea76325148c81e170a63b514b8818afbbb894c874c87cc4c865300c7b2d0e0fd8

Can anyone help me what's wrong with my golang code to be able to be decrypted by using string key directly on CryptoJS decrypt code (also I cant change the nodejs implemention since it's a legacy code)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

浅语花开 2025-01-24 06:17:08

代码基本上没问题,只有一些小问题:

  1. CryptoJS 不会自动禁用 CTR 等流密码模式的默认 PKCS7 填充。因此,Go代码中必须应用PKCS7填充。
  2. 由于CryptoJS代码使用内部PBKDF,因此解密需要OpenSSL格式(即Salted__的ASCII编码,后跟8字节salt和实际密文),十六进制编码。因此 Go 代码必须相应地格式化和编码数据。
  3. CryptoJS 在使用内部 PBKDF 时派生密钥和 IV。因此,在 CryptoJS 代码中,指定的 IV 在解密过程中被忽略。因此,在 Go 代码中,可以指定任何 IV。

以下代码对应于您的代码,并通过 PKCS#7 填充和结果的格式/编码进行扩展(请考虑代码中的注释)。请注意,在您的代码中,为了简单起见,使用了硬编码盐,但实际上出于安全原因必须应用随机生成的盐:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/hex"
    "fmt"

    evp "github.com/walkert/go-evp"
    "github.com/zenazn/pkcs7pad"
)

func main() {
    rawKey := "46ca2a49c8074dadb99843f6b86c5975"
    data := pkcs7pad.Pad([]byte("the quick brown fox jumps over the lazy dog"), 16) // 1. Pad the plaintext with PKCS#7
    fmt.Println("padded data: ", hex.EncodeToString(data))

    encryptedData := encrypt(rawKey, data)
    fmt.Println("encrypted data: ", encryptedData)
}

func encrypt(rawKey string, plainText []byte) string {
    salt := []byte("ABCDEFGH") // hardcoded at the moment

    // Gets key and IV from raw key.
    key, iv := evp.BytesToKeyAES256CBCMD5([]byte(salt), []byte(rawKey))

    // Create new AES cipher block
    block, err := aes.NewCipher(key)
    if err != nil {
        return err.Error()
    }

    cipherText := make([]byte, len(plainText))

    // Encrypt.
    encryptStream := cipher.NewCTR(block, iv)
    encryptStream.XORKeyStream(cipherText, plainText)

    ivHex := hex.EncodeToString(iv)
    encryptedDataHex := hex.EncodeToString([]byte("Salted__")) + hex.EncodeToString(salt) + hex.EncodeToString(cipherText) // 2. Apply the OpenSSL format, hex encode the result
    return ivHex + ":" + encryptedDataHex // 3. Any value for ivHex can be used here, e.g. "00000000000000000000000000000000"
}

输出为:

padded data:  74686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f670505050505
encrypted data:  3a010df5e7985f2d8b0c00e3a096347f:53616c7465645f5f41424344454647486036327f61cf3050fddd6ea76325148c81e170a63b514b8818afbbb894c874c87cc4c865300c7b2d0e0fd82826f3d3c5

该密文可以使用旧代码解密:

const decrypt = function (rawKey, encryptedData) {
    const split = encryptedData.split(':');
    if (split.length < 2) return '';

    const reb64 = CryptoJS.enc.Hex.parse(split[1]);
    const bytes = reb64.toString(CryptoJS.enc.Base64);
    
    const hash = CryptoJS.AES.decrypt(bytes, rawKey, {
        iv: split[0], // This is ignored if the internal PBKDF is used
        mode: CryptoJS.mode.CTR
    });
    const plain = hash.toString(CryptoJS.enc.Utf8);
    return plain;
}

const rawKey = '46ca2a49c8074dadb99843f6b86c5975';
const encryptedData = '3a010df5e7985f2d8b0c00e3a096347f:53616c7465645f5f41424344454647486036327f61cf3050fddd6ea76325148c81e170a63b514b8818afbbb894c874c87cc4c865300c7b2d0e0fd82826f3d3c5';

const decyptedData = decrypt(rawKey, encryptedData);
document.getElementById("pt").innerHTML = "decrypted data: " + decyptedData;
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<p style="font-family:'Courier New', monospace;" id="pt"></p>

The code is mostly OK, there are just a few minor issues:

  1. CryptoJS does not automatically disable the default PKCS7 padding for stream cipher modes like CTR. Therefore, PKCS7 padding must be applied in the Go code.
  2. Since the CryptoJS code uses the internal PBKDF, the OpenSSL format is required for decryption (i.e. the ASCII encoding of Salted__ followed by the 8 bytes salt and the actual ciphertext), hex encoded. So the Go code must format and encode the data accordingly.
  3. CryptoJS derives key and IV when using the internal PBKDF. Therefore, in the CryptoJS code, the specified IV is ignored during decryption. Hence, in the Go code, any IV can be specified.

The following code corresponds to your code, extended by the PKCS#7 padding and the formatting/encoding of the result (consider the comments in the code). Note that, as in your code, a hard-coded salt is used for simplicity, but in practice a randomly generated salt must be applied for security reasons:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/hex"
    "fmt"

    evp "github.com/walkert/go-evp"
    "github.com/zenazn/pkcs7pad"
)

func main() {
    rawKey := "46ca2a49c8074dadb99843f6b86c5975"
    data := pkcs7pad.Pad([]byte("the quick brown fox jumps over the lazy dog"), 16) // 1. Pad the plaintext with PKCS#7
    fmt.Println("padded data: ", hex.EncodeToString(data))

    encryptedData := encrypt(rawKey, data)
    fmt.Println("encrypted data: ", encryptedData)
}

func encrypt(rawKey string, plainText []byte) string {
    salt := []byte("ABCDEFGH") // hardcoded at the moment

    // Gets key and IV from raw key.
    key, iv := evp.BytesToKeyAES256CBCMD5([]byte(salt), []byte(rawKey))

    // Create new AES cipher block
    block, err := aes.NewCipher(key)
    if err != nil {
        return err.Error()
    }

    cipherText := make([]byte, len(plainText))

    // Encrypt.
    encryptStream := cipher.NewCTR(block, iv)
    encryptStream.XORKeyStream(cipherText, plainText)

    ivHex := hex.EncodeToString(iv)
    encryptedDataHex := hex.EncodeToString([]byte("Salted__")) + hex.EncodeToString(salt) + hex.EncodeToString(cipherText) // 2. Apply the OpenSSL format, hex encode the result
    return ivHex + ":" + encryptedDataHex // 3. Any value for ivHex can be used here, e.g. "00000000000000000000000000000000"
}

The output is:

padded data:  74686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f670505050505
encrypted data:  3a010df5e7985f2d8b0c00e3a096347f:53616c7465645f5f41424344454647486036327f61cf3050fddd6ea76325148c81e170a63b514b8818afbbb894c874c87cc4c865300c7b2d0e0fd82826f3d3c5

This ciphertext can be decrypted with the legacy code:

const decrypt = function (rawKey, encryptedData) {
    const split = encryptedData.split(':');
    if (split.length < 2) return '';

    const reb64 = CryptoJS.enc.Hex.parse(split[1]);
    const bytes = reb64.toString(CryptoJS.enc.Base64);
    
    const hash = CryptoJS.AES.decrypt(bytes, rawKey, {
        iv: split[0], // This is ignored if the internal PBKDF is used
        mode: CryptoJS.mode.CTR
    });
    const plain = hash.toString(CryptoJS.enc.Utf8);
    return plain;
}

const rawKey = '46ca2a49c8074dadb99843f6b86c5975';
const encryptedData = '3a010df5e7985f2d8b0c00e3a096347f:53616c7465645f5f41424344454647486036327f61cf3050fddd6ea76325148c81e170a63b514b8818afbbb894c874c87cc4c865300c7b2d0e0fd82826f3d3c5';

const decyptedData = decrypt(rawKey, encryptedData);
document.getElementById("pt").innerHTML = "decrypted data: " + decyptedData;
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<p style="font-family:'Courier New', monospace;" id="pt"></p>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文