refactor: (WIP) optimize exceptions and bytearray

This commit is contained in:
Hugo Pointcheval 2022-05-26 16:26:16 +02:00
parent 9bfe969c7d
commit 6939a8df7e
Signed by: hugo
GPG Key ID: A9E8E9615379254F
9 changed files with 385 additions and 160 deletions

View File

@ -3,7 +3,7 @@
// -----
// File: native_crypto.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 25/05/2022 10:48:20
// Last Modified: 26/05/2022 12:10:42
// -----
// Copyright (c) 2021
@ -23,7 +23,6 @@ export 'src/kdf/kdf.dart';
export 'src/keys/keys.dart';
// Utils
export 'src/utils/cipher_algorithm.dart';
export 'src/utils/convert.dart';
export 'src/utils/hash_algorithm.dart';
export 'src/utils/kdf_algorithm.dart';

View File

@ -3,81 +3,144 @@
// -----
// File: cipher_text.dart
// Created Date: 16/12/2021 16:59:53
// Last Modified: 24/05/2022 21:27:44
// Last Modified: 26/05/2022 16:22:49
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:native_crypto/src/interfaces/byte_array.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';
class CipherText extends ByteArray {
/// Represents a cipher text in Native Crypto.
///
/// It is represented as a [List] of [Uint8List] like:
/// ```txt
/// [[NONCE], [MESSAGE + TAG]]
/// ```
/// where:
/// - `[NONCE]` is a [Uint8List] of length [CipherText.ivLength]
/// - `[MESSAGE + TAG]` is a [Uint8List] of length [CipherText.dataLength]
///
/// To
///
/// So accessing just the Message or just the Tag is costly and should be
/// done only when needed.
class CipherText {
final int _ivLength;
final int _dataLength;
final int _messageLength;
final int _tagLength;
final Uint8List _iv;
final CipherAlgorithm? _cipherAlgorithm;
CipherText(Uint8List iv, Uint8List data, Uint8List? tag)
: _ivLength = iv.length,
_dataLength = data.length,
_tagLength = tag?.length ?? 0,
_iv = iv,
super((tag != null) ? Uint8List.fromList(data + tag) : data);
final Uint8List? _iv;
final Uint8List? _data; // Contains the message + tag (if any)
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));
CipherText._(
this._ivLength,
this._messageLength,
this._tagLength,
this._cipherAlgorithm,
this._iv,
this._data,
);
const CipherText.fromIvAndBytes(
Uint8List iv,
super.data, {
required int dataLength,
int tagLength = 0,
}) : _ivLength = iv.length,
_dataLength = dataLength,
_tagLength = tagLength,
_iv = iv;
/// Gets the [CipherAlgorithm] used to encrypt the [CipherText].
CipherAlgorithm get cipherAlgorithm {
if (_cipherAlgorithm.isNotNull) {
return _cipherAlgorithm!;
} else {
throw NativeCryptoException(
message: 'Cipher algorithm is not specified',
code: NativeCryptoExceptionCode.invalid_cipher.code,
);
}
}
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 [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 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.
/// Gets the length of the [CipherText]'s IV.
int get ivLength => _ivLength;
/// Gets the CipherText data length.
int get dataLength => _dataLength;
/// 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 CipherText tag length.
int get tagLength => _tagLength;
/// 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;
}

View File

@ -0,0 +1,89 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_text_wrapper.dart
// Created Date: 26/05/2022 14:27:32
// Last Modified: 26/05/2022 15:53:46
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto/src/utils/extensions.dart';
class CipherTextWrapper {
final CipherText? _single;
final List<CipherText>? _list;
CipherTextWrapper._(this._single, this._list);
factory CipherTextWrapper.single(CipherText cipherText) =>
CipherTextWrapper._(cipherText, null);
factory CipherTextWrapper.list(List<CipherText> cipherTexts) =>
CipherTextWrapper._(null, cipherTexts);
factory CipherTextWrapper.fromBytes(
// Uint8List bytes, {
// required int ivLength,
// required int dataLength,
// int tagLength = 0,
// int? chunkSize,
// }
) {
// TODO(hpcl): implement fromBytes
throw UnimplementedError();
}
bool get isSingle => _single.isNotNull;
bool get isList => _list.isNotNull;
/// Gets the [CipherText] if it's a single one.
///
/// Throws [NativeCryptoException] with
/// [NativeCryptoExceptionCode.invalid_data] if it's not a single one.
CipherText get single {
if (isSingle) {
return _single!;
} else {
throw NativeCryptoException(
message: 'CipherTextWrapper is not single',
code: NativeCryptoExceptionCode.invalid_data.code,
);
}
}
/// Gets the [List] of [CipherText] if it's a list.
///
/// Throws [NativeCryptoException] with
/// [NativeCryptoExceptionCode.invalid_data] if it's not a list.
List<CipherText> get list {
if (isList) {
return _list!;
} else {
throw NativeCryptoException(
message: 'CipherTextWrapper is not list',
code: NativeCryptoExceptionCode.invalid_data.code,
);
}
}
/// Gets the raw [Uint8List] of the [CipherText] or [List] of [CipherText].
Uint8List get raw {
if (isSingle) {
return single.bytes;
} else {
return list.map((cipherText) => cipherText.bytes).toList().sum();
}
}
int get chunkCount {
_single.isNull;
if (_single.isNotNull) {
return 1;
} else {
return _list?.length ?? 0;
}
}
}

