// 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.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().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().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().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().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().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().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().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().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.generate(7, (i) => i), ), throwsA(isA().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().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().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().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()), ); }); 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() .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() .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() .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() .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() .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() .having((e) => e.message, 'message', 'Missing parameter: p')), ); }); }); }