AES GCM使用web子加密进行加密,使用flutter加密进行解密



我正试图用SubtleCrypto加密网络扩展中的某些内容,并用密码学对其进行解密。我想使用密码加密消息,将其发送到应用程序,然后用相同的密码解密。为此,我使用带有pbkdf2 的AES GCM

我在Mozilla文档页面上找到了一个加密片段。然而,我激动地努力解密它。

我在术语方面也有问题。SubtleCrypto使用iv、salt和标签,而flutter加密使用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代码将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()在我的机器上工作,并为WebCryptoAPI代码中的数据返回value

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和salt通常是不安全的(当然,出于测试目的,这是可以的(。通常,它们是针对每个加密/密钥推导而随机生成的。由于salt和nonce/IV不是秘密的,它们通常与密文和标签连接,例如salt|nonce|ciphertext|tag,并在接收方分离。

实际上,SecretBox提供了fromConcatenation()方法,该方法应该分离随机数、密文和标签的级联。然而,这个实现返回(至少在早期版本中(一个损坏的密文,这可能是一个错误。


关于GCM和PBKDF2:上下文中的术语nonce/IV、盐和MAC/标签

GCM模式使用12字节的nonce,在WebCryptoAPI中(有时在其他库中(称为IV。PBKDF2在密钥派生中应用salt,在Dart中称为nonce。

命名nonce是合适的,因为IV(与相同密钥组合(和salt(与相同密码组合(只能使用一次。前者对GCM的安全性至关重要,特别是在这里。

MAC和标签是GCM身份验证标签的同义词。