dint-dev / cryptography

Cryptography for Flutter developers: encryption, digital signatures, key agreement, etc.
https://pub.dev/packages/cryptography
Apache License 2.0
161 stars 88 forks source link

Argon2id.deriveKey gives segmentation fault for large parameter values #177

Open elliotwutingfeng opened 8 months ago

elliotwutingfeng commented 8 months ago

I am testing out some Dart cryptography libraries for deriving Argon2id keys with large parameter values, such as the maximum allowed settings in the Bitwarden password manager, which as of today are 10 iterations, 16 parallelism, and 1024 MiB memory.

The following leads to a segmentation fault. Running the script with --old_gen_heap_size=2g sometimes gives a segmentation fault, but sometimes it works.

import 'dart:convert';
import 'dart:typed_data';

import 'package:cryptography/cryptography.dart';

Future<void> main() async {
  final Uint8List password = utf8.encode("passphrase");
  final List<int> salt =
      (await Sha256().hash(base64.decode("rNYWSe/wFO1k+Qxia0A96A=="))).bytes;

  // Bitwarden maximum settings
  final int kdfIterations = 10;
  final int kdfParallelism = 16;
  final int kdfMemory = 1024; // 1024 MiB

  final Argon2id algorithm = Argon2id(
    parallelism: kdfParallelism,
    memory: kdfMemory * 1024,
    iterations: kdfIterations,
    hashLength: 32,
  );
  final SecretKey newSecretKey =
      await algorithm.deriveKey(secretKey: SecretKey(password), nonce: salt);
  final List<int> newSecretKeyBytes = await newSecretKey.extractBytes();
  print('hashed password: $newSecretKeyBytes');
}

Equivalent working program using pointycastle.

import 'dart:convert';
import 'dart:typed_data';

import 'package:pointycastle/digests/sha256.dart';
import 'package:pointycastle/key_derivators/argon2_native_int_impl.dart';
import 'package:pointycastle/pointycastle.dart';

Future<void> main() async {
  final Uint8List password = utf8.encode("passphrase");
  final Uint8List salt =
      SHA256Digest().process(base64.decode("rNYWSe/wFO1k+Qxia0A96A=="));

  // Bitwarden maximum settings
  final int kdfIterations = 10;
  final int kdfParallelism = 16;
  final int kdfMemory = 1024; // 1024 MiB

  final List<int> newSecretKeyBytes = (Argon2BytesGenerator()
        ..init(Argon2Parameters(Argon2Parameters.ARGON2_id, salt,
            desiredKeyLength: 32,
            iterations: kdfIterations,
            memory: kdfMemory * 1024,
            lanes: kdfParallelism)))
      .process(Uint8List.fromList(password));
  print('hashed password: $newSecretKeyBytes');
}

Expected output for both programs:

hashed password: [207, 41, 183, 164, 139, 92, 97, 166, 115, 223, 111, 83, 7, 239, 193, 74, 180, 246, 103, 53, 177, 250, 203, 81, 148, 124, 62, 225, 215, 116, 170, 240]

I am aware that it is not advisable to set Argon2id KDF settings to such extreme values in either case, however, this may be worth some investigation.

elliotwutingfeng commented 4 weeks ago

An alternative solution would be to use the hashlib package, which is able to handle the aforementioned KDF settings (10 iterations, 16 parallelism, and 1024 MiB memory).