feat: rework bytearray and memory optimization, simplify API
This commit is contained in:
parent
6939a8df7e
commit
48ebabb54c
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: benchmark_page.dart
|
||||
// Created Date: 28/12/2021 15:12:39
|
||||
// Last Modified: 25/05/2022 17:16:12
|
||||
// Last Modified: 26/05/2022 20:19:28
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -40,7 +40,7 @@ class BenchmarkPage extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
List<int> testedSizes = [2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50];
|
||||
List<int> testedSizes = [2, 4, 8, 16, 32, 64, 128, 256];
|
||||
int multiplier = pow(2, 20).toInt(); // MiB
|
||||
|
||||
benchmarkStatus.print("[Benchmark] Sizes: ${testedSizes.join('/')}MiB\n");
|
||||
@ -86,7 +86,7 @@ class BenchmarkPage extends ConsumerWidget {
|
||||
if (usePc) {
|
||||
pc.decrypt(encryptedBigFile as Uint8List, state.secretKey.bytes);
|
||||
} else {
|
||||
await cipher.decrypt(encryptedBigFile as CipherText);
|
||||
await cipher.decrypt(encryptedBigFile as CipherTextWrapper);
|
||||
}
|
||||
after = DateTime.now();
|
||||
benchmark =
|
||||
@ -105,7 +105,7 @@ class BenchmarkPage extends ConsumerWidget {
|
||||
.appendln('[Benchmark] Finished: ${sum}MiB in $benchmark ms');
|
||||
benchmarkStatus.appendln('[Benchmark] Check the console for csv data');
|
||||
benchmarkStatus.appendln(csv);
|
||||
print(csv);
|
||||
debugPrint(csv);
|
||||
}
|
||||
|
||||
void _clear() {
|
||||
@ -135,7 +135,7 @@ class BenchmarkPage extends ConsumerWidget {
|
||||
}
|
||||
keyContent.print(state.secretKey.bytes.toString());
|
||||
|
||||
AES cipher = AES(state.secretKey, AESMode.gcm);
|
||||
AES cipher = AES(state.secretKey);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: cipher_page.dart
|
||||
// Created Date: 28/12/2021 13:33:15
|
||||
// Last Modified: 25/05/2022 10:49:30
|
||||
// Last Modified: 26/05/2022 20:39:37
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -12,10 +12,10 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:native_crypto/native_crypto.dart';
|
||||
import 'package:native_crypto/native_crypto_ext.dart';
|
||||
import 'package:native_crypto_example/widgets/button.dart';
|
||||
|
||||
import '../session.dart';
|
||||
import '../utils.dart';
|
||||
import '../widgets/output.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
@ -26,8 +26,8 @@ class CipherPage extends ConsumerWidget {
|
||||
final Output encryptionStatus = Output();
|
||||
final Output decryptionStatus = Output();
|
||||
|
||||
final TextEditingController _plainTextController = TextEditingController();
|
||||
CipherText? cipherText;
|
||||
final TextEditingController _plainTextController = TextEditingController()..text = 'PlainText';
|
||||
CipherTextWrapper? cipherText;
|
||||
|
||||
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
|
||||
Session state = ref.read(sessionProvider.state).state;
|
||||
@ -41,13 +41,10 @@ class CipherPage extends ConsumerWidget {
|
||||
} else {
|
||||
var stringToBytes = plainText.toBytes();
|
||||
cipherText = await cipher.encrypt(stringToBytes);
|
||||
encryptionStatus.print('String successfully encrypted.\n');
|
||||
encryptionStatus.append("Nonce: " +
|
||||
cipherText!.iv.toString() +
|
||||
"\nData: " +
|
||||
cipherText!.data.toString() +
|
||||
"\nTag: " +
|
||||
cipherText!.tag.toString());
|
||||
encryptionStatus.print('String successfully encrypted:\n');
|
||||
|
||||
CipherText unwrap = cipherText!.unwrap<CipherText>();
|
||||
encryptionStatus.append(unwrap.base16);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,17 +53,19 @@ class CipherPage extends ConsumerWidget {
|
||||
decryptionStatus.print('Encrypt before altering CipherText!');
|
||||
} else {
|
||||
// Add 1 to the first byte
|
||||
Uint8List _altered = cipherText!.data;
|
||||
Uint8List _altered = cipherText!.unwrap<CipherText>().bytes;
|
||||
_altered[0] += 1;
|
||||
// Recreate cipher text with altered data
|
||||
cipherText = CipherText(cipherText!.iv, _altered, cipherText!.tag);
|
||||
encryptionStatus.print('String successfully encrypted.\n');
|
||||
encryptionStatus.append("Nonce: " +
|
||||
cipherText!.iv.toString() +
|
||||
"\nData: " +
|
||||
cipherText!.data.toString() +
|
||||
"\nTag: " +
|
||||
cipherText!.tag.toString());
|
||||
cipherText = CipherTextWrapper.fromBytes(
|
||||
_altered,
|
||||
12,
|
||||
_altered.length - 28,
|
||||
16,
|
||||
);
|
||||
encryptionStatus.print('String successfully encrypted:\n');
|
||||
|
||||
CipherText unwrap = cipherText!.unwrap();
|
||||
encryptionStatus.appendln(unwrap.base16);
|
||||
decryptionStatus.print('CipherText altered!\nDecryption will fail.');
|
||||
}
|
||||
}
|
||||
@ -114,7 +113,7 @@ class CipherPage extends ConsumerWidget {
|
||||
}
|
||||
keyContent.print(state.secretKey.bytes.toString());
|
||||
|
||||
AES cipher = AES(state.secretKey, AESMode.gcm);
|
||||
AES cipher = AES(state.secretKey);
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: kdf_page.dart
|
||||
// Created Date: 28/12/2021 13:40:34
|
||||
// Last Modified: 23/05/2022 22:49:06
|
||||
// Last Modified: 26/05/2022 20:30:31
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -12,10 +12,10 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:native_crypto/native_crypto.dart';
|
||||
import 'package:native_crypto/native_crypto_ext.dart';
|
||||
import 'package:native_crypto_example/widgets/button.dart';
|
||||
|
||||
import '../session.dart';
|
||||
import '../utils.dart';
|
||||
import '../widgets/output.dart';
|
||||
|
||||
class KdfPage extends ConsumerWidget {
|
||||
@ -26,8 +26,8 @@ class KdfPage extends ConsumerWidget {
|
||||
final Output pbkdf2Status = Output();
|
||||
final Output hashStatus = Output(large: true);
|
||||
|
||||
final TextEditingController _pwdTextController = TextEditingController();
|
||||
final TextEditingController _messageTextController = TextEditingController();
|
||||
final TextEditingController _pwdTextController = TextEditingController()..text = 'Password';
|
||||
final TextEditingController _messageTextController = TextEditingController()..text = 'Message';
|
||||
|
||||
Future<void> _generate(WidgetRef ref) async {
|
||||
Session state = ref.read(sessionProvider.state).state;
|
||||
@ -66,7 +66,7 @@ class KdfPage extends ConsumerWidget {
|
||||
} else {
|
||||
Uint8List hash = await hasher.digest(message.toBytes());
|
||||
hashStatus.print(
|
||||
'Message successfully hashed with $hasher :${hash.toStr(to: Encoding.hex)}');
|
||||
'Message successfully hashed with $hasher :${hash.toStr(to: Encoding.base16)}');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,52 +0,0 @@
|
||||
// Author: Hugo Pointcheval
|
||||
// Email: git@pcl.ovh
|
||||
// -----
|
||||
// File: utils.dart
|
||||
// Created Date: 16/12/2021 16:28:00
|
||||
// Last Modified: 28/12/2021 14:40:21
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'dart:convert';
|
||||
|
||||
enum Encoding { utf16, base64, hex }
|
||||
|
||||
extension StringX on String {
|
||||
Uint8List toBytes({final from = Encoding.utf16}) {
|
||||
Uint8List bytes = Uint8List(0);
|
||||
switch (from) {
|
||||
case Encoding.utf16:
|
||||
bytes = Uint8List.fromList(runes.toList());
|
||||
break;
|
||||
case Encoding.base64:
|
||||
bytes = base64.decode(this);
|
||||
break;
|
||||
case Encoding.hex:
|
||||
bytes = Uint8List.fromList(
|
||||
List.generate(
|
||||
length ~/ 2,
|
||||
(i) => int.parse(substring(i * 2, (i * 2) + 2), radix: 16),
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
extension Uint8ListX on Uint8List {
|
||||
String toStr({final to = Encoding.utf16}) {
|
||||
String str = "";
|
||||
switch (to) {
|
||||
case Encoding.utf16:
|
||||
str = String.fromCharCodes(this);
|
||||
break;
|
||||
case Encoding.base64:
|
||||
str = base64.encode(this);
|
||||
break;
|
||||
case Encoding.hex:
|
||||
str = map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
11
packages/native_crypto/lib/native_crypto_ext.dart
Normal file
11
packages/native_crypto/lib/native_crypto_ext.dart
Normal file
@ -0,0 +1,11 @@
|
||||
// Author: Hugo Pointcheval
|
||||
// Email: git@pcl.ovh
|
||||
// -----
|
||||
// File: native_crypto_ext.dart
|
||||
// Created Date: 26/05/2022 19:36:54
|
||||
// Last Modified: 26/05/2022 19:38:44
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
export 'src/utils/encoding.dart';
|
||||
export 'src/utils/extensions.dart';
|
@ -1,49 +0,0 @@
|
||||
// Author: Hugo Pointcheval
|
||||
// Email: git@pcl.ovh
|
||||
// -----
|
||||
// File: aes_builder.dart
|
||||
// Created Date: 28/12/2021 12:03:11
|
||||
// Last Modified: 25/05/2022 10:47:11
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
import 'package:native_crypto/src/ciphers/aes/aes.dart';
|
||||
import 'package:native_crypto/src/interfaces/builder.dart';
|
||||
import 'package:native_crypto/src/keys/secret_key.dart';
|
||||
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
||||
|
||||
class AESBuilder implements Builder<AES> {
|
||||
SecretKey? _sk;
|
||||
Future<SecretKey>? _fsk;
|
||||
AESMode _mode = AESMode.gcm;
|
||||
|
||||
AESBuilder withGeneratedKey(int bitsCount) {
|
||||
_fsk = SecretKey.fromSecureRandom(bitsCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
AESBuilder withKey(SecretKey secretKey) {
|
||||
_sk = secretKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
AESBuilder using(AESMode mode) {
|
||||
_mode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AES> build() async {
|
||||
if (_sk == null) {
|
||||
if (_fsk == null) {
|
||||
throw const CipherInitException(
|
||||
message: 'You must specify or generate a secret key.',
|
||||
code: 'missing_key',
|
||||
);
|
||||
} else {
|
||||
_sk = await _fsk;
|
||||
}
|
||||
}
|
||||
return AES(_sk!, _mode);
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@
|
||||
// -----
|
||||
// File: builders.dart
|
||||
// Created Date: 23/05/2022 22:56:03
|
||||
// Last Modified: 23/05/2022 22:56:12
|
||||
// Last Modified: 26/05/2022 19:22:19
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
export 'aes_builder.dart';
|
||||
export 'decryption_builder.dart';
|
||||
|
@ -0,0 +1,46 @@
|
||||
// Author: Hugo Pointcheval
|
||||
// Email: git@pcl.ovh
|
||||
// -----
|
||||
// File: decryption_builder.dart
|
||||
// Created Date: 26/05/2022 19:07:52
|
||||
// Last Modified: 26/05/2022 19:21:00
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
|
||||
import 'package:native_crypto/src/interfaces/cipher.dart';
|
||||
|
||||
class DecryptionBuilder extends StatelessWidget {
|
||||
final Cipher cipher;
|
||||
final CipherTextWrapper data;
|
||||
final Widget Function(BuildContext context) onLoading;
|
||||
final Widget Function(BuildContext context, Object error) onError;
|
||||
final Widget Function(BuildContext context, Uint8List plainText) onSuccess;
|
||||
|
||||
const DecryptionBuilder({
|
||||
super.key,
|
||||
required this.cipher,
|
||||
required this.data,
|
||||
required this.onLoading,
|
||||
required this.onError,
|
||||
required this.onSuccess,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<Uint8List>(
|
||||
future: cipher.decrypt(data),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return onSuccess(context, snapshot.data!);
|
||||
} else if (snapshot.hasError) {
|
||||
return onError(context, snapshot.error!);
|
||||
}
|
||||
return onLoading(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: aes.dart
|
||||
// Created Date: 16/12/2021 16:28:00
|
||||
// Last Modified: 25/05/2022 21:17:10
|
||||
// Last Modified: 26/05/2022 19:43:22
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -13,88 +13,120 @@ import 'package:native_crypto/src/ciphers/aes/aes_key_size.dart';
|
||||
import 'package:native_crypto/src/ciphers/aes/aes_mode.dart';
|
||||
import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
|
||||
import 'package:native_crypto/src/core/cipher_text.dart';
|
||||
import 'package:native_crypto/src/core/cipher_text_list.dart';
|
||||
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
|
||||
import 'package:native_crypto/src/interfaces/cipher.dart';
|
||||
import 'package:native_crypto/src/keys/secret_key.dart';
|
||||
import 'package:native_crypto/src/platform.dart';
|
||||
import 'package:native_crypto/src/utils/cipher_algorithm.dart';
|
||||
import 'package:native_crypto/src/utils/extensions.dart';
|
||||
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
||||
|
||||
export 'package:native_crypto/src/ciphers/aes/aes_key_size.dart';
|
||||
export 'package:native_crypto/src/ciphers/aes/aes_mode.dart';
|
||||
export 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
|
||||
|
||||
/// An AES cipher.
|
||||
///
|
||||
/// [AES] is a [Cipher] that can be used to encrypt or decrypt data.
|
||||
class AES implements Cipher {
|
||||
final SecretKey key;
|
||||
final SecretKey _key;
|
||||
final AESMode mode;
|
||||
final AESPadding padding;
|
||||
|
||||
@override
|
||||
CipherAlgorithm get algorithm => CipherAlgorithm.aes;
|
||||
|
||||
AES(this.key, this.mode, {this.padding = AESPadding.none}) {
|
||||
if (!AESKeySize.supportedSizes.contains(key.bytes.length * 8)) {
|
||||
throw const CipherInitException(
|
||||
message: 'Invalid key length!',
|
||||
code: 'invalid_key_length',
|
||||
AES(SecretKey key, [this.mode = AESMode.gcm, this.padding = AESPadding.none])
|
||||
: _key = key {
|
||||
if (!AESKeySize.supportedSizes.contains(key.bitLength)) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Invalid key size! '
|
||||
'Expected: ${AESKeySize.supportedSizes.join(', ')} bits',
|
||||
code: NativeCryptoExceptionCode.invalid_key_length.code,
|
||||
);
|
||||
}
|
||||
|
||||
final Map<AESMode, List<AESPadding>> _supported = {
|
||||
AESMode.gcm: [AESPadding.none],
|
||||
};
|
||||
|
||||
if (!_supported[mode]!.contains(padding)) {
|
||||
throw const CipherInitException(
|
||||
message: 'Invalid padding!',
|
||||
code: 'invalid_padding',
|
||||
if (!mode.supportedPaddings.contains(padding)) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Invalid padding! '
|
||||
'Expected: ${mode.supportedPaddings.join(', ')}',
|
||||
code: NativeCryptoExceptionCode.invalid_padding.code,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 - 16,
|
||||
tagLength: 16,
|
||||
Future<Uint8List> _decrypt(CipherText cipherText,
|
||||
{int chunkCount = 0,}) async {
|
||||
final Uint8List? decrypted = await platform.decrypt(
|
||||
cipherText.bytes,
|
||||
_key.bytes,
|
||||
algorithm.name,
|
||||
);
|
||||
|
||||
if (decrypted.isNull) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Platform returned null when decrypting chunk #$chunkCount',
|
||||
code: NativeCryptoExceptionCode.platform_returned_null.code,
|
||||
);
|
||||
} else if (decrypted!.isEmpty) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Platform returned no data when decrypting chunk #$chunkCount',
|
||||
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
|
||||
);
|
||||
} else {
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async {
|
||||
final Uint8List? encrypted = await platform.encrypt(
|
||||
data,
|
||||
_key.bytes,
|
||||
algorithm.name,
|
||||
);
|
||||
|
||||
if (encrypted.isNull) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Platform returned null when encrypting chunk #$chunkCount',
|
||||
code: NativeCryptoExceptionCode.platform_returned_null.code,
|
||||
);
|
||||
} else if (encrypted!.isEmpty) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Platform returned no data when encrypting chunk #$chunkCount',
|
||||
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
|
||||
);
|
||||
} else {
|
||||
return CipherText.fromBytes(
|
||||
12,
|
||||
encrypted.length - 28,
|
||||
16,
|
||||
CipherAlgorithm.aes,
|
||||
encrypted,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> decrypt(CipherText cipherText) async {
|
||||
Future<Uint8List> decrypt(CipherTextWrapper cipherText) async {
|
||||
final BytesBuilder decryptedData = BytesBuilder(copy: false);
|
||||
|
||||
if (cipherText is CipherTextList) {
|
||||
for (final CipherText ct in cipherText.list) {
|
||||
decryptedData.add(await _decrypt(ct));
|
||||
if (cipherText.isList) {
|
||||
int chunkCount = 0;
|
||||
for (final CipherText chunk in cipherText.list) {
|
||||
decryptedData.add(await _decrypt(chunk, chunkCount: chunkCount++));
|
||||
}
|
||||
} else {
|
||||
decryptedData.add(await _decrypt(cipherText));
|
||||
decryptedData.add(await _decrypt(cipherText.single));
|
||||
}
|
||||
|
||||
return decryptedData.toBytes();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CipherText> encrypt(Uint8List data) async {
|
||||
Future<CipherTextWrapper> encrypt(Uint8List data) async {
|
||||
CipherTextWrapper cipherTextWrapper;
|
||||
Uint8List dataToEncrypt;
|
||||
final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil();
|
||||
|
||||
final CipherTextList cipherTextList = CipherTextList();
|
||||
|
||||
if (data.length > Cipher.bytesCountPerChunk) {
|
||||
final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil();
|
||||
if (chunkNb > 1) {
|
||||
cipherTextWrapper = CipherTextWrapper.empty();
|
||||
for (var i = 0; i < chunkNb; i++) {
|
||||
dataToEncrypt = i < (chunkNb - 1)
|
||||
? data.sublist(
|
||||
@ -102,11 +134,12 @@ class AES implements Cipher {
|
||||
(i + 1) * Cipher.bytesCountPerChunk,
|
||||
)
|
||||
: data.sublist(i * Cipher.bytesCountPerChunk);
|
||||
cipherTextList.add(await _encrypt(dataToEncrypt));
|
||||
cipherTextWrapper.add(await _encrypt(dataToEncrypt, chunkCount: i));
|
||||
}
|
||||
} else {
|
||||
return _encrypt(data);
|
||||
cipherTextWrapper = CipherTextWrapper.single(await _encrypt(data));
|
||||
}
|
||||
return cipherTextList;
|
||||
|
||||
return cipherTextWrapper;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: aes_key_size.dart
|
||||
// Created Date: 23/05/2022 22:10:07
|
||||
// Last Modified: 23/05/2022 22:33:32
|
||||
// Last Modified: 26/05/2022 18:45:01
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -13,9 +13,13 @@ enum AESKeySize {
|
||||
bits192(192),
|
||||
bits256(256);
|
||||
|
||||
/// Returns the number of bits supported by an [AESKeySize].
|
||||
static final List<int> supportedSizes = [128, 192, 256];
|
||||
|
||||
/// Returns the number of bits in this [AESKeySize].
|
||||
final int bits;
|
||||
|
||||
/// Returns the number of bytes in this [AESKeySize].
|
||||
int get bytes => bits ~/ 8;
|
||||
|
||||
const AESKeySize(this.bits);
|
||||
|
@ -3,9 +3,18 @@
|
||||
// -----
|
||||
// File: aes_mode.dart
|
||||
// Created Date: 23/05/2022 22:09:16
|
||||
// Last Modified: 25/05/2022 09:23:54
|
||||
// Last Modified: 26/05/2022 18:41:31
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
|
||||
|
||||
/// Defines the AES modes of operation.
|
||||
enum AESMode { gcm }
|
||||
enum AESMode {
|
||||
gcm([AESPadding.none]);
|
||||
|
||||
/// Returns the list of supported [AESPadding] for this [AESMode].
|
||||
final List<AESPadding> supportedPaddings;
|
||||
|
||||
const AESMode(this.supportedPaddings);
|
||||
}
|
||||
|
@ -3,49 +3,78 @@
|
||||
// -----
|
||||
// File: cipher_text.dart
|
||||
// Created Date: 16/12/2021 16:59:53
|
||||
// Last Modified: 26/05/2022 16:22:49
|
||||
// Last Modified: 26/05/2022 19:43:57
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
|
||||
import 'package:native_crypto/src/interfaces/byte_array.dart';
|
||||
import 'package:native_crypto/src/interfaces/cipher.dart';
|
||||
import 'package:native_crypto/src/utils/cipher_algorithm.dart';
|
||||
import 'package:native_crypto/src/utils/extensions.dart';
|
||||
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
||||
|
||||
/// Represents a cipher text in Native Crypto.
|
||||
/// Represents a cipher text in NativeCrypto.
|
||||
///
|
||||
/// It is represented as a [List] of [Uint8List] like:
|
||||
/// [CipherText] is a [ByteArray] that can be used to store encrypted data.
|
||||
/// It is represented like:
|
||||
/// ```txt
|
||||
/// [[NONCE], [MESSAGE + TAG]]
|
||||
/// [IV + MESSAGE + TAG]
|
||||
/// ```
|
||||
/// where:
|
||||
/// - `[NONCE]` is a [Uint8List] of length [CipherText.ivLength]
|
||||
/// - `[MESSAGE + TAG]` is a [Uint8List] of length [CipherText.dataLength]
|
||||
/// - IV's length is [CipherText.ivLength] bytes.
|
||||
/// - MESSAGE's length is [CipherText.messageLength] bytes.
|
||||
/// - TAG's length is [CipherText.tagLength] bytes.
|
||||
///
|
||||
/// To
|
||||
///
|
||||
/// So accessing just the Message or just the Tag is costly and should be
|
||||
/// done only when needed.
|
||||
class CipherText {
|
||||
/// Check [CipherTextWrapper] for more information.
|
||||
class CipherText extends ByteArray {
|
||||
final int _ivLength;
|
||||
final int _messageLength;
|
||||
final int _tagLength;
|
||||
|
||||
final CipherAlgorithm? _cipherAlgorithm;
|
||||
|
||||
final Uint8List? _iv;
|
||||
final Uint8List? _data; // Contains the message + tag (if any)
|
||||
|
||||
CipherText._(
|
||||
const CipherText._(
|
||||
this._ivLength,
|
||||
this._messageLength,
|
||||
this._tagLength,
|
||||
this._cipherAlgorithm,
|
||||
this._iv,
|
||||
this._data,
|
||||
super.bytes,
|
||||
);
|
||||
|
||||
factory CipherText.fromBytes(
|
||||
int ivLength,
|
||||
int messageLength,
|
||||
int tagLength,
|
||||
CipherAlgorithm? cipherAlgorithm,
|
||||
Uint8List bytes,
|
||||
) {
|
||||
if (bytes.length != ivLength + messageLength + tagLength) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Invalid cipher text length! '
|
||||
'Expected: ${ivLength + messageLength + tagLength} bytes',
|
||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||
);
|
||||
}
|
||||
|
||||
if (messageLength > Cipher.bytesCountPerChunk) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Cipher text is too big! Consider using chunks.',
|
||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||
);
|
||||
}
|
||||
|
||||
return CipherText._(
|
||||
ivLength,
|
||||
messageLength,
|
||||
tagLength,
|
||||
cipherAlgorithm,
|
||||
bytes,
|
||||
);
|
||||
}
|
||||
|
||||
/// Gets the [CipherAlgorithm] used to encrypt the [CipherText].
|
||||
CipherAlgorithm get cipherAlgorithm {
|
||||
if (_cipherAlgorithm.isNotNull) {
|
||||
@ -58,89 +87,12 @@ class CipherText {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the [Uint8List] of the [CipherText]'s IV.
|
||||
Uint8List get iv {
|
||||
if (_iv.isNotNull) {
|
||||
return _iv!;
|
||||
} else {
|
||||
throw NativeCryptoException(
|
||||
message: 'IV is not specified',
|
||||
code: NativeCryptoExceptionCode.invalid_data.code,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the length of the [CipherText]'s IV.
|
||||
int get ivLength => _ivLength;
|
||||
|
||||
/// Gets the [Uint8List] of the [CipherText]'s data.
|
||||
Uint8List get data {
|
||||
if (_data.isNotNull) {
|
||||
return _data!;
|
||||
} else {
|
||||
throw NativeCryptoException(
|
||||
message: 'Data is not specified',
|
||||
code: NativeCryptoExceptionCode.invalid_data.code,
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Gets the length of the [CipherText]'s Message.
|
||||
int get messageLength => _messageLength;
|
||||
|
||||
/// Gets the length of the [CipherText]'s data.
|
||||
int get dataLength => _messageLength + _tagLength;
|
||||
|
||||
// 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 => _iv;
|
||||
|
||||
// /// Gets the CipherText data.
|
||||
// Uint8List get data => _tagLength > 0
|
||||
// ? bytes.sublist(0, _dataLength - _tagLength)
|
||||
// : bytes;
|
||||
|
||||
// /// Gets the CipherText tag.
|
||||
// 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;
|
||||
|
||||
// /// Gets the CipherText data length.
|
||||
// int get dataLength => _dataLength;
|
||||
|
||||
// /// Gets the CipherText tag length.
|
||||
// int get tagLength => _tagLength;
|
||||
/// Gets the length of the [CipherText]'s Tag.
|
||||
int get tagLength => _tagLength;
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
// Author: Hugo Pointcheval
|
||||
// Email: git@pcl.ovh
|
||||
// -----
|
||||
// File: cipher_text_list.dart
|
||||
// Created Date: 23/05/2022 22:59:02
|
||||
// Last Modified: 24/05/2022 20:18:26
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:native_crypto/src/core/cipher_text.dart';
|
||||
|
||||
class CipherTextList extends CipherText {
|
||||
final List<CipherText> _list;
|
||||
|
||||
CipherTextList()
|
||||
: _list = [],
|
||||
super(Uint8List(0), Uint8List(0), Uint8List(0));
|
||||
|
||||
void add(CipherText cipherText) {
|
||||
_list.add(cipherText);
|
||||
}
|
||||
|
||||
List<CipherText> get list => _list;
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: cipher_text_wrapper.dart
|
||||
// Created Date: 26/05/2022 14:27:32
|
||||
// Last Modified: 26/05/2022 15:53:46
|
||||
// Last Modified: 26/05/2022 20:32:38
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -12,31 +12,85 @@ import 'dart:typed_data';
|
||||
import 'package:native_crypto/native_crypto.dart';
|
||||
import 'package:native_crypto/src/utils/extensions.dart';
|
||||
|
||||
/// Wrapper for [CipherText]
|
||||
///
|
||||
/// Typically, this object is the result of an encryption operation.
|
||||
/// For decryption you have to build this before using it.
|
||||
class CipherTextWrapper {
|
||||
final CipherText? _single;
|
||||
final List<CipherText>? _list;
|
||||
|
||||
CipherTextWrapper._(this._single, this._list);
|
||||
|
||||
/// Creates a [CipherTextWrapper] from a [CipherText].
|
||||
factory CipherTextWrapper.single(CipherText cipherText) =>
|
||||
CipherTextWrapper._(cipherText, null);
|
||||
|
||||
/// Creates a [CipherTextWrapper] from a [List] of [CipherText].
|
||||
factory CipherTextWrapper.list(List<CipherText> cipherTexts) =>
|
||||
CipherTextWrapper._(null, cipherTexts);
|
||||
|
||||
/// Creates an empty [List] in a [CipherTextWrapper].
|
||||
///
|
||||
/// This is useful when you want to create a [CipherTextWrapper] then
|
||||
/// fill it with data.
|
||||
factory CipherTextWrapper.empty() => CipherTextWrapper._(null, []);
|
||||
|
||||
/// Creates a [CipherTextWrapper] from a [Uint8List].
|
||||
///
|
||||
/// This is a convenience method to create a [CipherTextWrapper]
|
||||
/// from a [Uint8List]. It tries to detect if the [Uint8List] is a
|
||||
/// single [CipherText] or a list of [CipherText].
|
||||
///
|
||||
/// You can customize the chunk size by passing a [chunkSize] parameter.
|
||||
/// The default chunk size is [Cipher.bytesCountPerChunk].
|
||||
///
|
||||
/// Throw an [NativeCryptoExceptionCode] with
|
||||
/// [NativeCryptoExceptionCode.invalid_argument] if the [Uint8List] is
|
||||
/// not a valid [CipherText] or a [List] of [CipherText].
|
||||
factory CipherTextWrapper.fromBytes(
|
||||
// Uint8List bytes, {
|
||||
// required int ivLength,
|
||||
// required int dataLength,
|
||||
// int tagLength = 0,
|
||||
// int? chunkSize,
|
||||
// }
|
||||
) {
|
||||
// TODO(hpcl): implement fromBytes
|
||||
throw UnimplementedError();
|
||||
Uint8List bytes,
|
||||
int ivLength,
|
||||
int messageLength,
|
||||
int tagLength, {
|
||||
CipherAlgorithm? cipherAlgorithm,
|
||||
int? chunkSize,
|
||||
}) {
|
||||
chunkSize ??= Cipher.bytesCountPerChunk;
|
||||
Cipher.bytesCountPerChunk = chunkSize;
|
||||
|
||||
if (bytes.length <= chunkSize) {
|
||||
return CipherTextWrapper.single(
|
||||
CipherText.fromBytes(
|
||||
ivLength,
|
||||
messageLength,
|
||||
tagLength,
|
||||
cipherAlgorithm,
|
||||
bytes,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final cipherTexts = <CipherText>[];
|
||||
for (var i = 0; i < bytes.length; i += chunkSize) {
|
||||
final chunk = bytes.sublist(i, i + chunkSize);
|
||||
cipherTexts.add(
|
||||
CipherText.fromBytes(
|
||||
ivLength,
|
||||
messageLength,
|
||||
tagLength,
|
||||
cipherAlgorithm,
|
||||
chunk,
|
||||
),
|
||||
);
|
||||
}
|
||||
return CipherTextWrapper.list(cipherTexts);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the [CipherText] is a single [CipherText].
|
||||
bool get isSingle => _single.isNotNull;
|
||||
|
||||
/// Checks if the [CipherText] is a [List] of [CipherText].
|
||||
bool get isList => _list.isNotNull;
|
||||
|
||||
/// Gets the [CipherText] if it's a single one.
|
||||
@ -70,7 +124,7 @@ class CipherTextWrapper {
|
||||
}
|
||||
|
||||
/// Gets the raw [Uint8List] of the [CipherText] or [List] of [CipherText].
|
||||
Uint8List get raw {
|
||||
Uint8List get bytes {
|
||||
if (isSingle) {
|
||||
return single.bytes;
|
||||
} else {
|
||||
@ -78,6 +132,9 @@ class CipherTextWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the number of parts of the [CipherText] or [List] of [CipherText].
|
||||
///
|
||||
/// Check [Cipher.bytesCountPerChunk] for more information.
|
||||
int get chunkCount {
|
||||
_single.isNull;
|
||||
if (_single.isNotNull) {
|
||||
@ -86,4 +143,41 @@ class CipherTextWrapper {
|
||||
return _list?.length ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the [CipherText] or the [List] of [CipherText].
|
||||
///
|
||||
/// Throws [NativeCryptoException] with
|
||||
/// [NativeCryptoExceptionCode.invalid_data] if it's not a single or a list or
|
||||
/// if [T] is not [CipherText] or [List] of [CipherText].
|
||||
T unwrap<T>() {
|
||||
if (isSingle && T == CipherText) {
|
||||
return single as T;
|
||||
} else if (isList && T == List<CipherText>) {
|
||||
return list as T;
|
||||
} else {
|
||||
final String type =
|
||||
isSingle ? 'CipherText' : (isList ? 'List<CipherText>' : 'unknown');
|
||||
throw NativeCryptoException(
|
||||
message: 'CipherTextWrapper is not a $T but a $type, '
|
||||
'you should use unwrap<$type>()',
|
||||
code: NativeCryptoExceptionCode.invalid_data.code,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void add(CipherText cipherText) {
|
||||
if (isSingle) {
|
||||
throw NativeCryptoException(
|
||||
message: 'CipherTextWrapper is already single',
|
||||
code: NativeCryptoExceptionCode.invalid_data.code,
|
||||
);
|
||||
} else if (isList) {
|
||||
_list!.add(cipherText);
|
||||
} else {
|
||||
throw NativeCryptoException(
|
||||
message: 'CipherTextWrapper is not single or list',
|
||||
code: NativeCryptoExceptionCode.invalid_data.code,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@
|
||||
// -----
|
||||
// File: core.dart
|
||||
// Created Date: 23/05/2022 23:05:26
|
||||
// Last Modified: 25/05/2022 10:44:32
|
||||
// Last Modified: 26/05/2022 17:10:25
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
export 'cipher_text.dart';
|
||||
export 'cipher_text_list.dart';
|
||||
export 'cipher_text_wrapper.dart';
|
||||
|
22
packages/native_crypto/lib/src/interfaces/base_key.dart
Normal file
22
packages/native_crypto/lib/src/interfaces/base_key.dart
Normal file
@ -0,0 +1,22 @@
|
||||
// Author: Hugo Pointcheval
|
||||
// Email: git@pcl.ovh
|
||||
// -----
|
||||
// File: base_key.dart
|
||||
// Created Date: 16/12/2021 16:28:00
|
||||
// Last Modified: 26/05/2022 17:40:38
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
import 'package:native_crypto/src/interfaces/byte_array.dart';
|
||||
|
||||
/// Represents a key in NativeCrypto.
|
||||
///
|
||||
/// [BaseKey] is a [ByteArray] that can be used to store keys.
|
||||
///
|
||||
/// This interface is implemented by all the key classes.
|
||||
abstract class BaseKey extends ByteArray {
|
||||
const BaseKey(super.bytes);
|
||||
BaseKey.fromBase16(super.encoded) : super.fromBase16();
|
||||
BaseKey.fromBase64(super.encoded) : super.fromBase64();
|
||||
BaseKey.fromUtf8(super.input) : super.fromUtf8();
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: byte_array.dart
|
||||
// Created Date: 16/12/2021 17:54:16
|
||||
// Last Modified: 26/05/2022 14:25:05
|
||||
// Last Modified: 26/05/2022 17:13:27
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -13,6 +13,9 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:native_crypto/src/utils/encoding.dart';
|
||||
import 'package:native_crypto/src/utils/extensions.dart';
|
||||
|
||||
/// Represents a byte array.
|
||||
///
|
||||
/// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods.
|
||||
@immutable
|
||||
abstract class ByteArray {
|
||||
final Uint8List _bytes;
|
||||
@ -56,6 +59,12 @@ abstract class ByteArray {
|
||||
/// Gets the [ByteArray] bytes as an UTF-16 representation.
|
||||
String get utf16 => _bytes.toStr();
|
||||
|
||||
/// Gets the [ByteArray] length in bytes.
|
||||
int get length => _bytes.length;
|
||||
|
||||
/// Gets the [ByteArray] length in bits.
|
||||
int get bitLength => _bytes.length * 8;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is ByteArray) {
|
||||
|
@ -3,44 +3,48 @@
|
||||
// -----
|
||||
// File: cipher.dart
|
||||
// Created Date: 16/12/2021 16:28:00
|
||||
// Last Modified: 24/05/2022 19:55:38
|
||||
// Last Modified: 26/05/2022 17:38:26
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:native_crypto/src/core/cipher_text.dart';
|
||||
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
|
||||
import 'package:native_crypto/src/utils/cipher_algorithm.dart';
|
||||
|
||||
/// Represents a cipher.
|
||||
/// Represents a cipher in NativeCrypto.
|
||||
///
|
||||
/// In cryptography, a cipher is an algorithm for performing encryption
|
||||
/// In cryptography, a [Cipher] is an algorithm for performing encryption
|
||||
/// or decryption - a series of well-defined steps that can
|
||||
/// be followed as a procedure.
|
||||
///
|
||||
/// This interface is implemented by all the ciphers in NativeCrypto.
|
||||
abstract class Cipher {
|
||||
/// Returns the size of a chunk of data
|
||||
/// that can be processed by the cipher.
|
||||
static int _bytesCountPerChunk = 33554432;
|
||||
|
||||
/// Returns the size of a chunk of data
|
||||
/// that can be processed by the [Cipher].
|
||||
static int get bytesCountPerChunk => Cipher._bytesCountPerChunk;
|
||||
|
||||
/// Sets the size of a chunk of data
|
||||
/// that can be processed by the [Cipher].
|
||||
static set bytesCountPerChunk(int bytesCount) {
|
||||
_bytesCountPerChunk = bytesCount;
|
||||
}
|
||||
|
||||
/// Returns the standard algorithm name for this cipher
|
||||
/// Returns the standard algorithm for this [Cipher].
|
||||
CipherAlgorithm get algorithm;
|
||||
|
||||
|
||||
/// Encrypts data.
|
||||
/// Encrypts the [data].
|
||||
///
|
||||
/// Takes [Uint8List] data as parameter.
|
||||
/// Returns a [CipherText].
|
||||
Future<CipherText> encrypt(Uint8List data);
|
||||
/// Returns a [CipherTextWrapper].
|
||||
Future<CipherTextWrapper> encrypt(Uint8List data);
|
||||
|
||||
/// Decrypts cipher text.
|
||||
/// Decrypts the [cipherText]
|
||||
///
|
||||
/// Takes [CipherText] as parameter.
|
||||
/// Takes [CipherTextWrapper] as parameter.
|
||||
/// And returns plain text data as [Uint8List].
|
||||
Future<Uint8List> decrypt(CipherText cipherText);
|
||||
Future<Uint8List> decrypt(CipherTextWrapper cipherText);
|
||||
}
|
||||
|
@ -3,12 +3,12 @@
|
||||
// -----
|
||||
// File: interfaces.dart
|
||||
// Created Date: 23/05/2022 23:03:47
|
||||
// Last Modified: 23/05/2022 23:10:15
|
||||
// Last Modified: 26/05/2022 17:41:06
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
export 'base_key.dart';
|
||||
export 'builder.dart';
|
||||
export 'byte_array.dart';
|
||||
export 'cipher.dart';
|
||||
// export 'key.dart';
|
||||
export 'keyderivation.dart';
|
||||
|
@ -1,18 +0,0 @@
|
||||
// Author: Hugo Pointcheval
|
||||
// Email: git@pcl.ovh
|
||||
// -----
|
||||
// File: key.dart
|
||||
// Created Date: 16/12/2021 16:28:00
|
||||
// Last Modified: 23/05/2022 23:02:10
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
import 'package:native_crypto/src/interfaces/byte_array.dart';
|
||||
|
||||
/// A class representing a key.
|
||||
abstract class Key extends ByteArray {
|
||||
const Key(super.bytes);
|
||||
Key.fromBase16(super.encoded) : super.fromBase16();
|
||||
Key.fromBase64(super.encoded) : super.fromBase64();
|
||||
Key.fromUtf8(super.input) : super.fromUtf8();
|
||||
}
|
@ -3,18 +3,21 @@
|
||||
// -----
|
||||
// File: kdf.dart
|
||||
// Created Date: 18/12/2021 11:56:43
|
||||
// Last Modified: 23/05/2022 22:37:04
|
||||
// Last Modified: 26/05/2022 18:47:15
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
import 'package:native_crypto/src/keys/secret_key.dart';
|
||||
import 'package:native_crypto/src/utils/kdf_algorithm.dart';
|
||||
|
||||
/// Represents a Key Derivation Function
|
||||
/// Represents a Key Derivation Function (KDF) in NativeCrypto.
|
||||
///
|
||||
/// [KeyDerivation] function is a function that takes some
|
||||
/// parameters and returns a [SecretKey].
|
||||
abstract class KeyDerivation {
|
||||
/// Returns the standard algorithm name for this key derivation function
|
||||
/// Returns the standard algorithm for this key derivation function
|
||||
KdfAlgorithm get algorithm;
|
||||
|
||||
/// Derive key
|
||||
/// Derive a [SecretKey].
|
||||
Future<SecretKey> derive();
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: pbkdf2.dart
|
||||
// Created Date: 17/12/2021 14:50:42
|
||||
// Last Modified: 25/05/2022 10:45:00
|
||||
// Last Modified: 26/05/2022 18:51:59
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -12,10 +12,15 @@ import 'dart:typed_data';
|
||||
import 'package:native_crypto/src/interfaces/keyderivation.dart';
|
||||
import 'package:native_crypto/src/keys/secret_key.dart';
|
||||
import 'package:native_crypto/src/platform.dart';
|
||||
import 'package:native_crypto/src/utils/extensions.dart';
|
||||
import 'package:native_crypto/src/utils/hash_algorithm.dart';
|
||||
import 'package:native_crypto/src/utils/kdf_algorithm.dart';
|
||||
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
||||
|
||||
/// Represent a PBKDF2 Key Derivation Function (KDF) in NativeCrypto.
|
||||
///
|
||||
/// [Pbkdf2] is a function that takes password, salt, iteration count and
|
||||
/// derive a [SecretKey] of specified length.
|
||||
class Pbkdf2 extends KeyDerivation {
|
||||
final int _keyBytesCount;
|
||||
final int _iterations;
|
||||
@ -35,20 +40,42 @@ class Pbkdf2 extends KeyDerivation {
|
||||
@override
|
||||
Future<SecretKey> derive({String? password, String? salt}) async {
|
||||
if (password == null || salt == null) {
|
||||
throw const KeyDerivationException(
|
||||
message: "Password or Salt can't be null!",
|
||||
code: 'invalid_password_or_salt',
|
||||
throw NativeCryptoException(
|
||||
message: 'Password and salt cannot be null. '
|
||||
'Here is the password: $password, here is the salt: $salt',
|
||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||
);
|
||||
}
|
||||
|
||||
final Uint8List derivation = (await platform.pbkdf2(
|
||||
password,
|
||||
salt,
|
||||
_keyBytesCount,
|
||||
_iterations,
|
||||
_hash.name,
|
||||
)) ??
|
||||
Uint8List(0);
|
||||
final Uint8List? derivation = await platform.pbkdf2(
|
||||
password,
|
||||
salt,
|
||||
_keyBytesCount,
|
||||
_iterations,
|
||||
_hash.name,
|
||||
);
|
||||
|
||||
if (derivation.isNull) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Failed to derive a key! Platform returned null.',
|
||||
code: NativeCryptoExceptionCode.platform_returned_null.code,
|
||||
);
|
||||
}
|
||||
|
||||
if (derivation!.isEmpty) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Failed to derive a key! Platform returned no data.',
|
||||
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
|
||||
);
|
||||
}
|
||||
|
||||
if (derivation.length != _keyBytesCount) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Failed to derive a key! Platform returned '
|
||||
'${derivation.length} bytes, but expected $_keyBytesCount bytes.',
|
||||
code: NativeCryptoExceptionCode.platform_returned_invalid_data.code,
|
||||
);
|
||||
}
|
||||
|
||||
return SecretKey(derivation);
|
||||
}
|
||||
|
@ -3,46 +3,54 @@
|
||||
// -----
|
||||
// File: secret_key.dart
|
||||
// Created Date: 28/12/2021 13:36:54
|
||||
// Last Modified: 26/05/2022 11:56:06
|
||||
// Last Modified: 26/05/2022 19:26:35
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:native_crypto/src/interfaces/key.dart';
|
||||
import 'package:native_crypto/src/interfaces/base_key.dart';
|
||||
import 'package:native_crypto/src/interfaces/cipher.dart';
|
||||
import 'package:native_crypto/src/platform.dart';
|
||||
import 'package:native_crypto/src/utils/extensions.dart';
|
||||
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
||||
|
||||
/// A class representing a secret key.
|
||||
/// A secret key is a key that is not accessible by anyone else.
|
||||
/// It is used to encrypt and decrypt data.
|
||||
class SecretKey extends Key {
|
||||
/// Represents a secret key in NativeCrypto.
|
||||
///
|
||||
/// [SecretKey] is a [BaseKey] that can be used to store secret keys.
|
||||
/// A [SecretKey] is a key that can be used to encrypt or decrypt data with
|
||||
/// a symmetric [Cipher].
|
||||
class SecretKey extends BaseKey {
|
||||
const SecretKey(super.bytes);
|
||||
SecretKey.fromBase16(super.encoded) : super.fromBase16();
|
||||
SecretKey.fromBase64(super.encoded) : super.fromBase64();
|
||||
SecretKey.fromUtf8(super.input) : super.fromUtf8();
|
||||
|
||||
static Future<SecretKey> fromSecureRandom(int bitsCount) async {
|
||||
Uint8List? key;
|
||||
try {
|
||||
final Uint8List? _key = await platform.generateSecretKey(bitsCount);
|
||||
|
||||
if (_key == null || _key.isEmpty) {
|
||||
throw const KeyException(
|
||||
message: 'Could not generate secret key, platform returned null',
|
||||
code: 'platform_returned_null',
|
||||
);
|
||||
}
|
||||
|
||||
return SecretKey(_key);
|
||||
key = await platform.generateSecretKey(bitsCount);
|
||||
} catch (e, s) {
|
||||
if (e is KeyException) {
|
||||
rethrow;
|
||||
}
|
||||
throw KeyException(
|
||||
throw NativeCryptoException(
|
||||
message: '$e',
|
||||
code: 'failed_to_generate_secret_key',
|
||||
code: NativeCryptoExceptionCode.platform_throws.code,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
if (key.isNull) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Failed to generate a secret key! Platform returned null.',
|
||||
code: NativeCryptoExceptionCode.platform_returned_null.code,
|
||||
);
|
||||
}
|
||||
|
||||
if (key!.isEmpty) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Failed to generate a secret key! '
|
||||
'Platform returned no data.',
|
||||
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
|
||||
);
|
||||
}
|
||||
return SecretKey(key);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@
|
||||
// -----
|
||||
// File: cipher_algorithm.dart
|
||||
// Created Date: 23/05/2022 22:07:54
|
||||
// Last Modified: 23/05/2022 22:33:56
|
||||
// Last Modified: 26/05/2022 18:52:32
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
/// Represents different cipher algorithms
|
||||
enum CipherAlgorithm { aes, rsa }
|
||||
enum CipherAlgorithm { aes }
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: extensions.dart
|
||||
// Created Date: 26/05/2022 12:12:48
|
||||
// Last Modified: 26/05/2022 15:49:38
|
||||
// Last Modified: 26/05/2022 18:52:48
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -30,8 +30,8 @@ extension ListIntX on List<int> {
|
||||
}
|
||||
|
||||
extension ListUint8ListX on List<Uint8List> {
|
||||
|
||||
/// Reduce a [List] of [Uint8List] to a [Uint8List].
|
||||
|
||||
Uint8List sum() {
|
||||
for (var i = 1; i < length; i++) {
|
||||
first.addAll(this[i]);
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: hash_algorithm.dart
|
||||
// Created Date: 23/05/2022 22:01:59
|
||||
// Last Modified: 23/05/2022 22:47:08
|
||||
// Last Modified: 26/05/2022 18:53:38
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -11,12 +11,13 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:native_crypto/src/platform.dart';
|
||||
|
||||
/// Defines the hash algorithms.
|
||||
enum HashAlgorithm {
|
||||
sha256,
|
||||
sha384,
|
||||
sha512;
|
||||
|
||||
/// Hashes a message
|
||||
/// Digest the [data] using this [HashAlgorithm].
|
||||
Future<Uint8List> digest(Uint8List data) async {
|
||||
final Uint8List hash = (await platform.digest(data, name)) ?? Uint8List(0);
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
// -----
|
||||
// File: kdf_algorithm.dart
|
||||
// Created Date: 23/05/2022 22:36:24
|
||||
// Last Modified: 23/05/2022 22:36:36
|
||||
// Last Modified: 26/05/2022 18:53:50
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
/// Represents different key derivation functions
|
||||
enum KdfAlgorithm { pbkdf2 }
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: secret_key_test.dart
|
||||
// Created Date: 26/05/2022 10:52:41
|
||||
// Last Modified: 26/05/2022 12:07:33
|
||||
// Last Modified: 26/05/2022 19:24:44
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -42,10 +42,10 @@ void main() {
|
||||
await expectLater(
|
||||
() => SecretKey.fromSecureRandom(5),
|
||||
throwsA(
|
||||
isA<KeyException>().having(
|
||||
isA<NativeCryptoException>().having(
|
||||
(e) => e.code,
|
||||
'code',
|
||||
'platform_returned_null',
|
||||
'platform_returned_empty_data',
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -59,7 +59,7 @@ void main() {
|
||||
await expectLater(
|
||||
() => SecretKey.fromSecureRandom(5),
|
||||
throwsA(
|
||||
isA<KeyException>().having(
|
||||
isA<NativeCryptoException>().having(
|
||||
(e) => e.code,
|
||||
'code',
|
||||
'platform_returned_null',
|
||||
@ -81,7 +81,7 @@ void main() {
|
||||
await expectLater(
|
||||
() => SecretKey.fromSecureRandom(5),
|
||||
throwsA(
|
||||
isA<KeyException>()
|
||||
isA<NativeCryptoException>()
|
||||
.having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
@ -90,7 +90,7 @@ void main() {
|
||||
.having(
|
||||
(e) => e.code,
|
||||
'code',
|
||||
'failed_to_generate_secret_key',
|
||||
'platform_throws',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user