twonly-app-dependencies/hashlib/test/argon2_test.dart
2025-12-07 16:10:41 +01:00

565 lines
17 KiB
Dart

// Copyright (c) 2023, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.
import 'dart:typed_data';
import 'package:hashlib/codecs.dart';
import 'package:hashlib/hashlib.dart';
import 'package:test/test.dart';
void main() {
group('functionality test', () {
test("name", () {
expect(
Argon2(
parallelism: 1,
memorySizeKB: 10,
iterations: 1,
).name,
r'argon2id',
);
expect(
Argon2(
parallelism: 1,
memorySizeKB: 10,
iterations: 1,
type: Argon2Type.argon2id,
).name,
r'argon2id',
);
expect(
Argon2(
parallelism: 1,
memorySizeKB: 10,
iterations: 1,
type: Argon2Type.argon2d,
).name,
r'argon2d',
);
expect(
Argon2(
parallelism: 1,
memorySizeKB: 10,
iterations: 1,
type: Argon2Type.argon2i,
).name,
r'argon2i',
);
});
});
// Test cases are generated by https://argon2.online/
group('Argon2 v19 test', () {
test("argon2i m=16, t=2, p=1 @ out = 16", () {
final argon2 = Argon2(
version: Argon2Version.v13,
type: Argon2Type.argon2i,
hashLength: 16,
iterations: 2,
parallelism: 1,
memorySizeKB: 16,
salt: "some salt".codeUnits,
);
final password = 'password'.codeUnits;
final matcher = "bb5794ea66451b8fce3a84dd02d33949";
final encoded =
r"$argon2i$v=19$m=16,t=2,p=1$c29tZSBzYWx0$u1eU6mZFG4/OOoTdAtM5SQ";
var result = argon2.convert(password);
expect(result.toString(), equals(encoded));
expect(result.hex(), matcher);
expect(result.encoded(), encoded);
expect(argon2.encode(password), encoded);
});
test("argon2d m=16, t=2, p=1 @ out = 16", () {
final argon2 = Argon2(
version: Argon2Version.v13,
type: Argon2Type.argon2d,
hashLength: 16,
iterations: 2,
parallelism: 1,
memorySizeKB: 16,
salt: "some salt".codeUnits,
);
final matcher = "cf916880b91ba8a1390fff6b624baa27";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
});
test("argon2id m=16, t=2, p=1 @ out = 16", () {
final argon2 = Argon2(
version: Argon2Version.v13,
type: Argon2Type.argon2id,
hashLength: 16,
iterations: 2,
parallelism: 1,
memorySizeKB: 16,
salt: "some salt".codeUnits,
);
final matcher = "88c91661b3cea3c3853593608881f324";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
});
test("argon2i m=256, t=2, p=4 @ out = 32", () {
final argon2 = Argon2(
version: Argon2Version.v13,
type: Argon2Type.argon2i,
hashLength: 32,
iterations: 2,
parallelism: 4,
memorySizeKB: 256,
salt: "some salt".codeUnits,
);
final matcher =
"aaef1c23ce86889c7d76f5ea214760fb66900916546cde42ebdc47914daed123";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
});
test("argon2d m=256, t=2, p=4 @ out = 32", () {
final argon2 = Argon2(
version: Argon2Version.v13,
type: Argon2Type.argon2d,
hashLength: 32,
iterations: 2,
parallelism: 4,
memorySizeKB: 256,
salt: "some salt".codeUnits,
);
final matcher =
"c31433dbefadf7aa527bbbcc7beace0d8d70973719c6efb1c24fb21278569701";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
});
test("argon2id m=256, t=2, p=4 @ out = 32", () {
final argon2 = Argon2(
version: Argon2Version.v13,
type: Argon2Type.argon2id,
hashLength: 32,
iterations: 2,
parallelism: 4,
memorySizeKB: 256,
salt: "some salt".codeUnits,
);
final matcher =
"c23e4a305f649971527eda884bda6b481004aedd31740460da3d43db8946786f";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
});
test("encoded hash instance check", () {
final encoded =
r"$argon2id$v=19$m=128,t=1,p=4$c29tZSBzYWx0$24VHMpaU5EkkdH5rpdnb5zeOf3Y";
final matcher = "db8547329694e44924747e6ba5d9dbe7378e7f76";
final argon2 = Argon2.fromEncoded(fromCrypt(encoded));
expect(argon2.type, Argon2Type.argon2id);
expect(argon2.version, Argon2Version.v13);
expect(argon2.memorySizeKB, 128);
expect(argon2.parallelism, 4);
expect(argon2.iterations, 1);
expect(argon2.hashLength, 20);
expect(argon2.salt, "some salt".codeUnits);
var result = argon2.convert("password".codeUnits);
expect(result.hex(), matcher);
expect(result.encoded(), encoded);
});
test("argon2Verify with encoded", () {
final encoded =
r"$argon2id$v=19$m=128,t=1,p=4$c29tZSBzYWx0$24VHMpaU5EkkdH5rpdnb5zeOf3Y";
expect(argon2Verify(encoded, "password".codeUnits), isTrue);
});
test("argon2Verify without the hash", () {
final password = "password".codeUnits;
final encoded = r"$argon2id$v=19$m=128,t=1,p=4$c29tZSBzYWx0";
expect(() => argon2Verify(encoded, password), throwsArgumentError);
});
test("argon2Verify with password", () {
final matcher = "db8547329694e44924747e6ba5d9dbe7378e7f76";
var result = Argon2(
type: Argon2Type.argon2id,
salt: 'some salt'.codeUnits,
hashLength: 20,
iterations: 1,
parallelism: 4,
memorySizeKB: 128,
).verify(
fromHex(matcher),
"password".codeUnits,
);
expect(result, true);
});
test('argon2i', () {
final result = argon2i(
'password'.codeUnits,
"some salt".codeUnits,
hashLength: 16,
security: Argon2Security(
'custom',
m: 16,
p: 1,
t: 2,
),
);
final matcher = "bb5794ea66451b8fce3a84dd02d33949";
final encoded =
r"$argon2i$v=19$m=16,t=2,p=1$c29tZSBzYWx0$u1eU6mZFG4/OOoTdAtM5SQ";
expect(result.hex(), matcher);
expect(result.encoded(), encoded);
});
test('argon2d', () {
final result = argon2d(
'password'.codeUnits,
"some salt".codeUnits,
hashLength: 16,
security: Argon2Security(
'custom',
m: 16,
p: 1,
t: 2,
),
);
final matcher = "cf916880b91ba8a1390fff6b624baa27";
final salt = r"$argon2d$v=19$m=16,t=2,p=1$c29tZSBzYWx0";
expect(result.hex(), matcher);
expect(result.encoded(), startsWith(salt));
});
test('argon2id', () {
final result = argon2id(
'password'.codeUnits,
"some salt".codeUnits,
hashLength: 16,
security: Argon2Security(
'custom',
m: 16,
p: 1,
t: 2,
),
);
final matcher = "88c91661b3cea3c3853593608881f324";
final salt = r"$argon2id$v=19$m=16,t=2,p=1$c29tZSBzYWx0";
expect(result.hex(), matcher);
expect(result.encoded(), startsWith(salt));
});
test("multiple call with same instance", () {
final argon2 = Argon2(
version: Argon2Version.v13,
type: Argon2Type.argon2i,
hashLength: 16,
iterations: 2,
parallelism: 1,
memorySizeKB: 16,
salt: "some salt".codeUnits,
);
final matcher = "bb5794ea66451b8fce3a84dd02d33949";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
expect(argon2.convert('password'.codeUnits).hex(), matcher);
expect(argon2.convert('password'.codeUnits).hex(), matcher);
});
test("with personalization", () {
final personalization = "personalization".codeUnits;
final argon2 = Argon2.fromSecurity(
Argon2Security.test,
hashLength: 16,
salt: "some salt".codeUnits,
personalization: personalization,
);
final matcher = "33561b7ad59b447aa5f6e3113d2f32ca";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
expect(argon2.personalization, equals(personalization));
});
test("with key", () {
final key = "random key".codeUnits;
final argon2 = Argon2.fromSecurity(
Argon2Security.test,
hashLength: 16,
salt: "some salt".codeUnits,
key: key,
);
final matcher = "4ad0d2f98a6e7f6e3e99c520d1813c07";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
expect(argon2.key, equals(key));
});
test("with key and personalization", () {
final argon2 = Argon2.fromSecurity(
Argon2Security.test,
hashLength: 16,
salt: "some salt".codeUnits,
key: "random key".codeUnits,
personalization: "personalization".codeUnits,
);
final matcher = "0bd2a8c0386b4125a6f439f2f863fc35";
expect(argon2.convert('password'.codeUnits).hex(), matcher);
});
});
group('Argon2Context Tests', () {
// Test for Argon2Context constructor
test('Creates Argon2Context with valid parameters', () {
var salt = List<int>.generate(16, (i) => i);
var context = Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
salt: salt,
hashLength: 32,
);
expect(context.salt, equals(salt));
expect(context.passes, equals(3));
expect(context.lanes, equals(2));
expect(context.memorySizeKB, equals(8192));
expect(context.hashLength, equals(32));
});
// Tests for invalid parameters
test('Throws if hashLength is too small', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
hashLength: 3,
),
throwsA(isA<ArgumentError>().having(
(e) => e.message, 'message', 'The tag length must be at least 4')),
);
});
test('Throws if hashLength is too large', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
hashLength: 0x3FFFFFF + 1,
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The tag length must be at most 67108863')),
);
});
test('Throws if parallelism is too small', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 0,
memorySizeKB: 8192,
),
throwsA(isA<ArgumentError>().having(
(e) => e.message, 'message', 'The parallelism must be at least 1')),
);
});
test('Throws if parallelism is too large', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 0x7FFF + 1,
memorySizeKB: 8192,
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The parallelism must be at most 32767')),
);
});
test('Throws if iterations are too few', () {
expect(
() => Argon2Context(
iterations: 0,
parallelism: 2,
memorySizeKB: 8192,
),
throwsA(isA<ArgumentError>().having(
(e) => e.message, 'message', 'The iterations must be at least 1')),
);
});
test('Throws if iterations are too many', () {
expect(
() => Argon2Context(
iterations: 0x3FFFFFF + 1,
parallelism: 2,
memorySizeKB: 8192,
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The iterations must be at most 67108863')),
);
});
test('Throws if memorySizeKB is too small for parallelism', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 15,
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The memory size must be at least 8 * parallelism')),
);
});
test('Throws if memorySizeKB is too large', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 0x3FFFFFF + 1,
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The memorySizeKB must be at most 67108863')),
);
});
test('Throws if salt is too short', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
salt: List<int>.generate(7, (i) => i),
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The salt must be at least 8 bytes long')),
);
});
test('Throws if salt is too long', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
salt: Uint8List(0x3FFFFFF + 1),
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The salt must be at most 67108863 bytes long')),
);
});
test('Throws if key is too short', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
key: [],
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The key must be at least 1 bytes long')),
);
});
test('Throws if key is too long', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
key: Uint8List(0x3FFFFFF + 1),
),
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
'The key must be at most 67108863 bytes long')),
);
});
test('Throws if personalization data is too short', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
personalization: [],
),
throwsA(isA<ArgumentError>()),
);
});
test('Throws if personalization data is too long', () {
expect(
() => Argon2Context(
iterations: 3,
parallelism: 2,
memorySizeKB: 8192,
personalization: Uint8List(0x3FFFFFF + 1),
),
throwsArgumentError,
);
});
// Test for Argon2Context.fromEncoded factory method
test('Creates Argon2Context from encoded string with valid parameters', () {
var cryptData = fromCrypt(
r'$argon2id$v=19$m=8192,t=3,p=2$c29tZSBzYWx0$CZOgzrCgoVUzMoR/dcUZyw');
var context = Argon2Context.fromEncoded(cryptData);
expect(context.type, equals(Argon2Type.argon2id));
expect(context.version, equals(Argon2Version.v13));
expect(context.memorySizeKB, equals(8192));
expect(context.passes, equals(3));
expect(context.lanes, equals(2));
expect(context.salt.length, equals(9));
});
test('Throws if encoded string has invalid type', () {
var cryptData = fromCrypt(
r'$argon2x$v=19$m=8192,t=3,p=2$c29tZSBzYWx0$CZOgzrCgoVUzMoR/dcUZyw');
expect(
() => Argon2Context.fromEncoded(cryptData),
throwsA(isA<ArgumentError>()
.having((e) => e.message, 'message', 'Unknown type')),
);
});
test('Throws if encoded string has invalid version', () {
var cryptData = fromCrypt(
r'$argon2i$v=99$m=8192,t=3,p=2$c29tZSBzYWx0$CZOgzrCgoVUzMoR/dcUZyw');
expect(
() => Argon2Context.fromEncoded(cryptData),
throwsA(isA<ArgumentError>()
.having((e) => e.message, 'message', 'Unknown version')),
);
});
test('Throws if encoded string has no parameters', () {
var cryptData =
fromCrypt(r'$argon2i$v=19$c29tZSBzYWx0$CZOgzrCgoVUzMoR/dcUZyw');
expect(
() => Argon2Context.fromEncoded(cryptData),
throwsA(isA<ArgumentError>()
.having((e) => e.message, 'message', 'No paramters')),
);
});
test('Throws if encoded string is missing "m" parameter', () {
var cryptData = fromCrypt(
r'$argon2i$v=19$t=3,p=2$c29tZSBzYWx0$CZOgzrCgoVUzMoR/dcUZyw');
expect(
() => Argon2Context.fromEncoded(cryptData),
throwsA(isA<ArgumentError>()
.having((e) => e.message, 'message', 'Missing parameter: m')),
);
});
test('Throws if encoded string is missing "t" parameter', () {
var cryptData = fromCrypt(
r'$argon2i$v=19$m=8192,p=2$c29tZSBzYWx0$CZOgzrCgoVUzMoR/dcUZyw');
expect(
() => Argon2Context.fromEncoded(cryptData),
throwsA(isA<ArgumentError>()
.having((e) => e.message, 'message', 'Missing parameter: t')),
);
});
test('Throws if encoded string is missing "p" parameter', () {
var cryptData = fromCrypt(
r'$argon2i$v=19$m=8192,t=3$c29tZSBzYWx0$CZOgzrCgoVUzMoR/dcUZyw');
expect(
() => Argon2Context.fromEncoded(cryptData),
throwsA(isA<ArgumentError>()
.having((e) => e.message, 'message', 'Missing parameter: p')),
);
});
});
}