├── lib ├── argon2.dart └── src │ ├── argon2_platform.dart │ ├── argon2_platform_js.dart │ ├── argon2_platform_generic.dart │ ├── argon2_platform_io.dart │ ├── argon2_extension.dart │ ├── argon2_parameters.dart │ ├── argon2_utils.dart │ └── argon2_base.dart ├── .gitignore ├── pubspec.yaml ├── analysis_options.yaml ├── CHANGELOG.md ├── argon2.iml ├── example └── argon2_example.dart ├── .github └── workflows │ └── dart.yml ├── test ├── argon2_numbers_test.dart └── argon2_test.dart ├── README.md └── LICENSE /lib/argon2.dart: -------------------------------------------------------------------------------- 1 | /// Argon2 - Password Hashing Algorithm (pure Dart) 2 | library argon2; 3 | 4 | export 'src/argon2_base.dart'; 5 | export 'src/argon2_parameters.dart'; 6 | export 'src/argon2_extension.dart'; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | 8 | # Omit committing pubspec.lock for library packages; see 9 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 10 | pubspec.lock 11 | -------------------------------------------------------------------------------- /lib/src/argon2_platform.dart: -------------------------------------------------------------------------------- 1 | import 'argon2_platform_generic.dart' 2 | if (dart.library.io) 'argon2_platform_io.dart' 3 | if (dart.library.js) 'argon2_platform_js.dart'; 4 | 5 | abstract class Argon2Platform { 6 | static Argon2Platform get instance => getArgon2Platform(); 7 | 8 | const Argon2Platform(); 9 | 10 | String get platform; 11 | 12 | bool get isNative; 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/argon2_platform_js.dart: -------------------------------------------------------------------------------- 1 | import 'argon2_platform.dart'; 2 | 3 | class Argon2PlatformJS extends Argon2Platform { 4 | static final Argon2PlatformJS instance = Argon2PlatformJS(); 5 | 6 | const Argon2PlatformJS(); 7 | 8 | @override 9 | String get platform => 'JS'; 10 | 11 | @override 12 | bool get isNative => true; 13 | } 14 | 15 | Argon2Platform getArgon2Platform() => Argon2PlatformJS.instance; 16 | -------------------------------------------------------------------------------- /lib/src/argon2_platform_generic.dart: -------------------------------------------------------------------------------- 1 | import 'argon2_platform.dart'; 2 | 3 | class Argon2PlatformGeneric extends Argon2Platform { 4 | static final Argon2PlatformGeneric instance = Argon2PlatformGeneric(); 5 | 6 | const Argon2PlatformGeneric(); 7 | 8 | @override 9 | bool get isNative => false; 10 | 11 | @override 12 | String get platform => 'generic'; 13 | } 14 | 15 | Argon2Platform getArgon2Platform() => Argon2PlatformGeneric.instance; 16 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: argon2 2 | description: Pure Dart Argon2 algorithm (the winner of the Password Hash Competition 2015) for all Dart platforms (JS/Web, Flutter, VM/Native). 3 | version: 1.0.1 4 | homepage: https://github.com/gmpassos/argon2 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | pointycastle: ^3.1.2 11 | hex: ^0.2.0 12 | 13 | dev_dependencies: 14 | pedantic: ^1.11.0 15 | test: ^1.17.1 16 | dependency_validator: ^3.1.0 17 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Defines a default set of lint rules enforced for projects at Google. For 2 | # details and rationale, see 3 | # https://github.com/dart-lang/pedantic#enabled-lints. 4 | 5 | include: package:pedantic/analysis_options.yaml 6 | 7 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 8 | 9 | # Uncomment to specify additional rules. 10 | # linter: 11 | # rules: 12 | # - camel_case_types 13 | 14 | # analyzer: 15 | # exclude: 16 | # - path/to/excluded/files/** 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 2 | 3 | - Now using fixed `pointycastle` `Blake2b`: 4 | - pointycastle: ^3.1.2 5 | - https://github.com/bcgit/pc-dart/pull/108 6 | - Removed `argon2_blake2b.dart`, not necessary anymore. 7 | 8 | ## 1.0.0-no-null-safety 9 | 10 | - Same as `v1.0.0` but without Null Safety and using `pointycastle v2.0.1` (also without Null Safety). 11 | 12 | ## 1.0.0 13 | 14 | - Initial version converted from: 15 | - https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/generators/Argon2BytesGenerator.java 16 | -------------------------------------------------------------------------------- /argon2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/src/argon2_platform_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'argon2_platform.dart'; 4 | 5 | class Argon2PlatformIO extends Argon2Platform { 6 | static final Argon2PlatformIO instance = Argon2PlatformIO(); 7 | 8 | const Argon2PlatformIO(); 9 | 10 | @override 11 | String get platform { 12 | if (Platform.isAndroid) return 'Android'; 13 | if (Platform.isIOS) return 'iOS'; 14 | if (Platform.isWindows) return 'Windows'; 15 | if (Platform.isLinux) return 'Linux'; 16 | if (Platform.isFuchsia) return 'Fuchsia'; 17 | if (Platform.isMacOS) return 'MacOS'; 18 | 19 | return 'native'; 20 | } 21 | 22 | @override 23 | bool get isNative => true; 24 | } 25 | 26 | Argon2Platform getArgon2Platform() => Argon2PlatformIO.instance; 27 | -------------------------------------------------------------------------------- /example/argon2_example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:argon2/argon2.dart'; 4 | 5 | void main() { 6 | var password = 'pass123456'; 7 | var salt = 'somesalt'.toBytesLatin1(); 8 | 9 | var parameters = Argon2Parameters( 10 | Argon2Parameters.ARGON2_i, 11 | salt, 12 | version: Argon2Parameters.ARGON2_VERSION_10, 13 | iterations: 2, 14 | memoryPowerOf2: 16, 15 | ); 16 | 17 | print('Parameters: $parameters'); 18 | 19 | var argon2 = Argon2BytesGenerator(); 20 | 21 | argon2.init(parameters); 22 | 23 | var passwordBytes = parameters.converter.convert(password); 24 | 25 | print('Generating key from password...'); 26 | 27 | var result = Uint8List(32); 28 | argon2.generateBytes(passwordBytes, result, 0, result.length); 29 | 30 | var resultHex = result.toHexString(); 31 | 32 | print('Result: $resultHex'); 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | container: 15 | image: google/dart:latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Dart version 20 | run: dart --version 21 | - name: Install dependencies 22 | run: dart pub get 23 | - name: dart format 24 | run: dart format -o none --set-exit-if-changed . 25 | - name: dart analyze 26 | run: dart analyze --fatal-infos --fatal-warnings . 27 | - name: dependency_validator 28 | run: dart run dependency_validator 29 | - name: Run tests 30 | run: dart test 31 | - name: dart pub publish --dry-run 32 | run: dart pub publish --dry-run 33 | 34 | -------------------------------------------------------------------------------- /lib/src/argon2_extension.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:hex/hex.dart'; 5 | 6 | import 'argon2_platform.dart'; 7 | 8 | final bool _isNative = Argon2Platform.instance.isNative; 9 | 10 | extension ListExtension on List { 11 | List copy() => List.from(this); 12 | 13 | void setFrom(int startIndex, List from, int fromIndex, int length) { 14 | for (var i = 0; i < length; ++i) { 15 | this[startIndex + i] = from[fromIndex + i]; 16 | } 17 | } 18 | } 19 | 20 | extension ListIntExtension on List { 21 | Uint8List copy() => Uint8List.fromList(this); 22 | 23 | Uint8List toUint8List() => 24 | this is Uint8List ? this as Uint8List : Uint8List.fromList(this); 25 | 26 | void clear() => setAllElementsTo(0); 27 | 28 | void setAllElementsTo(int value) { 29 | for (var i = length - 1; i >= 0; --i) { 30 | this[i] = value; 31 | } 32 | } 33 | } 34 | 35 | extension StringExtension on String { 36 | Uint8List toBytesUTF8() => utf8.encode(this).toUint8List(); 37 | 38 | Uint8List toBytesLatin1() => latin1.encode(this); 39 | } 40 | 41 | extension Uint8ListExtension on Uint8List { 42 | ByteData get asByteData => ByteData.view(buffer); 43 | 44 | void reset() => setAllElementsTo(0); 45 | 46 | String toHexString() => HexCodec().encode(this); 47 | } 48 | 49 | extension IntExtension on int { 50 | int tripleShift32(int count) { 51 | if (_isNative) { 52 | return (this >> count) & ~(-1 << (32 - count)); 53 | } else { 54 | // assumes this > -(2^32 -1) 55 | count &= 0x1f; 56 | if (this >= 0) { 57 | return (this >> count); 58 | } else { 59 | return (this >> count) ^ ((0xFFFFFFFF) ^ ((1 << (32 - count)) - 1)); 60 | } 61 | } 62 | } 63 | 64 | int tripleShift64(int count) { 65 | if (_isNative) { 66 | return (this >> count) & ~(-1 << (64 - count)); 67 | } else { 68 | count &= 0x1f; 69 | if (this >= 0) { 70 | return (this >> count); 71 | } else { 72 | return (this >> count) ^ 73 | ((0xFFFFFFFFFFFFFFFF) ^ ((1 << (64 - count)) - 1)); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/argon2_numbers_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:argon2/src/argon2_extension.dart'; 4 | import 'package:argon2/src/argon2_utils.dart'; 5 | import 'package:hex/hex.dart'; 6 | import 'package:pointycastle/digests/blake2b.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | group('IntExtension', () { 11 | setUp(() {}); 12 | 13 | test('tripleShift32', () { 14 | //positive 15 | expect(((3185753779 * 3185753779) & 0xFFFFFFFF).tripleShift32(32), 16 | equals(0)); 17 | 18 | expect(25.tripleShift32(25), equals(0)); 19 | expect(13123423.tripleShift32(5), equals(410106)); 20 | 21 | //zero 22 | expect(0.tripleShift32(23), equals(0)); 23 | 24 | //negative 25 | expect((-12245234).tripleShift32(11), equals(2091172)); 26 | expect((-2938648).tripleShift32(4), equals(268251790)); 27 | }); 28 | 29 | test('tripleShift64', () { 30 | //positive 31 | expect(((3185753779 * 3185753779) & 0xFFFFFFFFFFFFFFFF).tripleShift64(32), 32 | equals(2363004521)); 33 | 34 | expect(25.tripleShift64(25), equals(0)); 35 | expect(13123423.tripleShift64(5), equals(410106)); 36 | 37 | //zero 38 | expect(0.tripleShift64(23), equals(0)); 39 | 40 | //negative 41 | expect((-12245234).tripleShift64(11), equals(9007199254735012)); 42 | expect((-2938648).tripleShift64(4), equals(1152921504606663310)); 43 | }); 44 | }); 45 | 46 | group('Longs', () { 47 | setUp(() {}); 48 | 49 | test('rotateRight', () { 50 | expect(Longs.rotateRight(2, 32), equals(8589934592)); 51 | expect(Longs.rotateRight(8589934594, 24), equals(2199023256064)); 52 | expect(Longs.rotateRight(2207613192706, 16), equals(721138890366386176)); 53 | expect(Longs.rotateRight(721141097979576832, 63), 54 | equals(1442282195959153664)); 55 | 56 | expect(Longs.rotateRight(432913097719682307, 32), 57 | equals(3472861365385954347)); 58 | expect(Longs.rotateRight(3836265417586443819, 24), 59 | equals(6558977414970937959)); 60 | expect(Longs.rotateRight(5274078427372293166, 16), 61 | equals(-8345652583420728045)); 62 | expect(Longs.rotateRight(-6821524402594562105, 63), 63 | equals(4803695268520427407)); 64 | }); 65 | }); 66 | 67 | group('Blake2bDigest', () { 68 | setUp(() {}); 69 | 70 | test('digest 32', () { 71 | var data = Uint8List.fromList(List.generate(1024, (i) => i)); 72 | 73 | var out = Uint8List(32); 74 | 75 | var blake = Blake2bDigest(digestSize: 32); 76 | print(blake); 77 | 78 | blake.init(); 79 | blake.update(data, 0, data.length); 80 | blake.update(data, 0, data.length); 81 | blake.doFinal(out, 0); 82 | 83 | expect( 84 | HexCodec().encode(out), 85 | equals( 86 | '6ed9bf545705dba5971e83a1f2a46a9dd5ac2fe8a934f13cee8d353003eaf908')); 87 | }); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/argon2_parameters.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'argon2_extension.dart'; 5 | 6 | /// The Argon2 parameters. 7 | class Argon2Parameters { 8 | static const int ARGON2_d = 0x00; 9 | static const int ARGON2_i = 0x01; 10 | static const int ARGON2_id = 0x02; 11 | 12 | static const int ARGON2_VERSION_10 = 0x10; 13 | static const int ARGON2_VERSION_13 = 0x13; 14 | 15 | static const int DEFAULT_ITERATIONS = 3; 16 | static const int DEFAULT_MEMORY_COST = 12; 17 | static const int DEFAULT_LANES = 1; 18 | static const int DEFAULT_TYPE = ARGON2_i; 19 | static const int DEFAULT_VERSION = ARGON2_VERSION_13; 20 | 21 | final int type; 22 | 23 | final Uint8List _salt; 24 | final Uint8List? _secret; 25 | final Uint8List? _additional; 26 | 27 | final int iterations; 28 | final int memory; 29 | final int lanes; 30 | 31 | final int version; 32 | 33 | final CharToByteConverter converter; 34 | 35 | Argon2Parameters( 36 | this.type, 37 | this._salt, { 38 | Uint8List? secret, 39 | Uint8List? additional, 40 | this.iterations = DEFAULT_ITERATIONS, 41 | int? memoryPowerOf2, 42 | int? memory, 43 | this.lanes = DEFAULT_LANES, 44 | this.version = DEFAULT_VERSION, 45 | this.converter = CharToByteConverter.UTF8, 46 | }) : memory = memoryPowerOf2 != null 47 | ? 1 << memoryPowerOf2 48 | : (memory ?? (1 << DEFAULT_MEMORY_COST)), 49 | _secret = secret, 50 | _additional = additional; 51 | 52 | Uint8List get salt => _salt; 53 | 54 | Uint8List? get secret => _secret; 55 | 56 | Uint8List? get additional => _additional; 57 | 58 | void clear() { 59 | _salt.clear(); 60 | _secret?.clear(); 61 | _additional?.clear(); 62 | } 63 | 64 | @override 65 | String toString() { 66 | return 'Argon2Parameters{ type: $type, iterations: $iterations, memory: $memory, lanes: $lanes, version: $version, converter: ${converter.name} }'; 67 | } 68 | } 69 | 70 | abstract class CharToByteConverter { 71 | static const UTF8 = CharToByteConverterUTF8(); 72 | static const ASCII = CharToByteConverterASCII(); 73 | 74 | /// Return the type name of the conversion. 75 | String get name; 76 | 77 | /// Return a byte encoded representation of the passed in [password]. 78 | /// - [param] password the characters to encode. 79 | Uint8List convert(String password); 80 | } 81 | 82 | class CharToByteConverterUTF8 implements CharToByteConverter { 83 | const CharToByteConverterUTF8(); 84 | 85 | @override 86 | String get name => 'UTF8'; 87 | 88 | @override 89 | Uint8List convert(String password) => utf8.encode(password).toUint8List(); 90 | } 91 | 92 | class CharToByteConverterASCII implements CharToByteConverter { 93 | const CharToByteConverterASCII(); 94 | 95 | @override 96 | String get name => 'ASCII'; 97 | 98 | @override 99 | Uint8List convert(String password) => latin1.encode(password).toUint8List(); 100 | } 101 | -------------------------------------------------------------------------------- /test/argon2_test.dart: -------------------------------------------------------------------------------- 1 | @Timeout(Duration(seconds: 180)) 2 | 3 | import 'dart:typed_data'; 4 | 5 | import 'package:argon2/argon2.dart'; 6 | import 'package:hex/hex.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | const int DEFAULT_OUTPUTLEN = 32; 10 | 11 | void main() { 12 | group('Argon2BytesGenerator', () { 13 | test('Hash Tests', () { 14 | var version = Argon2Parameters.ARGON2_VERSION_10; 15 | 16 | /* Multiple test cases for various input values */ 17 | _hashTest( 18 | version, 19 | 2, 20 | 16, 21 | 'password', 22 | 'somesalt', 23 | 'f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694', 24 | DEFAULT_OUTPUTLEN); 25 | 26 | _hashTest( 27 | version, 28 | 2, 29 | 20, 30 | 'password', 31 | 'somesalt', 32 | '9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9', 33 | DEFAULT_OUTPUTLEN); 34 | 35 | _hashTest( 36 | version, 37 | 2, 38 | 16, 39 | 'password', 40 | 'diffsalt', 41 | '79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497', 42 | DEFAULT_OUTPUTLEN); 43 | 44 | _hashTest( 45 | version, 46 | 2, 47 | 16, 48 | 'password', 49 | 'diffsalt', 50 | '1a097a5d1c80e579583f6e19c7e4763ccb7c522ca85b7d58143738e12ca39f8e6e42734c950ff2463675b97c37ba' 51 | '39feba4a9cd9cc5b4c798f2aaf70eb4bd044c8d148decb569870dbd923430b82a083f284beae777812cce18cdac68ee8ccef' 52 | 'c6ec9789f30a6b5a034591f51af830f4', 53 | 112); 54 | 55 | version = Argon2Parameters.ARGON2_VERSION_13; 56 | 57 | /* Multiple test cases for various input values */ 58 | _hashTest( 59 | version, 60 | 2, 61 | 16, 62 | 'password', 63 | 'somesalt', 64 | 'c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0', 65 | DEFAULT_OUTPUTLEN); 66 | 67 | _hashTest( 68 | version, 69 | 2, 70 | 20, 71 | 'password', 72 | 'somesalt', 73 | 'd1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41', 74 | DEFAULT_OUTPUTLEN); 75 | 76 | _hashTest( 77 | version, 78 | 2, 79 | 18, 80 | 'password', 81 | 'somesalt', 82 | '296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb', 83 | DEFAULT_OUTPUTLEN); 84 | 85 | _hashTest( 86 | version, 87 | 2, 88 | 8, 89 | 'password', 90 | 'somesalt', 91 | '89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f', 92 | DEFAULT_OUTPUTLEN); 93 | }); 94 | }); 95 | } 96 | 97 | void _hashTest(int version, int iterations, int memoryPowerOf2, String password, 98 | String salt, String passwordRef, int outputLength) { 99 | var parameters = Argon2Parameters( 100 | Argon2Parameters.ARGON2_i, 101 | salt.toBytesLatin1(), 102 | version: version, 103 | iterations: iterations, 104 | memoryPowerOf2: memoryPowerOf2, 105 | ); 106 | 107 | print('_hashTest> $parameters'); 108 | 109 | var gen = Argon2BytesGenerator(); 110 | gen.init(parameters); 111 | 112 | var result = Uint8List(outputLength); 113 | 114 | var passwordBytes = parameters.converter.convert(password); 115 | 116 | gen.generateBytes(passwordBytes, result, 0, result.length); 117 | 118 | expect(result, HexCodec().decode(passwordRef).toUint8List()); 119 | 120 | result.reset(); 121 | 122 | // Should be able to re-use generator after successful use 123 | gen.generateBytesFromString(password, result, 0, result.length); 124 | expect(result, HexCodec().decode(passwordRef).toUint8List()); 125 | } 126 | -------------------------------------------------------------------------------- /lib/src/argon2_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'argon2_extension.dart'; 4 | 5 | abstract class Pack { 6 | static int littleEndianToLong(Uint8List bs, int off) { 7 | var data = bs.asByteData; 8 | return data.getInt64(off, Endian.little); 9 | } 10 | 11 | static void littleEndianToLongAtList(Uint8List bs, int off, Uint64List ns) { 12 | for (var i = 0; i < ns.length; ++i) { 13 | ns[i] = littleEndianToLong(bs, off); 14 | off += 8; 15 | } 16 | } 17 | 18 | static Uint8List longToLittleEndianList(int n) { 19 | var bs = Uint8List(8); 20 | longToLittleEndianAtList(n, bs, 0); 21 | return bs; 22 | } 23 | 24 | static void longToLittleEndianAtList(int n, Uint8List bs, int off) { 25 | var data = bs.asByteData; 26 | data.setInt64(off, n, Endian.little); 27 | } 28 | 29 | static Uint8List longListToLittleEndianList(Uint64List ns) { 30 | var bs = Uint8List(8 * ns.length); 31 | longListToLittleEndianAtList(ns, bs, 0); 32 | return bs; 33 | } 34 | 35 | static void longListToLittleEndianAtList( 36 | Uint64List ns, Uint8List bs, int off) { 37 | for (var i = 0; i < ns.length; ++i) { 38 | longToLittleEndianAtList(ns[i], bs, off); 39 | off += 8; 40 | } 41 | } 42 | 43 | static int littleEndianToInt(Uint8List bs, int off) { 44 | var data = bs.asByteData; 45 | return data.getInt32(off, Endian.little); 46 | } 47 | 48 | static Uint8List intToLittleEndianList(int n) { 49 | var bs = Uint8List(4); 50 | intToLittleEndianAtList(n, bs, 0); 51 | return bs; 52 | } 53 | 54 | static void intToLittleEndianAtList(int n, Uint8List bs, int off) { 55 | var data = bs.asByteData; 56 | data.setInt32(off, n, Endian.little); 57 | } 58 | 59 | static Uint8List intListToLittleEndian(Uint32List ns) { 60 | var bs = Uint8List(4 * ns.length); 61 | intListToLittleEndianAtList(ns, bs, 0); 62 | return bs; 63 | } 64 | 65 | static void intListToLittleEndianAtList( 66 | Uint32List ns, Uint8List bs, int off) { 67 | for (var i = 0; i < ns.length; ++i) { 68 | intToLittleEndianAtList(ns[i], bs, off); 69 | off += 4; 70 | } 71 | } 72 | } 73 | 74 | abstract class Longs { 75 | static const _MASK_32 = 0xFFFFFFFF; 76 | 77 | static const _MASK32_HI_BITS = [ 78 | 0xFFFFFFFF, 79 | 0x7FFFFFFF, 80 | 0x3FFFFFFF, 81 | 0x1FFFFFFF, 82 | 0x0FFFFFFF, 83 | 0x07FFFFFF, 84 | 0x03FFFFFF, 85 | 0x01FFFFFF, 86 | 0x00FFFFFF, 87 | 0x007FFFFF, 88 | 0x003FFFFF, 89 | 0x001FFFFF, 90 | 0x000FFFFF, 91 | 0x0007FFFF, 92 | 0x0003FFFF, 93 | 0x0001FFFF, 94 | 0x0000FFFF, 95 | 0x00007FFF, 96 | 0x00003FFF, 97 | 0x00001FFF, 98 | 0x00000FFF, 99 | 0x000007FF, 100 | 0x000003FF, 101 | 0x000001FF, 102 | 0x000000FF, 103 | 0x0000007F, 104 | 0x0000003F, 105 | 0x0000001F, 106 | 0x0000000F, 107 | 0x00000007, 108 | 0x00000003, 109 | 0x00000001, 110 | 0x00000000 111 | ]; 112 | 113 | static int rotateRight(int n, int distance) { 114 | if (distance == 0) { 115 | // do nothing: 116 | return n; 117 | } 118 | 119 | var hi32 = (n >> 32) & 0xFFFFFFFF; 120 | var lo32 = (n) & 0xFFFFFFFF; 121 | 122 | if (distance >= 32) { 123 | var swap = hi32; 124 | hi32 = lo32; 125 | lo32 = swap; 126 | distance -= 32; 127 | 128 | if (distance == 0) { 129 | return (hi32 << 32) | lo32; 130 | } 131 | } 132 | 133 | final distance32 = (32 - distance); 134 | final m = _MASK32_HI_BITS[distance32]; 135 | 136 | final hi32cp = hi32; 137 | 138 | hi32 = hi32 >> distance; 139 | hi32 |= (((lo32 & m) << distance32) & _MASK_32); 140 | 141 | lo32 = lo32 >> distance; 142 | lo32 |= (((hi32cp & m) << distance32) & _MASK_32); 143 | 144 | return (hi32 << 32) | lo32; 145 | } 146 | 147 | static int toInt32(int n) => (n & 0xFFFFFFFF); 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # argon2 2 | 3 | [![pub package](https://img.shields.io/pub/v/argon2.svg?logo=dart&logoColor=00b9fc)](https://pub.dev/packages/argon2) 4 | [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) 5 | 6 | [![CI](https://img.shields.io/github/workflow/status/gmpassos/argon2/Dart%20CI/master?logo=github-actions&logoColor=white)](https://github.com/gmpassos/argon2/actions) 7 | [![GitHub Tag](https://img.shields.io/github/v/tag/gmpassos/argon2?logo=git&logoColor=white)](https://github.com/gmpassos/argon2/releases) 8 | [![New Commits](https://img.shields.io/github/commits-since/gmpassos/argon2/latest?logo=git&logoColor=white)](https://github.com/gmpassos/argon2/network) 9 | [![Last Commits](https://img.shields.io/github/last-commit/gmpassos/argon2?logo=git&logoColor=white)](https://github.com/gmpassos/argon2/commits/master) 10 | [![Pull Requests](https://img.shields.io/github/issues-pr/gmpassos/argon2?logo=github&logoColor=white)](https://github.com/gmpassos/argon2/pulls) 11 | [![Code size](https://img.shields.io/github/languages/code-size/gmpassos/argon2?logo=github&logoColor=white)](https://github.com/gmpassos/argon2) 12 | [![License](https://img.shields.io/github/license/gmpassos/argon2?logo=open-source-initiative&logoColor=green)](https://github.com/gmpassos/argon2/blob/master/LICENSE) 13 | 14 | Pure Dart Argon2 algorithm (the winner of the Password Hash Competition 2015) 15 | for all Dart platforms (JS/Web, Flutter, VM/Native). 16 | 17 | Based on the results of: 18 | - https://password-hashing.net/ 19 | - https://www.ietf.org/archive/id/draft-irtf-cfrg-argon2-03.txt 20 | 21 | Converted to Dart from: 22 | - https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/generators/Argon2BytesGenerator.java 23 | - License: MIT 24 | 25 | ## Usage 26 | 27 | ```dart 28 | import 'dart:typed_data'; 29 | 30 | import 'package:argon2/argon2.dart'; 31 | 32 | void main() { 33 | var password = 'pass123456'; 34 | var salt = 'somesalt'.toBytesLatin1(); 35 | 36 | var parameters = Argon2Parameters( 37 | Argon2Parameters.ARGON2_i, 38 | salt, 39 | version: Argon2Parameters.ARGON2_VERSION_10, 40 | iterations: 2, 41 | memoryPowerOf2: 16, 42 | ); 43 | 44 | print('Parameters: $parameters'); 45 | 46 | var argon2 = Argon2BytesGenerator(); 47 | 48 | argon2.init(parameters); 49 | 50 | var passwordBytes = parameters.converter.convert(password); 51 | 52 | print('Generating key from password...'); 53 | 54 | var result = Uint8List(32); 55 | argon2.generateBytes(passwordBytes, result, 0, result.length); 56 | 57 | var resultHex = result.toHexString(); 58 | 59 | print('Result: $resultHex'); 60 | } 61 | 62 | ``` 63 | 64 | Output: 65 | 66 | ```text 67 | Parameters: Argon2Parameters{ type: 1, iterations: 2, memory: 65536, lanes: 1, version: 16, converter: UTF8 } 68 | Generating key from password... 69 | Result: 297f7d074e07aaa46316ea006c1440dfe5707746426ec1df6c1d303ab5fd5533 70 | ``` 71 | 72 | ## Source 73 | 74 | The official source code is [hosted @ GitHub][github_argon2]: 75 | 76 | - https://github.com/gmpassos/argon2 77 | 78 | [github_argon2]: https://github.com/gmpassos/argon2 79 | 80 | # Features and bugs 81 | 82 | Please file feature requests and bugs at the [issue tracker][tracker]. 83 | 84 | # Contribution 85 | 86 | Any help from the open-source community is always welcome and needed: 87 | - Found an issue? 88 | - Please fill a bug report with details. 89 | - Wish a feature? 90 | - Open a feature request with use cases. 91 | - Are you using and liking the project? 92 | - Promote the project: create an article, do a post or make a donation. 93 | - Are you a developer? 94 | - Fix a bug and send a pull request. 95 | - Implement a new feature. 96 | - Improve the Unit Tests. 97 | - Have you already helped in any way? 98 | - **Many thanks from me, the contributors and everybody that uses this project!** 99 | 100 | 101 | [tracker]: https://github.com/gmpassos/argon2/issues 102 | 103 | # Author 104 | 105 | Graciliano M. Passos: [gmpassos@GitHub][github]. 106 | 107 | [github]: https://github.com/gmpassos 108 | 109 | ## License 110 | 111 | [Apache License - Version 2.0][apache_license] 112 | 113 | [apache_license]: https://www.apache.org/licenses/LICENSE-2.0.txt 114 | 115 | *Please, provide a reference to this project and the author for 116 | any derivative work.* 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /lib/src/argon2_base.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:argon2/src/argon2_utils.dart'; 4 | import 'package:pointycastle/api.dart'; 5 | import 'package:pointycastle/digests/blake2b.dart'; 6 | 7 | import 'argon2_extension.dart'; 8 | import 'argon2_parameters.dart'; 9 | 10 | /// Argon2 PBKDF. 11 | /// 12 | /// Based on the results of: 13 | /// - https://password-hashing.net/ 14 | /// - https://www.ietf.org/archive/id/draft-irtf-cfrg-argon2-03.txt 15 | /// 16 | /// Converted to Dart from: 17 | /// - https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/generators/Argon2BytesGenerator.java 18 | /// 19 | /// LICENSE (MIT): 20 | /// ```text 21 | /// Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) 22 | ///

