AES GCM 使用 web 微妙加密进行加密并使用颤振加密进行解密

Posted

技术标签:

【中文标题】AES GCM 使用 web 微妙加密进行加密并使用颤振加密进行解密【英文标题】:AES GCM encrypt with web subtlecrypto and decrypt with flutter cryptography 【发布时间】:2021-04-05 11:04:20 【问题描述】:

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

我能够在 Mozilla 文档页面上找到加密 sn-p。但是,我很难在颤抖中解密它。

我也遇到了术语方面的问题。 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);

我做错了什么?

【问题讨论】:

“但是,我很难在颤抖中解密它。” 你是怎么解决这个问题的?有什么症状? 【参考方案1】:

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 代码中,必须相应地应用 256 位作为 PBKDF2 调用中的密钥大小。

另外,由于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

请注意,静态随机数/IV 和 salt 通常是不安全的(当然,出于测试目的,这很好)。通常,它们是为每个加密/密钥派生随机生成的。由于 salt 和 nonce/IV 不是秘密的,它们通常与密文和标签连接,例如盐 |随机数 |密文 |标签,并在接收方分开。

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


关于 GCM 和 PBKDF2 上下文中的 nonce/IV、salt 和 MAC/tag 术语:

GCM 模式使用 12 字节的 nonce,在 WebCryptoAPI(有时在其他库中)称为 IV,s。 here。 PBKDF2 在密钥推导中应用了盐,在 Dart 中称为随机数。

nonce 的命名是恰当的,因为 IV(与相同的密钥组合)和 salt(与相同的密码组合)只能使用一次。前者对于 GCM 安全尤其重要。 here.

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

【讨论】:

这一切都很有意义。如果不是那个错误,这应该也可以吗?:final encrypted = base64Decode('xgBc/QD1jE/s1/8A1MdEsqwqh4bUTlfpIk12SeziA9Pw'); final secretBox = SecretBox.fromConcatenation(encrypted, nonceLength: 12, macLength: 16); @RobinDijkhof - 正确,没有错误,这将起作用。在当前版本的 Cryptography 2.0.1 中,该错误仍然存​​在(已测试)。这甚至可以在description 中给出的源代码中看到:在final cipherText = List&lt;int&gt;.unmodifiable(... 中使用了错误的偏移量。

以上是关于AES GCM 使用 web 微妙加密进行加密并使用颤振加密进行解密的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法过滤 aes 256 gcm 加密数据库中的数据?

AES GCM加密模式的初始向量IV怎么确定

使用 C# 与 PHP 的 AES GCM 加密

AES GCM 解密绕过 JAVA 中的身份验证

如何使用 AES-GCM 对 C#.NET 加密()然后 JS WebCryptoApi 解密()?

golang 在golang中使用AES256 GCM加密文本