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 // File: benchmark_page.dart
// Created Date: 28/12/2021 15:12:39 // 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 // Copyright (c) 2021
@ -24,6 +24,51 @@ class BenchmarkPage extends ConsumerWidget {
final Output keyContent = Output(); final Output keyContent = Output();
final Output benchmarkStatus = Output(large: true); 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, Future<void> _benchmark(WidgetRef ref, Cipher cipher,
{bool usePc = false}) async { {bool usePc = false}) async {
Session state = ref.read(sessionProvider.state).state; 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"); benchmarkStatus.print("Benchmark 2/4/8/16/32/64/128/256MB\n");
List<int> testedSizes = [2, 4, 8, 16, 32, 64, 128, 256]; List<int> testedSizes = [2, 4, 8, 16, 32, 64, 128, 256];
String csv = String csv = "size;encryption time;decryption time;crypto time\n";
"size;encryption time;encode time;decryption time;crypto time\n";
var beforeBench = DateTime.now(); var beforeBench = DateTime.now();
Cipher.bytesCountPerChunk = Cipher.bytesCountPerChunk;
benchmarkStatus
.append('[Benchmark] ${Cipher.bytesCountPerChunk} bytes/chunk \n');
for (int size in testedSizes) { for (int size in testedSizes) {
var b = ByteData(size * 1000000); var b = Uint8List(size * 1000000);
//var bigFile = Uint8List.view();
csv += "${size * 1000000};"; csv += "${size * 1000000};";
var cryptoTime = 0; var cryptoTime = 0;
@ -51,10 +97,9 @@ class BenchmarkPage extends ConsumerWidget {
var before = DateTime.now(); var before = DateTime.now();
Object encryptedBigFile; Object encryptedBigFile;
if (usePc) { if (usePc) {
encryptedBigFile = encryptedBigFile = pc.encrypt(b, state.secretKey.bytes);
pc.encrypt(b.buffer.asUint8List(), state.secretKey.bytes);
} else { } else {
encryptedBigFile = await cipher.encrypt(b.buffer.asUint8List()); encryptedBigFile = await cipher.encrypt(b);
} }
var after = DateTime.now(); var after = DateTime.now();
@ -62,7 +107,7 @@ class BenchmarkPage extends ConsumerWidget {
after.millisecondsSinceEpoch - before.millisecondsSinceEpoch; after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
benchmarkStatus.append('[$size MB] Encryption took $benchmark ms\n'); benchmarkStatus.append('[$size MB] Encryption took $benchmark ms\n');
csv += "$benchmark;"; csv += "$benchmark";
cryptoTime += benchmark; cryptoTime += benchmark;
// Decryption // Decryption
@ -129,6 +174,9 @@ class BenchmarkPage extends ConsumerWidget {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
), ),
keyContent, keyContent,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
@ -146,6 +194,17 @@ class BenchmarkPage extends ConsumerWidget {
), ),
], ],
), ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
() => _benchmarkEncryptionOnly(ref, cipher),
"NC Persistence",
),
],
),
],
),
benchmarkStatus, benchmarkStatus,
], ],
), ),

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: aes.dart // File: aes.dart
// Created Date: 16/12/2021 16:28:00 // 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 // Copyright (c) 2022
@ -39,6 +39,7 @@ class AES implements Cipher {
final Map<AESMode, List<AESPadding>> _supported = { final Map<AESMode, List<AESPadding>> _supported = {
AESMode.gcm: [AESPadding.none], AESMode.gcm: [AESPadding.none],
AESMode.cbc: [AESPadding.pkcs5],
}; };
if (!_supported[mode]!.contains(padding)) { 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 @override
Future<Uint8List> decrypt(CipherText cipherText) async { Future<Uint8List> decrypt(CipherText cipherText) async {
final BytesBuilder decryptedData = BytesBuilder(copy: false); final BytesBuilder decryptedData = BytesBuilder(copy: false);
if (cipherText is CipherTextList) { if (cipherText is CipherTextList) {
for (final CipherText ct in cipherText.list) { for (final CipherText ct in cipherText.list) {
final Uint8List d = await platform.decrypt( decryptedData.add(await _decrypt(ct));
ct.bytes,
key.bytes,
algorithm.name,
) ??
Uint8List(0);
decryptedData.add(d);
} }
} else { } else {
final Uint8List d = await platform.decrypt( decryptedData.add(await _decrypt(cipherText));
cipherText.bytes,
key.bytes,
algorithm.name,
) ??
Uint8List(0);
decryptedData.add(d);
} }
return decryptedData.toBytes(); return decryptedData.toBytes();
@ -75,43 +84,23 @@ class AES implements Cipher {
@override @override
Future<CipherText> encrypt(Uint8List data) async { Future<CipherText> encrypt(Uint8List data) async {
Uint8List dataToEncrypt; Uint8List dataToEncrypt;
final CipherTextList cipherTextList = CipherTextList(); final CipherTextList cipherTextList = CipherTextList();
// If data is bigger than 32mB -> split in chunks
if (data.length > CipherTextList.chunkSize) { if (data.length > Cipher.bytesCountPerChunk) {
final int chunkNb = (data.length / CipherTextList.chunkSize).ceil(); final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil();
for (var i = 0; i < chunkNb; i++) { for (var i = 0; i < chunkNb; i++) {
dataToEncrypt = i < (chunkNb - 1) dataToEncrypt = i < (chunkNb - 1)
? data.sublist( ? data.sublist(
i * CipherTextList.chunkSize, i * Cipher.bytesCountPerChunk,
(i + 1) * CipherTextList.chunkSize, (i + 1) * Cipher.bytesCountPerChunk,
) )
: data.sublist(i * CipherTextList.chunkSize); : data.sublist(i * Cipher.bytesCountPerChunk);
final Uint8List c = await platform.encrypt( cipherTextList.add(await _encrypt(dataToEncrypt));
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
} }
} else { } else {
final Uint8List c = return _encrypt(data);
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 cipherTextList; return cipherTextList;
} }
} }

View File

@ -3,9 +3,9 @@
// ----- // -----
// File: aes_mode.dart // File: aes_mode.dart
// Created Date: 23/05/2022 22:09:16 // 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 // Copyright (c) 2022
/// Defines the AES modes of operation. /// Defines the AES modes of operation.
enum AESMode { gcm } enum AESMode { gcm, cbc }

View File

@ -3,9 +3,9 @@
// ----- // -----
// File: aes_padding.dart // File: aes_padding.dart
// Created Date: 23/05/2022 22:10:17 // 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 // Copyright (c) 2022
/// Represents different paddings. /// Represents different paddings.
enum AESPadding { none } enum AESPadding { none, pkcs5 }

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: cipher_text.dart // File: cipher_text.dart
// Created Date: 16/12/2021 16:59:53 // 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 // Copyright (c) 2021
@ -16,23 +16,61 @@ class CipherText extends ByteArray {
final int _dataLength; final int _dataLength;
final int _tagLength; final int _tagLength;
CipherText(Uint8List iv, Uint8List data, Uint8List tag) final Uint8List _iv;
CipherText(Uint8List iv, Uint8List data, Uint8List? tag)
: _ivLength = iv.length, : _ivLength = iv.length,
_dataLength = data.length, _dataLength = data.length,
_tagLength = tag.length, _tagLength = tag?.length ?? 0,
super(Uint8List.fromList(iv + data + tag)); _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. /// Gets the CipherText IV.
Uint8List get iv => bytes.sublist(0, _ivLength); Uint8List get iv => _iv;
/// Gets the CipherText data. /// 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. /// Gets the CipherText tag.
Uint8List get tag => bytes.sublist( Uint8List get tag => _tagLength > 0
_ivLength + _dataLength, ? bytes.sublist(_dataLength - _tagLength, _dataLength)
_ivLength + _dataLength + _tagLength, : Uint8List(0);
);
/// Gets the CipherText data and tag.
Uint8List get payload => bytes;
/// Gets the CipherText IV length. /// Gets the CipherText IV length.
int get ivLength => _ivLength; int get ivLength => _ivLength;

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: cipher_text_list.dart // File: cipher_text_list.dart
// Created Date: 23/05/2022 22:59:02 // 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 // Copyright (c) 2022
@ -12,7 +12,6 @@ import 'dart:typed_data';
import 'package:native_crypto/src/core/cipher_text.dart'; import 'package:native_crypto/src/core/cipher_text.dart';
class CipherTextList extends CipherText { class CipherTextList extends CipherText {
static const int chunkSize = 33554432;
final List<CipherText> _list; final List<CipherText> _list;
CipherTextList() CipherTextList()

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: cipher.dart // File: cipher.dart
// Created Date: 16/12/2021 16:28:00 // 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 // 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 /// or decryption - a series of well-defined steps that can
/// be followed as a procedure. /// be followed as a procedure.
abstract class Cipher { 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 /// Returns the standard algorithm name for this cipher
CipherAlgorithm get algorithm; CipherAlgorithm get algorithm;
/// Encrypts data. /// Encrypts data.
/// ///
/// Takes [Uint8List] data as parameter. /// Takes [Uint8List] data as parameter.

View File

@ -1,6 +1,7 @@
package fr.pointcheval.native_crypto_android package fr.pointcheval.native_crypto_android
import androidx.annotation.NonNull 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.kdf.Pbkdf2
import fr.pointcheval.native_crypto_android.keys.SecretKey import fr.pointcheval.native_crypto_android.keys.SecretKey
import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm
@ -23,6 +24,8 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel private lateinit var channel: MethodChannel
private val name = "plugins.hugop.cl/native_crypto" private val name = "plugins.hugop.cl/native_crypto"
private var cipherInstance: Cipher? = null
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, name) channel = MethodChannel(flutterPluginBinding.binaryMessenger, name)
channel.setMethodCallHandler(this) channel.setMethodCallHandler(this)
@ -38,11 +41,11 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler {
when (call.method) { when (call.method) {
"digest" -> methodCallTask = handleDigest(call.arguments()) "digest" -> methodCallTask = handleDigest(call.arguments())
"generateSecretKey" -> methodCallTask = handleGenerateSecretKey(call.arguments()) "generateSecretKey" -> methodCallTask = handleGenerateSecretKey(call.arguments())
"generateKeyPair" -> result.notImplemented()
"pbkdf2" -> methodCallTask = handlePbkdf2(call.arguments()) "pbkdf2" -> methodCallTask = handlePbkdf2(call.arguments())
"encrypt" -> methodCallTask = handleEncrypt(call.arguments()) "encryptAsList" -> methodCallTask = handleEncryptAsList(call.arguments())
"decrypt" -> methodCallTask = handleDecrypt(call.arguments()) "decryptAsList" -> methodCallTask = handleDecryptAsList(call.arguments())
"generateSharedSecretKey" -> result.notImplemented() "encrypt" -> methodCallTask = handleCrypt(call.arguments(), true)
"decrypt" -> methodCallTask = handleCrypt(call.arguments(), false)
else -> result.notImplemented() else -> result.notImplemented()
} }
@ -95,7 +98,17 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler {
} }
} }
private fun handleEncrypt(arguments: Map<String, Any>?): Task<ByteArray> { private fun lazyLoadCipher(cipherAlgorithm: CipherAlgorithm) {
if (cipherInstance == null) {
cipherInstance = cipherAlgorithm.getCipher()
} else {
if (cipherInstance!!.algorithm != cipherAlgorithm) {
cipherInstance = cipherAlgorithm.getCipher()
}
}
}
private fun handleEncryptAsList(arguments: Map<String, Any>?): Task<List<ByteArray>> {
return Task { return Task {
val data: ByteArray = val data: ByteArray =
Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray
@ -104,13 +117,29 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler {
Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String
val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm)
val cipher = cipherAlgorithm.getCipher() lazyLoadCipher(cipherAlgorithm)
cipher.encrypt(data, key) cipherInstance!!.encryptAsList(data, key)
} }
} }
private fun handleDecrypt(arguments: Map<String, Any>?): Task<ByteArray> { 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 { return Task {
val data: ByteArray = val data: ByteArray =
Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray
@ -119,9 +148,13 @@ class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler {
Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String
val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm)
val cipher = cipherAlgorithm.getCipher() lazyLoadCipher(cipherAlgorithm)
cipher.decrypt(data, key) if (forEncryption) {
cipherInstance!!.encrypt(data, key)
} else {
cipherInstance!!.decrypt(data, key)
}
} }
} }
} }

View File

@ -11,6 +11,10 @@ class AES : Cipher {
override val algorithm: CipherAlgorithm override val algorithm: CipherAlgorithm
get() = CipherAlgorithm.aes 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 { /* override fun encrypt(data: ByteArray, key: ByteArray): ByteArray {
val sk: SecretKey = SecretKeySpec(key, "AES") val sk: SecretKey = SecretKeySpec(key, "AES")
val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding") val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")
@ -35,25 +39,46 @@ class AES : Cipher {
}*/ }*/
override fun encrypt(data: ByteArray, key: ByteArray): ByteArray { override fun encrypt(data: ByteArray, key: ByteArray): ByteArray {
val sk: SecretKey = SecretKeySpec(key, "AES") val list : List<ByteArray> = encryptAsList(data, key)
val cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding") return list.first().plus(list.last())
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) }
// javax.crypto representation = [CIPHERTEXT(n-16)]
val bytes = cipher.doFinal(data) override fun encryptAsList(data: ByteArray, key: ByteArray): List<ByteArray> {
val iv = cipher.iv val sk = SecretKeySpec(key, "AES")
if (cipherInstance == null || !forEncryption || secretKey != sk) {
secretKey = sk
forEncryption = true
// native.crypto representation = [IV(16) || CIPHERTEXT(n-16)] // native.crypto representation = [IV(16) || CIPHERTEXT(n-16)]
return iv.plus(bytes) // 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: ByteArray = cipherInstance!!.doFinal(data)
val iv: ByteArray = cipherInstance!!.iv
// native.crypto representation = [IV(16) || CIPHERTEXT(n-16)]
return listOf(iv, bytes)
} }
override fun decrypt(data: ByteArray, key: ByteArray): ByteArray { 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)] // javax.crypto representation = [CIPHERTEXT(n-16)]
val iv: ByteArray = data.take(16).toByteArray()
val payload: ByteArray = data.drop(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 ivSpec = IvParameterSpec(iv)
val cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding") cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, ivSpec)
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, sk, ivSpec) forEncryption = false
return cipher.doFinal(payload) 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 encrypt(data: ByteArray, key: ByteArray): ByteArray
fun decrypt(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 include: package:wyatt_analysis/analysis_options.flutter.experimental.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -2,74 +2,13 @@
// Email: git@pcl.ovh // Email: git@pcl.ovh
// ----- // -----
// File: native_crypto_platform_interface.dart // File: native_crypto_platform_interface.dart
// Created Date: 25/12/2021 16:43:49 // Created Date: 24/05/2022 19:39:11
// Last Modified: 25/12/2021 17:39:39 // 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'; export 'src/method_channel/method_channel_native_crypto.dart';
import './src/platform_interface.dart'; export 'src/platform_interface/native_crypto_platform.dart';
export 'src/utils/exception.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');
}
}

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: native_crypto_method_channel.dart // File: native_crypto_method_channel.dart
// Created Date: 25/12/2021 16:58:04 // 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 // Copyright (c) 2021
@ -11,19 +11,17 @@ import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:native_crypto_platform_interface/src/platform_interface/native_crypto_platform.dart';
import '../native_crypto_platform_interface.dart';
/// An implementation of [NativeCryptoPlatform] that uses method channels. /// An implementation of [NativeCryptoPlatform] that uses method channels.
class MethodChannelNativeCrypto extends NativeCryptoPlatform { class MethodChannelNativeCrypto extends NativeCryptoPlatform {
/// The method channel used to interact with the native platform. /// The method channel used to interact with the native platform.
@visibleForTesting @visibleForTesting
MethodChannel methodChannel = MethodChannel channel = const MethodChannel('plugins.hugop.cl/native_crypto');
const MethodChannel('plugins.hugop.cl/native_crypto');
@override @override
Future<Uint8List?> digest(Uint8List data, String algorithm) { Future<Uint8List?> digest(Uint8List data, String algorithm) {
return methodChannel.invokeMethod<Uint8List>( return channel.invokeMethod<Uint8List>(
'digest', 'digest',
<String, dynamic>{ <String, dynamic>{
'data': data, 'data': data,
@ -34,7 +32,7 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
@override @override
Future<Uint8List?> generateSecretKey(int bitsCount) { Future<Uint8List?> generateSecretKey(int bitsCount) {
return methodChannel.invokeMethod<Uint8List>( return channel.invokeMethod<Uint8List>(
'generateSecretKey', 'generateSecretKey',
<String, dynamic>{ <String, dynamic>{
'bitsCount': bitsCount, 'bitsCount': bitsCount,
@ -43,14 +41,14 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
} }
@override @override
Future<Uint8List?> generateKeyPair() { Future<Uint8List?> pbkdf2(
return methodChannel.invokeMethod<Uint8List>('generateKeyPair'); String password,
} String salt,
int keyBytesCount,
@override int iterations,
Future<Uint8List?> pbkdf2(String password, String salt, int keyBytesCount, String algorithm,
int iterations, String algorithm) { ) {
return methodChannel.invokeMethod<Uint8List>( return channel.invokeMethod<Uint8List>(
'pbkdf2', 'pbkdf2',
<String, dynamic>{ <String, dynamic>{
'password': password, 'password': password,
@ -63,8 +61,44 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
} }
@override @override
Future<Uint8List?> encrypt(Uint8List data, Uint8List key, String algorithm) { Future<List<Uint8List>?> encryptAsList(
return methodChannel.invokeMethod<Uint8List>( 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', 'encrypt',
<String, dynamic>{ <String, dynamic>{
'data': data, 'data': data,
@ -75,8 +109,12 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
} }
@override @override
Future<Uint8List?> decrypt(Uint8List data, Uint8List key, String algorithm) { Future<Uint8List?> decrypt(
return methodChannel.invokeMethod<Uint8List>( Uint8List data,
Uint8List key,
String algorithm,
) {
return channel.invokeMethod<Uint8List>(
'decrypt', 'decrypt',
<String, dynamic>{ <String, dynamic>{
'data': data, '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 version: 0.1.0
environment: environment:
sdk: ">=2.15.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"
flutter: ">=2.5.0" flutter: ">=2.5.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
plugin_platform_interface: ^2.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter 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