From 0d03030e2620e212e6bd81563b7fbbf649cf565c Mon Sep 17 00:00:00 2001 From: Tamal Patra Date: Mon, 18 May 2020 12:21:11 +0530 Subject: [PATCH] #22, #14 - happy_hash - new randomString() - hashedAll() logic upgrade - new optional prefixLibrary flag (default is true) - unit tests --- src/sprightly/lib/utils/happy_hash.dart | 28 ++++-- src/sprightly/test/utils/happy_hash_test.dart | 97 ++++++++++++++++++- 2 files changed, 114 insertions(+), 11 deletions(-) diff --git a/src/sprightly/lib/utils/happy_hash.dart b/src/sprightly/lib/utils/happy_hash.dart index a0dc599..14ae0ac 100644 --- a/src/sprightly/lib/utils/happy_hash.dart +++ b/src/sprightly/lib/utils/happy_hash.dart @@ -20,15 +20,19 @@ enum HashLibrary { hmac_sha512 } +final Random _random = Random.secure(); + String hashedAll( List items, { int hashLength = 16, HashLibrary library = HashLibrary.sha1, String key, + bool prefixLibrary = true, }) { - key ??= (Random().nextInt(1000000000) + DateTime.now().millisecondsSinceEpoch) - .toString(); - final utf8Key = utf8.encode(key); + final keyLength = + null == hashLength ? 256 : hashLength < 4096 ? hashLength : 4096; + final _key = key ?? randomString(keyLength); + final utf8Key = utf8.encode(_key); final sink = AccumulatorSink(); final byteChunks = items.map((str) => utf8.encode(str)); ByteConversionSink chunks; @@ -79,12 +83,14 @@ String hashedAll( } byteChunks.forEach((bt) => chunks.add(bt)); chunks.close(); - var result = "${library.toString().split(".").last.replaceAll("_", "")}" - ":${sink.events.single}"; + var result = + "${prefixLibrary ? library.toString().split(".").last.replaceAll("_", "") + ':' : ''}" + "${sink.events.single}"; if (null == hashLength) return result; if (result.length < hashLength) { - final repeater = hashed(result + key, hashLength: null, library: library); - while (result.length < hashLength) result += repeater; + result += _key; + if (result.length < hashLength) + result += randomString(hashLength - result.length); } return result.substring(0, hashLength); } @@ -94,10 +100,18 @@ String hashed( int hashLength = 16, HashLibrary library = HashLibrary.sha1, String key, + bool prefixLibrary = true, }) => hashedAll( [item], hashLength: hashLength, library: library, key: key, + prefixLibrary: prefixLibrary, ); + +String randomString([int length = 16]) { + final unicodeIntegers = + List.generate(length, (index) => _random.nextInt(256)); + return base64Encode(unicodeIntegers).substring(0, length); +} diff --git a/src/sprightly/test/utils/happy_hash_test.dart b/src/sprightly/test/utils/happy_hash_test.dart index 98b9462..c3161e1 100644 --- a/src/sprightly/test/utils/happy_hash_test.dart +++ b/src/sprightly/test/utils/happy_hash_test.dart @@ -10,11 +10,13 @@ void main() { String item = WordPair.random().asString; List items = List.generate(10, (i) => WordPair.random().asString); - var hashLengths = List.generate(4, (i) => Random().nextInt(pow(10, i))); + var hashLengths = + List.generate(4, (i) => Random.secure().nextInt(pow(10, i))); HashLibrary.values.forEach((library) { libraryName = library.toString().split('.').last; isHmac = libraryName.startsWith('hmac_'); + final prefix = library.toString().split(".").last.replaceAll("_", "") + ':'; group('happy hash: library: $libraryName', () { if (!isHmac) { @@ -28,6 +30,7 @@ void main() { }); test('hashed(): length check', () { hashLengths.forEach((hashLength) { + print('check with length: $hashLength'); // act var result = hashed(item, hashLength: hashLength, library: library); @@ -56,9 +59,19 @@ void main() { }); } else { group('With key', () { - var key = (Random().nextInt(1000000000) + - DateTime.now().millisecondsSinceEpoch) - .toString(); + var key = 'k4q2d8CRLDXpxfBPic6xGkqCnm7Bk2bi9S2G7cB0eQTR3' + 'SE15ap789aUcdqbI5tXfv+H0bxsHxTtCYhWwd3I27fcPJ' + '9MGOnUF6ukr/Mjt/4y+6v+qUuqvqowLFlXal1kLsE2Q2H' + 'GyUzCPeiWZUuih9XyA9rbMRditEgmp3/+jJzjIPu0juwx' + 'NXFiw/TnZroLeGTiyod1u54qRRi9EhYgIJAtV0iOQbrYj' + 'Ujm31Z80hRrHO/eX2DKgB55XKZY+AQbGS5zKXntlA6war' + 'mcR2bGfbRct0ZBIq92PvRDfKcSR9tlM7/JPNX99uiXLCQ' + 'uHqsh5Ys4JvLBfqOv4bmI7SX+al5fiTmCermTFnmV9IU3' + 'DgYq3rUb46TxeQBpWoF7LjmQ52af0F5ZwqeFLjADB4jE8' + 'uIt+Scj1qaIBBE87gq16sWs1ig6DGhBkanS52LMWU/jG5' + 'pnkOjtZ/CA/ykYNAlV6rEAhH1FUhY/wT+pKJ9lf4/2BbF' + '+MF0mGJcZ2hjiaef8nGTK5v3h5j42N3gonv26lDmRXOxA' + 'P3XDs7esVNqonHsI1XAdVYRun'; test('hashed(): Consistency check', () { // act @@ -70,6 +83,7 @@ void main() { }); test('hashed(): length check', () { hashLengths.forEach((hashLength) { + print('check with length: $hashLength'); // act var result = hashed(item, hashLength: hashLength, library: library, key: key); @@ -138,6 +152,81 @@ void main() { }); }); } + test('hashed(): prefixLibrary check', () { + // act + final resultWith = hashed(item, library: library); + final resultWithout = + hashed(item, library: library, prefixLibrary: false); + + // assert + expect(resultWith.startsWith(prefix), isTrue); + expect(resultWithout.startsWith(prefix), isFalse); + }); + test('hashedAll(): prefixLibrary check', () { + // act + final resultWith = hashedAll(items, library: library); + final resultWithout = + hashedAll(items, library: library, prefixLibrary: false); + + // assert + expect(resultWith.startsWith(prefix), isTrue); + expect(resultWithout.startsWith(prefix), isFalse); + }); + }); + }); + group('happy hash: randomString()', () { + test('check with default length', () { + print('check with length: 16'); + // arrange & act + final count = 50; + final hashLength = 16; + final result = List.generate(count, (index) => randomString()); + final set = Set.from(result); + print('sample: ${result[0]}'); + + // assert + expect(result.length, set.length); + set.forEach((element) { + expect(element.length, hashLength); + }); + }); + hashLengths.forEach((hashLength) { + test('check with length', () { + print('check with length: $hashLength'); + // arrange & act + final count = 50; + final result = + List.generate(count, (index) => randomString(hashLength)); + final set = Set.from(result); + print('sample: ${result[0]}'); + + // assert + if (hashLength == 0) { + expect(result.length, count); + expect(set.length, 1); + } else { + expect(result.length, set.length); + } + set.forEach((element) { + expect(element.length, hashLength); + }); + }); + }); + test('check with really long length', () { + print('check with length: 4096'); + // arrange & act + final count = 50; + final hashLength = 4096; + final result = + List.generate(count, (index) => randomString(hashLength)); + final set = Set.from(result); + print('sample: ${result[0]}'); + + // assert + expect(result.length, set.length); + set.forEach((element) { + expect(element.length, hashLength); + }); }); }); }