Skip to content

Commit

Permalink
Implement BCrypt algorithm.
Browse files Browse the repository at this point in the history
- New class: `Bcrypt`
- New methods: `bcrypt`, `bcryptSalt`, `bcryptVerify`, `bcryptDigest`

Other changes:
- Convert all asUint...List to Uint...List.view
- Refactor: `Argon2.fromEncoded` will now accept `CryptData` only.
- New method: `Argon2Context.fromEncoded` accepting `CryptData`.
  • Loading branch information
dipu-bd committed Jun 30, 2024
1 parent 1129d26 commit 17c20be
Show file tree
Hide file tree
Showing 24 changed files with 1,729 additions and 339 deletions.
171 changes: 86 additions & 85 deletions BENCHMARK.md

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# next
# 1.18.0

- Implement BCrypt algorithm.
- New class: `Bcrypt`
- New methods: `bcrypt`, `bcryptSalt`, `bcryptVerify`, `bcryptDigest`
- Convert all asUint...List to Uint...List.view
- Refactor: `Argon2.fromEncoded` will now accept `CryptData` only.
- New method: `Argon2Context.fromEncoded` accepting `CryptData`.

# 1.17.0

Expand Down
184 changes: 93 additions & 91 deletions README.md

Large diffs are not rendered by default.

26 changes: 6 additions & 20 deletions benchmark/argon2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,13 @@ import 'dart:math';
import 'dart:typed_data';

import 'package:argon2/argon2.dart' as argon2;
import 'package:benchmark_harness/benchmark_harness.dart';
import 'package:hashlib/hashlib.dart';

Random random = Random();

class Argon2BenchmarkBase extends BenchmarkBase {
Argon2BenchmarkBase(String name) : super(name);
import 'base.dart';

@override
double measure() {
final watch = Stopwatch()..start();
run();
watch.reset();
run();
run();
run();
return (watch.elapsedMicroseconds / 3).floorToDouble();
}
}
Random random = Random();

