diff --git a/app_crypto/lib/encrypt.dart b/app_crypto/lib/encrypt.dart index 2a9bc2e..780e7db 100644 --- a/app_crypto/lib/encrypt.dart +++ b/app_crypto/lib/encrypt.dart @@ -1,4 +1,10 @@ export 'package:tekartik_app_crypto/src/encrypt.dart' show encrypt, decrypt, StringEncrypter, defaultEncryptedFromRawPassword; +export 'src/aes.dart' + show + aesEncrypterFromPassword, + encryptTextPassword16FromText, + aesDecrypt, + aesEncrypt; export 'src/salsa20.dart' show salsa20EncrypterFromPassword; diff --git a/app_crypto/lib/src/aes.dart b/app_crypto/lib/src/aes.dart new file mode 100644 index 0000000..ac59ae0 --- /dev/null +++ b/app_crypto/lib/src/aes.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; +import 'package:encrypt/encrypt.dart'; + +import 'encrypt.dart'; + +/// Simple md5 hash +String encryptTextPassword16FromText(String password) { + return base64Encode(md5.convert(utf8.encode(password)).bytes) + .substring(0, 16); +} + +/// aes encrypted using any password (use MD5), random data prepended +StringEncrypter aesEncrypterFromPassword(String password) { + return _AesStringEncrypter(_aesEncrypterFromPassword(password)); +} + +Encrypter _aesEncrypterFromPassword(String password) { + final key = Key.fromUtf8(password); // _generateEncryptPassword(password)); + return Encrypter(AES(key)); +} + +class _AesStringEncrypter implements StringEncrypter { + final Encrypter encrypter; + + _AesStringEncrypter(this.encrypter); + @override + String decrypt(String encrypted) { + // Read the initial value that was prepended + assert(encrypted.length >= 24); + final iv = base64.decode(encrypted.substring(0, 24)); + + // Extract the real input + encrypted = encrypted.substring(24); + + // Decode the input + var decoded = encrypter.decrypt64(encrypted, iv: IV(iv)); + return decoded; + } + + IV _generateIv() { + final iv = IV.fromSecureRandom(16); + return iv; + } + + @override + String encrypt(String input) { + final iv = _generateIv(); + + // Encode the input value + final encoded = encrypter.encrypt(input, iv: iv).base64; + + var ivEncoded = iv.base64; + assert(ivEncoded.length == 24); + // Prepend the initial value + return '$ivEncoded$encoded'; + } +} + +class AesWithIVEntrypter extends _AesStringEncrypter { + final IV iv; + + /// Must be 16 bytes + final String password; + AesWithIVEntrypter(this.password, this.iv) + : super(_aesEncrypterFromPassword(password)); + @override + IV _generateIv() { + return iv; + } +} + +/// encrypt the [decoded] text using [password]. +/// +/// [password] must be ascii character of length 16, 24 or 32. +/// +/// returns a base 64 encrypted string. +/// +/// Encryption used is AES. +String aesEncrypt(String decoded, String password) => + aesEncrypterFromPassword(password).encrypt(decoded); + +/// decrypt the [encoded] text using [password]. +/// +/// [encoded] is base 64 string got from the encrypt method using the same +/// [password] +/// +/// Encryption used is AES. +String aesDecrypt(String encoded, String password) => + aesEncrypterFromPassword(password).decrypt(encoded); diff --git a/app_crypto/lib/src/encrypt.dart b/app_crypto/lib/src/encrypt.dart index 44057e9..3073b6f 100644 --- a/app_crypto/lib/src/encrypt.dart +++ b/app_crypto/lib/src/encrypt.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:encrypt/encrypt.dart'; /// encrypt the [decoded] text using [password]. @@ -9,7 +11,7 @@ import 'package:encrypt/encrypt.dart'; /// Encryption used is AES. String _encrypt(String decoded, String password) { final key = Key.fromUtf8(password); - final iv = IV.fromLength(16); + final iv = IV(Uint8List(16)); final encrypter = Encrypter(AES(key)); return encrypter.encrypt(decoded, iv: iv).base64; } @@ -22,7 +24,7 @@ String _encrypt(String decoded, String password) { /// Encryption used is AES. String _decrypt(String encoded, String password) { final key = Key.fromUtf8(password); - final iv = IV.fromLength(16); + final iv = IV(Uint8List(16)); final encrypter = Encrypter(AES(key)); return encrypter.decrypt(Encrypted.fromBase64(encoded), iv: iv); } diff --git a/app_crypto/lib/src/encrypt_codec.dart b/app_crypto/lib/src/encrypt_codec.dart index 636fa97..8a57190 100644 --- a/app_crypto/lib/src/encrypt_codec.dart +++ b/app_crypto/lib/src/encrypt_codec.dart @@ -33,7 +33,7 @@ class EncryptCodec with Codec { EncryptConverter(encrypter: encrypter); } -/// String password +/// String password - fixed IV with 0 EncryptCodec defaultEncryptCodec({required String rawPassword}) => EncryptCodec(encrypter: defaultEncryptedFromRawPassword(rawPassword)); diff --git a/app_crypto/pubspec.yaml b/app_crypto/pubspec.yaml index 3068dd4..28e9c57 100644 --- a/app_crypto/pubspec.yaml +++ b/app_crypto/pubspec.yaml @@ -15,7 +15,9 @@ dependencies: ref: dart3a version: '>=0.10.7' meta: '>=1.1.6' - encrypt: ^5.0.3 + encrypt: '>=3.0.0' # <=5.0.3' + # Temp aes issue limited to 5.0.1 + # encrypt: '<=5.0.3' crypto: # Temp web compile issue pointycastle: '>=3.2.0-rc0' @@ -23,7 +25,14 @@ dependencies: dev_dependencies: dev_test: test: '>=1.5.0' + # downgrade testing + file: '>=7.0.0' build_runner: '>=1.2.7' build_test: any build_web_compilers: any process_run: '>=0.12.0+1' + +_dependency_overrides: + encrypt: + git: + url: https://github.com/leocavalcante/encrypt \ No newline at end of file diff --git a/app_crypto/test/aes_test.dart b/app_crypto/test/aes_test.dart index b2376a9..c169ec5 100644 --- a/app_crypto/test/aes_test.dart +++ b/app_crypto/test/aes_test.dart @@ -1,23 +1,98 @@ import 'package:encrypt/encrypt.dart'; +import 'package:tekartik_app_crypto/encrypt.dart'; +import 'package:tekartik_app_crypto/src/aes.dart'; +import 'package:tekartik_app_crypto/src/generate_password.dart' + show generatePassword; import 'package:test/test.dart'; -String encrypt(String decoded, String password) { - final key = Key.fromUtf8(password); - final iv = IV.fromLength(16); - final encrypter = Encrypter(AES(key)); - return encrypter.encrypt(decoded, iv: iv).base64; -} +void main() { + void aesRoundTrip(String decoded, String password) { + var encrypter = aesEncrypterFromPassword(password); + var encrypted = encrypter.encrypt(decoded); + print('${decoded.length}:${encrypted.length}'); + encrypter = aesEncrypterFromPassword(password); + expect(encrypter.decrypt(encrypted), decoded); + } -String decrypt(String encoded, String password) { - final key = Key.fromUtf8(password); - final iv = IV.fromLength(16); - final encrypter = Encrypter(AES(key)); - return encrypter.decrypt(Encrypted.fromBase64(encoded), iv: iv); -} + test('aes encrypt decrypt', () { + var password = r'E4x*$TwbkJC-xK4KGC4zJF9j*Rh&WLgR'; + var encrypter = AesWithIVEntrypter(password, IV.allZerosOfLength(16)); -void main() { - test('AES encrypt/decrypt', () { + expect(encrypt('test', password), 'amGhyRRLUIoE59IiEys5Vw=='); + expect(encrypter.encrypt('test'), + 'AAAAAAAAAAAAAAAAAAAAAA==amGhyRRLUIoE59IiEys5Vw=='); + expect( + encrypter.decrypt('AAAAAAAAAAAAAAAAAAAAAA==amGhyRRLUIoE59IiEys5Vw=='), + 'test'); + }); + + test('aes decrypt', () { var password = r'E4x*$TwbkJC-xK4KGC4zJF9j*Rh&WLgR'; - expect(decrypt('amGhyRRLUIoE59IiEys5Vw==', password), 'test'); + var encrypter = aesEncrypterFromPassword(password); + + expect( + encrypter.encrypt('test'), + isNot( + 'kEH3mkatSK4yiDu95hZj3Q==0aN1ouMw1HhKY6L8sibkgA==')); // - different each time + expect( + encrypter.decrypt('kEH3mkatSK4yiDu95hZj3Q==0aN1ouMw1HhKY6L8sibkgA=='), + 'test'); + expect( + aesDecrypt( + 'kEH3mkatSK4yiDu95hZj3Q==0aN1ouMw1HhKY6L8sibkgA==', password), + 'test'); + expect(aesEncrypt('test', password), + isNot('19/EiVx5ICKR/IpS05DYmA==rqLU4PjkNP8W/SiI1dVgAA==')); + expect( + aesDecrypt( + '19/EiVx5ICKR/IpS05DYmA==rqLU4PjkNP8W/SiI1dVgAA==', password), + 'test'); + expect(aesDecrypt(aesEncrypt('test', password), password), 'test'); + }); + test('aes', () { + var aes = aesEncrypterFromPassword(encryptTextPassword16FromText('test')); + var encrypted = aes.encrypt('test'); + aes = aesEncrypterFromPassword(encryptTextPassword16FromText('test')); + expect(aes.decrypt(encrypted), 'test'); + + String textWithLength(int length) { + return List.generate(length, (i) => i.toString().substring(0, 1)).join(); + } + + aesRoundTrip('test', encryptTextPassword16FromText('test')); + //aesRoundTrip('', ''); + aesRoundTrip('1', encryptTextPassword16FromText('2')); + aesRoundTrip(textWithLength(4096), + encryptTextPassword16FromText(textWithLength(4096))); + // _salsa20RoundTrip(textWithLength(40960), textWithLength(40960)); + aesRoundTrip(textWithLength(4096000), + encryptTextPassword16FromText(textWithLength(4096000))); + + var password = generatePassword(); + aes = aesEncrypterFromPassword(password); + var sw = Stopwatch()..start(); + var count = 1000; + for (var i = 0; i < count; i++) { + aes.decrypt(aes.encrypt(textWithLength(count * 10))); + } + print('aes round trip: ${sw.elapsedMilliseconds} ms'); + sw = Stopwatch()..start(); + + for (var i = 0; i < count; i++) { + aesDecrypt(aesEncrypt(textWithLength(count * 10), password), password); + } + print('aesDecrypt/aesEncrypt: ${sw.elapsedMilliseconds} ms'); + + var encrypteds = List.generate(count, (index) => ''); + sw = Stopwatch()..start(); + for (var i = 0; i < count; i++) { + encrypteds[i] = aes.encrypt(textWithLength(count * 10)); + } + print('aes encrypt: ${sw.elapsedMilliseconds} ms'); + sw = Stopwatch()..start(); + for (var i = 0; i < count; i++) { + aes.decrypt(encrypteds[i]); + } + print('aes decrypt: ${sw.elapsedMilliseconds} ms'); }); } diff --git a/app_crypto/test/crypto_test.dart b/app_crypto/test/crypto_test.dart index 4bd8bbf..3bef1c1 100644 --- a/app_crypto/test/crypto_test.dart +++ b/app_crypto/test/crypto_test.dart @@ -8,14 +8,6 @@ void main() { expect(decrypt(encrypt(decoded, password), password), decoded); } - void salsa20RoundTrip(String decoded, String password) { - var encrypter = salsa20EncrypterFromPassword(password); - var encrypted = encrypter.encrypt(decoded); - print('${decoded.length}:${encrypted.length}'); - encrypter = salsa20EncrypterFromPassword(password); - expect(encrypter.decrypt(encrypted), decoded); - } - test('test', () { var password = r'E4x*$TwbkJC-xK4KGC4zJF9j*Rh&WLgR'; expect(encrypt('test', password), 'amGhyRRLUIoE59IiEys5Vw=='); @@ -45,37 +37,15 @@ void main() { } }); - test('generatePassword', () { - expect(generatePassword().length, 32); - expect(generatePassword(length: 64).length, 64); - }); - - test('salsa20', () { - var salsa = salsa20EncrypterFromPassword('test'); - var encrypted = salsa.encrypt('test'); - salsa = salsa20EncrypterFromPassword('test'); - expect(salsa.decrypt(encrypted), 'test'); - + test('legacy', () { String textWithLength(int length) { return List.generate(length, (i) => i.toString().substring(0, 1)).join(); } - salsa20RoundTrip('test', 'test'); - salsa20RoundTrip('', ''); - salsa20RoundTrip('1', '2'); - salsa20RoundTrip(textWithLength(4096), textWithLength(4096)); - // _salsa20RoundTrip(textWithLength(40960), textWithLength(40960)); - salsa20RoundTrip(textWithLength(4096000), textWithLength(4096000)); - var password = generatePassword(); - salsa = salsa20EncrypterFromPassword(password); + var sw = Stopwatch()..start(); var count = 1000; - for (var i = 0; i < count; i++) { - salsa.decrypt(salsa.encrypt(textWithLength(count * 10))); - } - print('salsa: ${sw.elapsedMilliseconds} ms'); - sw = Stopwatch()..start(); for (var i = 0; i < count; i++) { decrypt(encrypt(textWithLength(count * 10), password), password); @@ -84,16 +54,6 @@ void main() { var encrypteds = List.generate(count, (index) => ''); sw = Stopwatch()..start(); - for (var i = 0; i < count; i++) { - encrypteds[i] = salsa.encrypt(textWithLength(count * 10)); - } - print('salsa encrypt: ${sw.elapsedMilliseconds} ms'); - sw = Stopwatch()..start(); - for (var i = 0; i < count; i++) { - salsa.decrypt(encrypteds[i]); - } - print('salsa decrypt: ${sw.elapsedMilliseconds} ms'); - sw = Stopwatch()..start(); for (var i = 0; i < count; i++) { encrypteds[i] = encrypt(textWithLength(count * 10), password); } diff --git a/app_crypto/test/password_generator_test.dart b/app_crypto/test/password_generator_test.dart index 3215d7d..f605f15 100644 --- a/app_crypto/test/password_generator_test.dart +++ b/app_crypto/test/password_generator_test.dart @@ -5,5 +5,7 @@ void main() { test('generatePassword', () { expect(generatePassword().length, 32); expect(generatePassword(length: 10).length, 10); + + expect(generatePassword(length: 64).length, 64); }); } diff --git a/app_crypto/test/raw_aes_test.dart b/app_crypto/test/raw_aes_test.dart new file mode 100644 index 0000000..be45e2c --- /dev/null +++ b/app_crypto/test/raw_aes_test.dart @@ -0,0 +1,28 @@ +import 'dart:typed_data'; + +import 'package:encrypt/encrypt.dart'; +import 'package:test/test.dart'; + +String aesEncrypt(String decoded, String password) { + final key = Key.fromUtf8(password); + // final iv = IV.fromLength(16); + final iv = IV(Uint8List(16)); + final encrypter = Encrypter(AES(key)); + return encrypter.encrypt(decoded, iv: iv).base64; +} + +String aesDecrypt(String encoded, String password) { + final key = Key.fromUtf8(password); + // final iv = IV.fromLength(16); + final iv = IV(Uint8List(16)); + final encrypter = Encrypter(AES(key)); + return encrypter.decrypt(Encrypted.fromBase64(encoded), iv: iv); +} + +void main() { + test('AES encrypt/decrypt', () { + var password = r'E4x*$TwbkJC-xK4KGC4zJF9j*Rh&WLgR'; + expect(aesEncrypt('test', password), 'amGhyRRLUIoE59IiEys5Vw=='); + expect(aesDecrypt('amGhyRRLUIoE59IiEys5Vw==', password), 'test'); + }); +} diff --git a/app_crypto/test/salsa20_test.dart b/app_crypto/test/salsa20_test.dart new file mode 100644 index 0000000..3c06100 --- /dev/null +++ b/app_crypto/test/salsa20_test.dart @@ -0,0 +1,53 @@ +import 'package:tekartik_app_crypto/encrypt.dart'; +import 'package:tekartik_app_crypto/src/generate_password.dart' + show generatePassword; +import 'package:test/test.dart'; + +void main() { + void salsa20RoundTrip(String decoded, String password) { + var encrypter = salsa20EncrypterFromPassword(password); + var encrypted = encrypter.encrypt(decoded); + print('${decoded.length}:${encrypted.length}'); + encrypter = salsa20EncrypterFromPassword(password); + expect(encrypter.decrypt(encrypted), decoded); + } + + test('salsa20', () { + var salsa = salsa20EncrypterFromPassword('test'); + var encrypted = salsa.encrypt('test'); + salsa = salsa20EncrypterFromPassword('test'); + expect(salsa.decrypt(encrypted), 'test'); + + String textWithLength(int length) { + return List.generate(length, (i) => i.toString().substring(0, 1)).join(); + } + + salsa20RoundTrip('test', 'test'); + salsa20RoundTrip('', ''); + salsa20RoundTrip('1', '2'); + salsa20RoundTrip(textWithLength(4096), textWithLength(4096)); + // _salsa20RoundTrip(textWithLength(40960), textWithLength(40960)); + salsa20RoundTrip(textWithLength(4096000), textWithLength(4096000)); + + var password = generatePassword(); + salsa = salsa20EncrypterFromPassword(password); + var sw = Stopwatch()..start(); + var count = 1000; + for (var i = 0; i < count; i++) { + salsa.decrypt(salsa.encrypt(textWithLength(count * 10))); + } + print('salsa round trip: ${sw.elapsedMilliseconds} ms'); + + var encrypteds = List.generate(count, (index) => ''); + sw = Stopwatch()..start(); + for (var i = 0; i < count; i++) { + encrypteds[i] = salsa.encrypt(textWithLength(count * 10)); + } + print('salsa encrypt: ${sw.elapsedMilliseconds} ms'); + sw = Stopwatch()..start(); + for (var i = 0; i < count; i++) { + salsa.decrypt(encrypteds[i]); + } + print('salsa decrypt: ${sw.elapsedMilliseconds} ms'); + }); +}