Шифрование AES GCM с помощью тонкого веб-шифрования и дешифрование с помощью криптографии флаттера

Я пытаюсь зашифровать что-то в веб-расширении с помощью SubtleCrypto и расшифровать его с помощью криптографии . Я хочу использовать пароль, чтобы зашифровать сообщение, отправить его в приложение и расшифровать с тем же паролем. Для этого я использую AES GCM с pbkdf2

Мне удалось найти фрагмент шифрования на странице документации 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 ответ

Решение

В коде Dart существуют следующие проблемы:

  • Код WebCryptoAPI объединяет тег GCM с зашифрованным текстом в порядке зашифрованного текста | тег. В коде Dart обе части должны быть соответственно разделены.
    Также в коде Dart nonce / IV не учитывается. Возможное исправление:
         //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, поэтому в коде Dart в , 256 бит должны применяться в качестве размера ключа в вызове PBKDF2 соответственно.

  • Кроме того, поскольку он содержит вызовы асинхронных методов, он должен быть отмечен знаком ключевое слово.

С этими изменениями работает на моей машине и возвращается для данных из кода WebCryptoAPI:

      salt:          AEgQquiRsy/xXEuSGQDBsA== 
iv:            xgBc/QD1jE/s1/8A 
got encrypted: 1MdEsqwqh4bUTlfpIk12SeziA9Pw

Обратите внимание, что статический одноразовый номер / IV и соль обычно небезопасны (конечно, для целей тестирования это нормально). Обычно они генерируются случайным образом для каждого шифрования / получения ключа. Поскольку соль и одноразовый номер / IV не являются секретными, они обычно объединяются с зашифрованным текстом и тегом, например, соль | nonce | зашифрованный текст | тег и разделены на стороне получателя.

Фактически предоставляет метод который должен разделять конкатенацию одноразового номера, зашифрованного текста и тега. Однако эта реализация возвращает (по крайней мере, в более ранних версиях) поврежденный зашифрованный текст, что, вероятно, является ошибкой.


Что касается терминов nonce / IV, salt и MAC / tag в контексте GCM и PBKDF2:

В режиме GCM используется 12-байтовый одноразовый номер, который в WebCryptoAPI (а иногда и в других библиотеках) называется IV. здесь. PBKDF2 применяет соль при выводе ключа, которая в Dart называется nonce.

Одноразовый идентификатор именования подходит в том смысле , что IV (в сочетании с одним и тем же ключом) и соль (в сочетании с одним и тем же паролем) могут использоваться только один раз. Первое важно для безопасности GCM, в частности, s. здесь.

MAC и тег являются синонимами тега аутентификации GCM.

Другие вопросы по тегам