AES GCM 使用 web subtlecrypto 加密并使用 flutter cryptography 解密
AES GCM encrypt with web subtlecrypto and decrypt with flutter cryptography
我正在尝试使用 SubtleCrypto and decrypt it in flutter with cryptography 加密网络扩展中的内容。我想使用密码来加密消息,将其发送到应用程序并使用相同的密码对其进行解密。为此,我将 AES GCM 与 pbkdf2
一起使用
我能够在 Mozilla 文档页面上找到一个加密片段。但是,我很难在 flutter 中解密它。
我在术语方面也有问题。 SubtleCrypto 使用 iv、salt 和 tags 而 flutter cryptography 使用 nonce 和 mac.
Javascript代码:
test(){
// const salt = window.crypto.getRandomValues(new Uint8Array(16));
// const iv = window.crypto.getRandomValues(new Uint8Array(12));
const salt = new Uint8Array([0, 72, 16, 170, 232, 145, 179, 47, 241, 92, 75, 146, 25, 0, 193, 176]);
const iv = new Uint8Array([198, 0, 92, 253, 0, 245, 140, 79, 236, 215, 255, 0]);
console.log('salt: ', salt);
console.log('iv: ', iv);
console.log('salt: ', btoa(String.fromCharCode(...salt)));
console.log('iv: ', btoa(String.fromCharCode(...iv)));
this.encrypt('value', salt, iv).then(x => console.log('got encrypted: ', x));
}
getKeyMaterial(): Promise<CryptoKey> {
const password = 'key';
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
'raw',
enc.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
}
async encrypt(plaintext: string, salt: Uint8Array, iv: Uint8Array): Promise<string> {
const keyMaterial = await this.getKeyMaterial();
const key = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256},
true,
[ 'encrypt', 'decrypt' ]
);
const encoder = new TextEncoder();
const tes = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv
},
key,
encoder.encode(plaintext)
);
return btoa(String.fromCharCode(...new Uint8Array(tes)));
}
扑飞镖代码:
void decrypt(){
final algorithm = AesGcm.with256bits();
final encrypted = base64Decode('1MdEsqwqh4bUTlfpIk12SeziA9Pw');
final secretBox = SecretBox.fromConcatenation(encrypted, nonceLength: 12, macLength: 0);
// // Encrypt
final data = await algorithm.decrypt(
secretBox,
secretKey: await getKey(),
);
String res = utf8.decode(data);
}
Future<SecretKey> getKey() async{
final pbkdf2 = Pbkdf2(
macAlgorithm: Hmac.sha256(),
iterations: 100000,
bits: 128,
);
// Password we want to hash
final secretKey = SecretKey(utf8.encode('key'));
// A random salt
final salt = [0, 72, 16, 170, 232, 145, 179, 47, 241, 92, 75, 146, 25, 0, 193, 176];
// Calculate a hash that can be stored in the database
final newSecretKey = await pbkdf2.deriveKey(
secretKey: secretKey,
nonce: salt,
);
return Future<SecretKey>.value(newSecretKey);
}
我做错了什么?
Dart代码中存在以下问题:
- WebCryptoAPI 代码按照 ciphertext | 顺序将 GCM 标签与密文连接起来标签。在 Dart 代码中,这两部分必须相应地分开。
此外,在 Dart 代码中, nonce/IV 没有被考虑在内。 decrypt()
的可能修复是:
//final secretBox = SecretBox.fromConcatenation(encrypted, nonceLength: 12, macLength: 0);
Uint8List ciphertext = encrypted.sublist(0, encrypted.length - 16);
Uint8List mac = encrypted.sublist(encrypted.length - 16);
Uint8List iv = base64Decode('xgBc/QD1jE/s1/8A'); // should als be concatenated, e.g. iv | ciphertext | tag
SecretBox secretBox = new SecretBox(ciphertext, nonce: iv, mac: new Mac(mac));
此外,WebCryptoAPI代码使用AES-256,所以在getKey()
中的Dart代码中,PBKDF2调用中的密钥大小必须相应地应用256位。
另外,由于decrypt()
包含异步方法调用,所以必须用async
关键字标记。
通过这些更改,decrypt()
在我的机器上工作,returns value
用于来自 WebCryptoAPI 代码的数据:
function test(){
// const salt = window.crypto.getRandomValues(new Uint8Array(16));
// const iv = window.crypto.getRandomValues(new Uint8Array(12));
const salt = new Uint8Array([0, 72, 16, 170, 232, 145, 179, 47, 241, 92, 75, 146, 25, 0, 193, 176]);
const iv = new Uint8Array([198, 0, 92, 253, 0, 245, 140, 79, 236, 215, 255, 0]);
console.log('salt: ', salt);
console.log('iv: ', iv);
console.log('salt: ', btoa(String.fromCharCode(...salt)));
console.log('iv: ', btoa(String.fromCharCode(...iv)));
encrypt('value', salt, iv).then(x => console.log('got encrypted:', x));
}
function getKeyMaterial() {
const password = 'key';
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
'raw',
enc.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
}
async function encrypt(plaintext, salt, iv) {
const keyMaterial = await getKeyMaterial();
const key = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256},
true,
[ 'encrypt', 'decrypt' ]
);
const encoder = new TextEncoder();
const tes = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv
},
key,
encoder.encode(plaintext)
);
return btoa(String.fromCharCode(...new Uint8Array(tes)));
}
test();
salt: AEgQquiRsy/xXEuSGQDBsA==
iv: xgBc/QD1jE/s1/8A
got encrypted: 1MdEsqwqh4bUTlfpIk12SeziA9Pw
请注意,静态 nonce/IV 和盐通常是不安全的(当然,出于测试目的,这很好)。通常,它们是针对每个 encryption/key 推导随机生成的。由于 salt 和 nonce/IV 不是秘密的,因此它们通常与密文和标签连接在一起,例如盐 |随机数 |密文 |标记,并在收件人端分开。
实际上SecretBox
提供了方法fromConcatenation()
,它应该分离随机数、密文和标签的串联。但是,此实现 returns(至少在早期版本中)是一个损坏的密文,这可能是一个错误。
关于 GCM 和 PBKDF2 上下文中的术语 nonce/IV、salt 和 MAC/tag:
GCM 模式使用 12 字节的随机数,在 WebCryptoAPI(有时在其他库中)中称为 IV,s。 here。 PBKDF2 在密钥推导中应用了一个盐,在 Dart 中称为随机数。
命名nonce是合适的,因为IV(结合相同的密钥)和salt(结合相同的密码)只能使用一次。前者对于 GCM 安全性尤其重要,s。 here.
MAC 和 tag 是 GCM 身份验证标签的同义词。
我正在尝试使用 SubtleCrypto and decrypt it in flutter with cryptography 加密网络扩展中的内容。我想使用密码来加密消息,将其发送到应用程序并使用相同的密码对其进行解密。为此,我将 AES GCM 与 pbkdf2
一起使用我能够在 Mozilla 文档页面上找到一个加密片段。但是,我很难在 flutter 中解密它。
我在术语方面也有问题。 SubtleCrypto 使用 iv、salt 和 tags 而 flutter cryptography 使用 nonce 和 mac.
Javascript代码:
test(){
// const salt = window.crypto.getRandomValues(new Uint8Array(16));
// const iv = window.crypto.getRandomValues(new Uint8Array(12));
const salt = new Uint8Array([0, 72, 16, 170, 232, 145, 179, 47, 241, 92, 75, 146, 25, 0, 193, 176]);
const iv = new Uint8Array([198, 0, 92, 253, 0, 245, 140, 79, 236, 215, 255, 0]);
console.log('salt: ', salt);
console.log('iv: ', iv);
console.log('salt: ', btoa(String.fromCharCode(...salt)));
console.log('iv: ', btoa(String.fromCharCode(...iv)));
this.encrypt('value', salt, iv).then(x => console.log('got encrypted: ', x));
}
getKeyMaterial(): Promise<CryptoKey> {
const password = 'key';
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
'raw',
enc.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
}
async encrypt(plaintext: string, salt: Uint8Array, iv: Uint8Array): Promise<string> {
const keyMaterial = await this.getKeyMaterial();
const key = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256},
true,
[ 'encrypt', 'decrypt' ]
);
const encoder = new TextEncoder();
const tes = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv
},
key,
encoder.encode(plaintext)
);
return btoa(String.fromCharCode(...new Uint8Array(tes)));
}
扑飞镖代码:
void decrypt(){
final algorithm = AesGcm.with256bits();
final encrypted = base64Decode('1MdEsqwqh4bUTlfpIk12SeziA9Pw');
final secretBox = SecretBox.fromConcatenation(encrypted, nonceLength: 12, macLength: 0);
// // Encrypt
final data = await algorithm.decrypt(
secretBox,
secretKey: await getKey(),
);
String res = utf8.decode(data);
}
Future<SecretKey> getKey() async{
final pbkdf2 = Pbkdf2(
macAlgorithm: Hmac.sha256(),
iterations: 100000,
bits: 128,
);
// Password we want to hash
final secretKey = SecretKey(utf8.encode('key'));
// A random salt
final salt = [0, 72, 16, 170, 232, 145, 179, 47, 241, 92, 75, 146, 25, 0, 193, 176];
// Calculate a hash that can be stored in the database
final newSecretKey = await pbkdf2.deriveKey(
secretKey: secretKey,
nonce: salt,
);
return Future<SecretKey>.value(newSecretKey);
}
我做错了什么?
Dart代码中存在以下问题:
- WebCryptoAPI 代码按照 ciphertext | 顺序将 GCM 标签与密文连接起来标签。在 Dart 代码中,这两部分必须相应地分开。
此外,在 Dart 代码中, nonce/IV 没有被考虑在内。decrypt()
的可能修复是:
//final secretBox = SecretBox.fromConcatenation(encrypted, nonceLength: 12, macLength: 0);
Uint8List ciphertext = encrypted.sublist(0, encrypted.length - 16);
Uint8List mac = encrypted.sublist(encrypted.length - 16);
Uint8List iv = base64Decode('xgBc/QD1jE/s1/8A'); // should als be concatenated, e.g. iv | ciphertext | tag
SecretBox secretBox = new SecretBox(ciphertext, nonce: iv, mac: new Mac(mac));
此外,WebCryptoAPI代码使用AES-256,所以在
getKey()
中的Dart代码中,PBKDF2调用中的密钥大小必须相应地应用256位。另外,由于
decrypt()
包含异步方法调用,所以必须用async
关键字标记。
通过这些更改,decrypt()
在我的机器上工作,returns value
用于来自 WebCryptoAPI 代码的数据:
function test(){
// const salt = window.crypto.getRandomValues(new Uint8Array(16));
// const iv = window.crypto.getRandomValues(new Uint8Array(12));
const salt = new Uint8Array([0, 72, 16, 170, 232, 145, 179, 47, 241, 92, 75, 146, 25, 0, 193, 176]);
const iv = new Uint8Array([198, 0, 92, 253, 0, 245, 140, 79, 236, 215, 255, 0]);
console.log('salt: ', salt);
console.log('iv: ', iv);
console.log('salt: ', btoa(String.fromCharCode(...salt)));
console.log('iv: ', btoa(String.fromCharCode(...iv)));
encrypt('value', salt, iv).then(x => console.log('got encrypted:', x));
}
function getKeyMaterial() {
const password = 'key';
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
'raw',
enc.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
}
async function encrypt(plaintext, salt, iv) {
const keyMaterial = await getKeyMaterial();
const key = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256},
true,
[ 'encrypt', 'decrypt' ]
);
const encoder = new TextEncoder();
const tes = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv
},
key,
encoder.encode(plaintext)
);
return btoa(String.fromCharCode(...new Uint8Array(tes)));
}
test();
salt: AEgQquiRsy/xXEuSGQDBsA==
iv: xgBc/QD1jE/s1/8A
got encrypted: 1MdEsqwqh4bUTlfpIk12SeziA9Pw
请注意,静态 nonce/IV 和盐通常是不安全的(当然,出于测试目的,这很好)。通常,它们是针对每个 encryption/key 推导随机生成的。由于 salt 和 nonce/IV 不是秘密的,因此它们通常与密文和标签连接在一起,例如盐 |随机数 |密文 |标记,并在收件人端分开。
实际上SecretBox
提供了方法fromConcatenation()
,它应该分离随机数、密文和标签的串联。但是,此实现 returns(至少在早期版本中)是一个损坏的密文,这可能是一个错误。
关于 GCM 和 PBKDF2 上下文中的术语 nonce/IV、salt 和 MAC/tag:
GCM 模式使用 12 字节的随机数,在 WebCryptoAPI(有时在其他库中)中称为 IV,s。 here。 PBKDF2 在密钥推导中应用了一个盐,在 Dart 中称为随机数。
命名nonce是合适的,因为IV(结合相同的密钥)和salt(结合相同的密码)只能使用一次。前者对于 GCM 安全性尤其重要,s。 here.
MAC 和 tag 是 GCM 身份验证标签的同义词。