AES GCM 使用网络微妙加密加密并使用颤振加密解密

时间:2021-04-05 11:04:20

标签: flutter dart encryption subtlecrypto

我正在尝试使用 SubtleCrypto 加密 webextension 中的某些内容,并使用 cryptography 对其进行解密。我想使用密码来加密消息,将其发送到应用程序并使用相同的密码解密。为此,我使用带有 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);
}

我做错了什么?

1 个答案:

答案 0 :(得分: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 和 nonce/IV 不是秘密的,它们通常与密文和标签连接,例如盐 |随机数 |密文|标签,并在收件人端分开。

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


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

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

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

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

相关问题