class HashlibArgon2iBenchmark extends Argon2BenchmarkBase {
class HashlibArgon2iBenchmark extends KDFBenchmarkBase {
final Argon2Security security;

HashlibArgon2iBenchmark(this.security) : super('hashlib');
Expand All @@ -43,7 +29,7 @@ class HashlibArgon2iBenchmark extends Argon2BenchmarkBase {
}
}

class HashlibArgon2dBenchmark extends Argon2BenchmarkBase {
class HashlibArgon2dBenchmark extends KDFBenchmarkBase {
final Argon2Security security;

HashlibArgon2dBenchmark(this.security) : super('hashlib');
Expand All @@ -61,7 +47,7 @@ class HashlibArgon2dBenchmark extends Argon2BenchmarkBase {
}
}

class HashlibArgon2idBenchmark extends Argon2BenchmarkBase {
class HashlibArgon2idBenchmark extends KDFBenchmarkBase {
final Argon2Security security;

HashlibArgon2idBenchmark(this.security) : super('hashlib');
Expand All @@ -79,7 +65,7 @@ class HashlibArgon2idBenchmark extends Argon2BenchmarkBase {
}
}

class Argon2Argon2idBenchmark extends Argon2BenchmarkBase {
class Argon2Argon2idBenchmark extends KDFBenchmarkBase {
final Argon2Security security;

Argon2Argon2idBenchmark(this.security) : super('argon2');
Expand Down
15 changes: 15 additions & 0 deletions benchmark/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ import 'package:benchmark_harness/benchmark_harness.dart';

Random random = Random();

class KDFBenchmarkBase extends BenchmarkBase {
KDFBenchmarkBase(String name) : super(name);

@override
double measure() {
final watch = Stopwatch()..start();
run();
watch.reset();
run();
run();
run();
return (watch.elapsedMicroseconds / 3).floorToDouble();
}
}

abstract class Benchmark extends BenchmarkBase {
final int size;
final int iter;
Expand Down
39 changes: 39 additions & 0 deletions benchmark/bcrypt.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2024, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.

import 'dart:math';

import 'package:hashlib/hashlib.dart';

import 'base.dart';

Random random = Random();

class HashlibBenchmark extends KDFBenchmarkBase {
final salt = randomBytes(16);
final BcryptSecurity security;
final password = 'long password'.codeUnits;

HashlibBenchmark(this.security) : super('hashlib');

@override
void run() {
bcryptDigest(password, salt: salt, security: security);
}
}

void main() {
double runtime;
print('--------- Hashlib/BCRYPT ----------');
runtime = HashlibBenchmark(BcryptSecurity.test).measure();
print('hashlib/bcrypt[test]: ${runtime / 1000} ms');
runtime = HashlibBenchmark(BcryptSecurity.little).measure();
print('hashlib/bcrypt[little]: ${runtime / 1000} ms');
runtime = HashlibBenchmark(BcryptSecurity.moderate).measure();
print('hashlib/bcrypt[moderate]: ${runtime / 1000} ms');
runtime = HashlibBenchmark(BcryptSecurity.good).measure();
print('hashlib/bcrypt[good]: ${runtime / 1000} ms');
runtime = HashlibBenchmark(BcryptSecurity.strong).measure();
print('hashlib/bcrypt[strong]: ${runtime / 1000} ms');
print('');
}
17 changes: 15 additions & 2 deletions benchmark/benchmark.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:hashlib/hashlib.dart';

import 'argon2.dart' as argon2;
import 'base.dart';
import 'bcrypt.dart' as bcrypt;
import 'blake2b.dart' as blake2b;
import 'blake2s.dart' as blake2s;
import 'hmac_md5.dart' as md5_hmac;
Expand Down Expand Up @@ -221,7 +222,7 @@ void measureHashFunctions() {
// Key Derivation Algorithm Benchmarks
// ---------------------------------------------------------------------
void measureKeyDerivation() {
dump('Argon2 and scrypt benchmarks on different security parameters:');
dump('Key derivator algorithm benchmarks on different security parameters:');
dump('');
var argon2Levels = [
Argon2Security.test,
Expand All @@ -237,14 +238,26 @@ void measureKeyDerivation() {
ScryptSecurity.good,
ScryptSecurity.strong,
];
var bcryptLevels = [
BcryptSecurity.test,
BcryptSecurity.little,
BcryptSecurity.moderate,
BcryptSecurity.good,
BcryptSecurity.strong,
];
var algorithms = {
'scrypt': scryptLevels.map((e) => scrypt.HashlibBenchmark(e)),
'bcrypt': bcryptLevels.map((e) => bcrypt.HashlibBenchmark(e)),
'argon2i': argon2Levels.map((e) => argon2.HashlibArgon2iBenchmark(e)),
'argon2d': argon2Levels.map((e) => argon2.HashlibArgon2dBenchmark(e)),
'argon2id': argon2Levels.map((e) => argon2.HashlibArgon2idBenchmark(e)),
};

var names = argon2Levels.map((e) => e.name);
var names = {
...argon2Levels.map((e) => e.name),
...scryptLevels.map((e) => e.name),
...bcryptLevels.map((e) => e.name),
}.toList();
var separator = names.map((e) => ('-' * (e.length + 2)));
dump('| Algorithms | ${argon2Levels.map((e) => e.name).join(' | ')} |');
dump('|------------|${separator.join('|')}|');
Expand Down
39 changes: 13 additions & 26 deletions benchmark/scrypt.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,18 @@
import 'dart:math';
import 'dart:typed_data';

import 'package:benchmark_harness/benchmark_harness.dart';
import 'package:hashlib/hashlib.dart';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/key_derivators/scrypt.dart' as pc;

import 'base.dart';

Random random = Random();

class ScryptBenchmarkBase extends BenchmarkBase {
class HashlibBenchmark extends KDFBenchmarkBase {
final ScryptSecurity security;

ScryptBenchmarkBase(String name, this.security) : super(name);

@override
double measure() {
final watch = Stopwatch()..start();
run();
watch.reset();
run();
run();
run();
return (watch.elapsedMicroseconds / 3).floorToDouble();
}
}

class HashlibBenchmark extends ScryptBenchmarkBase {
HashlibBenchmark(ScryptSecurity security) : super('hashlib', security);
HashlibBenchmark(this.security) : super('hashlib');

@override
void run() {
Expand All @@ -42,9 +28,10 @@ class HashlibBenchmark extends ScryptBenchmarkBase {
}
}

class PointyCastleBenchmark extends ScryptBenchmarkBase {
PointyCastleBenchmark(ScryptSecurity security)
: super('PointyCastle', security);
class PointyCastleBenchmark extends KDFBenchmarkBase {
final ScryptSecurity security;

PointyCastleBenchmark(this.security) : super('PointyCastle');

@override
void run() {
Expand Down Expand Up @@ -82,14 +69,14 @@ void main() {
print('');
print('--------- PointyCastle/SCRYPT ----------');
runtime = PointyCastleBenchmark(ScryptSecurity.test).measure();
print('hashlib/scrypt[test]: ${runtime / 1000} ms');
print('pc/scrypt[test]: ${runtime / 1000} ms');
runtime = PointyCastleBenchmark(ScryptSecurity.little).measure();
print('hashlib/scrypt[little]: ${runtime / 1000} ms');
print('pc/scrypt[little]: ${runtime / 1000} ms');
runtime = PointyCastleBenchmark(ScryptSecurity.moderate).measure();
print('hashlib/scrypt[moderate]: ${runtime / 1000} ms');
print('pc/scrypt[moderate]: ${runtime / 1000} ms');
runtime = PointyCastleBenchmark(ScryptSecurity.good).measure();
print('hashlib/scrypt[good]: ${runtime / 1000} ms');
print('pc/scrypt[good]: ${runtime / 1000} ms');
runtime = PointyCastleBenchmark(ScryptSecurity.strong).measure();
print('hashlib/scrypt[strong]: ${runtime / 1000} ms');
print('pc/scrypt[strong]: ${runtime / 1000} ms');
print('');
}
42 changes: 9 additions & 33 deletions lib/src/algorithms/argon2/argon2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ export 'security.dart';
/// Example of password hashing using Argon2:
///
/// ```dart
/// final salt = utf.encode("some salt")
/// final password = utf8.encode('password');
/// final argon2 = Argon2(
/// version: Argon2Version.v13,
/// type: Argon2Type.argon2id,
/// hashLength: 32,
/// iterations: 2,
/// parallelism: 8,
/// memorySizeKB: 1 << 18,
/// salt: "some salt".codeUnits,
/// salt: salt,
/// );
///
/// final digest = argon2.encode('password'.codeUnits);
/// final digest = argon2.encode(password);
/// ```
///
/// [phc]: https://www.password-hashing.net/
Expand Down Expand Up @@ -128,45 +129,20 @@ class Argon2 extends KeyDerivatorBase {
);
}

/// Creates an [Argon2] instance from an [encoded] PHC-compliant string.
/// Creates an [Argon2] instance from an encoded PHC-compliant string.
///
/// The encoded string may look like this:
/// `$argon2i$v=19$m=16,t=2,p=1$c29tZSBzYWx0$u1eU6mZFG4/OOoTdAtM5SQ`
factory Argon2.fromEncoded(
String encoded, {
CryptData data, {
List<int>? key,
List<int>? personalization,
}) {
var data = fromCrypt(encoded);
var type =
Argon2Type.values.singleWhere((e) => "$e".split('.').last == data.id);
Argon2Version version =
Argon2Version.values.singleWhere((e) => '${e.value}' == data.version);
if (data.params == null) {
throw ArgumentError('No paramters');
}
var m = data.params!['m'];
if (m == null) {
throw ArgumentError('Missing parameter: m');
}
var t = data.params!['t'];
if (t == null) {
throw ArgumentError('Missing parameter: t');
}
var p = data.params!['p'];
if (p == null) {
throw ArgumentError('Missing parameter: p');
}
return Argon2(
type: type,
version: version,
iterations: int.parse(t),
parallelism: int.parse(p),
memorySizeKB: int.parse(m),
salt: data.saltBytes(),
hashLength: data.hashBytes()?.lengthInBytes,
var ctx = Argon2Context.fromEncoded(
data,
key: key,
personalization: personalization,
);
return Argon2._(ctx);
}
}
Loading

0 comments on commit 17c20be

Please sign in to comment.