23 | /// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 24 | /// and associated documentation files (the "Software"), to deal in the Software without restriction, 25 | /// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 26 | /// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 27 | /// subject to the following conditions: 28 | ///

29 | /// The above copyright notice and this permission notice shall be included in all copies or substantial 30 | /// portions of the Software. 31 | ///

32 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 33 | /// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 34 | /// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 35 | /// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 36 | /// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 37 | /// DEALINGS IN THE SOFTWARE. 38 | /// ``` 39 | /// 40 | class Argon2BytesGenerator { 41 | static const int ARGON2_BLOCK_SIZE = 1024; 42 | static const int ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE ~/ 8; 43 | 44 | static const int ARGON2_ADDRESSES_IN_BLOCK = 128; 45 | 46 | static const int ARGON2_PREHASH_DIGEST_LENGTH = 64; 47 | static const int ARGON2_PREHASH_SEED_LENGTH = 72; 48 | 49 | static const int ARGON2_SYNC_POINTS = 4; 50 | 51 | /// Minimum and maximum number of lanes (degree of parallelism). 52 | static const int MIN_PARALLELISM = 1; 53 | static const int MAX_PARALLELISM = 16777216; 54 | 55 | /// Minimum and maximum digest size in bytes. 56 | static const int MIN_OUTLEN = 4; 57 | 58 | /// Minimum and maximum number of passes. 59 | static const int MIN_ITERATIONS = 1; 60 | 61 | static const int M32L = 0xFFFFFFFFFFFFFFFF; 62 | 63 | static final Uint8List _ZERO_BYTES = Uint8List(4); 64 | 65 | late Argon2Parameters _parameters; 66 | late List<_Block> _memory; 67 | late int _segmentLength; 68 | late int _laneLength; 69 | 70 | Argon2BytesGenerator(); 71 | 72 | Argon2Parameters get parameters => _parameters; 73 | 74 | /// Initialise the Argon2BytesGenerator from the parameters. 75 | /// - [param] parameters Argon2 configuration. 76 | void init(Argon2Parameters parameters) { 77 | _parameters = parameters; 78 | 79 | if (parameters.lanes < Argon2BytesGenerator.MIN_PARALLELISM) { 80 | throw ArgumentError.value(parameters.lanes, 'parameters.lanes', 81 | 'lanes must be greater than ${Argon2BytesGenerator.MIN_PARALLELISM}'); 82 | } else if (parameters.lanes > Argon2BytesGenerator.MAX_PARALLELISM) { 83 | throw ArgumentError.value(parameters.lanes, 'parameters.lanes', 84 | 'lanes must be less than ${Argon2BytesGenerator.MAX_PARALLELISM}'); 85 | } else if (parameters.memory < 2 * parameters.lanes) { 86 | throw ArgumentError.value(parameters.memory, 'parameters.memory', 87 | 'memory is less than: ${(2 * parameters.lanes)} expected ${(2 * parameters.lanes)}'); 88 | } else if (parameters.iterations < Argon2BytesGenerator.MIN_ITERATIONS) { 89 | throw ArgumentError.value(parameters.iterations, 'parameters.iterations', 90 | 'iterations is less than: ${Argon2BytesGenerator.MIN_ITERATIONS}'); 91 | } 92 | 93 | _doInit(parameters); 94 | } 95 | 96 | int generateBytesFromString(String password, Uint8List out, 97 | [int outOff = 0, int? outLen]) => 98 | generateBytes( 99 | _parameters.converter.convert(password), out, outOff, outLen); 100 | 101 | int generateBytes(Uint8List password, Uint8List out, 102 | [int outOff = 0, int? outLen]) { 103 | outLen ??= out.length; 104 | 105 | if (outLen < Argon2BytesGenerator.MIN_OUTLEN) { 106 | throw ArgumentError.value(outLen, 'outLen', 107 | 'output length less than ${Argon2BytesGenerator.MIN_OUTLEN}'); 108 | } 109 | 110 | var tmpBlockBytes = Uint8List(ARGON2_BLOCK_SIZE); 111 | 112 | _initialize(tmpBlockBytes, password, outLen); 113 | _fillMemoryBlocks(); 114 | _digest(tmpBlockBytes, out, outOff, outLen); 115 | 116 | _reset(); 117 | 118 | return outLen; 119 | } 120 | 121 | /// Clear memory. 122 | void _reset() { 123 | for (var i = _memory.length - 1; i >= 0; --i) { 124 | var b = _memory[i]; 125 | b.clear(); 126 | } 127 | } 128 | 129 | void _doInit(Argon2Parameters parameters) { 130 | /* 2. Align memory size */ 131 | /* Minimum memoryBlocks = 8L blocks, where L is the number of lanes */ 132 | var memoryBlocks = parameters.memory; 133 | 134 | if (memoryBlocks < 135 | 2 * Argon2BytesGenerator.ARGON2_SYNC_POINTS * parameters.lanes) { 136 | memoryBlocks = 137 | 2 * Argon2BytesGenerator.ARGON2_SYNC_POINTS * parameters.lanes; 138 | } 139 | 140 | _segmentLength = memoryBlocks ~/ 141 | (parameters.lanes * Argon2BytesGenerator.ARGON2_SYNC_POINTS); 142 | _laneLength = _segmentLength * Argon2BytesGenerator.ARGON2_SYNC_POINTS; 143 | 144 | /* Ensure that all segments have equal length */ 145 | memoryBlocks = _segmentLength * 146 | (parameters.lanes * Argon2BytesGenerator.ARGON2_SYNC_POINTS); 147 | 148 | _initMemory(memoryBlocks); 149 | } 150 | 151 | void _initMemory(int memoryBlocks) { 152 | _memory = List<_Block>.generate(memoryBlocks, (i) => _Block()); 153 | } 154 | 155 | void _fillMemoryBlocks() { 156 | var filler = _FillBlock(); 157 | var position = _Position(); 158 | for (var pass = 0; pass < _parameters.iterations; ++pass) { 159 | position.pass = pass; 160 | 161 | for (var slice = 0; slice < ARGON2_SYNC_POINTS; ++slice) { 162 | position.slice = slice; 163 | 164 | for (var lane = 0; lane < _parameters.lanes; ++lane) { 165 | position.lane = lane; 166 | 167 | _fillSegment(filler, position); 168 | } 169 | } 170 | } 171 | } 172 | 173 | void _fillSegment(_FillBlock filler, _Position position) { 174 | _Block? addressBlock; 175 | _Block? inputBlock; 176 | 177 | var dataIndependentAddressing = _isDataIndependentAddressing(position); 178 | var startingIndex = _getStartingIndex(position); 179 | var currentOffset = position.lane * _laneLength + 180 | position.slice * _segmentLength + 181 | startingIndex; 182 | var prevOffset = _getPrevOffset(currentOffset); 183 | 184 | if (dataIndependentAddressing) { 185 | addressBlock = filler.addressBlock.clear(); 186 | inputBlock = filler.inputBlock.clear(); 187 | 188 | _initAddressBlocks(filler, position, inputBlock, addressBlock); 189 | } 190 | 191 | final withXor = _isWithXor(position); 192 | 193 | for (var index = startingIndex; index < _segmentLength; ++index) { 194 | var pseudoRandom = _getPseudoRandom(filler, index, addressBlock, 195 | inputBlock, prevOffset, dataIndependentAddressing); 196 | var refLane = _getRefLane(position, pseudoRandom); 197 | var refColumn = _getRefColumn( 198 | position, index, pseudoRandom, refLane == position.lane); 199 | 200 | /* 2 Creating a new block */ 201 | var prevBlock = _memory[prevOffset]; 202 | var refBlock = _memory[((_laneLength) * refLane + refColumn)]; 203 | var currentBlock = _memory[currentOffset]; 204 | 205 | if (withXor) { 206 | filler.fillBlockWithXor(prevBlock, refBlock, currentBlock); 207 | } else { 208 | filler.fillBlock2(prevBlock, refBlock, currentBlock); 209 | } 210 | 211 | prevOffset = currentOffset; 212 | currentOffset++; 213 | } 214 | } 215 | 216 | bool _isDataIndependentAddressing(_Position position) { 217 | return (_parameters.type == Argon2Parameters.ARGON2_i) || 218 | (_parameters.type == Argon2Parameters.ARGON2_id && 219 | (position.pass == 0) && 220 | (position.slice < ARGON2_SYNC_POINTS / 2)); 221 | } 222 | 223 | void _initAddressBlocks(_FillBlock filler, _Position position, 224 | _Block inputBlock, _Block addressBlock) { 225 | inputBlock._v[0] = _intToLong(position.pass); 226 | inputBlock._v[1] = _intToLong(position.lane); 227 | inputBlock._v[2] = _intToLong(position.slice); 228 | inputBlock._v[3] = _intToLong(_memory.length); 229 | inputBlock._v[4] = _intToLong(_parameters.iterations); 230 | inputBlock._v[5] = _intToLong(_parameters.type); 231 | 232 | if ((position.pass == 0) && (position.slice == 0)) { 233 | /* Don't forget to generate the first block of addresses: */ 234 | _nextAddresses(filler, inputBlock, addressBlock); 235 | } 236 | } 237 | 238 | bool _isWithXor(_Position position) { 239 | return !(position.pass == 0 || 240 | _parameters.version == Argon2Parameters.ARGON2_VERSION_10); 241 | } 242 | 243 | int _getPrevOffset(int currentOffset) { 244 | if (currentOffset % _laneLength == 0) { 245 | /* Last block in this lane */ 246 | return currentOffset + _laneLength - 1; 247 | } else { 248 | /* Previous block */ 249 | return currentOffset - 1; 250 | } 251 | } 252 | 253 | static int _getStartingIndex(_Position position) { 254 | if ((position.pass == 0) && (position.slice == 0)) { 255 | return 2; /* we have already generated the first two blocks */ 256 | } else { 257 | return 0; 258 | } 259 | } 260 | 261 | void _nextAddresses( 262 | _FillBlock filler, _Block inputBlock, _Block addressBlock) { 263 | inputBlock._v[6]++; 264 | filler.fillBlock(inputBlock, addressBlock); 265 | filler.fillBlock(addressBlock, addressBlock); 266 | } 267 | 268 | /* 1.2 Computing the index of the reference block */ 269 | /* 1.2.1 Taking pseudo-random value from the previous block */ 270 | int _getPseudoRandom(_FillBlock filler, int index, _Block? addressBlock, 271 | _Block? inputBlock, int prevOffset, bool dataIndependentAddressing) { 272 | if (dataIndependentAddressing) { 273 | var addressIndex = index % ARGON2_ADDRESSES_IN_BLOCK; 274 | if (addressIndex == 0) { 275 | _nextAddresses(filler, inputBlock!, addressBlock!); 276 | } 277 | return addressBlock!._v[addressIndex]; 278 | } else { 279 | return _memory[prevOffset]._v[0]; 280 | } 281 | } 282 | 283 | int _getRefLane(_Position position, int pseudoRandom) { 284 | var refLane = (pseudoRandom.tripleShift64(32) % _parameters.lanes); 285 | 286 | if ((position.pass == 0) && (position.slice == 0)) { 287 | /* Can not reference other lanes yet */ 288 | refLane = position.lane; 289 | } 290 | return refLane; 291 | } 292 | 293 | int _getRefColumn( 294 | _Position position, int index, int pseudoRandom, bool sameLane) { 295 | int referenceAreaSize; 296 | int startPosition; 297 | 298 | if (position.pass == 0) { 299 | startPosition = 0; 300 | 301 | if (sameLane) { 302 | /* The same lane => add current segment */ 303 | referenceAreaSize = position.slice * _segmentLength + index - 1; 304 | } else { 305 | /* pass == 0 && !sameLane => position.slice > 0*/ 306 | referenceAreaSize = 307 | position.slice * _segmentLength + ((index == 0) ? (-1) : 0); 308 | } 309 | } else { 310 | startPosition = ((position.slice + 1) * _segmentLength) % _laneLength; 311 | 312 | if (sameLane) { 313 | referenceAreaSize = _laneLength - _segmentLength + index - 1; 314 | } else { 315 | referenceAreaSize = 316 | _laneLength - _segmentLength + ((index == 0) ? (-1) : 0); 317 | } 318 | } 319 | 320 | var relativePosition = pseudoRandom & 0xFFFFFFFF; 321 | relativePosition = (relativePosition * relativePosition).tripleShift64(32); 322 | relativePosition = referenceAreaSize - 323 | 1 - 324 | (referenceAreaSize * relativePosition).tripleShift64(32); 325 | 326 | return (startPosition + relativePosition) % _laneLength; 327 | } 328 | 329 | void _digest(Uint8List tmpBlockBytes, Uint8List out, int outOff, int outLen) { 330 | var finalBlock = _memory[_laneLength - 1]; 331 | 332 | /* XOR the last blocks */ 333 | for (var i = 1; i < _parameters.lanes; i++) { 334 | var lastBlockInLane = i * _laneLength + (_laneLength - 1); 335 | finalBlock.xorWith(_memory[lastBlockInLane]); 336 | } 337 | 338 | finalBlock.toBytes(tmpBlockBytes); 339 | 340 | _hash(tmpBlockBytes, out, outOff, outLen); 341 | } 342 | 343 | /// H' - hash - variable length hash function 344 | void _hash(Uint8List input, Uint8List out, int outOff, int outLen) { 345 | var outLenBytes = Uint8List(4); 346 | Pack.intToLittleEndianAtList(outLen, outLenBytes, 0); 347 | 348 | var blake2bLength = 64; 349 | 350 | if (outLen <= blake2bLength) { 351 | var blake = Blake2bDigest(digestSize: outLen); 352 | 353 | blake.update(outLenBytes, 0, outLenBytes.length); 354 | blake.update(input, 0, input.length); 355 | blake.doFinal(out, outOff); 356 | } else { 357 | var digest = Blake2bDigest(digestSize: blake2bLength); 358 | 359 | var outBuffer = Uint8List(blake2bLength); 360 | 361 | /* V1 */ 362 | digest.update(outLenBytes, 0, outLenBytes.length); 363 | digest.update(input, 0, input.length); 364 | digest.doFinal(outBuffer, 0); 365 | 366 | var halfLen = blake2bLength ~/ 2, outPos = outOff; 367 | out.setFrom(outPos, outBuffer, 0, halfLen); 368 | 369 | outPos += halfLen; 370 | 371 | var r = ((outLen + 31) ~/ 32) - 2; 372 | 373 | for (var i = 2; i <= r; i++, outPos += halfLen) { 374 | digest.reset(); 375 | /* V2 to Vr */ 376 | digest.update(outBuffer, 0, outBuffer.length); 377 | digest.doFinal(outBuffer, 0); 378 | 379 | out.setFrom(outPos, outBuffer, 0, halfLen); 380 | } 381 | 382 | var lastLength = outLen - 32 * r; 383 | 384 | /* Vr+1 */ 385 | digest = Blake2bDigest(digestSize: lastLength); 386 | digest.update(outBuffer, 0, outBuffer.length); 387 | digest.doFinal(out, outPos); 388 | } 389 | } 390 | 391 | void _initialize( 392 | Uint8List tmpBlockBytes, Uint8List password, int outputLength) { 393 | /** 394 | * H0 = H64(p, τ, m, t, v, y, |P|, P, |S|, S, |L|, K, |X|, X) 395 | * -> 64 byte (ARGON2_PREHASH_DIGEST_LENGTH) 396 | */ 397 | 398 | var blake = Blake2bDigest(digestSize: ARGON2_PREHASH_DIGEST_LENGTH); 399 | 400 | var values = Uint32List.fromList([ 401 | _parameters.lanes, 402 | outputLength, 403 | _parameters.memory, 404 | _parameters.iterations, 405 | _parameters.version, 406 | _parameters.type 407 | ]); 408 | 409 | Pack.intListToLittleEndianAtList(values, tmpBlockBytes, 0); 410 | blake.update(tmpBlockBytes, 0, values.length * 4); 411 | 412 | _addByteString(tmpBlockBytes, blake, password); 413 | _addByteString(tmpBlockBytes, blake, _parameters.salt); 414 | _addByteString(tmpBlockBytes, blake, _parameters.secret); 415 | _addByteString(tmpBlockBytes, blake, _parameters.additional); 416 | 417 | var initialHashWithZeros = Uint8List(ARGON2_PREHASH_SEED_LENGTH); 418 | blake.doFinal(initialHashWithZeros, 0); 419 | 420 | _fillFirstBlocks(tmpBlockBytes, initialHashWithZeros); 421 | } 422 | 423 | static void _addByteString(Uint8List tmpBlockBytes, Digest digest, 424 | [Uint8List? octets]) { 425 | if (octets == null) { 426 | digest.update(_ZERO_BYTES, 0, 4); 427 | return; 428 | } 429 | 430 | Pack.intToLittleEndianAtList(octets.length, tmpBlockBytes, 0); 431 | digest.update(tmpBlockBytes, 0, 4); 432 | digest.update(octets, 0, octets.length); 433 | } 434 | 435 | /// (H0 || 0 || i) 72 byte -> 1024 byte 436 | /// (H0 || 1 || i) 72 byte -> 1024 byte 437 | void _fillFirstBlocks( 438 | Uint8List tmpBlockBytes, Uint8List initialHashWithZeros) { 439 | var initialHashWithOnes = Uint8List(ARGON2_PREHASH_SEED_LENGTH); 440 | initialHashWithOnes.setFrom( 441 | 0, initialHashWithZeros, 0, ARGON2_PREHASH_DIGEST_LENGTH); 442 | 443 | initialHashWithOnes[ARGON2_PREHASH_DIGEST_LENGTH] = 1; 444 | 445 | for (var i = 0; i < _parameters.lanes; i++) { 446 | Pack.intToLittleEndianAtList( 447 | i, initialHashWithZeros, ARGON2_PREHASH_DIGEST_LENGTH + 4); 448 | Pack.intToLittleEndianAtList( 449 | i, initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH + 4); 450 | 451 | _hash(initialHashWithZeros, tmpBlockBytes, 0, ARGON2_BLOCK_SIZE); 452 | _memory[i * _laneLength + 0].fromBytes(tmpBlockBytes); 453 | 454 | _hash(initialHashWithOnes, tmpBlockBytes, 0, ARGON2_BLOCK_SIZE); 455 | _memory[i * _laneLength + 1].fromBytes(tmpBlockBytes); 456 | } 457 | } 458 | 459 | static int _intToLong(int x) => (x & M32L); 460 | } 461 | 462 | class _FillBlock { 463 | final _Block _r = _Block(); 464 | final _Block _z = _Block(); 465 | 466 | _Block addressBlock = _Block(); 467 | _Block inputBlock = _Block(); 468 | 469 | void _applyBlake() { 470 | /* Apply Blake2 on columns of 64-bit words: (0,1,...,15) , then 471 | (16,17,..31)... finally (112,113,...127) */ 472 | for (var i = 0; i < 8; i++) { 473 | var i16 = 16 * i; 474 | _roundFunction( 475 | _z, 476 | i16, 477 | i16 + 1, 478 | i16 + 2, 479 | i16 + 3, 480 | i16 + 4, 481 | i16 + 5, 482 | i16 + 6, 483 | i16 + 7, 484 | i16 + 8, 485 | i16 + 9, 486 | i16 + 10, 487 | i16 + 11, 488 | i16 + 12, 489 | i16 + 13, 490 | i16 + 14, 491 | i16 + 15); 492 | } 493 | 494 | /* Apply Blake2 on rows of 64-bit words: (0,1,16,17,...112,113), then 495 | (2,3,18,19,...,114,115).. finally (14,15,30,31,...,126,127) */ 496 | for (var i = 0; i < 8; i++) { 497 | var i2 = 2 * i; 498 | _roundFunction( 499 | _z, 500 | i2, 501 | i2 + 1, 502 | i2 + 16, 503 | i2 + 17, 504 | i2 + 32, 505 | i2 + 33, 506 | i2 + 48, 507 | i2 + 49, 508 | i2 + 64, 509 | i2 + 65, 510 | i2 + 80, 511 | i2 + 81, 512 | i2 + 96, 513 | i2 + 97, 514 | i2 + 112, 515 | i2 + 113); 516 | } 517 | } 518 | 519 | void fillBlock(_Block Y, _Block currentBlock) { 520 | _z.copyBlock(Y); 521 | _applyBlake(); 522 | currentBlock.xor(Y, _z); 523 | } 524 | 525 | void fillBlock2(_Block X, _Block Y, _Block currentBlock) { 526 | _r.xor(X, Y); 527 | _z.copyBlock(_r); 528 | _applyBlake(); 529 | currentBlock.xor(_r, _z); 530 | } 531 | 532 | void fillBlockWithXor(_Block X, _Block Y, _Block currentBlock) { 533 | _r.xor(X, Y); 534 | _z.copyBlock(_r); 535 | _applyBlake(); 536 | currentBlock.xorWith2(_r, _z); 537 | } 538 | 539 | static void _roundFunction( 540 | _Block block, 541 | int v0, 542 | int v1, 543 | int v2, 544 | int v3, 545 | int v4, 546 | int v5, 547 | int v6, 548 | int v7, 549 | int v8, 550 | int v9, 551 | int v10, 552 | int v11, 553 | int v12, 554 | int v13, 555 | int v14, 556 | int v15) { 557 | final v = block._v; 558 | 559 | _F(v, v0, v4, v8, v12); 560 | _F(v, v1, v5, v9, v13); 561 | _F(v, v2, v6, v10, v14); 562 | _F(v, v3, v7, v11, v15); 563 | 564 | _F(v, v0, v5, v10, v15); 565 | _F(v, v1, v6, v11, v12); 566 | _F(v, v2, v7, v8, v13); 567 | _F(v, v3, v4, v9, v14); 568 | } 569 | 570 | static void _F(Uint64List v, int a, int b, int c, int d) { 571 | _quarterRound(v, a, b, d, 32); 572 | _quarterRound(v, c, d, b, 24); 573 | _quarterRound(v, a, b, d, 16); 574 | _quarterRound(v, c, d, b, 63); 575 | } 576 | 577 | static void _quarterRound(Uint64List v, int x, int y, int z, int s) { 578 | var a = v[x]; 579 | var b = v[y]; 580 | var c = v[z]; 581 | 582 | a += b + 2 * Longs.toInt32(a) * Longs.toInt32(b); 583 | c = Longs.rotateRight(c ^ a, s); 584 | 585 | v[x] = a; 586 | v[z] = c; 587 | } 588 | } 589 | 590 | class _Block { 591 | static const int SIZE = Argon2BytesGenerator.ARGON2_QWORDS_IN_BLOCK; 592 | 593 | /// 128 * 8 Byte QWords. 594 | final Uint64List _v = Uint64List(SIZE); 595 | 596 | _Block(); 597 | 598 | void fromBytes(Uint8List input) { 599 | if (input.length < Argon2BytesGenerator.ARGON2_BLOCK_SIZE) { 600 | throw ArgumentError.value( 601 | input.length, 'input.length', 'input shorter than blocksize'); 602 | } 603 | 604 | Pack.littleEndianToLongAtList(input, 0, _v); 605 | } 606 | 607 | void toBytes(Uint8List output) { 608 | if (output.length < Argon2BytesGenerator.ARGON2_BLOCK_SIZE) { 609 | throw ArgumentError.value( 610 | output.length, 'output.length', 'output shorter than blocksize'); 611 | } 612 | 613 | Pack.longListToLittleEndianAtList(_v, output, 0); 614 | } 615 | 616 | void copyBlock(_Block other) { 617 | _v.setAll(0, other._v); 618 | } 619 | 620 | void xor(_Block b1, _Block b2) { 621 | var v0 = _v; 622 | var v1 = b1._v; 623 | var v2 = b2._v; 624 | 625 | for (var i = SIZE - 1; i >= 0; --i) { 626 | v0[i] = v1[i] ^ v2[i]; 627 | } 628 | } 629 | 630 | void xorWith(_Block b1) { 631 | var v0 = _v; 632 | var v1 = b1._v; 633 | for (var i = SIZE - 1; i >= 0; --i) { 634 | v0[i] ^= v1[i]; 635 | } 636 | } 637 | 638 | void xorWith2(_Block b1, _Block b2) { 639 | var v0 = _v; 640 | var v1 = b1._v; 641 | var v2 = b2._v; 642 | for (var i = SIZE - 1; i >= 0; --i) { 643 | v0[i] ^= v1[i] ^ v2[i]; 644 | } 645 | } 646 | 647 | _Block clear() { 648 | _v.setAllElementsTo(0); 649 | 650 | return this; 651 | } 652 | } 653 | 654 | class _Position { 655 | int pass; 656 | int lane; 657 | int slice; 658 | 659 | _Position([this.pass = 0, this.lane = 0, this.slice = 0]); 660 | } 661 | --------------------------------------------------------------------------------