perf: x10 perfomance improvement on android with better list management

This commit is contained in:
Hugo Pointcheval 2022-05-24 23:59:10 +02:00
parent 6397e10c05
commit a7affea1e1
Signed by: hugo
GPG Key ID: A9E8E9615379254F
17 changed files with 487 additions and 322 deletions

View File

@ -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",
),
],
),
],
),

View File

@ -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;
}
}

View File

@ -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 }

View File

@ -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 }

View File

@ -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;

View File

@ -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()

View File

@ -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.

View File

@ -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)
}
}
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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';

View File

@ -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,
},
);
}
}

View File

@ -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 {}

View File

@ -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');
}
}

View File

@ -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);
}

View File

@ -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