Nodejs AES-256-GCM 在客户端通过浏览器 webcrypto api 加密和解密
Posted
技术标签:
【中文标题】Nodejs AES-256-GCM 在客户端通过浏览器 webcrypto api 加密和解密【英文标题】:Nodejs AES-256-GCM encryption and decryption in client by browser webcrypto api 【发布时间】:2021-11-07 13:45:49 【问题描述】:我在客户端生成一对公钥/私钥并将publicKey
发送到服务器,后端将在其一侧生成sharedKey
并回复我publicKey
,这有助于我生成sharedKey
在客户端上也用于加密/解密。所以我在 Nodejs 上通过 AES-256-GCM 加密了一条消息,并在 Client 上解密了这条消息。
后端:
export function encrypt(sharedKey: string, message: string)
const firstIv = getRandomIV();
const cipher = crypto.createCipheriv(
'aes-256-gcm',
Buffer.from(sharedKey, 'base64'),
firstIv
);
const encrypted = cipher.update(message, 'utf8');
return Buffer.from(encrypted + cipher.final()).toString('base64');
function getRandomIV()
return crypto.randomBytes(12);
客户端:
async function decrypt(encryptedData: Uint8Array)
const aesKey = await generateAesKey();
const nonce = encryptedData.subarray(0, SERVER_ENCRYPTION_IV_LENGTH);
const data = encryptedData.subarray(SERVER_ENCRYPTION_IV_LENGTH);
const decrypted = await crypto.subtle.decrypt(
name: 'AES-GCM',
iv: nonce,
,
aesKey,
data
);
return
decrypted: new Uint8Array(decrypted),
decryptedString: new TextDecoder().decode(decrypted),
;
async function generateAesKey()
const publicKey = await getServerPublicKey();
const privateKey = await getPrivateKey();
const sharedSecret = await crypto.subtle.deriveBits(
name: 'ECDH',
public: publicKey!,
,
privateKey,
256
);
const aesSecret = await crypto.subtle.digest('SHA-256', sharedSecret);
return crypto.subtle.importKey('raw', aesSecret, 'AES-GCM', true, [
'encrypt',
'decrypt',
]);
现在,我无法在客户端解密服务器加密响应并遇到DOMException
错误,我不知道为什么?
【问题讨论】:
请创建一个minimal reproducible example。您甚至没有证明您尝试过 AES 密钥是否匹配(例如,通过比较 AES 密钥的十六进制字符串)。 你是对的,为了你的信息,当我在客户端加密消息并在后端解密它时,它可以工作,并且后端/客户端上的sharedKey
是真的并且没有问题.正如我在问题中所写,主要问题是后端的加密阶段和客户端的解密。
您在 NodeJS 代码中使用的不是 GCM,而是 CTR。你应该相应地改变它。此外,您应该发布复制所需的所有功能,例如getRandomValue()
、concatUint8Array()
、generateAesKey()
等
对不起,我的问题,我更新了我的问题
generateAesKey
函数生成的sharedKey
与后端生成的相同,没有问题。
【参考方案1】:
GCM 使用由 NodeJS/Crypto 单独处理的身份验证标签,而 WebCrypto 会自动将其与密文连接。
因此,在 NodeJS 代码中,标签必须明确确定并附加到密文中。当前的 NodeJS 代码中缺少这一点,可以按如下方式考虑。注意标记与cipher.getAuthTag()
及其连接的确定:
var crypto = require('crypto');
function encrypt(key, plaintext)
var nonce = getRandomIV();
var cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
var nonceCiphertextTag = Buffer.concat([
nonce,
cipher.update(plaintext),
cipher.final(),
cipher.getAuthTag() // Fix: Get tag with cipher.getAuthTag() and concatenate: nonce|ciphertext|tag
]);
return nonceCiphertextTag.toString('base64');
function getRandomIV()
return crypto.randomBytes(12);
var message = Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8');
var sharedKey = Buffer.from('MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE=', 'base64');
var ciphertext = encrypt(sharedKey, message);
console.log(ciphertext); // wRE5KM6FG81QSMNvG0xR+iaIeF77cyyeBceGS5NkcYaD17K9nL0/helnqRBOkD9pLVoWM/nRAcaKg/YdvfNJcO1Zn/7ZM0k=
可能的输出是
wRE5KM6FG81QSMNvG0xR+iaIeF77cyyeBceGS5NkcYaD17K9nL0/helnqRBOkD9pLVoWM/nRAcaKg/YdvfNJcO1Zn/7ZM0k=
以下 WebCrypto 端的解密代码本质上是基于您的代码(没有从共享密钥派生密钥,这与当前问题无关):
(async () =>
var nonceCiphertextTag = base64ToArrayBuffer('wRE5KM6FG81QSMNvG0xR+iaIeF77cyyeBceGS5NkcYaD17K9nL0/helnqRBOkD9pLVoWM/nRAcaKg/YdvfNJcO1Zn/7ZM0k=');
var nonceCiphertextTag = new Uint8Array(nonceCiphertextTag);
var decrypted = await decrypt(nonceCiphertextTag);
console.log(decrypted); // The quick brown fox jumps over the lazy dog
)();
async function decrypt(nonceCiphertextTag)
const SERVER_ENCRYPTION_IV_LENGTH = 12; // For GCM a nonce length of 12 bytes is recommended!
var nonce = nonceCiphertextTag.subarray(0, SERVER_ENCRYPTION_IV_LENGTH);
var ciphertextTag = nonceCiphertextTag.subarray(SERVER_ENCRYPTION_IV_LENGTH);
var aesKey = base64ToArrayBuffer('MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE=');
aesKey = await window.crypto.subtle.importKey('raw', aesKey, 'AES-GCM', true, ['encrypt', 'decrypt']);
var decrypted = await crypto.subtle.decrypt(name: 'AES-GCM', iv: nonce, aesKey, ciphertextTag);
return new TextDecoder().decode(decrypted);
// Helper
// https://***.com/a/21797381/9014097
function base64ToArrayBuffer(base64)
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++)
bytes[i] = binary_string.charCodeAt(i);
return bytes.buffer;
成功解密NodeJS端的密文:
The quick brown fox jumps over the lazy dog
【讨论】:
您好,我在这里与您建立了一个聊天室。你看见了吗?你能帮帮我吗? @AliTorki - 我没有足够的时间来完成这项任务,老实说,我不想提交。对不起。我建议您在 SO 上发布您的问题。通过这种方式,您将接触到一个大型社区,并且成功回答的机会将会增加。但谁知道呢,也许我会回答你关于 SO 的一两个问题。祝你好运。以上是关于Nodejs AES-256-GCM 在客户端通过浏览器 webcrypto api 加密和解密的主要内容,如果未能解决你的问题,请参考以下文章
golang 在golang中使用AES256 GCM加密文本
golang 在golang中使用AES256 GCM解密文本
跨平台AES 256 GCM Javascript和Elixir
有没有办法过滤 aes 256 gcm 加密数据库中的数据?