View File

@ -3,47 +3,58 @@
// -----
// File: byte_array.dart
// Created Date: 16/12/2021 17:54:16
// Last Modified: 23/05/2022 23:07:03
// Last Modified: 26/05/2022 14:25:05
// -----
// Copyright (c) 2021
import 'dart:convert' as convert;
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:native_crypto/src/utils/convert.dart';
import 'package:native_crypto/src/utils/encoding.dart';
import 'package:native_crypto/src/utils/extensions.dart';
@immutable
abstract class ByteArray {
final Uint8List _bytes;
/// Creates a [ByteArray] from a [Uint8List].
const ByteArray(this._bytes);
/// Creates an ByteArray object from a hexdecimal string.
/// Creates a [ByteArray] object from a hexdecimal string.
ByteArray.fromBase16(String encoded)
: _bytes = Convert.decodeHexString(encoded);
: _bytes = encoded.toBytes(from: Encoding.base16);
/// Creates an ByteArray object from a Base64 string.
/// Creates a [ByteArray] object from a Base64 string.
ByteArray.fromBase64(String encoded)
: _bytes = convert.base64.decode(encoded);
: _bytes = encoded.toBytes(from: Encoding.base64);
/// Creates an ByteArray object from a UTF-8 string.
ByteArray.fromUtf8(String input)
: _bytes = Uint8List.fromList(convert.utf8.encode(input));
/// Creates a [ByteArray] object from an UTF-8 string.
ByteArray.fromUtf8(String encoded)
: _bytes = encoded.toBytes(from: Encoding.utf8);
/// Creates an ByteArray object from a length.
/// Creates a [ByteArray] object from an UTF-16 string.
ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes();
/// Creates an empty [ByteArray] object from a length.
ByteArray.fromLength(int length) : _bytes = Uint8List(length);
/// Gets the ByteArray bytes.
// ignore: unnecessary_getters_setters
/// Creates a [ByteArray] object from a [List] of [int].
ByteArray.fromList(List<int> list) : _bytes = list.toTypedList();
/// Gets the [ByteArray] bytes.
Uint8List get bytes => _bytes;
/// Gets the ByteArray bytes as a Hexadecimal representation.
String get base16 =>
_bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
/// Gets the [ByteArray] bytes as a Hexadecimal representation.
String get base16 => _bytes.toStr(to: Encoding.base16);
/// Gets the ByteArray bytes as a Base64 representation.
String get base64 => convert.base64.encode(_bytes);
/// Gets the [ByteArray] bytes as a Base64 representation.
String get base64 => _bytes.toStr(to: Encoding.base64);
/// Gets the [ByteArray] bytes as an UTF-8 representation.
String get utf8 => _bytes.toStr(to: Encoding.utf8);
/// Gets the [ByteArray] bytes as an UTF-16 representation.
String get utf16 => _bytes.toStr();
@override
bool operator ==(Object other) {

View File

@ -3,7 +3,7 @@
// -----
// File: secret_key.dart
// Created Date: 28/12/2021 13:36:54
// Last Modified: 25/05/2022 10:45:55
// Last Modified: 26/05/2022 11:56:06
// -----
// Copyright (c) 2021
@ -24,13 +24,22 @@ class SecretKey extends Key {
static Future<SecretKey> fromSecureRandom(int bitsCount) async {
try {
final Uint8List _key =
(await platform.generateSecretKey(bitsCount)) ?? Uint8List(0);
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);
} catch (e, s) {
if (e is KeyException) {
rethrow;
}
throw KeyException(
message: 'Failed to generate a secret key!',
message: '$e',
code: 'failed_to_generate_secret_key',
stackTrace: s,
);

View File

@ -1,23 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: convert.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 23/05/2022 22:39:19
// -----
// Copyright (c) 2021
import 'dart:typed_data';
abstract class Convert {
static Uint8List decodeHexString(String input) {
assert(input.length.isEven, 'Input needs to be an even length.');
return Uint8List.fromList(
List.generate(
input.length ~/ 2,
(i) => int.parse(input.substring(i * 2, (i * 2) + 2), radix: 16),
).toList(),
);
}
}

View File

@ -0,0 +1,10 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: encoding.dart
// Created Date: 26/05/2022 12:12:34
// Last Modified: 26/05/2022 12:18:09
// -----
// Copyright (c) 2022
enum Encoding { utf8, utf16, base64, base16 }

View File

@ -0,0 +1,95 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: extensions.dart
// Created Date: 26/05/2022 12:12:48
// Last Modified: 26/05/2022 15:49:38
// -----
// Copyright (c) 2022
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:typed_data';
import 'package:native_crypto/src/utils/encoding.dart';
extension ObjectX on Object? {
/// Returns `true` if the object is `null`.
bool get isNull => this == null;
/// Returns `true` if the object is **not** `null`.
bool get isNotNull => this != null;
/// Prints the object to the console.
void log() => developer.log(toString());
}
extension ListIntX on List<int> {
/// Converts a [List] of int to a [Uint8List].
Uint8List toTypedList() => Uint8List.fromList(this);
}
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]);
removeAt(i);
}
return first;
}
}
extension StringX on String {
/// Converts a [String] to a [Uint8List] using the specified [Encoding].
Uint8List toBytes({final Encoding from = Encoding.utf16}) {
Uint8List bytes;
switch (from) {
case Encoding.utf8:
bytes = utf8.encode(this).toTypedList();
break;
case Encoding.utf16:
bytes = runes.toList().toTypedList();
break;
case Encoding.base64:
bytes = base64.decode(this);
break;
case Encoding.base16:
assert(length.isEven, 'String needs to be an even length.');
bytes = List.generate(
length ~/ 2,
(i) => int.parse(substring(i * 2, (i * 2) + 2), radix: 16),
).toList().toTypedList();
}
return bytes;
}
}
extension Uint8ListX on Uint8List {
/// Converts a [Uint8List] to a [String] using the specified [Encoding].
String toStr({final Encoding to = Encoding.utf16}) {
String str;
switch (to) {
case Encoding.utf8:
str = utf8.decode(this);
break;
case Encoding.utf16:
str = String.fromCharCodes(this);
break;
case Encoding.base64:
str = base64.encode(this);
break;
case Encoding.base16:
str = List.generate(
length,
(i) => this[i].toRadixString(16).padLeft(2, '0'),
).join();
}
return str;
}
/// Returns a concatenation of this with the other [Uint8List].
Uint8List operator +(final Uint8List other) =>
[...this, ...other].toTypedList();
}

