英文:
How to decrypt message that's encrypted in AES-GCM-256?
问题
我需要在Node.js / JavaScript服务器和用Rust编写的客户端之间建立客户端-服务器连接。消息必须使用AES-GCM-256加密。在Rust中(版本1.67.0),我使用[aes-gcm crate][1]。
以下是Rust代码引发的错误:`aead::Error`。这里的`cipher.decrypt()`有什么问题?
我确信Node.js实现是正确的。我认为Rust代码中的`key`变量应该与Node.js中的相同的密钥(`12341234123412341234123412341234`)。
**Node.js /发送方实现**
```javascript
const crypto = require('crypto')
const aes256gcm = (key) => {
const encrypt = (str) => {
const iv = crypto.randomBytes(12);
const ivString = iv.toString("base64")
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let enc1 = cipher.update(str, 'utf8');
let enc2 = cipher.final();
let result = Buffer.concat([enc1, enc2, iv, cipher.getAuthTag()]).toString("base64");
return { result, iv: ivString }
};
const decrypt = (enc) => {
enc = Buffer.from(enc, "base64");
const iv = enc.slice(enc.length - 28, enc.length - 16);
const tag = enc.slice(enc.length - 16);
enc = enc.slice(0, enc.length - 28);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
let str = decipher.update(enc, null, 'utf8');
str += decipher.final('utf8');
return str;
};
return {
encrypt,
decrypt,
};
};
const cipher = aes256gcm("12341234123412341234123412341234"); // 仅作测试,密钥必须是32位
const ct = cipher.encrypt('Hello world!!!');
console.log("加密消息:", ct.result)
console.log("iv / 随机数:", ct.iv)
const pt = cipher.decrypt(ct.result);
console.log("解密消息:", pt); // 这个工作得很好!
Node.js加密结果
加密消息: Zf5aB0bbVGGX3k9Yt6x+9daxCGZO0MmwYW8VUsOY4j3gNYXP47hvfGgd
iv / 随机数: fvXWsQhmTtDJsGFv
解密消息: Hello world!!!
解密部分(Rust)/接收方实现
use aes_gcm::{
aead::{Aead, KeyInit, OsRng},
Aes256Gcm, Nonce,
};
use base64::{engine::general_purpose, Engine as _};
fn main() {
// 来自发送方的主密钥
let master = "12341234123412341234123412341234".as_bytes();
let cipher = Aes256Gcm::new_from_slice(master).unwrap();
// 来自发送方的随机数 / 随机向量
let nonce_str_base64 = "fvXWsQhmTtDJsGFv";
let nonce_str: Vec<u8> = general_purpose::STANDARD.decode(nonce_str_base64).unwrap();
let nonce = Nonce::from_slice(&nonce_str); // 96位;每条消息唯一
// 来自发送方的加密文本
let ciphertext_base64 = "Zf5aB0bbVGGX3k9Yt6x+9daxCGZO0MmwYW8VUsOY4j3gNYXP47hvfGgd";
let ciphertext = general_purpose::STANDARD.decode(ciphertext_base64).unwrap();
// 在这里得到aead::Error
match cipher.decrypt(nonce, ciphertext.as_slice()) {
Ok(decrypted) => {
let result = String::from_utf8(decrypted).unwrap();
println!("结果:{}", result);
}
Err(err) => print!("{}", err), // 打印错误:aead::Error
};
}
<details>
<summary>英文:</summary>
I need to make a client-server connection from node.js/javascript server to a client written in Rust. The message had to be encrypted with AES-GCM-256. While in Rust (version 1.67.0) I use [aes-gcm crate][1].
The Rust code below throws an error: `aead::Error`. What's wrong with the `cipher.decrypt()` here ?
I'm sure that the node.js implementation is correct. I think that the `key` variable in Rust code should be the same key from the node.js (`12341234123412341234123412341234`)
**Implementation in Node.js / sender side**
const crypto = require('crypto')
const aes256gcm = (key) => {
const encrypt = (str) => {
const iv = new crypto.randomBytes(12);
const ivString = iv.toString("base64")
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let enc1 = cipher.update(str, 'utf8');
let enc2 = cipher.final();
let result = Buffer.concat([enc1, enc2, iv, cipher.getAuthTag()]).toString("base64");
return { result, iv: ivString }
};
const decrypt = (enc) => {
enc = Buffer.from(enc, "base64");
const iv = enc.slice(enc.length - 28, enc.length - 16);
const tag = enc.slice(enc.length - 16);
enc = enc.slice(0, enc.length - 28);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
let str = decipher.update(enc, null, 'utf8');
str += decipher.final('utf8');
return str;
};
return {
encrypt,
decrypt,
};
};
const cipher = aes256gcm("12341234123412341234123412341234"); // just a test key must be 32
const ct = cipher.encrypt('Hello world!!!');
console.log("encrypted message: ", ct.result)
console.log("iv / nonce : ", ct.iv)
const pt = cipher.decrypt(ct.result);
console.log("decrypted message: ", pt); // this works flawlessly!
**Encryption Result by Node.js**
encrypted message: Zf5aB0bbVGGX3k9Yt6x+9daxCGZO0MmwYW8VUsOY4j3gNYXP47hvfGgd
iv / nonce : fvXWsQhmTtDJsGFv
decrypted message: Hello world!!!
**The decryption part (Rust) / Receiver Side**
use aes_gcm::{
aead::{Aead, KeyInit, OsRng},
Aes256Gcm, Nonce,
};
use base64::{engine::general_purpose, Engine as _};
fn main() {
// master key from sender
let master = "12341234123412341234123412341234".as_bytes();
let cipher = Aes256Gcm::new_from_slice(master).unwrap();
// nonce / iv from sender
let nonce_str_base64 = "fvXWsQhmTtDJsGFv";
let nonce_str: Vec<u8> = general_purpose::STANDARD.decode(nonce_str_base64).unwrap();
let nonce = Nonce::from_slice(&nonce_str); // 96-bits; unique per message
// encrypted text from sender
let ciphertext_base64 = "Zf5aB0bbVGGX3k9Yt6x+9daxCGZO0MmwYW8VUsOY4j3gNYXP47hvfGgd";
let ciphertext = general_purpose::STANDARD.decode(ciphertext_base64).unwrap();
// gets aead::Error here
match cipher.decrypt(nonce, ciphertext.as_slice()) {
Ok(decrypted) => {
let result = String::from_utf8(decrypted).unwrap();
println!("result: {}", result);
}
Err(err) => print!("{}", err), <--- prints error: aead::Error
};
}
[1]: https://docs.rs/aes-gcm/latest/aes_gcm/index.html
</details>
# 答案1
**得分**: 0
AES-256-GCM由三个部分组成:
1. `payload` 或加密文本,
2. `iv` 或 nonce,一个仅生成一次的唯一随机数,
3. 以及 `tag`,它是认证的一部分,用于确保加密消息未被更改。
[aes_gcm][1] crate 使用 `payload` + `tag` 进行解密。因此,解决方案是从结果消息中移除 `iv` 部分。我只是在发送方(Node.js)上进行了更改:
从这个:
```javascript
let result = Buffer.concat([enc1, enc2, iv, cipher.getAuthTag()]).toString("base64");
改成了这个:
let result = Buffer.concat([enc1, enc2, cipher.getAuthTag()]).toString("base64");
nonce/iv 部分通过函数参数提供给 cipher.decrypt
。
英文:
AES-256-GCM consists of three parts:
payload
or ciphered text,iv
or nonce, a unique random number that generated once- and the
tag
that is part of the authentication that ensures the encrypted message has not been altered
The aes_gcm crate uses payload
+ tag
for decryption. So the solution is to remove the iv
part from the result message. I just change the sender side (Node.js) from this:
let result = Buffer.concat([enc1, enc2, iv, cipher.getAuthTag()]).toString("base64");
to this:
let result = Buffer.concat([enc1, enc2, cipher.getAuthTag()]).toString("base64");
the nonce/iv part is supplied to cipher.decrypt
through function parameter
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论