perf: x10 perfomance improvement on android with better list management
This commit is contained in:
parent
6397e10c05
commit
a7affea1e1
@ -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<void> _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<int> 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<void> _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<int> 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",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -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<AESMode, List<AESPadding>> _supported = {
|
||||
AESMode.gcm: [AESPadding.none],
|
||||
AESMode.cbc: [AESPadding.pkcs5],
|
||||
};
|
||||
|
||||
if (!_supported[mode]!.contains(padding)) {
|
||||
@ -46,27 +47,35 @@ class AES implements Cipher {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> _decrypt(CipherText cipherText) async {
|
||||
return await platform.decryptAsList(
|
||||
[cipherText.iv, cipherText.payload],
|
||||
key.bytes,
|
||||
algorithm.name,
|
||||
) ??
|
||||
Uint8List(0);
|
||||
}
|
||||
|
||||
Future<CipherText> _encrypt(Uint8List data) async {
|
||||
final List<Uint8List> cipherText =
|
||||
await platform.encryptAsList(data, key.bytes, algorithm.name) ??
|
||||
List.empty();
|
||||
return CipherText.fromPairIvAndBytes(
|
||||
cipherText,
|
||||
dataLength: cipherText.last.length,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> 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<CipherText> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
@ -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<Uint8List> 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;
|
||||
|
@ -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<CipherText> _list;
|
||||
|
||||
CipherTextList()
|
||||
|
@ -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.
|
||||
|
@ -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<String, Any>?): Task<ByteArray> {
|
||||
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<String, Any>?): Task<ByteArray> {
|
||||
private fun handleEncryptAsList(arguments: Map<String, Any>?): Task<List<ByteArray>> {
|
||||
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<String, Any>?): Task<ByteArray> {
|
||||
return Task {
|
||||
val data: List<ByteArray> =
|
||||
Objects.requireNonNull(arguments?.get(Constants.DATA)) as List<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)
|
||||
|
||||
cipherInstance!!.decryptAsList(data, key)
|
||||
}
|
||||
}
|
||||
|
||||
// **EN**Crypt and **DE**Crypt
|
||||
private fun handleCrypt(arguments: Map<String, Any>?, forEncryption: Boolean): Task<ByteArray> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ByteArray> = encryptAsList(data, key)
|
||||
return list.first().plus(list.last())
|
||||
}
|
||||
|
||||
override fun encryptAsList(data: ByteArray, key: ByteArray): List<ByteArray> {
|
||||
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<ByteArray>, 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)
|
||||
}
|
||||
}
|
@ -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<ByteArray>
|
||||
fun decryptAsList(data: List<ByteArray>, key: ByteArray): ByteArray
|
||||
}
|
@ -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
|
@ -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<Uint8List?> digest(Uint8List data, String algorithm) {
|
||||
throw UnimplementedError('digest is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> generateSecretKey(int bitsCount) {
|
||||
throw UnimplementedError('generateSecretKey is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> generateKeyPair() {
|
||||
throw UnimplementedError('generateKeyPair is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> pbkdf2(String password, String salt, int keyBytesCount,
|
||||
int iterations, String algorithm) {
|
||||
throw UnimplementedError('pbkdf2 is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> encrypt(Uint8List data, Uint8List key, String algorithm) {
|
||||
throw UnimplementedError('encrypt is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> decrypt(Uint8List data, Uint8List key, String algorithm) {
|
||||
throw UnimplementedError('decrypt is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> 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';
|
||||
|
@ -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<Uint8List?> digest(Uint8List data, String algorithm) {
|
||||
return methodChannel.invokeMethod<Uint8List>(
|
||||
return channel.invokeMethod<Uint8List>(
|
||||
'digest',
|
||||
<String, dynamic>{
|
||||
'data': data,
|
||||
@ -34,7 +32,7 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
|
||||
|
||||
@override
|
||||
Future<Uint8List?> generateSecretKey(int bitsCount) {
|
||||
return methodChannel.invokeMethod<Uint8List>(
|
||||
return channel.invokeMethod<Uint8List>(
|
||||
'generateSecretKey',
|
||||
<String, dynamic>{
|
||||
'bitsCount': bitsCount,
|
||||
@ -43,14 +41,14 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> generateKeyPair() {
|
||||
return methodChannel.invokeMethod<Uint8List>('generateKeyPair');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> pbkdf2(String password, String salt, int keyBytesCount,
|
||||
int iterations, String algorithm) {
|
||||
return methodChannel.invokeMethod<Uint8List>(
|
||||
Future<Uint8List?> pbkdf2(
|
||||
String password,
|
||||
String salt,
|
||||
int keyBytesCount,
|
||||
int iterations,
|
||||
String algorithm,
|
||||
) {
|
||||
return channel.invokeMethod<Uint8List>(
|
||||
'pbkdf2',
|
||||
<String, dynamic>{
|
||||
'password': password,
|
||||
@ -63,8 +61,44 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> encrypt(Uint8List data, Uint8List key, String algorithm) {
|
||||
return methodChannel.invokeMethod<Uint8List>(
|
||||
Future<List<Uint8List>?> encryptAsList(
|
||||
Uint8List data,
|
||||
Uint8List key,
|
||||
String algorithm,
|
||||
) {
|
||||
return channel.invokeListMethod(
|
||||
'encryptAsList',
|
||||
<String, dynamic>{
|
||||
'data': data,
|
||||
'key': key,
|
||||
'algorithm': algorithm,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> decryptAsList(
|
||||
List<Uint8List> data,
|
||||
Uint8List key,
|
||||
String algorithm,
|
||||
) {
|
||||
return channel.invokeMethod<Uint8List>(
|
||||
'decryptAsList',
|
||||
<String, dynamic>{
|
||||
'data': data,
|
||||
'key': key,
|
||||
'algorithm': algorithm,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> encrypt(
|
||||
Uint8List data,
|
||||
Uint8List key,
|
||||
String algorithm,
|
||||
) {
|
||||
return channel.invokeMethod<Uint8List>(
|
||||
'encrypt',
|
||||
<String, dynamic>{
|
||||
'data': data,
|
||||
@ -75,8 +109,12 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> decrypt(Uint8List data, Uint8List key, String algorithm) {
|
||||
return methodChannel.invokeMethod<Uint8List>(
|
||||
Future<Uint8List?> decrypt(
|
||||
Uint8List data,
|
||||
Uint8List key,
|
||||
String algorithm,
|
||||
) {
|
||||
return channel.invokeMethod<Uint8List>(
|
||||
'decrypt',
|
||||
<String, dynamic>{
|
||||
'data': data,
|
||||
@ -85,23 +123,4 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> generateSharedSecretKey(
|
||||
Uint8List salt,
|
||||
int keyBytesCount,
|
||||
Uint8List ephemeralPrivateKey,
|
||||
Uint8List otherPublicKey,
|
||||
String hkdfAlgorithm) {
|
||||
return methodChannel.invokeMethod<Uint8List>(
|
||||
'generateSharedSecretKey',
|
||||
<String, dynamic>{
|
||||
'salt': salt,
|
||||
'keyBytesCount': keyBytesCount,
|
||||
'ephemeralPrivateKey': ephemeralPrivateKey,
|
||||
'otherPublicKey': otherPublicKey,
|
||||
'hkdfAlgorithm': hkdfAlgorithm,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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 {}
|
@ -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<Uint8List?> digest(Uint8List data, String algorithm) {
|
||||
throw UnimplementedError('digest is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> generateSecretKey(int bitsCount) {
|
||||
throw UnimplementedError('generateSecretKey is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> pbkdf2(
|
||||
String password,
|
||||
String salt,
|
||||
int keyBytesCount,
|
||||
int iterations,
|
||||
String algorithm,
|
||||
) {
|
||||
throw UnimplementedError('pbkdf2 is not implemented');
|
||||
}
|
||||
|
||||
Future<List<Uint8List>?> encryptAsList(
|
||||
Uint8List data,
|
||||
Uint8List key,
|
||||
String algorithm,
|
||||
) {
|
||||
throw UnimplementedError('encryptAsList is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> decryptAsList(
|
||||
List<Uint8List> data,
|
||||
Uint8List key,
|
||||
String algorithm,
|
||||
) {
|
||||
throw UnimplementedError('decryptAsList is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> encrypt(
|
||||
Uint8List data,
|
||||
Uint8List key,
|
||||
String algorithm,
|
||||
) {
|
||||
throw UnimplementedError('encrypt is not implemented');
|
||||
}
|
||||
|
||||
Future<Uint8List?> decrypt(
|
||||
Uint8List data,
|
||||
Uint8List key,
|
||||
String algorithm,
|
||||
) {
|
||||
throw UnimplementedError('decrypt is not implemented');
|
||||
}
|
||||
}
|
@ -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<String, String>? details = platformException.details != null
|
||||
? Map<String, String>.from(
|
||||
platformException.details as Map<String, String>,
|
||||
)
|
||||
: null;
|
||||
|
||||
String message = platformException.message ?? '';
|
||||
|
||||
if (details != null) {
|
||||
message = details['message'] ?? message;
|
||||
}
|
||||
|
||||
return NativeCryptoException(message);
|
||||
}
|
@ -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
|
||||
wyatt_analysis:
|
||||
git:
|
||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||
ref: wyatt_analysis-v2.1.0
|
||||
path: packages/wyatt_analysis
|
Loading…
x
Reference in New Issue
Block a user