View File

@ -3,21 +3,41 @@
// -----
// File: exception.dart
// Created Date: 24/05/2022 18:54:48
// Last Modified: 25/05/2022 10:43:29
// Last Modified: 26/05/2022 15:36:56
// -----
// Copyright (c) 2022
// ignore_for_file: constant_identifier_names
import 'dart:developer';
import 'package:flutter/services.dart';
enum NativeCryptoExceptionCode {
unknown,
not_implemented,
invalid_argument,
invalid_key,
invalid_key_length,
invalid_algorithm,
invalid_padding,
invalid_mode,
invalid_cipher,
invalid_data,
platform_not_supported,
platform_returned_invalid_data,
platform_returned_empty_data,
platform_returned_null;
String get code => toString().split('.').last.toLowerCase();
}
class NativeCryptoException implements Exception {
const NativeCryptoException({
NativeCryptoException({
this.message,
String? code,
this.stackTrace,
// ignore: unnecessary_this
}) : this.code = code ?? 'unknown';
}) : code = code ?? NativeCryptoExceptionCode.unknown.code;
/// The long form message of the exception.
final String? message;
@ -73,7 +93,7 @@ class NativeCryptoException implements Exception {
)
: null;
String code = 'unknown';
String code = NativeCryptoExceptionCode.unknown.code;
String message = platformException.message ?? '';
if (details != null) {
@ -103,51 +123,3 @@ class NativeCryptoException implements Exception {
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => message.hashCode ^ code.hashCode ^ stackTrace.hashCode;
}
class KeyException extends NativeCryptoException {
const KeyException({
super.message,
super.code,
super.stackTrace,
});
}
class KeyDerivationException extends NativeCryptoException {
const KeyDerivationException({
super.message,
super.code,
super.stackTrace,
});
}
class CipherInitException extends NativeCryptoException {
const CipherInitException({
super.message,
super.code,
super.stackTrace,
});
}
class EncryptionException extends NativeCryptoException {
const EncryptionException({
super.message,
super.code,
super.stackTrace,
});
}
class DecryptionException extends NativeCryptoException {
const DecryptionException({
super.message,
super.code,
super.stackTrace,
});
}
class NotImplementedException extends NativeCryptoException {
const NotImplementedException({
super.message,
super.code,
super.stackTrace,
});
}