Node.js 中的 AES 256 GCM 加密和 Golang 中的解密

问题描述 投票:0回答:1

在尝试将遗留代码从 Node.js 迁移到 Golang 时,我正在尝试 AES 加密和解密。以下是问题陈述。

  1. 我们有一个从 Node.js 中的 AES 256 GCM 加密逻辑获得的令牌,目前几乎在所有地方都在使用
  2. 用 Go 编写的新服务将需要使用此令牌并使用 AES 256 GCM 解密提取数据 - 这不起作用(代码片段中列出了错误)

我尝试理解并复制 Golang 中解密的要求(随机数/初始化向量)。以下是我正在使用的代码片段。代码有问题吗?任何帮助表示赞赏。蒂亚!

加密代码:

const crypto = require('crypto');

const createKey = secret => secret.padEnd(32, secret);
const randBytes = crypto.randomBytes(16);
const createIv = () => {
  let randStr = Buffer.from("1234567890123456").toString('base64');
  return randStr.slice(0,16);
}

const b64urlSafe = str => str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
const b64urlUnsafe = str => {
  let decoded = str;
  if (decoded.length % 4 !== 0) {
    decoded += ('===').slice(0, 4 - (decoded.length % 4));
  }
  return decoded.replace(/-/g, '+').replace(/_/g, '/');
};

const defaultSecret = 'goodthingstaketimesometime123456';
const defaultKey = createKey(defaultSecret);

/**
 * Creates a cipher using AES-256-GCM
 *
 * @param {string} text the plaintext
 * @param {string} secret the secret (optional)
 * @returns a ciphertext (including an auth tag, separated by an underscore)
 */
const createCipher = function (text, secret = null) {
  const iv = createIv();
  const key = secret ? createKey(secret) : defaultKey;
  let cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  let crypted = cipher.update(text, 'utf8', 'base64');
  crypted += cipher.final('base64');
  cipher.getAuthTag()
  return `${b64urlSafe(crypted)}.${b64urlSafe(cipher.getAuthTag().toString('base64'))}.${b64urlSafe(iv)}`;
};

解密代码:

func decryptTokenFromNode() {
    fmt.Println("======decryptTokenFromNode function ======")
    token := "KZhf9KXZUKmH2jfhYIc68M4x/60gzx6+5aYujPI8ZYc4xaO16mVdtpOXKRjP+cPAk9ftNzFOrngll4sqK0jPYDqJkVdBv+9Kw==="
    iv := "MTIzNDU2Nzg5MDEy"
    ciphertext, _ := base64.StdEncoding.DecodeString(token)
    nonce, _ := base64.StdEncoding.DecodeString(iv)

    key := []byte("goodthingstaketimesometime123456")

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err.Error())
    }

    aesgcm, err := cipher.NewGCM(block)
    if err != nil {
        panic(err.Error())
    }

    fmt.Println("nonce length:", len(nonce))

    plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        panic(err.Error())
    }

    fmt.Printf("%s\n", plaintext)
}

错误:

panic: cipher: message authentication failed

goroutine 1 [running]:
main.decryptTokenFromNode()
    /Users/santhosh/Quizizz/auth-service/decrypt.go:120 +0x208
main.main()
    /Users/santhosh/Quizizz/auth-service/decrypt.go:128 +0x20
exit status 2
node.js go aes aes-gcm
1个回答
0
投票

您发布的密文无法使用 NodeJS 代码生成,因为 NodeJS 代码使用 Base64url(并且发布的密文应用标准 Base64)。另外,三重

=
无效。
如果密文在没有认证的情况下被解密,明文可以被识别为:

encrypting this string to verify seal and open in golang

如果用 NodeJS 代码重新加密该明文,结果就是密文:

KZhf9KXZUKmH2jfhYIc68M4x_60gzx6-5aYujPI8ZYc4xaO16mVdtpOXKRjP-cPAk9ftNzFOrng.ll4sqK0jPYDqJkVdBv-9Kw.MTIzNDU2Nzg5MDEy

其前两部分基本上与您发布的密文相同(除了

.
分隔符、Base64的两个不同字符(
+
/
)和Base64url(
-
_
) ) 字母,以及不正确的填充。也许密文中的差异是复制/粘贴错误。


为了能够解密密文,必须将这三个部分分开。第一部分对应于 Base64url 编码的密文,第二部分对应于 Base64url 编码的身份验证标签,第三部分是随机数(与前两部分相反,Base64url 编码值用作随机数,长度为 16 个字节,并且不是原始值)。

分离后,密文和标签将进行Base64url解码并按顺序连接

ciphertext|tag

使用此数据,将使用 AES-GCM 进行解密 - Go 代码:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "fmt"
    "strings"
)

func main() {
    decryptTokenFromNode()
}

func decryptTokenFromNode() {
    fmt.Println("======decryptTokenFromNode function ======")

    // Separate Base64url encoded ciphertext, tag and nonce
    token := "KZhf9KXZUKmH2jfhYIc68M4x_60gzx6-5aYujPI8ZYc4xaO16mVdtpOXKRjP-cPAk9ftNzFOrng.ll4sqK0jPYDqJkVdBv-9Kw.MTIzNDU2Nzg5MDEy"
    data := strings.Split(token, ".")
    ciphertext, _ := base64.RawURLEncoding.DecodeString(data[0])
    tag, _ := base64.RawURLEncoding.DecodeString(data[1])
    nonce := ([]byte)(data[2])

    // Concatenate raw cipheretext and tag
    ciphertext = append(ciphertext, tag...)

    key := []byte("goodthingstaketimesometime123456")
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err.Error())
    }

    // Import 16 bytes nonce
    aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
    if err != nil {
        panic(err.Error())
    }

    plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        panic(err.Error())
    }

    fmt.Printf("%s\n", plaintext)
}

有了这段代码,就可以成功解密密文了。


安全:

NodeJS 代码中引人注目的是随机数的生成。首先,使用16字节随机数,这与GCM推荐的12字节长度不同。因此,必须使用

NewGCMWithNonceSize()
。关于长度,出于兼容性和效率原因,建议使用 12 字节随机数。
其次,使用了static随机数,这对于 GCM 来说是一个严重的漏洞。正确的方法是为每个加密创建一个随机随机数(最合理的是 12 字节随机数),它仅用于串联(与其他部分类似)进行 Base64url 编码。也许这甚至是有意的,它只是您的示例代码中的一个错误。

第二个漏洞是使用字符串作为密钥。键应该是随机字节序列而不是字符串。如果要使用字符串作为密钥材料,则应应用密钥派生函数(至少 PBKDF2 或更好的更现代的 Argon2)。

© www.soinside.com 2019 - 2024. All rights reserved.