From a7affea1e14192b870306aa9c17270d688ed8fab Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Tue, 24 May 2022 23:59:10 +0200 Subject: [PATCH] perf: x10 perfomance improvement on android with better list management --- .../example/lib/pages/benchmark_page.dart | 101 ++++++++++++++---- .../lib/src/ciphers/aes/aes.dart | 77 ++++++------- .../lib/src/ciphers/aes/aes_mode.dart | 4 +- .../lib/src/ciphers/aes/aes_padding.dart | 4 +- .../lib/src/core/cipher_text.dart | 58 ++++++++-- .../lib/src/core/cipher_text_list.dart | 3 +- .../lib/src/interfaces/cipher.dart | 13 ++- .../NativeCryptoAndroidPlugin.kt | 71 ++++++++---- .../native_crypto_android/ciphers/AES.kt | 49 ++++++--- .../interfaces/Cipher.kt | 2 + .../analysis_options.yaml | 5 +- .../lib/native_crypto_platform_interface.dart | 75 ++----------- .../method_channel_native_crypto.dart | 95 +++++++++------- .../lib/src/platform_interface.dart | 97 ----------------- .../native_crypto_platform.dart | 92 ++++++++++++++++ .../lib/src/utils/exception.dart | 53 +++++++++ .../pubspec.yaml | 10 +- 17 files changed, 487 insertions(+), 322 deletions(-) rename packages/native_crypto_platform_interface/lib/src/{ => method_channel}/method_channel_native_crypto.dart (53%) delete mode 100644 packages/native_crypto_platform_interface/lib/src/platform_interface.dart create mode 100644 packages/native_crypto_platform_interface/lib/src/platform_interface/native_crypto_platform.dart create mode 100644 packages/native_crypto_platform_interface/lib/src/utils/exception.dart diff --git a/packages/native_crypto/example/lib/pages/benchmark_page.dart b/packages/native_crypto/example/lib/pages/benchmark_page.dart index 34e8ba0..e444eac 100644 --- a/packages/native_crypto/example/lib/pages/benchmark_page.dart +++ b/packages/native_crypto/example/lib/pages/benchmark_page.dart @@ -3,7 +3,7 @@ // ----- // File: benchmark_page.dart // Created Date: 28/12/2021 15:12:39 -// Last Modified: 24/05/2022 17:23:33 +// Last Modified: 24/05/2022 23:43:59 // ----- // Copyright (c) 2021 @@ -24,6 +24,51 @@ class BenchmarkPage extends ConsumerWidget { final Output keyContent = Output(); final Output benchmarkStatus = Output(large: true); + Future _benchmarkEncryptionOnly( + WidgetRef ref, + Cipher cipher, + ) async { + Session state = ref.read(sessionProvider.state).state; + AesGcm pc = AesGcm(); + + if (state.secretKey.bytes.isEmpty) { + benchmarkStatus + .print('No SecretKey!\nGo in Key tab and generate or derive one.'); + return; + } + + benchmarkStatus.print("Benchmark 2/4/8/16/32/64/128/256MB\n"); + List testedSizes = [2, 4, 8, 16, 32, 64, 128, 256]; + String csv = "size;encryption time\n"; + + var beforeBench = DateTime.now(); + Cipher.bytesCountPerChunk = Cipher.bytesCountPerChunk; + benchmarkStatus + .append('[Benchmark] ${Cipher.bytesCountPerChunk} bytes/chunk \n'); + for (int size in testedSizes) { + var b = Uint8List(size * 1000000); + csv += "${size * 1000000};"; + + // Encryption + var before = DateTime.now(); + var encryptedBigFile = await cipher.encrypt(b); + var after = DateTime.now(); + + var benchmark = + after.millisecondsSinceEpoch - before.millisecondsSinceEpoch; + benchmarkStatus.append('[$size MB] Encryption took $benchmark ms\n'); + + csv += "$benchmark\n"; + } + var afterBench = DateTime.now(); + var benchmark = + afterBench.millisecondsSinceEpoch - beforeBench.millisecondsSinceEpoch; + var sum = testedSizes.reduce((a, b) => a + b); + benchmarkStatus.append( + 'Benchmark finished.\nGenerated, and encrypted $sum MB in $benchmark ms'); + debugPrint("[Benchmark cvs]\n$csv"); + } + Future _benchmark(WidgetRef ref, Cipher cipher, {bool usePc = false}) async { Session state = ref.read(sessionProvider.state).state; @@ -37,13 +82,14 @@ class BenchmarkPage extends ConsumerWidget { benchmarkStatus.print("Benchmark 2/4/8/16/32/64/128/256MB\n"); List testedSizes = [2, 4, 8, 16, 32, 64, 128, 256]; - String csv = - "size;encryption time;encode time;decryption time;crypto time\n"; + String csv = "size;encryption time;decryption time;crypto time\n"; var beforeBench = DateTime.now(); + Cipher.bytesCountPerChunk = Cipher.bytesCountPerChunk; + benchmarkStatus + .append('[Benchmark] ${Cipher.bytesCountPerChunk} bytes/chunk \n'); for (int size in testedSizes) { - var b = ByteData(size * 1000000); - //var bigFile = Uint8List.view(); + var b = Uint8List(size * 1000000); csv += "${size * 1000000};"; var cryptoTime = 0; @@ -51,10 +97,9 @@ class BenchmarkPage extends ConsumerWidget { var before = DateTime.now(); Object encryptedBigFile; if (usePc) { - encryptedBigFile = - pc.encrypt(b.buffer.asUint8List(), state.secretKey.bytes); + encryptedBigFile = pc.encrypt(b, state.secretKey.bytes); } else { - encryptedBigFile = await cipher.encrypt(b.buffer.asUint8List()); + encryptedBigFile = await cipher.encrypt(b); } var after = DateTime.now(); @@ -62,7 +107,7 @@ class BenchmarkPage extends ConsumerWidget { after.millisecondsSinceEpoch - before.millisecondsSinceEpoch; benchmarkStatus.append('[$size MB] Encryption took $benchmark ms\n'); - csv += "$benchmark;"; + csv += "$benchmark"; cryptoTime += benchmark; // Decryption @@ -129,20 +174,34 @@ class BenchmarkPage extends ConsumerWidget { alignment: Alignment.centerLeft, ), keyContent, - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Button( - () => _benchmark(ref, cipher), - "NativeCrypto", + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Button( + () => _benchmark(ref, cipher), + "NativeCrypto", + ), + Button( + () => _benchmark(ref, cipher, usePc: true), + "PointyCastle", + ), + Button( + _clear, + "Clear", + ), + ], ), - Button( - () => _benchmark(ref, cipher, usePc: true), - "PointyCastle", - ), - Button( - _clear, - "Clear", + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Button( + () => _benchmarkEncryptionOnly(ref, cipher), + "NC Persistence", + ), + ], ), ], ), diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes.dart b/packages/native_crypto/lib/src/ciphers/aes/aes.dart index a1ae944..d740757 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes.dart @@ -3,7 +3,7 @@ // ----- // File: aes.dart // Created Date: 16/12/2021 16:28:00 -// Last Modified: 23/05/2022 23:06:05 +// Last Modified: 24/05/2022 23:29:42 // ----- // Copyright (c) 2022 @@ -39,6 +39,7 @@ class AES implements Cipher { final Map> _supported = { AESMode.gcm: [AESPadding.none], + AESMode.cbc: [AESPadding.pkcs5], }; if (!_supported[mode]!.contains(padding)) { @@ -46,27 +47,35 @@ class AES implements Cipher { } } + Future _decrypt(CipherText cipherText) async { + return await platform.decryptAsList( + [cipherText.iv, cipherText.payload], + key.bytes, + algorithm.name, + ) ?? + Uint8List(0); + } + + Future _encrypt(Uint8List data) async { + final List cipherText = + await platform.encryptAsList(data, key.bytes, algorithm.name) ?? + List.empty(); + return CipherText.fromPairIvAndBytes( + cipherText, + dataLength: cipherText.last.length, + ); + } + @override Future decrypt(CipherText cipherText) async { final BytesBuilder decryptedData = BytesBuilder(copy: false); + if (cipherText is CipherTextList) { for (final CipherText ct in cipherText.list) { - final Uint8List d = await platform.decrypt( - ct.bytes, - key.bytes, - algorithm.name, - ) ?? - Uint8List(0); - decryptedData.add(d); + decryptedData.add(await _decrypt(ct)); } } else { - final Uint8List d = await platform.decrypt( - cipherText.bytes, - key.bytes, - algorithm.name, - ) ?? - Uint8List(0); - decryptedData.add(d); + decryptedData.add(await _decrypt(cipherText)); } return decryptedData.toBytes(); @@ -75,43 +84,23 @@ class AES implements Cipher { @override Future encrypt(Uint8List data) async { Uint8List dataToEncrypt; + final CipherTextList cipherTextList = CipherTextList(); - // If data is bigger than 32mB -> split in chunks - if (data.length > CipherTextList.chunkSize) { - final int chunkNb = (data.length / CipherTextList.chunkSize).ceil(); + + if (data.length > Cipher.bytesCountPerChunk) { + final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil(); for (var i = 0; i < chunkNb; i++) { dataToEncrypt = i < (chunkNb - 1) ? data.sublist( - i * CipherTextList.chunkSize, - (i + 1) * CipherTextList.chunkSize, + i * Cipher.bytesCountPerChunk, + (i + 1) * Cipher.bytesCountPerChunk, ) - : data.sublist(i * CipherTextList.chunkSize); - final Uint8List c = await platform.encrypt( - dataToEncrypt, - key.bytes, - algorithm.name, - ) ?? - Uint8List(0); - cipherTextList.add( - CipherText( - c.sublist(0, 12), - c.sublist(12, c.length - 16), - c.sublist(c.length - 16, c.length), - ), - ); // TODO(hpcl): generify this + : data.sublist(i * Cipher.bytesCountPerChunk); + cipherTextList.add(await _encrypt(dataToEncrypt)); } } else { - final Uint8List c = - await platform.encrypt(data, key.bytes, algorithm.name) ?? - Uint8List(0); - - return CipherText( - c.sublist(0, 12), - c.sublist(12, c.length - 16), - c.sublist(c.length - 16, c.length), - ); // TODO(hpcl): generify this + return _encrypt(data); } - return cipherTextList; } } diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart b/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart index 554cdde..1ff7b5d 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart @@ -3,9 +3,9 @@ // ----- // File: aes_mode.dart // Created Date: 23/05/2022 22:09:16 -// Last Modified: 23/05/2022 22:10:31 +// Last Modified: 24/05/2022 23:17:01 // ----- // Copyright (c) 2022 /// Defines the AES modes of operation. -enum AESMode { gcm } +enum AESMode { gcm, cbc } diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes_padding.dart b/packages/native_crypto/lib/src/ciphers/aes/aes_padding.dart index 06e323b..676f526 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes_padding.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_padding.dart @@ -3,9 +3,9 @@ // ----- // File: aes_padding.dart // Created Date: 23/05/2022 22:10:17 -// Last Modified: 23/05/2022 22:13:28 +// Last Modified: 24/05/2022 23:17:25 // ----- // Copyright (c) 2022 /// Represents different paddings. -enum AESPadding { none } +enum AESPadding { none, pkcs5 } diff --git a/packages/native_crypto/lib/src/core/cipher_text.dart b/packages/native_crypto/lib/src/core/cipher_text.dart index cd840a3..fcf09aa 100644 --- a/packages/native_crypto/lib/src/core/cipher_text.dart +++ b/packages/native_crypto/lib/src/core/cipher_text.dart @@ -3,7 +3,7 @@ // ----- // File: cipher_text.dart // Created Date: 16/12/2021 16:59:53 -// Last Modified: 23/05/2022 23:02:10 +// Last Modified: 24/05/2022 21:27:44 // ----- // Copyright (c) 2021 @@ -16,23 +16,61 @@ class CipherText extends ByteArray { final int _dataLength; final int _tagLength; - CipherText(Uint8List iv, Uint8List data, Uint8List tag) + final Uint8List _iv; + + CipherText(Uint8List iv, Uint8List data, Uint8List? tag) : _ivLength = iv.length, _dataLength = data.length, - _tagLength = tag.length, - super(Uint8List.fromList(iv + data + tag)); + _tagLength = tag?.length ?? 0, + _iv = iv, + super((tag != null) ? Uint8List.fromList(data + tag) : data); + + CipherText.fromBytes( + Uint8List bytes, { + required int ivLength, + required int dataLength, + int tagLength = 0, + }) : _ivLength = ivLength, + _dataLength = dataLength, + _tagLength = tagLength, + _iv = bytes.sublist(0, ivLength), + super(bytes.sublist(ivLength, bytes.length - tagLength)); + + const CipherText.fromIvAndBytes( + Uint8List iv, + super.data, { + required int dataLength, + int tagLength = 0, + }) : _ivLength = iv.length, + _dataLength = dataLength, + _tagLength = tagLength, + _iv = iv; + + CipherText.fromPairIvAndBytes( + List pair, { + required int dataLength, + int tagLength = 0, + }) : _ivLength = pair.first.length, + _dataLength = dataLength, + _tagLength = tagLength, + _iv = pair.first, + super(pair.last); /// Gets the CipherText IV. - Uint8List get iv => bytes.sublist(0, _ivLength); + Uint8List get iv => _iv; /// Gets the CipherText data. - Uint8List get data => bytes.sublist(_ivLength, _ivLength + _dataLength); + Uint8List get data => _tagLength > 0 + ? bytes.sublist(0, _dataLength - _tagLength) + : bytes; /// Gets the CipherText tag. - Uint8List get tag => bytes.sublist( - _ivLength + _dataLength, - _ivLength + _dataLength + _tagLength, - ); + Uint8List get tag => _tagLength > 0 + ? bytes.sublist(_dataLength - _tagLength, _dataLength) + : Uint8List(0); + + /// Gets the CipherText data and tag. + Uint8List get payload => bytes; /// Gets the CipherText IV length. int get ivLength => _ivLength; diff --git a/packages/native_crypto/lib/src/core/cipher_text_list.dart b/packages/native_crypto/lib/src/core/cipher_text_list.dart index 9d16211..dab2709 100644 --- a/packages/native_crypto/lib/src/core/cipher_text_list.dart +++ b/packages/native_crypto/lib/src/core/cipher_text_list.dart @@ -3,7 +3,7 @@ // ----- // File: cipher_text_list.dart // Created Date: 23/05/2022 22:59:02 -// Last Modified: 23/05/2022 23:05:02 +// Last Modified: 24/05/2022 20:18:26 // ----- // Copyright (c) 2022 @@ -12,7 +12,6 @@ import 'dart:typed_data'; import 'package:native_crypto/src/core/cipher_text.dart'; class CipherTextList extends CipherText { - static const int chunkSize = 33554432; final List _list; CipherTextList() diff --git a/packages/native_crypto/lib/src/interfaces/cipher.dart b/packages/native_crypto/lib/src/interfaces/cipher.dart index df8296a..8941ee8 100644 --- a/packages/native_crypto/lib/src/interfaces/cipher.dart +++ b/packages/native_crypto/lib/src/interfaces/cipher.dart @@ -3,7 +3,7 @@ // ----- // File: cipher.dart // Created Date: 16/12/2021 16:28:00 -// Last Modified: 23/05/2022 23:06:20 +// Last Modified: 24/05/2022 19:55:38 // ----- // Copyright (c) 2021 @@ -18,9 +18,20 @@ import 'package:native_crypto/src/utils/cipher_algorithm.dart'; /// or decryption - a series of well-defined steps that can /// be followed as a procedure. abstract class Cipher { + /// Returns the size of a chunk of data + /// that can be processed by the cipher. + static int _bytesCountPerChunk = 33554432; + + static int get bytesCountPerChunk => Cipher._bytesCountPerChunk; + + static set bytesCountPerChunk(int bytesCount) { + _bytesCountPerChunk = bytesCount; + } + /// Returns the standard algorithm name for this cipher CipherAlgorithm get algorithm; + /// Encrypts data. /// /// Takes [Uint8List] data as parameter. diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCryptoAndroidPlugin.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCryptoAndroidPlugin.kt index 2e1b5e3..36cd412 100644 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCryptoAndroidPlugin.kt +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCryptoAndroidPlugin.kt @@ -1,6 +1,7 @@ package fr.pointcheval.native_crypto_android import androidx.annotation.NonNull +import fr.pointcheval.native_crypto_android.interfaces.Cipher import fr.pointcheval.native_crypto_android.kdf.Pbkdf2 import fr.pointcheval.native_crypto_android.keys.SecretKey import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm @@ -23,6 +24,8 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler { private lateinit var channel: MethodChannel private val name = "plugins.hugop.cl/native_crypto" + private var cipherInstance: Cipher? = null + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, name) channel.setMethodCallHandler(this) @@ -38,11 +41,11 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler { when (call.method) { "digest" -> methodCallTask = handleDigest(call.arguments()) "generateSecretKey" -> methodCallTask = handleGenerateSecretKey(call.arguments()) - "generateKeyPair" -> result.notImplemented() "pbkdf2" -> methodCallTask = handlePbkdf2(call.arguments()) - "encrypt" -> methodCallTask = handleEncrypt(call.arguments()) - "decrypt" -> methodCallTask = handleDecrypt(call.arguments()) - "generateSharedSecretKey" -> result.notImplemented() + "encryptAsList" -> methodCallTask = handleEncryptAsList(call.arguments()) + "decryptAsList" -> methodCallTask = handleDecryptAsList(call.arguments()) + "encrypt" -> methodCallTask = handleCrypt(call.arguments(), true) + "decrypt" -> methodCallTask = handleCrypt(call.arguments(), false) else -> result.notImplemented() } @@ -95,22 +98,17 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler { } } - private fun handleEncrypt(arguments: Map?): Task { - return Task { - val data: ByteArray = - Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray - val key: ByteArray = Objects.requireNonNull(arguments?.get(Constants.KEY)) as ByteArray - val algorithm: String = - Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String - - val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) - val cipher = cipherAlgorithm.getCipher() - - cipher.encrypt(data, key) + private fun lazyLoadCipher(cipherAlgorithm: CipherAlgorithm) { + if (cipherInstance == null) { + cipherInstance = cipherAlgorithm.getCipher() + } else { + if (cipherInstance!!.algorithm != cipherAlgorithm) { + cipherInstance = cipherAlgorithm.getCipher() + } } } - private fun handleDecrypt(arguments: Map?): Task { + private fun handleEncryptAsList(arguments: Map?): Task> { return Task { val data: ByteArray = Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray @@ -119,9 +117,44 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler { Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) - val cipher = cipherAlgorithm.getCipher() + lazyLoadCipher(cipherAlgorithm) - cipher.decrypt(data, key) + cipherInstance!!.encryptAsList(data, key) + } + } + + private fun handleDecryptAsList(arguments: Map?): Task { + return Task { + val data: List = + Objects.requireNonNull(arguments?.get(Constants.DATA)) as List + val key: ByteArray = Objects.requireNonNull(arguments?.get(Constants.KEY)) as ByteArray + val algorithm: String = + Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String + + val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) + lazyLoadCipher(cipherAlgorithm) + + cipherInstance!!.decryptAsList(data, key) + } + } + + // **EN**Crypt and **DE**Crypt + private fun handleCrypt(arguments: Map?, forEncryption: Boolean): Task { + return Task { + val data: ByteArray = + Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray + val key: ByteArray = Objects.requireNonNull(arguments?.get(Constants.KEY)) as ByteArray + val algorithm: String = + Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String + + val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) + lazyLoadCipher(cipherAlgorithm) + + if (forEncryption) { + cipherInstance!!.encrypt(data, key) + } else { + cipherInstance!!.decrypt(data, key) + } } } } diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/ciphers/AES.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/ciphers/AES.kt index cbb9706..1bb2c3b 100644 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/ciphers/AES.kt +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/ciphers/AES.kt @@ -11,6 +11,10 @@ class AES : Cipher { override val algorithm: CipherAlgorithm get() = CipherAlgorithm.aes + var forEncryption: Boolean = true + var cipherInstance: javax.crypto.Cipher? = null; + var secretKey: SecretKeySpec? = null; + /* override fun encrypt(data: ByteArray, key: ByteArray): ByteArray { val sk: SecretKey = SecretKeySpec(key, "AES") val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding") @@ -35,25 +39,46 @@ class AES : Cipher { }*/ override fun encrypt(data: ByteArray, key: ByteArray): ByteArray { - val sk: SecretKey = SecretKeySpec(key, "AES") - val cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding") - cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) + val list : List = encryptAsList(data, key) + return list.first().plus(list.last()) + } + + override fun encryptAsList(data: ByteArray, key: ByteArray): List { + val sk = SecretKeySpec(key, "AES") + if (cipherInstance == null || !forEncryption || secretKey != sk) { + secretKey = sk + forEncryption = true + // native.crypto representation = [IV(16) || CIPHERTEXT(n-16)] + // javax.crypto representation = [CIPHERTEXT(n-16)] + cipherInstance = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding") + cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) + } // javax.crypto representation = [CIPHERTEXT(n-16)] - val bytes = cipher.doFinal(data) - val iv = cipher.iv + val bytes: ByteArray = cipherInstance!!.doFinal(data) + val iv: ByteArray = cipherInstance!!.iv // native.crypto representation = [IV(16) || CIPHERTEXT(n-16)] - return iv.plus(bytes) + return listOf(iv, bytes) } override fun decrypt(data: ByteArray, key: ByteArray): ByteArray { - val sk: SecretKey = SecretKeySpec(key, "AES") - // native.crypto representation = [IV(16) || CIPHERTEXT(n-16)] - val iv: ByteArray = data.take(16).toByteArray() // javax.crypto representation = [CIPHERTEXT(n-16)] + val iv: ByteArray = data.take(16).toByteArray() val payload: ByteArray = data.drop(16).toByteArray() + return decryptAsList(listOf(iv, payload), key) + } + + override fun decryptAsList(data: List, key: ByteArray): ByteArray { + if (cipherInstance == null) { + // native.crypto representation = [IV(16) || CIPHERTEXT(n-16)] + // javax.crypto representation = [CIPHERTEXT(n-16)] + cipherInstance = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding") + } + val sk = SecretKeySpec(key, "AES") + val iv: ByteArray = data.first() val ivSpec = IvParameterSpec(iv) - val cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding") - cipher.init(javax.crypto.Cipher.DECRYPT_MODE, sk, ivSpec) - return cipher.doFinal(payload) + cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, ivSpec) + forEncryption = false + val payload: ByteArray = data.last() + return cipherInstance!!.doFinal(payload) } } \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Cipher.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Cipher.kt index 1667507..5893d25 100644 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Cipher.kt +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Cipher.kt @@ -7,4 +7,6 @@ interface Cipher { fun encrypt(data: ByteArray, key: ByteArray): ByteArray fun decrypt(data: ByteArray, key: ByteArray): ByteArray + fun encryptAsList(data: ByteArray, key: ByteArray): List + fun decryptAsList(data: List, key: ByteArray): ByteArray } \ No newline at end of file diff --git a/packages/native_crypto_platform_interface/analysis_options.yaml b/packages/native_crypto_platform_interface/analysis_options.yaml index a5744c1..db48808 100644 --- a/packages/native_crypto_platform_interface/analysis_options.yaml +++ b/packages/native_crypto_platform_interface/analysis_options.yaml @@ -1,4 +1 @@ -include: package:flutter_lints/flutter.yaml - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +include: package:wyatt_analysis/analysis_options.flutter.experimental.yaml \ No newline at end of file diff --git a/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface.dart b/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface.dart index 1d6fbd5..c85bda1 100644 --- a/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface.dart +++ b/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface.dart @@ -2,74 +2,13 @@ // Email: git@pcl.ovh // ----- // File: native_crypto_platform_interface.dart -// Created Date: 25/12/2021 16:43:49 -// Last Modified: 25/12/2021 17:39:39 +// Created Date: 24/05/2022 19:39:11 +// Last Modified: 24/05/2022 19:39:58 // ----- -// Copyright (c) 2021 +// Copyright (c) 2022 -import 'dart:typed_data'; +library native_crypto_platform_interface; -import './src/method_channel_native_crypto.dart'; -import './src/platform_interface.dart'; - -/// The interface that implementations of path_provider must implement. -/// -/// Platform implementations should extend this class rather than implement it as `NativeCrypto` -/// does not consider newly added methods to be breaking changes. Extending this class -/// (using `extends`) ensures that the subclass will get the default implementation, while -/// platform implementations that `implements` this interface will be broken by newly added -/// [NativeCryptoPlatform] methods. -abstract class NativeCryptoPlatform extends PlatformInterface { - /// Constructs a NativeCryptoPlatform. - NativeCryptoPlatform() : super(token: _token); - - static final Object _token = Object(); - - static NativeCryptoPlatform _instance = MethodChannelNativeCrypto(); - - /// The default instance of [NativeCryptoPlatform] to use. - /// - /// Defaults to [MethodChannelPathProvider]. - static NativeCryptoPlatform get instance => _instance; - - /// Platform-specific plugins should set this with their own platform-specific - /// class that extends [NativeCryptoPlatform] when they register themselves. - static set instance(NativeCryptoPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future digest(Uint8List data, String algorithm) { - throw UnimplementedError('digest is not implemented'); - } - - Future generateSecretKey(int bitsCount) { - throw UnimplementedError('generateSecretKey is not implemented'); - } - - Future generateKeyPair() { - throw UnimplementedError('generateKeyPair is not implemented'); - } - - Future pbkdf2(String password, String salt, int keyBytesCount, - int iterations, String algorithm) { - throw UnimplementedError('pbkdf2 is not implemented'); - } - - Future encrypt(Uint8List data, Uint8List key, String algorithm) { - throw UnimplementedError('encrypt is not implemented'); - } - - Future decrypt(Uint8List data, Uint8List key, String algorithm) { - throw UnimplementedError('decrypt is not implemented'); - } - - Future generateSharedSecretKey( - Uint8List salt, - int keyBytesCount, - Uint8List ephemeralPrivateKey, - Uint8List otherPublicKey, - String hkdfAlgorithm) { - throw UnimplementedError('generateSharedSecretKey is not implemented'); - } -} +export 'src/method_channel/method_channel_native_crypto.dart'; +export 'src/platform_interface/native_crypto_platform.dart'; +export 'src/utils/exception.dart'; diff --git a/packages/native_crypto_platform_interface/lib/src/method_channel_native_crypto.dart b/packages/native_crypto_platform_interface/lib/src/method_channel/method_channel_native_crypto.dart similarity index 53% rename from packages/native_crypto_platform_interface/lib/src/method_channel_native_crypto.dart rename to packages/native_crypto_platform_interface/lib/src/method_channel/method_channel_native_crypto.dart index 3cab922..7205096 100644 --- a/packages/native_crypto_platform_interface/lib/src/method_channel_native_crypto.dart +++ b/packages/native_crypto_platform_interface/lib/src/method_channel/method_channel_native_crypto.dart @@ -3,7 +3,7 @@ // ----- // File: native_crypto_method_channel.dart // Created Date: 25/12/2021 16:58:04 -// Last Modified: 25/12/2021 18:58:53 +// Last Modified: 24/05/2022 22:59:32 // ----- // Copyright (c) 2021 @@ -11,19 +11,17 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; - -import '../native_crypto_platform_interface.dart'; +import 'package:native_crypto_platform_interface/src/platform_interface/native_crypto_platform.dart'; /// An implementation of [NativeCryptoPlatform] that uses method channels. class MethodChannelNativeCrypto extends NativeCryptoPlatform { /// The method channel used to interact with the native platform. @visibleForTesting - MethodChannel methodChannel = - const MethodChannel('plugins.hugop.cl/native_crypto'); + MethodChannel channel = const MethodChannel('plugins.hugop.cl/native_crypto'); @override Future digest(Uint8List data, String algorithm) { - return methodChannel.invokeMethod( + return channel.invokeMethod( 'digest', { 'data': data, @@ -34,7 +32,7 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform { @override Future generateSecretKey(int bitsCount) { - return methodChannel.invokeMethod( + return channel.invokeMethod( 'generateSecretKey', { 'bitsCount': bitsCount, @@ -43,14 +41,14 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform { } @override - Future generateKeyPair() { - return methodChannel.invokeMethod('generateKeyPair'); - } - - @override - Future pbkdf2(String password, String salt, int keyBytesCount, - int iterations, String algorithm) { - return methodChannel.invokeMethod( + Future pbkdf2( + String password, + String salt, + int keyBytesCount, + int iterations, + String algorithm, + ) { + return channel.invokeMethod( 'pbkdf2', { 'password': password, @@ -63,8 +61,44 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform { } @override - Future encrypt(Uint8List data, Uint8List key, String algorithm) { - return methodChannel.invokeMethod( + Future?> encryptAsList( + Uint8List data, + Uint8List key, + String algorithm, + ) { + return channel.invokeListMethod( + 'encryptAsList', + { + 'data': data, + 'key': key, + 'algorithm': algorithm, + }, + ); + } + + @override + Future decryptAsList( + List data, + Uint8List key, + String algorithm, + ) { + return channel.invokeMethod( + 'decryptAsList', + { + 'data': data, + 'key': key, + 'algorithm': algorithm, + }, + ); + } + + @override + Future encrypt( + Uint8List data, + Uint8List key, + String algorithm, + ) { + return channel.invokeMethod( 'encrypt', { 'data': data, @@ -75,8 +109,12 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform { } @override - Future decrypt(Uint8List data, Uint8List key, String algorithm) { - return methodChannel.invokeMethod( + Future decrypt( + Uint8List data, + Uint8List key, + String algorithm, + ) { + return channel.invokeMethod( 'decrypt', { 'data': data, @@ -85,23 +123,4 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform { }, ); } - - @override - Future generateSharedSecretKey( - Uint8List salt, - int keyBytesCount, - Uint8List ephemeralPrivateKey, - Uint8List otherPublicKey, - String hkdfAlgorithm) { - return methodChannel.invokeMethod( - 'generateSharedSecretKey', - { - 'salt': salt, - 'keyBytesCount': keyBytesCount, - 'ephemeralPrivateKey': ephemeralPrivateKey, - 'otherPublicKey': otherPublicKey, - 'hkdfAlgorithm': hkdfAlgorithm, - }, - ); - } } diff --git a/packages/native_crypto_platform_interface/lib/src/platform_interface.dart b/packages/native_crypto_platform_interface/lib/src/platform_interface.dart deleted file mode 100644 index 31f8037..0000000 --- a/packages/native_crypto_platform_interface/lib/src/platform_interface.dart +++ /dev/null @@ -1,97 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: platform_interface.dart -// Created Date: 25/12/2021 16:52:56 -// Last Modified: 27/12/2021 21:25:39 -// ----- -// Copyright (c) 2021 - -import 'package:meta/meta.dart'; - -/// Base class for platform interfaces. -/// -/// Provides a static helper method for ensuring that platform interfaces are -/// implemented using `extends` instead of `implements`. -/// -/// Platform interface classes are expected to have a private static token object which will be -/// be passed to [verifyToken] along with a platform interface object for verification. -/// -/// Sample usage: -/// -/// ```dart -/// abstract class NativeCryptoPlatform extends PlatformInterface { -/// NativeCryptoPlatform() : super(token: _token); -/// -/// static NativeCryptoPlatform _instance = MethodChannelNativeCrypto(); -/// -/// static const Object _token = Object(); -/// -/// static NativeCryptoPlatform get instance => _instance; -/// -/// /// Platform-specific plugins should set this with their own platform-specific -/// /// class that extends [NativeCryptoPlatform] when they register themselves. -/// static set instance(NativeCryptoPlatform instance) { -/// PlatformInterface.verifyToken(instance, _token); -/// _instance = instance; -/// } -/// -/// } -/// ``` -/// -/// Mockito mocks of platform interfaces will fail the verification, in test code only it is possible -/// to include the [MockPlatformInterfaceMixin] for the verification to be temporarily disabled. See -/// [MockPlatformInterfaceMixin] for a sample of using Mockito to mock a platform interface. -abstract class PlatformInterface { - /// Pass a private, class-specific `const Object()` as the `token`. - PlatformInterface({required Object token}) : _instanceToken = token; - - final Object? _instanceToken; - - /// Ensures that the platform instance has a token that matches the - /// provided token and throws [AssertionError] if not. - /// - /// This is used to ensure that implementers are using `extends` rather than - /// `implements`. - /// - /// Subclasses of [MockPlatformInterfaceMixin] are assumed to be valid in debug - /// builds. - /// - /// This is implemented as a static method so that it cannot be overridden - /// with `noSuchMethod`. - static void verifyToken(PlatformInterface instance, Object token) { - if (instance is MockPlatformInterfaceMixin) { - bool assertionsEnabled = false; - assert(() { - assertionsEnabled = true; - return true; - }()); - if (!assertionsEnabled) { - throw AssertionError( - '`MockPlatformInterfaceMixin` is not intended for use in release builds.'); - } - return; - } - if (!identical(token, instance._instanceToken)) { - throw AssertionError( - 'Platform interfaces must not be implemented with `implements`'); - } - } -} - -/// A [PlatformInterface] mixin that can be combined with mockito's `Mock`. -/// -/// It passes the [PlatformInterface.verifyToken] check even though it isn't -/// using `extends`. -/// -/// This class is intended for use in tests only. -/// -/// Sample usage (assuming NativeCryptoPlatform extends [PlatformInterface]: -/// -/// ```dart -/// class NativeCryptoPlatformMock extends Mock -/// with MockPlatformInterfaceMixin -/// implements NativeCryptoPlatform {} -/// ``` -@visibleForTesting -abstract class MockPlatformInterfaceMixin implements PlatformInterface {} diff --git a/packages/native_crypto_platform_interface/lib/src/platform_interface/native_crypto_platform.dart b/packages/native_crypto_platform_interface/lib/src/platform_interface/native_crypto_platform.dart new file mode 100644 index 0000000..b5caf6b --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/platform_interface/native_crypto_platform.dart @@ -0,0 +1,92 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: native_crypto_platform_interface.dart +// Created Date: 25/12/2021 16:43:49 +// Last Modified: 24/05/2022 22:58:31 +// ----- +// Copyright (c) 2021 + +import 'dart:typed_data'; + +import 'package:native_crypto_platform_interface/src/method_channel/method_channel_native_crypto.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// The interface that implementations of path_provider must implement. +/// +/// Platform implementations should extend this class rather than implement +/// it as `NativeCrypto` does not consider newly added methods to be +/// breaking changes. Extending this class (using `extends`) ensures +/// that the subclass will get the default implementation, while platform +/// implementations that `implements` this interface will be +/// broken by newly added [NativeCryptoPlatform] methods. +abstract class NativeCryptoPlatform extends PlatformInterface { + /// Constructs a NativeCryptoPlatform. + NativeCryptoPlatform() : super(token: _token); + + static final Object _token = Object(); + + static NativeCryptoPlatform _instance = MethodChannelNativeCrypto(); + + /// The default instance of [NativeCryptoPlatform] to use. + /// + /// Defaults to [MethodChannelNativeCrypto]. + static NativeCryptoPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [NativeCryptoPlatform] when they register themselves. + static set instance(NativeCryptoPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future digest(Uint8List data, String algorithm) { + throw UnimplementedError('digest is not implemented'); + } + + Future generateSecretKey(int bitsCount) { + throw UnimplementedError('generateSecretKey is not implemented'); + } + + Future pbkdf2( + String password, + String salt, + int keyBytesCount, + int iterations, + String algorithm, + ) { + throw UnimplementedError('pbkdf2 is not implemented'); + } + + Future?> encryptAsList( + Uint8List data, + Uint8List key, + String algorithm, + ) { + throw UnimplementedError('encryptAsList is not implemented'); + } + + Future decryptAsList( + List data, + Uint8List key, + String algorithm, + ) { + throw UnimplementedError('decryptAsList is not implemented'); + } + + Future encrypt( + Uint8List data, + Uint8List key, + String algorithm, + ) { + throw UnimplementedError('encrypt is not implemented'); + } + + Future decrypt( + Uint8List data, + Uint8List key, + String algorithm, + ) { + throw UnimplementedError('decrypt is not implemented'); + } +} diff --git a/packages/native_crypto_platform_interface/lib/src/utils/exception.dart b/packages/native_crypto_platform_interface/lib/src/utils/exception.dart new file mode 100644 index 0000000..24af864 --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/utils/exception.dart @@ -0,0 +1,53 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: exception.dart +// Created Date: 24/05/2022 18:54:48 +// Last Modified: 24/05/2022 18:58:39 +// ----- +// Copyright (c) 2022 + +import 'package:flutter/services.dart'; + +class NativeCryptoException implements Exception { + final String message; + const NativeCryptoException(this.message); +} + +/// Catches a [PlatformException] and returns an [Exception]. +/// +/// If the [Exception] is a [PlatformException], +/// a [NativeCryptoException] is returned. +Never convertPlatformException(Object exception, StackTrace stackTrace) { + if (exception is! Exception || exception is! PlatformException) { + Error.throwWithStackTrace(exception, stackTrace); + } + + Error.throwWithStackTrace( + platformExceptionToNativeCryptoException(exception, stackTrace), + stackTrace, + ); +} + +/// Converts a [PlatformException] into a [NativeCryptoException]. +/// +/// A [PlatformException] can only be converted to a [NativeCryptoException] +/// if the `details` of the exception exist. +NativeCryptoException platformExceptionToNativeCryptoException( + PlatformException platformException, + StackTrace stackTrace, +) { + final Map? details = platformException.details != null + ? Map.from( + platformException.details as Map, + ) + : null; + + String message = platformException.message ?? ''; + + if (details != null) { + message = details['message'] ?? message; + } + + return NativeCryptoException(message); +} diff --git a/packages/native_crypto_platform_interface/pubspec.yaml b/packages/native_crypto_platform_interface/pubspec.yaml index 3e90763..fd0ceb5 100644 --- a/packages/native_crypto_platform_interface/pubspec.yaml +++ b/packages/native_crypto_platform_interface/pubspec.yaml @@ -3,15 +3,21 @@ description: A common interface for NativeCrypto plugin. version: 0.1.0 environment: - sdk: ">=2.15.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" flutter: ">=2.5.0" dependencies: flutter: sdk: flutter + + plugin_platform_interface: ^2.1.2 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^1.0.4 \ No newline at end of file + wyatt_analysis: + git: + url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages + ref: wyatt_analysis-v2.1.0 + path: packages/wyatt_analysis \ No newline at end of file