feat(api)!: rework full api with better object oriented architecture

This commit is contained in:
Hugo Pointcheval 2023-02-22 20:16:40 +01:00
parent 8044ccfa43
commit c8ff1149d7
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
68 changed files with 1861 additions and 2660 deletions

View File

@ -1 +1 @@
include: package:wyatt_analysis/analysis_options.flutter.experimental.yaml
include: package:wyatt_analysis/analysis_options.flutter.yaml

View File

@ -1,11 +1,8 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: native_crypto.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 26/05/2022 12:10:42
// -----
// Copyright (c) 2021
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
/// Fast and powerful cryptographic functions
/// thanks to javax.crypto, CommonCrypto and CryptoKit.
@ -13,18 +10,13 @@
/// Author: Hugo Pointcheval
library native_crypto;
export 'package:native_crypto_platform_interface/src/utils/exception.dart';
export 'package:native_crypto_platform_interface/src/core/exceptions/exception.dart';
export 'src/builders/builders.dart';
export 'src/ciphers/ciphers.dart';
export 'src/core/core.dart';
export 'src/interfaces/interfaces.dart';
export 'src/kdf/kdf.dart';
export 'src/keys/keys.dart';
// Utils
export 'src/utils/cipher_algorithm.dart';
export 'src/utils/hash_algorithm.dart';
export 'src/utils/kdf_algorithm.dart';
// ignore: constant_identifier_names
const String AUTHOR = 'Hugo Pointcheval';
export 'src/digest/digest.dart';
export 'src/domain/domain.dart';
export 'src/kdf/pbkdf2.dart';
export 'src/keys/secret_key.dart';
export 'src/random/secure_random.dart';

View File

@ -1,11 +0,0 @@
// 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';

View File

@ -1,10 +1,8 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: builders.dart
// Created Date: 23/05/2022 22:56:03
// Last Modified: 26/05/2022 19:22:19
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'decryption_builder.dart';
export 'encryption_builder.dart';

View File

@ -1,46 +1,60 @@
// 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
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
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';
import 'package:native_crypto/src/core/utils/cipher_text.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;
import 'package:native_crypto/src/domain/cipher.dart';
import 'package:native_crypto/src/domain/cipher_chunk.dart';
/// {@template decryption_builder}
/// A [StatelessWidget] that builds a [FutureBuilder] that will decrypt a
/// [CipherText] using a [Cipher].
/// {@endtemplate}
class DecryptionBuilder<T extends CipherChunk> extends StatelessWidget {
/// {@macro decryption_builder}
const DecryptionBuilder({
super.key,
required this.cipher,
required this.data,
required this.cipherText,
required this.onLoading,
required this.onError,
required this.onSuccess,
super.key,
});
/// The [Cipher] that will be used to decrypt the [CipherText].
final Cipher<T> cipher;
/// The [CipherText] that will be decrypted.
final CipherText<T> cipherText;
/// The [Widget] that will be displayed while the [CipherText] is being
/// decrypted.
final Widget Function(BuildContext context) onLoading;
/// The [Widget] that will be displayed if an error occurs while decrypting
/// the [CipherText].
final Widget Function(BuildContext context, Object error) onError;
/// The [Widget] that will be displayed once the [CipherText] has been
/// decrypted.
final Widget Function(BuildContext context, Uint8List plainText) 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);
},
);
}
Widget build(BuildContext context) => FutureBuilder<Uint8List>(
future: cipher.decrypt(cipherText),
builder: (context, snapshot) {
if (snapshot.hasData) {
return onSuccess(context, snapshot.data!);
} else if (snapshot.hasError) {
return onError(context, snapshot.error!);
}
return onLoading(context);
},
);
}

View File

@ -0,0 +1,61 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:native_crypto/src/core/utils/cipher_text.dart';
import 'package:native_crypto/src/domain/cipher.dart';
import 'package:native_crypto/src/domain/cipher_chunk.dart';
/// {@template encryption_builder}
/// A [StatelessWidget] that builds a [FutureBuilder] that will
/// encrypt a [Uint8List] using a [Cipher].
/// {@endtemplate}
class EncryptionBuilder<T extends CipherChunk> extends StatelessWidget {
/// {@macro encryption_builder}
const EncryptionBuilder({
required this.cipher,
required this.plainText,
required this.onLoading,
required this.onError,
required this.onSuccess,
super.key,
});
/// The [Cipher] that will be used to encrypt the [Uint8List].
final Cipher<T> cipher;
/// The [Uint8List] that will be encrypted.
final Uint8List plainText;
/// The [Widget] that will be displayed while the [Uint8List] is being
/// encrypted.
final Widget Function(BuildContext context) onLoading;
/// The [Widget] that will be displayed if an error occurs while encrypting
/// the [Uint8List].
final Widget Function(BuildContext context, Object error) onError;
/// The [Widget] that will be displayed once the [Uint8List] has been
/// encrypted.
final Widget Function(BuildContext context, CipherText<T> cipherText)
onSuccess;
@override
Widget build(BuildContext context) => FutureBuilder<CipherText<T>>(
future: cipher.encrypt(plainText),
builder: (context, snapshot) {
if (snapshot.hasData) {
return onSuccess(context, snapshot.data!);
} else if (snapshot.hasError) {
return onError(context, snapshot.error!);
}
return onLoading(context);
},
);
}

View File

@ -1,181 +1,300 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 27/05/2022 12:13:28
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:io';
import 'dart:typed_data';
import 'package:native_crypto/src/ciphers/aes/aes_cipher_chunk.dart';
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_wrapper.dart';
import 'package:native_crypto/src/interfaces/cipher.dart';
import 'package:native_crypto/src/core/constants/constants.dart';
import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart';
import 'package:native_crypto/src/core/utils/cipher_text.dart';
import 'package:native_crypto/src/core/utils/platform.dart';
import 'package:native_crypto/src/domain/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 'aes_cipher_chunk.dart';
export 'aes_key_size.dart';
export 'aes_mode.dart';
export 'aes_padding.dart';
/// An AES cipher.
/// {@template aes}
/// AES cipher.
///
/// [AES] is a [Cipher] that can be used to encrypt or decrypt data.
class AES implements Cipher {
final SecretKey _key;
/// [AES] is a symmetric cipher which means that the same key is used to encrypt
/// and decrypt the data.
/// {@endtemplate}
class AES implements Cipher<AESCipherChunk> {
const AES({
required this.key,
required this.mode,
required this.padding,
this.chunkSize = Constants.defaultChunkSize,
});
static const String _algorithm = 'aes';
/// The key used to encrypt and decrypt the data.
final SecretKey key;
/// The [AESMode] used by this [AES].
final AESMode mode;
/// The [AESPadding] used by this [AES].
final AESPadding padding;
@override
CipherAlgorithm get algorithm => CipherAlgorithm.aes;
/// The size of the cipher text chunks.
final int chunkSize;
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,
@override
Future<Uint8List> decrypt(CipherText<AESCipherChunk> cipherText) async {
final BytesBuilder plainText = BytesBuilder(copy: false);
final chunks = cipherText.chunks;
int i = 0;
for (final chunk in chunks) {
plainText.add(await _decryptChunk(chunk.bytes, count: i++));
}
return plainText.toBytes();
}
@override
Future<void> decryptFile(File cipherTextFile, File plainTextFile) {
if (!cipherTextFile.existsSync()) {
throw ArgumentError.value(
cipherTextFile.path,
'cipherTextFile.path',
'File does not exist!',
);
}
if (plainTextFile.existsSync()) {
throw ArgumentError.value(
plainTextFile.path,
'plainTextFile.path',
'File already exists!',
);
}
return platform.decryptFile(
cipherTextPath: cipherTextFile.path,
plainTextPath: plainTextFile.path,
key: key.bytes,
algorithm: _algorithm,
);
}
@override
Future<CipherText<AESCipherChunk>> encrypt(Uint8List plainText) async {
final chunks = <AESCipherChunk>[];
final chunkedPlainText = plainText.chunked(chunkSize);
int i = 0;
for (final plainTextChunk in chunkedPlainText) {
final bytes = await _encryptChunk(plainTextChunk, count: i++);
chunks.add(
AESCipherChunk(
bytes,
ivLength: mode.ivLength,
tagLength: mode.tagLength,
),
);
}
return CipherText.fromChunks(
chunks,
chunkFactory: (bytes) => AESCipherChunk(
bytes,
ivLength: mode.ivLength,
tagLength: mode.tagLength,
),
chunkSize: chunkSize,
);
}
@override
Future<void> encryptFile(File plainTextFile, File cipherTextFile) {
if (!plainTextFile.existsSync()) {
throw ArgumentError.value(
plainTextFile.path,
'plainTextFile.path',
'File does not exist!',
);
}
if (cipherTextFile.existsSync()) {
throw ArgumentError.value(
cipherTextFile.path,
'cipherTextFile.path',
'File already exists!',
);
}
return platform.encryptFile(
plainTextPath: plainTextFile.path,
cipherTextPath: cipherTextFile.path,
key: key.bytes,
algorithm: _algorithm,
);
}
/// Encrypts the [plainText] with the [iv] chosen by the Flutter side.
///
/// Prefer using [encrypt] instead which will generate a
/// random [iv] on the native side.
///
/// Note: this method doesn't chunk the data. It can lead to memory issues
/// if the [plainText] is too big. Use [encrypt] instead.
Future<AESCipherChunk> encryptWithIV(
Uint8List plainText,
Uint8List iv,
) async {
// Check if the cipher is correctly initialized
_isCorrectlyInitialized();
if (iv.length != mode.ivLength) {
throw ArgumentError.value(
iv.length,
'iv.length',
'Invalid iv length! '
'Expected: ${mode.ivLength}',
);
}
final bytes = await platform.encryptWithIV(
plainText: plainText,
iv: iv,
key: key.bytes,
algorithm: _algorithm,
);
// TODO(hpcl): move these checks to the platform interface
if (bytes == null) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes',
);
}
if (bytes.isEmpty) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned no data',
);
}
return AESCipherChunk(
bytes,
ivLength: mode.ivLength,
tagLength: mode.tagLength,
);
}
/// Ensures that the cipher is correctly initialized.
bool _isCorrectlyInitialized() {
final keySize = key.length * 8;
if (!AESKeySize.supportedSizes.contains(keySize)) {
throw ArgumentError.value(
keySize,
'keySize',
'Invalid key size! '
'Expected: ${AESKeySize.supportedSizes.join(', ')}',
);
}
if (!mode.supportedPaddings.contains(padding)) {
throw NativeCryptoException(
message: 'Invalid padding! '
throw ArgumentError.value(
padding,
'padding',
'Invalid padding! '
'Expected: ${mode.supportedPaddings.join(', ')}',
code: NativeCryptoExceptionCode.invalid_padding.code,
);
}
return true;
}
Future<Uint8List> _decrypt(
CipherText cipherText, {
int chunkCount = 0,
/// Encrypts the plain text chunk.
Future<Uint8List> _encryptChunk(Uint8List plainChunk, {int count = 0}) async {
// Check if the cipher is correctly initialized
_isCorrectlyInitialized();
Uint8List? bytes;
try {
bytes = await platform.encrypt(
plainChunk,
key: key.bytes,
algorithm: _algorithm,
);
} on NativeCryptoException catch (e) {
throw e.copyWith(
message: 'Failed to encrypt chunk #$count: ${e.message}',
);
}
// TODO(hpcl): move these checks to the platform interface
if (bytes == null) {
throw NativeCryptoException(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes on chunk #$count',
);
}
if (bytes.isEmpty) {
throw NativeCryptoException(
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned no data on chunk #$count',
);
}
return bytes;
}
/// Decrypts the cipher text chunk.
Future<Uint8List> _decryptChunk(
Uint8List cipherChunk, {
int count = 0,
}) async {
Uint8List? decrypted;
// Check if the cipher is correctly initialized
_isCorrectlyInitialized();
Uint8List? bytes;
try {
decrypted = await platform.decrypt(
cipherText.bytes,
_key.bytes,
algorithm.name,
bytes = await platform.decrypt(
cipherChunk,
key: key.bytes,
algorithm: _algorithm,
);
} catch (e, s) {
} on NativeCryptoException catch (e) {
throw e.copyWith(
message: 'Failed to decrypt chunk #$count: ${e.message}',
);
}
// TODO(hpcl): move these checks to the platform interface
if (bytes == null) {
throw NativeCryptoException(
message: '$e',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes on chunk #$count',
);
}
if (decrypted.isNull) {
if (bytes.isEmpty) {
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 {
Uint8List? encrypted;
try {
encrypted = await platform.encrypt(
data,
_key.bytes,
algorithm.name,
);
} catch (e, s) {
throw NativeCryptoException(
message: '$e on chunk #$chunkCount',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned no data on chunk #$count',
);
}
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 {
try {
return CipherText.fromBytes(
encrypted,
ivLength: 12,
messageLength: encrypted.length - 28,
tagLength: 16,
cipherAlgorithm: CipherAlgorithm.aes,
);
} on NativeCryptoException catch (e, s) {
throw NativeCryptoException(
message: '${e.message} on chunk #$chunkCount',
code: e.code,
stackTrace: s,
);
}
}
}
@override
Future<Uint8List> decrypt(CipherTextWrapper cipherText) async {
final BytesBuilder decryptedData = BytesBuilder(copy: false);
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.single));
}
return decryptedData.toBytes();
}
@override
Future<CipherTextWrapper> encrypt(Uint8List data) async {
if (data.isEmpty) {
return CipherTextWrapper.empty();
}
CipherTextWrapper cipherTextWrapper;
Uint8List dataToEncrypt;
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(
i * Cipher.bytesCountPerChunk,
(i + 1) * Cipher.bytesCountPerChunk,
)
: data.sublist(i * Cipher.bytesCountPerChunk);
cipherTextWrapper.add(await _encrypt(dataToEncrypt, chunkCount: i));
}
} else {
cipherTextWrapper = CipherTextWrapper.single(await _encrypt(data));
}
return cipherTextWrapper;
return bytes;
}
}

View File

@ -0,0 +1,67 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/domain/cipher_chunk.dart';
class AESCipherChunk extends CipherChunk {
const AESCipherChunk(
super.bytes, {
required this.ivLength,
required this.tagLength,
});
/// Creates a [AESCipherChunk] from a [List<int>].
AESCipherChunk.fromList(
super.list, {
required this.ivLength,
required this.tagLength,
}) : super.fromList();
/// Creates a [AESCipherChunk] from a [String] encoded in UTF-8.
AESCipherChunk.fromUtf8(
super.encoded, {
required this.ivLength,
required this.tagLength,
}) : super.fromUtf8();
/// Creates a [AESCipherChunk] from a [String] encoded in UTF-16.
AESCipherChunk.fromUtf16(
super.encoded, {
required this.ivLength,
required this.tagLength,
}) : super.fromUtf16();
/// Creates a [AESCipherChunk] from a [String] encoded in Hexadecimal.
AESCipherChunk.fromBase16(
super.encoded, {
required this.ivLength,
required this.tagLength,
}) : super.fromBase16();
/// Creates a [AESCipherChunk] from a [String] encoded in Base64.
AESCipherChunk.fromBase64(
super.encoded, {
required this.ivLength,
required this.tagLength,
}) : super.fromBase64();
/// Intialization vector length.
final int ivLength;
/// Tag length.
final int tagLength;
/// Returns the initialization vector, or nonce of the [AESCipherChunk].
Uint8List get iv => bytes.sublist(0, ivLength);
/// Returns the tag of the [AESCipherChunk].
Uint8List get tag => bytes.sublist(bytes.length - tagLength, bytes.length);
/// Returns the message of the [AESCipherChunk].
Uint8List get message => bytes.sublist(ivLength, bytes.length - tagLength);
}

View File

@ -1,18 +1,20 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes_key_size.dart
// Created Date: 23/05/2022 22:10:07
// Last Modified: 26/05/2022 18:45:01
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
/// Defines all available key sizes.
/// {@template aes_key_size}
/// Defines the key size of an AES cipher.
/// {@endtemplate}
enum AESKeySize {
bits128(128),
bits192(192),
bits256(256);
/// {@macro aes_key_size}
const AESKeySize(this.bits);
/// Returns the number of bits supported by an [AESKeySize].
static final List<int> supportedSizes = [128, 192, 256];
@ -21,6 +23,4 @@ enum AESKeySize {
/// Returns the number of bytes in this [AESKeySize].
int get bytes => bits ~/ 8;
const AESKeySize(this.bits);
}

View File

@ -1,17 +1,29 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes_mode.dart
// Created Date: 23/05/2022 22:09:16
// Last Modified: 26/05/2022 21:03:26
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
import 'package:native_crypto/src/core/constants/constants.dart';
/// {@template aes_mode}
/// Defines the AES modes of operation.
/// {@endtemplate}
enum AESMode {
gcm([AESPadding.none], 12, 16);
/// GCM mode.
gcm(
[AESPadding.none],
Constants.aesGcmNonceLength,
Constants.aesGcmTagLength,
);
/// {@macro aes_mode}
const AESMode(
this.supportedPaddings, [
this.ivLength = 16,
this.tagLength = 0,
]);
/// Returns the list of supported [AESPadding] for this [AESMode].
final List<AESPadding> supportedPaddings;
@ -21,10 +33,4 @@ enum AESMode {
/// Returns the default tag length for this [AESMode].
final int tagLength;
const AESMode(
this.supportedPaddings, [
this.ivLength = 16,
this.tagLength = 0,
]);
}

View File

@ -1,11 +1,11 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes_padding.dart
// Created Date: 23/05/2022 22:10:17
// Last Modified: 25/05/2022 09:23:49
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
/// Represents different paddings.
enum AESPadding { none }
/// Padding used for AES encryption.
enum AESPadding {
/// No padding.
none,
}

View File

@ -1,10 +1,7 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: ciphers.dart
// Created Date: 23/05/2022 22:56:30
// Last Modified: 23/05/2022 22:56:47
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'aes/aes.dart';

View File

@ -1,117 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_text.dart
// Created Date: 16/12/2021 16:59:53
// Last Modified: 27/05/2022 12:09:47
// -----
// 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 NativeCrypto.
///
/// [CipherText] is a [ByteArray] that can be used to store encrypted data.
/// It is represented like:
/// ```txt
/// [IV + MESSAGE + TAG]
/// ```
/// where:
/// - IV's length is [CipherText.ivLength] bytes.
/// - MESSAGE's length is [CipherText.messageLength] bytes.
/// - TAG's length is [CipherText.tagLength] bytes.
///
/// Check [CipherTextWrapper] for more information.
class CipherText extends ByteArray {
final int _ivLength;
final int _messageLength;
final int _tagLength;
final CipherAlgorithm? _cipherAlgorithm;
const CipherText._(
this._ivLength,
this._messageLength,
this._tagLength,
this._cipherAlgorithm,
super.bytes,
);
factory CipherText.fromBytes(
Uint8List bytes, {
required int ivLength,
required int tagLength,
int? messageLength,
CipherAlgorithm? cipherAlgorithm,
}) {
messageLength ??= bytes.length - ivLength - tagLength;
if (ivLength.isNegative ||
messageLength.isNegative ||
tagLength.isNegative) {
throw NativeCryptoException(
message: 'Invalid length! Must be positive.',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
if (bytes.isEmpty) {
throw NativeCryptoException(
message: 'Passed data is empty!',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
if (bytes.length != ivLength + messageLength + tagLength) {
throw NativeCryptoException(
message: 'Invalid cipher text length! '
'Expected: ${ivLength + messageLength + tagLength} bytes '
'got: ${bytes.length} 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) {
return _cipherAlgorithm!;
} else {
throw NativeCryptoException(
message: 'Cipher algorithm is not specified',
code: NativeCryptoExceptionCode.invalid_cipher.code,
);
}
}
/// Gets the length of the [CipherText]'s IV.
int get ivLength => _ivLength;
/// Gets the length of the [CipherText]'s Message.
int get messageLength => _messageLength;
/// Gets the length of the [CipherText]'s Tag.
int get tagLength => _tagLength;
}

View File

@ -1,191 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_text_wrapper.dart
// Created Date: 26/05/2022 14:27:32
// Last Modified: 27/05/2022 13:43:29
// -----
// Copyright (c) 2022
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 tagLength,
CipherAlgorithm? cipherAlgorithm,
int? chunkSize,
}) {
chunkSize ??= Cipher.bytesCountPerChunk;
Cipher.bytesCountPerChunk = chunkSize;
final int messageLength = bytes.length - ivLength - tagLength;
if (messageLength <= chunkSize) {
return CipherTextWrapper.single(
CipherText.fromBytes(
bytes,
ivLength: ivLength,
tagLength: tagLength,
cipherAlgorithm: cipherAlgorithm,
),
);
} else {
final cipherTexts = <CipherText>[];
for (var i = 0; i < bytes.length; i += chunkSize + ivLength + tagLength) {
final chunk = bytes.trySublist(i, i + chunkSize + ivLength + tagLength);
try {
cipherTexts.add(
CipherText.fromBytes(
chunk,
ivLength: ivLength,
tagLength: tagLength,
cipherAlgorithm: cipherAlgorithm,
),
);
} on NativeCryptoException catch (e, s) {
throw NativeCryptoException(
message: '${e.message} on chunk #$i',
code: e.code,
stackTrace: s,
);
}
}
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.
///
/// 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 bytes {
if (isSingle) {
return single.bytes;
} else {
return list.map((cipherText) => cipherText.bytes).toList().combine();
}
}
/// 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) {
return 1;
} else {
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,
);
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
abstract class Constants {
/// The default chunk size in bytes used for encryption and decryption.
static const int defaultChunkSize = 33554432;
static const int aesGcmNonceLength = 12;
static const int aesGcmTagLength = 16;
}

View File

@ -1,11 +1,11 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: core.dart
// Created Date: 23/05/2022 23:05:26
// Last Modified: 26/05/2022 17:10:25
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'cipher_text.dart';
export 'cipher_text_wrapper.dart';
export './constants/constants.dart';
export './enums/enums.dart';
export './extensions/extensions.dart';
export './utils/utils.dart';
export 'utils/platform.dart';

View File

@ -0,0 +1,17 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
/// An encoding used to convert a byte array to a string and vice-versa.
enum Encoding {
/// UTF-8 encoding, as defined by the Unicode standard.
utf8,
/// UTF-16 encoding, as defined by the Unicode standard.
utf16,
/// Base64 encoding, as defined by RFC 4648.
base64,
/// Hexadecimal encoding.
base16,
}

View File

@ -0,0 +1,8 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'encoding.dart';
export 'hash_algorithm.dart';

View File

@ -0,0 +1,15 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
/// The hash algorithm to use in Message Digest and HMAC.
enum HashAlgorithm {
/// The SHA-256 hash algorithm.
sha256,
/// The SHA-384 hash algorithm.
sha384,
/// The SHA-512 hash algorithm.
sha512,
}

View File

@ -0,0 +1,10 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'list_int_extension.dart';
export 'list_uint8_list_extension.dart';
export 'string_extension.dart';
export 'uint8_list_extension.dart';

View File

@ -0,0 +1,12 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
extension ListIntExtension on List<int> {
/// Converts a [List] of int to a [Uint8List].
Uint8List toTypedList() => Uint8List.fromList(this);
}

View File

@ -0,0 +1,19 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart';
extension ListUint8ListExtension on List<Uint8List> {
/// Reduce a [List] of [Uint8List] to a [Uint8List].
Uint8List combine() {
if (isEmpty) {
return Uint8List(0);
}
return reduce((value, element) => value | element);
}
}

View File

@ -0,0 +1,36 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:convert';
import 'dart:typed_data';
import 'package:native_crypto/src/core/enums/encoding.dart';
import 'package:native_crypto/src/core/extensions/list_int_extension.dart';
extension StringExtension on String {
/// Converts a [String] to a [Uint8List] using the specified [Encoding].
Uint8List toBytes({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;
}
}

View File

@ -0,0 +1,69 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:convert';
import 'dart:typed_data';
import 'package:native_crypto/src/core/enums/encoding.dart';
import 'package:native_crypto/src/core/extensions/list_int_extension.dart';
extension Uint8ListExtension on Uint8List {
/// Returns a concatenation of this with the other [Uint8List].
Uint8List plus(Uint8List other) => [...this, ...other].toTypedList();
/// Returns a concatenation of this with the other
/// [Uint8List] using the `|` operator as a shortcut.
Uint8List operator |(Uint8List other) => plus(other);
/// Returns a sublist of this from the [start] index to the [end] index.
/// If [end] is greater than the length of the list, it is set to the length.
Uint8List trySublist(int start, [int? end]) {
if (isEmpty) {
return this;
}
int ending = end ?? length;
if (ending > length) {
ending = length;
}
return sublist(start, ending);
}
/// Returns a [List] of [Uint8List] of the specified [chunkSize].
List<Uint8List> chunked(int chunkSize) {
if (isEmpty) {
return [];
}
return List.generate(
(length / chunkSize).ceil(),
(i) => trySublist(i * chunkSize, (i * chunkSize) + chunkSize),
);
}
/// Converts a [Uint8List] to a [String] using the specified [Encoding].
String toStr({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;
}
}

View File

@ -0,0 +1,12 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/domain/cipher_chunk.dart';
/// A factory that creates a [CipherChunk] of type [T] from a [Uint8List].
typedef ChunkFactory<T extends CipherChunk> = T Function(Uint8List chunk);

View File

@ -0,0 +1,47 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/core/constants/constants.dart';
import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart';
import 'package:native_crypto/src/core/utils/chunk_factory.dart';
import 'package:native_crypto/src/domain/byte_array.dart';
import 'package:native_crypto/src/domain/cipher_chunk.dart';
/// {@template cipher_text}
/// A [CipherText] is a [ByteArray] that is used to store a text encrypted by a
/// Cipher.
/// {@endtemplate}
class CipherText<T extends CipherChunk> extends ByteArray {
/// {@macro cipher_text}
CipherText(
super.bytes, {
required ChunkFactory<T> this.chunkFactory,
this.chunkSize = Constants.defaultChunkSize,
}) : chunks = bytes.chunked(chunkSize).map(chunkFactory).toList();
/// Creates a [CipherText] from a [List<T>] of [CipherChunk].
CipherText.fromChunks(
this.chunks, {
required ChunkFactory<T> this.chunkFactory,
this.chunkSize = Constants.defaultChunkSize,
}) : super(
chunks.fold<Uint8List>(
Uint8List(0),
(acc, chunk) => acc | chunk.bytes,
),
);
/// Factory used to create [CipherChunk] from an Uint8List.
final ChunkFactory<T>? chunkFactory;
/// List of [CipherChunk] that compose the [CipherText].
final List<T> chunks;
/// Size of one chunk.
final int chunkSize;
}

View File

@ -0,0 +1,9 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
NativeCryptoPlatform platform = NativeCryptoPlatform.instance;

View File

@ -0,0 +1,8 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'chunk_factory.dart';
export 'cipher_text.dart';

View File

@ -0,0 +1,8 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'hash.dart';
export 'hmac.dart';

View File

@ -0,0 +1,62 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/core/enums/hash_algorithm.dart';
import 'package:native_crypto/src/core/utils/platform.dart';
import 'package:native_crypto/src/domain/hash.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
class _Hash extends Hash {
const _Hash(this.algorithm);
@override
Future<Uint8List> digest(Uint8List data) async {
final hash = await platform.hash(data, algorithm: algorithm.name);
// TODO(hpcl): move these checks to the platform interface
if (hash == null) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes',
);
}
if (hash.isEmpty) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned no data.',
);
}
return hash;
}
@override
final HashAlgorithm algorithm;
}
/// A [Hash] that uses the SHA-256 algorithm.
class Sha256 extends _Hash {
factory Sha256() => _instance ??= Sha256._();
Sha256._() : super(HashAlgorithm.sha256);
static Sha256? _instance;
}
/// A [Hash] that uses the SHA-384 algorithm.
class Sha384 extends _Hash {
factory Sha384() => _instance ??= Sha384._();
Sha384._() : super(HashAlgorithm.sha384);
static Sha384? _instance;
}
/// A [Hash] that uses the SHA-512 algorithm.
class Sha512 extends _Hash {
factory Sha512() => _instance ??= Sha512._();
Sha512._() : super(HashAlgorithm.sha512);
static Sha512? _instance;
}

View File

@ -0,0 +1,67 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/core/enums/hash_algorithm.dart';
import 'package:native_crypto/src/core/utils/platform.dart';
import 'package:native_crypto/src/domain/hmac.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
class _Hmac extends Hmac {
const _Hmac(this.algorithm);
@override
Future<Uint8List> digest(Uint8List data, SecretKey key) async {
final hmac = await platform.hmac(
data,
key: key.bytes,
algorithm: algorithm.name,
);
// TODO(hpcl): move these checks to the platform interface
if (hmac == null) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes',
);
}
if (hmac.isEmpty) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned no data.',
);
}
return hmac;
}
@override
final HashAlgorithm algorithm;
}
/// A [Hmac] that uses the SHA-256 algorithm.
class HmacSha256 extends _Hmac {
factory HmacSha256() => _instance ??= HmacSha256._();
HmacSha256._() : super(HashAlgorithm.sha256);
static HmacSha256? _instance;
}
/// A [Hmac] that uses the SHA-384 algorithm.
class HmacSha384 extends _Hmac {
factory HmacSha384() => _instance ??= HmacSha384._();
HmacSha384._() : super(HashAlgorithm.sha384);
static HmacSha384? _instance;
}
/// A [Hmac] that uses the SHA-512 algorithm.
class HmacSha512 extends _Hmac {
factory HmacSha512() => _instance ??= HmacSha512._();
HmacSha512._() : super(HashAlgorithm.sha512);
static HmacSha512? _instance;
}

View File

@ -0,0 +1,37 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:native_crypto/src/domain/byte_array.dart';
/// {@template base_key}
/// 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.
///
/// Note: [BaseKey] is named [BaseKey] instead of Key to avoid conflicts with
/// the Key class from Flutter.
/// {@endtemplate}
abstract class BaseKey extends ByteArray {
/// {@macro base_key}
const BaseKey(super.bytes);
/// Creates a [BaseKey] from a [List<int>].
BaseKey.fromList(super.list) : super.fromList();
/// Creates a [BaseKey] from a [String] encoded in UTF-8.
BaseKey.fromUtf8(super.encoded) : super.fromUtf8();
/// Creates a [BaseKey] from a [String] encoded in UTF-16.
BaseKey.fromUtf16(super.encoded) : super.fromUtf16();
/// Creates a [BaseKey] from a [String] encoded in Hexadecimal.
BaseKey.fromBase16(super.encoded) : super.fromBase16();
/// Creates a [BaseKey] from a [String] encoded in Base64.
BaseKey.fromBase64(super.encoded) : super.fromBase64();
}

View File

@ -1,48 +1,49 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: byte_array.dart
// Created Date: 16/12/2021 17:54:16
// Last Modified: 26/05/2022 17:13:27
// -----
// Copyright (c) 2021
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:native_crypto/src/utils/encoding.dart';
import 'package:native_crypto/src/utils/extensions.dart';
import 'package:equatable/equatable.dart';
import 'package:native_crypto/src/core/enums/encoding.dart';
import 'package:native_crypto/src/core/extensions/list_int_extension.dart';
import 'package:native_crypto/src/core/extensions/string_extension.dart';
import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart';
/// {@template byte_array}
/// Represents a byte array.
///
/// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods.
@immutable
abstract class ByteArray {
final Uint8List _bytes;
/// Creates a [ByteArray] from a [Uint8List].
/// {@endtemplate}
abstract class ByteArray extends Equatable {
/// {@macro byte_array}
const ByteArray(this._bytes);
/// Creates a [ByteArray] object from a hexdecimal string.
ByteArray.fromBase16(String encoded)
: _bytes = encoded.toBytes(from: Encoding.base16);
/// Creates a [ByteArray] object from a [List] of [int].
ByteArray.fromList(List<int> list) : _bytes = list.toTypedList();
/// Creates a [ByteArray] object from a Base64 string.
ByteArray.fromBase64(String encoded)
: _bytes = encoded.toBytes(from: Encoding.base64);
/// Creates an empty [ByteArray] object from a length.
ByteArray.fromLength(int length, {int fill = 0})
: _bytes = Uint8List(length)..fillRange(0, length, fill);
/// Creates a [ByteArray] object from an UTF-16 string.
ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes();
/// Creates a [ByteArray] object from an UTF-8 string.
ByteArray.fromUtf8(String encoded)
: _bytes = encoded.toBytes(from: Encoding.utf8);
/// Creates a [ByteArray] object from an UTF-16 string.
ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes();
/// Creates a [ByteArray] object from a Base64 string.
ByteArray.fromBase64(String encoded)
: _bytes = encoded.toBytes(from: Encoding.base64);
/// Creates an empty [ByteArray] object from a length.
ByteArray.fromLength(int length) : _bytes = Uint8List(length);
/// Creates a [ByteArray] object from a hexdecimal string.
ByteArray.fromBase16(String encoded)
: _bytes = encoded.toBytes(from: Encoding.base16);
/// Creates a [ByteArray] object from a [List] of [int].
ByteArray.fromList(List<int> list) : _bytes = list.toTypedList();
final Uint8List _bytes;
/// Gets the [ByteArray] bytes.
Uint8List get bytes => _bytes;
@ -62,31 +63,6 @@ abstract class ByteArray {
/// 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) {
for (int i = 0; i < _bytes.length; i++) {
if (_bytes[i] != other._bytes[i]) {
return false;
}
}
return true;
}
return false;
}
@override
int get hashCode {
int hash = 0;
for (int i = 0; i < _bytes.length; i++) {
hash = _bytes[i] + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
List<Object?> get props => [_bytes];
}

View File

@ -0,0 +1,32 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:io';
import 'dart:typed_data';
import 'package:native_crypto/src/core/utils/cipher_text.dart';
import 'package:native_crypto/src/domain/cipher_chunk.dart';
/// {@template cipher}
/// Abstract class that defines the behavior of a Cipher.
/// {@endtemplate}
abstract class Cipher<T extends CipherChunk> {
/// {@macro cipher}
const Cipher();
/// Encrypts a [Uint8List] and returns a [CipherText].
Future<CipherText<T>> encrypt(Uint8List plainText);
/// Decrypts a [CipherText] and returns a [Uint8List].
Future<Uint8List> decrypt(CipherText<T> cipherText);
/// Encrypts a File located at [plainTextFile] and saves the result
/// at [cipherTextFile].
Future<void> encryptFile(File plainTextFile, File cipherTextFile);
/// Decrypts a File located at [cipherTextFile] and saves the result
/// at [plainTextFile].
Future<void> decryptFile(File cipherTextFile, File plainTextFile);
}

View File

@ -0,0 +1,31 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:native_crypto/src/domain/byte_array.dart';
/// {@template cipher_chunk}
/// A [CipherChunk] is a [ByteArray] that is used to store a chunk of data
/// encrypted by a Cipher.
/// {@endtemplate}
abstract class CipherChunk extends ByteArray {
/// {@macro cipher_chunk}
const CipherChunk(super.bytes);
/// Creates a [CipherChunk] from a [List<int>].
CipherChunk.fromList(super.list) : super.fromList();
/// Creates a [CipherChunk] from a [String] encoded in UTF-8.
CipherChunk.fromUtf8(super.encoded) : super.fromUtf8();
/// Creates a [CipherChunk] from a [String] encoded in UTF-16.
CipherChunk.fromUtf16(super.encoded) : super.fromUtf16();
/// Creates a [CipherChunk] from a [String] encoded in Hexadecimal.
CipherChunk.fromBase16(super.encoded) : super.fromBase16();
/// Creates a [CipherChunk] from a [String] encoded in Base64.
CipherChunk.fromBase64(super.encoded) : super.fromBase64();
}

View File

@ -0,0 +1,14 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'base_key.dart';
export 'byte_array.dart';
export 'cipher.dart';
export 'cipher_chunk.dart';
export 'hash.dart';
export 'hmac.dart';
export 'key_derivation_function.dart';
export 'random.dart';

View File

@ -0,0 +1,24 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/core/enums/hash_algorithm.dart';
/// {@template hash}
/// A [Hash] is a one-way function that takes arbitrary-sized data and
/// outputs a fixed-sized hash value.
/// {@endtemplate}
abstract class Hash {
/// {@macro hash}
const Hash();
/// The [HashAlgorithm] used by this [Hash].
HashAlgorithm get algorithm;
/// Digests the given [data] and returns the hash.
Future<Uint8List> digest(Uint8List data);
}

View File

@ -0,0 +1,25 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/core/enums/hash_algorithm.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
/// {@template hmac}
/// A HMAC is a cryptographic hash that uses a key to sign a message.
/// The receiver verifies the hash by recomputing it using the same key.
/// {@endtemplate}
abstract class Hmac {
/// {@macro hmac}
const Hmac();
/// The [HashAlgorithm] used by this [Hmac].
HashAlgorithm get algorithm;
/// Digests the given [data] and returns the hmac.
Future<Uint8List> digest(Uint8List data, SecretKey key);
}

View File

@ -0,0 +1,27 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
/// {@template key_derivation_function}
/// A [KeyDerivationFunction] is a function that derives a key from an
/// [Uint8List] key material.
/// {@endtemplate}
abstract class KeyDerivationFunction {
/// {@macro key_derivation_function}
const KeyDerivationFunction();
/// Derives a key from a [keyMaterial].
Future<Uint8List> derive(
Uint8List keyMaterial,
);
/// Verifies a [keyMaterial] against an [expected] value.
Future<bool> verify(
Uint8List keyMaterial,
Uint8List expected,
);
}

View File

@ -0,0 +1,18 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
/// {@template random}
/// A [Random] is a source of random bytes.
/// {@endtemplate}
abstract class Random {
/// {@macro random}
const Random();
/// Generates a random [Uint8List] of [length] bytes.
Future<Uint8List> generate(int length);
}

View File

@ -1,22 +0,0 @@
// 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();
}

View File

@ -1,14 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: builder.dart
// Created Date: 28/12/2021 12:02:34
// Last Modified: 23/05/2022 22:38:44
// -----
// Copyright (c) 2021
// ignore_for_file: one_member_abstracts
abstract class Builder<T> {
Future<T> build();
}

View File

@ -1,53 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 26/05/2022 21:21:07
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
import 'package:native_crypto/src/utils/cipher_algorithm.dart';
/// Represents a cipher in NativeCrypto.
///
/// 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 {
static const int _bytesCountPerChunkDefault = 33554432;
static int _bytesCountPerChunk = _bytesCountPerChunkDefault;
/// Returns the default number of bytes per chunk.
static int get defaultBytesCountPerChunk => _bytesCountPerChunkDefault;
/// 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 for this [Cipher].
CipherAlgorithm get algorithm;
/// Encrypts the [data].
///
/// Takes [Uint8List] data as parameter.
/// Returns a [CipherTextWrapper].
Future<CipherTextWrapper> encrypt(Uint8List data);
/// Decrypts the [cipherText]
///
/// Takes [CipherTextWrapper] as parameter.
/// And returns plain text data as [Uint8List].
Future<Uint8List> decrypt(CipherTextWrapper cipherText);
}

View File

@ -1,14 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: interfaces.dart
// Created Date: 23/05/2022 23:03:47
// 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 'keyderivation.dart';

View File

@ -1,23 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: kdf.dart
// Created Date: 18/12/2021 11:56:43
// 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 (KDF) in NativeCrypto.
///
/// [KeyDerivation] function is a function that takes some
/// parameters and returns a [SecretKey].
abstract class KeyDerivation {
/// Returns the standard algorithm for this key derivation function
KdfAlgorithm get algorithm;
/// Derive a [SecretKey].
Future<SecretKey> derive();
}

View File

@ -1,10 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: kdf.dart
// Created Date: 23/05/2022 22:57:11
// Last Modified: 23/05/2022 23:04:15
// -----
// Copyright (c) 2022
export 'pbkdf2.dart';

View File

@ -1,115 +1,93 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: pbkdf2.dart
// Created Date: 17/12/2021 14:50:42
// Last Modified: 26/05/2022 23:19:46
// -----
// Copyright (c) 2021
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
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:flutter/foundation.dart';
import 'package:native_crypto/native_crypto.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;
final HashAlgorithm _hash;
/// {@template pbkdf2}
/// A PBKDF2 is a password-based key derivation function.
/// {@endtemplate}
class Pbkdf2 extends KeyDerivationFunction {
/// {@macro pbkdf2}
const Pbkdf2({
required this.hashAlgorithm,
required this.iterations,
required this.salt,
required this.length,
});
/// The [HashAlgorithm] used by this [Pbkdf2].
final HashAlgorithm hashAlgorithm;
/// The number of iterations.
final int iterations;
/// The salt.
final Uint8List salt;
/// The length of the derived key in bytes.
final int length;
@override
KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2;
Future<Uint8List> derive(Uint8List keyMaterial) async {
if (length == 0) {
// If the length is 0, return an empty list
return Uint8List(0);
}
Pbkdf2({
required int keyBytesCount,
required int iterations,
HashAlgorithm algorithm = HashAlgorithm.sha256,
}) : _keyBytesCount = keyBytesCount,
_iterations = iterations,
_hash = algorithm {
if (keyBytesCount < 0) {
throw NativeCryptoException(
message: 'keyBytesCount must be positive.',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
if (length < 0) {
throw ArgumentError.value(length, 'length', 'must be positive');
}
if (iterations <= 0) {
throw NativeCryptoException(
message: 'iterations must be strictly positive.',
code: NativeCryptoExceptionCode.invalid_argument.code,
throw ArgumentError.value(
iterations,
'iterations',
'must greater than 0',
);
}
// Call the platform interface to derive the key
final bytes = await platform.pbkdf2(
password: keyMaterial,
salt: salt,
iterations: iterations,
length: length,
hashAlgorithm: hashAlgorithm.name,
);
// TODO(hpcl): move these checks to the platform interface
if (bytes == null) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes',
);
}
if (bytes.length != length) {
throw NativeCryptoException(
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned bytes of wrong length: '
'expected $length, got ${bytes.length}',
);
}
return bytes;
}
@override
Future<SecretKey> derive({String? password, String? salt}) async {
Uint8List? derivation;
Future<bool> verify(Uint8List keyMaterial, Uint8List expected) =>
derive(keyMaterial).then((actual) {
if (actual.length != expected.length) {
return false;
}
return listEquals(actual, expected);
});
if (_keyBytesCount == 0) {
return SecretKey(Uint8List(0));
}
if (password.isNull) {
throw NativeCryptoException(
message: 'Password cannot be null.',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
if (salt.isNull) {
throw NativeCryptoException(
message: 'Salt cannot be null.',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
try {
derivation = await platform.pbkdf2(
password!,
salt!,
_keyBytesCount,
_iterations,
_hash.name,
);
} catch (e, s) {
throw NativeCryptoException(
message: '$e',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
);
}
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);
}
Future<SecretKey> call({required String password}) =>
derive(password.toBytes()).then(SecretKey.new);
}

View File

@ -1,10 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: keys.dart
// Created Date: 23/05/2022 23:04:04
// Last Modified: 23/05/2022 23:04:07
// -----
// Copyright (c) 2022
export 'secret_key.dart';

View File

@ -1,60 +1,43 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: secret_key.dart
// Created Date: 28/12/2021 13:36:54
// Last Modified: 26/05/2022 23:13:10
// -----
// Copyright (c) 2021
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
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';
import 'package:native_crypto/src/domain/base_key.dart';
import 'package:native_crypto/src/random/secure_random.dart';
/// {@template secret_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].
/// a symmetric Cipher.
/// {@endtemplate}
class SecretKey extends BaseKey {
/// {@macro secret_key}
const SecretKey(super.bytes);
/// Creates a [SecretKey] from a [List<int>].
SecretKey.fromList(super.list) : super.fromList();
/// Creates a [SecretKey] from a [String] encoded in UTF-8.
SecretKey.fromUtf8(super.encoded) : super.fromUtf8();
/// Creates a [SecretKey] from a [String] encoded in UTF-16.
SecretKey.fromUtf16(super.encoded) : super.fromUtf16();
/// Creates a [SecretKey] from a [String] encoded in Hexadecimal.
SecretKey.fromBase16(super.encoded) : super.fromBase16();
/// Creates a [SecretKey] from a [String] encoded in Base64.
SecretKey.fromBase64(super.encoded) : super.fromBase64();
SecretKey.fromUtf8(super.input) : super.fromUtf8();
static Future<SecretKey> fromSecureRandom(int bitsCount) async {
Uint8List? key;
if (bitsCount == 0) {
return SecretKey(Uint8List(0));
}
/// Generates a random [SecretKey] of the given [length] in bytes.
static Future<SecretKey> fromSecureRandom(int length) async {
const random = SecureRandom();
final bytes = await random.generate(length);
try {
key = await platform.generateSecretKey(bitsCount);
} catch (e, s) {
throw NativeCryptoException(
message: '$e',
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);
return SecretKey(bytes);
}
}

View File

@ -1,12 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: platform.dart
// Created Date: 27/12/2021 22:03:58
// Last Modified: 25/05/2022 10:09:18
// -----
// Copyright (c) 2021
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
NativeCryptoPlatform platform = NativeCryptoPlatform.instance;

View File

@ -0,0 +1,50 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto/src/core/utils/platform.dart';
import 'package:native_crypto/src/domain/random.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
/// {@template secure_random}
/// A [SecureRandom] is a source of secure random bytes.
/// {@endtemplate}
class SecureRandom extends Random {
/// {@macro secure_random}
const SecureRandom();
@override
Future<Uint8List> generate(int length) async {
if (length < 0) {
throw ArgumentError.value(length, 'length', 'must be positive');
}
if (length == 0) {
// If the length is 0, return an empty list
return Uint8List(0);
}
// Call the platform interface to generate the secure random bytes
final bytes = await platform.generateSecureRandom(length);
// TODO(hpcl): move these checks to the platform interface
if (bytes == null) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes',
);
}
if (bytes.length != length) {
throw NativeCryptoException(
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned bytes of wrong length: '
'expected $length, got ${bytes.length}',
);
}
return bytes;
}
}

View File

@ -1,11 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_algorithm.dart
// Created Date: 23/05/2022 22:07:54
// Last Modified: 26/05/2022 18:52:32
// -----
// Copyright (c) 2022
/// Represents different cipher algorithms
enum CipherAlgorithm { aes }

View File

@ -1,10 +0,0 @@
// 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

@ -1,101 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: extensions.dart
// Created Date: 26/05/2022 12:12:48
// Last Modified: 27/05/2022 12:26:55
// -----
// 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 combine() {
if (isEmpty) return Uint8List(0);
return reduce((value, element) => value.plus(element));
}
}
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 plus(final Uint8List other) => [...this, ...other].toTypedList();
/// Returns a sublist of this from the [start] index to the [end] index.
/// If [end] is greater than the length of the list, it is set to the length
Uint8List trySublist(int start, [int? end]) {
if (isEmpty) return this;
int ending = end ?? length;
if (ending > length) ending = length;
return sublist(start, ending);
}
}

View File

@ -1,51 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: hash_algorithm.dart
// Created Date: 23/05/2022 22:01:59
// Last Modified: 26/05/2022 22:59:04
// -----
// Copyright (c) 2022
import 'dart:typed_data';
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';
/// Defines the hash algorithms.
enum HashAlgorithm {
sha256,
sha384,
sha512;
/// Digest the [data] using this [HashAlgorithm].
Future<Uint8List> digest(Uint8List data) async {
Uint8List? hash;
try {
hash = await platform.digest(data, name);
} catch (e, s) {
throw NativeCryptoException(
message: '$e',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
);
}
if (hash.isNull) {
throw NativeCryptoException(
message: 'Failed to digest data! Platform returned null.',
code: NativeCryptoExceptionCode.platform_returned_null.code,
);
}
if (hash!.isEmpty) {
throw NativeCryptoException(
message: 'Failed to digest data! Platform returned no data.',
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
);
}
return hash;
}
}

View File

@ -1,11 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: kdf_algorithm.dart
// Created Date: 23/05/2022 22:36:24
// Last Modified: 26/05/2022 18:53:50
// -----
// Copyright (c) 2022
/// Represents different key derivation functions
enum KdfAlgorithm { pbkdf2 }

View File

@ -2,22 +2,21 @@ name: native_crypto
description: Fast and secure cryptography for Flutter.
version: 0.1.1
publish_to: 'none'
publish_to: "none"
environment:
sdk: ">=2.17.0 <3.0.0"
flutter: ">=2.5.0"
dependencies:
flutter:
sdk: flutter
flutter: { sdk: flutter }
native_crypto_android:
git:
url: https://github.com/hugo-pcl/native-crypto-flutter.git
ref: native_crypto_android-v0.1.1
path: packages/native_crypto_android
native_crypto_ios:
git:
url: https://github.com/hugo-pcl/native-crypto-flutter.git
@ -29,19 +28,19 @@ dependencies:
url: https://github.com/hugo-pcl/native-crypto-flutter.git
ref: native_crypto_platform_interface-v0.1.1
path: packages/native_crypto_platform_interface
equatable: ^2.0.5
dev_dependencies:
flutter_test:
sdk: flutter
flutter_test: { sdk: flutter }
mockito: ^5.2.0
plugin_platform_interface: ^2.1.2
mockito: ^5.3.2
plugin_platform_interface: ^2.1.3
wyatt_analysis:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_analysis-v2.1.0
path: packages/wyatt_analysis
hosted:
url: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
name: wyatt_analysis
version: 2.4.0
flutter:
plugin:
@ -49,4 +48,4 @@ flutter:
android:
default_package: native_crypto_android
ios:
default_package: native_crypto_ios
default_package: native_crypto_ios

View File

@ -0,0 +1,8 @@
# melos_managed_dependency_overrides: native_crypto_android,native_crypto_ios,native_crypto_platform_interface,native_crypto_web
dependency_overrides:
native_crypto_android:
path: ../native_crypto_android
native_crypto_ios:
path: ../native_crypto_ios
native_crypto_platform_interface:
path: ../native_crypto_platform_interface

View File

@ -0,0 +1,193 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
class MockNativeCryptoAPI implements NativeCryptoAPI {
static Uint8List? Function(int length)? generateSecureRandomFn;
static Uint8List? Function(
Uint8List data,
String algorithm,
)? hashFn;
static Uint8List? Function(
Uint8List data,
Uint8List key,
String algorithm,
)? hmacFn;
static Uint8List? Function(
Uint8List cipherText,
Uint8List key,
String algorithm,
)? decryptFn;
static bool? Function(
String cipherTextPath,
String plainTextPath,
Uint8List key,
String algorithm,
)? decryptFileFn;
static Uint8List? Function(
Uint8List plainText,
Uint8List key,
String algorithm,
)? encryptFn;
static bool? Function(
String plainTextPath,
String cipherTextPath,
Uint8List key,
String algorithm,
)? encryptFileFn;
static Uint8List? Function(
Uint8List plainText,
Uint8List key,
Uint8List iv,
String algorithm,
)? encryptWithIVFn;
static Uint8List? Function(
Uint8List password,
Uint8List salt,
int iterations,
int length,
String algorithm,
)? pbkdf2Fn;
@override
Future<DecryptResponse> decrypt(DecryptRequest argRequest) async =>
decryptFn != null
? DecryptResponse(
plainText: decryptFn!(
argRequest.cipherText!,
argRequest.key!,
argRequest.algorithm!,
),
)
: DecryptResponse(
plainText: Uint8List.fromList([1, 2, 3]),
);
@override
Future<DecryptFileResponse> decryptFile(
DecryptFileRequest argRequest,
) async =>
decryptFileFn != null
? DecryptFileResponse(
success: decryptFileFn!(
argRequest.cipherTextPath!,
argRequest.plainTextPath!,
argRequest.key!,
argRequest.algorithm!,
),
)
: DecryptFileResponse(success: true);
@override
Future<EncryptResponse> encrypt(EncryptRequest argRequest) async =>
encryptFn != null
? EncryptResponse(
cipherText: encryptFn!(
argRequest.plainText!,
argRequest.key!,
argRequest.algorithm!,
),
)
: EncryptResponse(
cipherText: Uint8List.fromList([1, 2, 3]),
);
@override
Future<EncryptFileResponse> encryptFile(
EncryptFileRequest argRequest,
) async =>
encryptFileFn != null
? EncryptFileResponse(
success: encryptFileFn!(
argRequest.plainTextPath!,
argRequest.cipherTextPath!,
argRequest.key!,
argRequest.algorithm!,
),
)
: EncryptFileResponse(success: true);
@override
Future<EncryptResponse> encryptWithIV(
EncryptWithIVRequest argRequest,
) async =>
encryptWithIVFn != null
? EncryptResponse(
cipherText: encryptWithIVFn!(
argRequest.plainText!,
argRequest.key!,
argRequest.iv!,
argRequest.algorithm!,
),
)
: EncryptResponse(
cipherText: Uint8List.fromList([1, 2, 3]),
);
@override
Future<GenerateSecureRandomResponse> generateSecureRandom(
GenerateSecureRandomRequest argRequest,
) async =>
generateSecureRandomFn != null
? GenerateSecureRandomResponse(
random: generateSecureRandomFn!(argRequest.length!),
)
: GenerateSecureRandomResponse(
random: Uint8List.fromList([1, 2, 3]),
);
@override
Future<HashResponse> hash(HashRequest argRequest) async => hashFn != null
? HashResponse(
hash: hashFn!(
argRequest.data!,
argRequest.algorithm!,
),
)
: HashResponse(
hash: Uint8List.fromList([1, 2, 3]),
);
@override
Future<HmacResponse> hmac(HmacRequest argRequest) async => hmacFn != null
? HmacResponse(
hmac: hmacFn!(
argRequest.data!,
argRequest.key!,
argRequest.algorithm!,
),
)
: HmacResponse(
hmac: Uint8List.fromList([1, 2, 3]),
);
@override
Future<Pbkdf2Response> pbkdf2(Pbkdf2Request argRequest) async =>
pbkdf2Fn != null
? Pbkdf2Response(
key: pbkdf2Fn!(
argRequest.password!,
argRequest.salt!,
argRequest.iterations!,
argRequest.length!,
argRequest.hashAlgorithm!,
),
)
: Pbkdf2Response(
key: Uint8List.fromList([1, 2, 3]),
);
}

View File

@ -1,192 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: mock_native_crypto_platform.dart
// Created Date: 25/05/2022 23:34:34
// Last Modified: 26/05/2022 11:40:24
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockNativeCryptoPlatform extends Fake
with MockPlatformInterfaceMixin
implements NativeCryptoPlatform {
Uint8List? data;
List<Uint8List>? dataAsList;
Uint8List? key;
String? algorithm;
int? bitsCount;
String? password;
String? salt;
int? keyBytesCount;
int? iterations;
Uint8List? Function()? response;
List<Uint8List>? Function()? responseAsList;
// ignore: use_setters_to_change_properties
void setResponse(Uint8List? Function()? response) {
this.response = response;
}
// ignore: use_setters_to_change_properties
void setResponseAsList(List<Uint8List>? Function()? responseAsList) {
this.responseAsList = responseAsList;
}
void setDecryptExpectations({
required Uint8List data,
required Uint8List key,
required String algorithm,
}) {
this.data = data;
this.key = key;
this.algorithm = algorithm;
}
@override
Future<Uint8List?> decrypt(
Uint8List data,
Uint8List key,
String algorithm,
) async {
expect(data, this.data);
expect(key, this.key);
expect(algorithm, this.algorithm);
return response?.call();
}
void setDecryptAsListExpectations({
required List<Uint8List> data,
required Uint8List key,
required String algorithm,
}) {
dataAsList = data;
this.key = key;
this.algorithm = algorithm;
}
@override
Future<Uint8List?> decryptAsList(
List<Uint8List> data,
Uint8List key,
String algorithm,
) async {
expect(data, dataAsList);
expect(key, this.key);
expect(algorithm, this.algorithm);
return response?.call();
}
void setDigestExpectations({
required Uint8List data,
required String algorithm,
}) {
this.data = data;
this.algorithm = algorithm;
}
@override
Future<Uint8List?> digest(Uint8List data, String algorithm) async {
expect(data, this.data);
expect(algorithm, this.algorithm);
return response?.call();
}
void setEncryptExpectations({
required Uint8List data,
required Uint8List key,
required String algorithm,
}) {
this.data = data;
this.key = key;
this.algorithm = algorithm;
}
@override
Future<Uint8List?> encrypt(
Uint8List data,
Uint8List key,
String algorithm,
) async {
expect(data, this.data);
expect(key, this.key);
expect(algorithm, this.algorithm);
return response?.call();
}
void setEncryptAsListExpectations({
required Uint8List data,
required Uint8List key,
required String algorithm,
}) =>
setEncryptExpectations(
data: data,
key: key,
algorithm: algorithm,
);
@override
Future<List<Uint8List>?> encryptAsList(
Uint8List data,
Uint8List key,
String algorithm,
) async {
expect(data, this.data);
expect(key, this.key);
expect(algorithm, this.algorithm);
return responseAsList?.call();
}
// ignore: use_setters_to_change_properties
void setGenerateKeyExpectations({required int bitsCount}) {
this.bitsCount = bitsCount;
}
@override
Future<Uint8List?> generateSecretKey(int bitsCount) async {
expect(bitsCount, this.bitsCount);
return response?.call();
}
void setPbkdf2Expectations({
required String password,
required String salt,
required int keyBytesCount,
required int iterations,
required String algorithm,
}) {
this.password = password;
this.salt = salt;
this.iterations = iterations;
this.keyBytesCount = keyBytesCount;
this.algorithm = algorithm;
}
@override
Future<Uint8List?> pbkdf2(
String password,
String salt,
int keyBytesCount,
int iterations,
String algorithm,
) async {
expect(password, this.password);
expect(salt, this.salt);
expect(keyBytesCount, this.keyBytesCount);
expect(iterations, this.iterations);
expect(algorithm, this.algorithm);
return response?.call();
}
}

View File

@ -1,323 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes_cipher_test.dart
// Created Date: 26/05/2022 23:20:53
// Last Modified: 27/05/2022 16:39:44
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
import '../mocks/mock_native_crypto_platform.dart';
void main() {
final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform();
NativeCryptoPlatform.instance = mock;
setUp(() {
Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk;
});
group('Constructor', () {
test('throws on invalid key length', () {
expect(
() => AES(SecretKey(Uint8List(0))),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_key_length',
)
.having(
(e) => e.message,
'message',
contains('Invalid key'),
),
),
);
});
test('creates a valid instance', () {
expect(
AES(
SecretKey(Uint8List(16)),
),
isA<AES>(),
);
});
});
group('encrypt', () {
test('returns a valid cipher text wrapper', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(16 + 28));
final aes = AES(SecretKey(Uint8List(16)));
expect(
await aes.encrypt(Uint8List(16)),
isA<CipherTextWrapper>().having((e) => e.isSingle, 'is single', isTrue),
);
});
test('returns a valid cipher text with multiple chunks', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(16 + 28)); // Returns 1 encrypted chunk
Cipher.bytesCountPerChunk = 16;
final aes = AES(SecretKey(Uint8List(16)));
expect(
await aes.encrypt(Uint8List(16 * 3)),
isA<CipherTextWrapper>().having((e) => e.isList, 'is list', isTrue),
);
});
test('handles returning empty list', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(0));
final aes = AES(SecretKey(Uint8List(16)));
await expectLater(
() => aes.encrypt(Uint8List(16)),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_empty_data',
),
),
);
});
test('handles returning null', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => null);
final aes = AES(SecretKey(Uint8List(16)));
await expectLater(
() => aes.encrypt(Uint8List(16)),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
),
),
);
});
test('handles throwing PlatformException', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(
() => throw PlatformException(
code: 'native_crypto',
message: 'dummy error',
),
);
final aes = AES(SecretKey(Uint8List(16)));
await expectLater(
() => aes.encrypt(Uint8List(16)),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.message,
'message',
contains(
'PlatformException(native_crypto, dummy error, null, null)',
),
)
.having(
(e) => e.code,
'code',
'platform_throws',
),
),
);
});
});
group('decrypt', () {
test('returns a valid Uint8List', () async {
mock
..setDecryptExpectations(
data: Uint8List(16 + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(16));
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List(16 + 28);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
expect(
await aes.decrypt(wrapper),
isA<Uint8List>().having((e) => e.length, 'length', 16),
);
});
test('returns a valid Uint8List on decrypting multiple chunks', () async {
const int chunkSize = 8;
mock
..setDecryptExpectations(
data: Uint8List(chunkSize + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(chunkSize));
Cipher.bytesCountPerChunk = chunkSize;
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List((chunkSize + 28) * 3);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
expect(
await aes.decrypt(wrapper),
isA<Uint8List>().having((e) => e.length, 'length', chunkSize * 3),
);
});
test('handles returning empty list', () async {
mock
..setDecryptExpectations(
data: Uint8List(16 + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(0));
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List(16 + 28);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
await expectLater(
() => aes.decrypt(wrapper),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_empty_data',
),
),
);
});
test('handles returning null', () async {
mock
..setDecryptExpectations(
data: Uint8List(16 + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => null);
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List(16 + 28);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
await expectLater(
() => aes.decrypt(wrapper),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
),
),
);
});
test('handles throwing PlatformException', () async {
mock
..setDecryptExpectations(
data: Uint8List(16 + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(
() => throw PlatformException(
code: 'native_crypto',
message: 'dummy error',
),
);
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List(16 + 28);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
await expectLater(
() => aes.decrypt(wrapper),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.message,
'message',
contains(
'PlatformException(native_crypto, dummy error, null, null)',
),
)
.having(
(e) => e.code,
'code',
'platform_throws',
),
),
);
});
});
}

View File

@ -1,192 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_text_test.dart
// Created Date: 26/05/2022 20:45:38
// Last Modified: 26/05/2022 21:29:51
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/native_crypto.dart';
void main() {
setUp(() {
Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk;
});
group('fromBytes', () {
test('throws if length is not the one expected', () {
final Uint8List bytes = Uint8List.fromList([1, 2, 3, 4, 5]);
expect(
() => CipherText.fromBytes(
bytes,
ivLength: 1,
messageLength: 1,
tagLength: 1,
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('Invalid cipher text length'),
),
),
);
});
test('throws if length is bigger than expected', () {
final Uint8List bytes = Uint8List.fromList([1, 3, 3, 3, 1]);
Cipher.bytesCountPerChunk = 2;
expect(
() => CipherText.fromBytes(
bytes,
ivLength: 1,
messageLength: 3,
tagLength: 1,
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('Cipher text is too big'),
),
),
);
});
test('throws if data is empty', () {
final Uint8List bytes = Uint8List(0);
expect(
() => CipherText.fromBytes(
bytes,
ivLength: 1,
messageLength: 3,
tagLength: 1,
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('Passed data is empty'),
),
),
);
});
test('throws if one of the length is negative', () {
final Uint8List bytes = Uint8List(0);
expect(
() => CipherText.fromBytes(
bytes,
ivLength: -1,
messageLength: 1,
tagLength: 1,
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('Invalid length'),
),
),
);
});
});
group('get.cipherAlgorithm', () {
test('throws if not set', () {
final CipherText cipherText = CipherText.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
messageLength: 1,
tagLength: 1,
);
expect(
() => cipherText.cipherAlgorithm,
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_cipher',
)
.having(
(e) => e.message,
'message',
contains('Cipher algorithm is not specified'),
),
),
);
});
test('returns the expected value', () {
final CipherText cipherText = CipherText.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
messageLength: 1,
tagLength: 1,
cipherAlgorithm: CipherAlgorithm.aes,
);
expect(cipherText.cipherAlgorithm, CipherAlgorithm.aes);
});
});
group('Lengths', () {
test('get.ivLength returns the expected value', () {
final CipherText cipherText = CipherText.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
messageLength: 1,
tagLength: 1,
);
expect(cipherText.ivLength, 1);
});
test('get.messageLength returns the expected value', () {
final CipherText cipherText = CipherText.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
messageLength: 1,
tagLength: 1,
);
expect(cipherText.messageLength, 1);
});
test('get.tagLength returns the expected value', () {
final CipherText cipherText = CipherText.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
messageLength: 1,
tagLength: 1,
);
expect(cipherText.tagLength, 1);
});
});
}

View File

@ -1,349 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_text_wrapper_test.dart
// Created Date: 26/05/2022 21:35:41
// Last Modified: 27/05/2022 13:46:54
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/native_crypto.dart';
void main() {
late CipherText single;
late List<CipherText> list;
setUp(() {
Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk;
single = CipherText.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
messageLength: 1,
tagLength: 1,
);
list = [
CipherText.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
messageLength: 1,
tagLength: 1,
),
CipherText.fromBytes(
Uint8List.fromList([4, 5, 6]),
ivLength: 1,
messageLength: 1,
tagLength: 1,
),
];
});
group('single', () {
test('makes isSingle true', () {
final wrapper = CipherTextWrapper.single(single);
expect(wrapper.isSingle, isTrue);
});
test('makes isList false', () {
final wrapper = CipherTextWrapper.single(single);
expect(wrapper.isList, isFalse);
});
test('makes CipherText the single value', () {
final wrapper = CipherTextWrapper.single(single);
expect(wrapper.single, single);
});
test('throws when trying to get list', () {
final wrapper = CipherTextWrapper.single(single);
expect(
() => wrapper.list,
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_data',
)
.having(
(e) => e.message,
'message',
contains('is not list'),
),
),
);
});
test('makes wrapper returns bytes of CipherText', () {
final wrapper = CipherTextWrapper.single(single);
expect(wrapper.bytes, single.bytes);
});
test('makes chunkCount = 1', () {
final wrapper = CipherTextWrapper.single(single);
expect(wrapper.chunkCount, 1);
});
test('makes unwrap() returns only CipherText', () {
final wrapper = CipherTextWrapper.single(single);
expect(wrapper.unwrap<CipherText>(), single);
});
test('makes unwrap() throws when trying to unwrap List', () {
final wrapper = CipherTextWrapper.single(single);
expect(
() => wrapper.unwrap<List<CipherText>>(),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_data',
)
.having(
(e) => e.message,
'message',
contains('you should use unwrap'),
),
),
);
});
test('makes adding is not supported', () {
final wrapper = CipherTextWrapper.single(single);
expect(
() => wrapper.add(single),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_data',
)
.having(
(e) => e.message,
'message',
contains('is already single'),
),
),
);
});
});
group('list', () {
test('makes isList true', () {
final wrapper = CipherTextWrapper.list(list);
expect(wrapper.isList, isTrue);
});
test('makes isSingle false', () {
final wrapper = CipherTextWrapper.list(list);
expect(wrapper.isSingle, isFalse);
});
test('makes List<CipherText> the list value', () {
final wrapper = CipherTextWrapper.list(list);
expect(wrapper.list, list);
});
test('throws when trying to get single', () {
final wrapper = CipherTextWrapper.list(list);
expect(
() => wrapper.single,
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_data',
)
.having(
(e) => e.message,
'message',
contains('is not single'),
),
),
);
});
test('makes wrapper returns bytes of all CipherText joined', () {
final wrapper = CipherTextWrapper.list(list);
expect(wrapper.bytes, Uint8List.fromList([1, 2, 3, 4, 5, 6]));
});
test('makes chunkCount = 2', () {
final wrapper = CipherTextWrapper.list(list);
expect(wrapper.chunkCount, 2);
});
test('makes unwrap() returns List<CipherText>', () {
final wrapper = CipherTextWrapper.list(list);
expect(wrapper.unwrap<List<CipherText>>(), list);
});
test('makes unwrap() throws when trying to unwrap single', () {
final wrapper = CipherTextWrapper.list(list);
expect(
() => wrapper.unwrap<CipherText>(),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_data',
)
.having(
(e) => e.message,
'message',
contains('you should use unwrap'),
),
),
);
});
test('makes adding is supported', () {
final originalList = List<CipherText>.from(list);
final wrapper = CipherTextWrapper.list(list)..add(single);
printOnFailure(list.length.toString());
expect(wrapper.list, [...originalList, single]);
});
});
group('empty', () {
test('makes isList true', () {
final wrapper = CipherTextWrapper.empty();
expect(wrapper.isList, isTrue);
});
test('makes isSingle false', () {
final wrapper = CipherTextWrapper.empty();
expect(wrapper.isSingle, isFalse);
});
test('makes List<CipherText> the list value', () {
final wrapper = CipherTextWrapper.empty();
expect(wrapper.list, <CipherText>[]);
});
test('throws when trying to get single', () {
final wrapper = CipherTextWrapper.empty();
expect(
() => wrapper.single,
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_data',
)
.having(
(e) => e.message,
'message',
contains('is not single'),
),
),
);
});
test('makes wrapper returns empty bytes', () {
final wrapper = CipherTextWrapper.empty();
expect(wrapper.bytes, Uint8List.fromList([]));
});
test('makes chunkCount = 0', () {
final wrapper = CipherTextWrapper.empty();
expect(wrapper.chunkCount, 0);
});
test('makes unwrap() returns empty List<CipherText>', () {
final wrapper = CipherTextWrapper.empty();
expect(wrapper.unwrap<List<CipherText>>(), <CipherText>[]);
});
test('makes unwrap() throws when trying to unwrap single', () {
final wrapper = CipherTextWrapper.empty();
expect(
() => wrapper.unwrap<CipherText>(),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_data',
)
.having(
(e) => e.message,
'message',
contains('you should use unwrap'),
),
),
);
});
test('makes adding is supported', () {
final wrapper = CipherTextWrapper.empty()..add(single);
expect(wrapper.list, [single]);
});
});
group('fromBytes', () {
test('creates single from bytes when no too big', () {
final wrapper = CipherTextWrapper.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
tagLength: 1,
);
expect(wrapper.isSingle, isTrue);
expect(wrapper.single, single);
});
test('creates list from bytes when too big', () {
Cipher.bytesCountPerChunk = 1;
final wrapper = CipherTextWrapper.fromBytes(
Uint8List.fromList([1, 2, 3, 4, 5, 6]),
ivLength: 1,
tagLength: 1,
);
expect(wrapper.isList, isTrue);
expect(wrapper.list, list);
});
test('modifies Cipher.bytesCountPerChunk', () {
expect(Cipher.bytesCountPerChunk, Cipher.defaultBytesCountPerChunk);
CipherTextWrapper.fromBytes(
Uint8List.fromList([1, 2, 3]),
ivLength: 1,
tagLength: 1,
chunkSize: 3,
);
expect(Cipher.bytesCountPerChunk, 3);
});
test('throws if trying to build list with bad parameters', () {
Cipher.bytesCountPerChunk = 1; // length of a message
expect(
() => CipherTextWrapper.fromBytes(
Uint8List.fromList([1, 2, 3, 4, 5, 6]),
ivLength: 2,
tagLength: 1,
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('on chunk #'),
),
),
);
});
});
}

View File

@ -0,0 +1,116 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
import '../mocks/mock_native_crypto_api.dart';
void main() {
setUp(() {
// Mock the platform interface API
NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto(
api: MockNativeCryptoAPI(),
);
});
group('Hash', () {
test('$Sha256 digest correctly', () async {
final hash = await Sha256().digest('abc'.toBytes());
expect(hash, isNotNull);
expect(
hash,
Uint8List.fromList([1, 2, 3]),
);
});
test('$Sha256 digest throws if platform returns null', () async {
MockNativeCryptoAPI.hashFn = (input, algorithm) => null;
expect(
() async => Sha256().digest('abc'.toBytes()),
throwsA(isA<NativeCryptoException>()),
);
});
test('$Sha256 digest throws if platform returns invalid data', () async {
MockNativeCryptoAPI.hashFn = (input, algorithm) => Uint8List(0);
expect(
() async => Sha256().digest('abcd'.toBytes()),
throwsA(isA<NativeCryptoException>()),
);
});
test('$Sha256 returns correct $HashAlgorithm', () async {
final hash = Sha256();
expect(hash.algorithm, HashAlgorithm.sha256);
});
test('$Sha384 returns correct $HashAlgorithm', () async {
final hash = Sha384();
expect(hash.algorithm, HashAlgorithm.sha384);
});
test('$Sha512 returns correct $HashAlgorithm', () async {
final hash = Sha512();
expect(hash.algorithm, HashAlgorithm.sha512);
});
});
group('Hmac', () {
test('$HmacSha256 digest correctly', () async {
final hash = await HmacSha256()
.digest('abc'.toBytes(), SecretKey.fromUtf16('key'));
expect(hash, isNotNull);
expect(
hash,
Uint8List.fromList([1, 2, 3]),
);
});
test('$HmacSha256 digest throws if platform returns null', () async {
MockNativeCryptoAPI.hmacFn = (input, key, algorithm) => null;
expect(
() async =>
HmacSha256().digest('abc'.toBytes(), SecretKey.fromUtf16('key')),
throwsA(isA<NativeCryptoException>()),
);
});
test('$HmacSha256 digest throws if platform returns invalid data',
() async {
MockNativeCryptoAPI.hmacFn = (input, key, algorithm) => Uint8List(0);
expect(
() async =>
HmacSha256().digest('abc'.toBytes(), SecretKey.fromUtf16('key')),
throwsA(isA<NativeCryptoException>()),
);
});
test('$HmacSha256 returns correct $HashAlgorithm', () async {
final hash = HmacSha256();
expect(hash.algorithm, HashAlgorithm.sha256);
});
test('$HmacSha384 returns correct $HashAlgorithm', () async {
final hash = HmacSha384();
expect(hash.algorithm, HashAlgorithm.sha384);
});
test('$HmacSha512 returns correct $HashAlgorithm', () async {
final hash = HmacSha512();
expect(hash.algorithm, HashAlgorithm.sha512);
});
});
}

View File

@ -1,128 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: hash_algorithm_test.dart
// Created Date: 26/05/2022 22:28:53
// Last Modified: 26/05/2022 23:03:03
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/src/utils/hash_algorithm.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
import '../mocks/mock_native_crypto_platform.dart';
void main() {
final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform();
NativeCryptoPlatform.instance = mock;
group('name', () {
test('is sha256 for HashAlgorithm.sha256', () {
expect(HashAlgorithm.sha256.name, 'sha256');
});
test('is sha384 for HashAlgorithm.sha384', () {
expect(HashAlgorithm.sha384.name, 'sha384');
});
test('is sha512 for HashAlgorithm.sha512', () {
expect(HashAlgorithm.sha512.name, 'sha512');
});
});
group('digest', () {
test('handles returning empty list', () async {
mock
..setDigestExpectations(
data: Uint8List.fromList([1, 2, 3]),
algorithm: 'sha256',
)
..setResponse(() => Uint8List(0));
await expectLater(
() => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_empty_data',
),
),
);
});
test('handles returning null', () async {
mock
..setDigestExpectations(
data: Uint8List.fromList([1, 2, 3]),
algorithm: 'sha256',
)
..setResponse(() => null);
await expectLater(
() => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
),
),
);
});
test('handles throwing PlatformException', () async {
mock
..setDigestExpectations(
data: Uint8List.fromList([1, 2, 3]),
algorithm: 'sha256',
)
..setResponse(
() => throw PlatformException(
code: 'native_crypto',
message: 'dummy error',
),
);
await expectLater(
() => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.message,
'message',
'PlatformException(native_crypto, dummy error, null, null)',
)
.having(
(e) => e.code,
'code',
'platform_throws',
),
),
);
});
test('returns data on success', () async {
final hash = Uint8List.fromList([4, 5, 6]);
mock
..setDigestExpectations(
data: Uint8List.fromList([1, 2, 3]),
algorithm: 'sha256',
)
..setResponse(() => hash);
final result = await HashAlgorithm.sha256.digest(
Uint8List.fromList(
[1, 2, 3],
),
);
expect(
result,
hash,
);
});
});
}

View File

@ -1,280 +1,155 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: pbkdf2_test.dart
// Created Date: 26/05/2022 22:37:27
// Last Modified: 26/05/2022 23:20:11
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
import '../mocks/mock_native_crypto_platform.dart';
import '../mocks/mock_native_crypto_api.dart';
void main() {
final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform();
NativeCryptoPlatform.instance = mock;
setUp(() {
// Mock the platform interface API
NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto(
api: MockNativeCryptoAPI(),
);
group('Constructor', () {
test('throws if keyBytesCount is negative', () {
expect(
() => Pbkdf2(keyBytesCount: -1, iterations: 10000),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('must be positive'),
),
),
);
});
test('throws if iterations is negative or 0', () {
expect(
() => Pbkdf2(keyBytesCount: 32, iterations: -1),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('must be strictly positive'),
),
),
);
});
MockNativeCryptoAPI.pbkdf2Fn = null;
});
group('derive', () {
test('throws if password is null', () async {
final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000);
await expectLater(
() => pbkdf2.derive(
salt: 'salt',
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('cannot be null'),
),
),
);
});
test('throws if salt is null', () async {
final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000);
await expectLater(
() => pbkdf2.derive(
password: 'password',
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('cannot be null'),
),
),
);
});
test('handles returning empty list', () async {
mock
..setPbkdf2Expectations(
password: 'password',
salt: 'salt',
keyBytesCount: 32,
iterations: 10000,
algorithm: 'sha256',
)
..setResponse(() => Uint8List(0));
final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000);
await expectLater(
() => pbkdf2.derive(
password: 'password',
salt: 'salt',
),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_empty_data',
),
),
);
});
test('handles returning null', () async {
mock
..setPbkdf2Expectations(
password: 'password',
salt: 'salt',
keyBytesCount: 32,
iterations: 10000,
algorithm: 'sha256',
)
..setResponse(() => null);
final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000);
await expectLater(
() => pbkdf2.derive(
password: 'password',
salt: 'salt',
),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
),
),
);
});
test('handles returning data with wrong length', () async {
mock
..setPbkdf2Expectations(
password: 'password',
salt: 'salt',
keyBytesCount: 32,
iterations: 10000,
algorithm: 'sha256',
)
..setResponse(() => Uint8List(33));
final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000);
await expectLater(
() => pbkdf2.derive(
password: 'password',
salt: 'salt',
),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_invalid_data',
),
),
);
});
test('handles throwing PlatformException', () async {
mock
..setPbkdf2Expectations(
password: 'password',
salt: 'salt',
keyBytesCount: 32,
iterations: 10000,
algorithm: 'sha256',
)
..setResponse(
() => throw PlatformException(
code: 'native_crypto',
message: 'dummy error',
),
);
final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000);
await expectLater(
() => pbkdf2.derive(
password: 'password',
salt: 'salt',
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.message,
'message',
'PlatformException(native_crypto, dummy error, null, null)',
)
.having(
(e) => e.code,
'code',
'platform_throws',
),
),
);
});
test('returns SecretKey on success', () async {
final data = Uint8List.fromList([1, 2, 3, 4, 5, 6]);
final sk = SecretKey(data);
mock
..setPbkdf2Expectations(
password: 'password',
salt: 'salt',
keyBytesCount: 6,
iterations: 10000,
algorithm: 'sha256',
)
..setResponse(() => data);
final pbkdf = Pbkdf2(keyBytesCount: 6, iterations: 10000);
final result = await pbkdf.derive(
password: 'password',
salt: 'salt',
group('$Pbkdf2', () {
test('derive key correctly', () async {
final key = await Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 1,
length: 3,
hashAlgorithm: HashAlgorithm.sha256,
).derive(
Uint8List.fromList([1, 2, 3]),
);
expect(key, isNotNull);
expect(key.length, 3);
expect(
result,
sk,
key,
Uint8List.fromList([1, 2, 3]),
);
});
test('return empty SecretKey when keyBytesCount is set to 0', () async {
final sk = SecretKey(Uint8List(0));
mock
..setPbkdf2Expectations(
password: 'password',
salt: 'salt',
keyBytesCount: 0,
iterations: 10000,
algorithm: 'sha256',
)
..setResponse(() => Uint8List(0));
final pbkdf = Pbkdf2(keyBytesCount: 0, iterations: 10000);
final result = await pbkdf.derive(
password: 'password',
salt: 'salt',
);
test('derive key with invalid length throws', () async {
expect(
result,
sk,
() => Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 1,
length: -1,
hashAlgorithm: HashAlgorithm.sha256,
).derive(
Uint8List.fromList([1, 2, 3]),
),
throwsA(isA<ArgumentError>()),
);
});
test('derive key with invalid iterations throws', () async {
expect(
() => Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 0,
length: 3,
hashAlgorithm: HashAlgorithm.sha256,
).derive(
Uint8List.fromList([1, 2, 3]),
),
throwsA(isA<ArgumentError>()),
);
});
test('derive key with 0 length returns empty list', () async {
final key = await Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 1,
length: 0,
hashAlgorithm: HashAlgorithm.sha256,
).derive(
Uint8List.fromList([1, 2, 3]),
);
expect(key, isNotNull);
expect(key.length, 0);
});
test('derive key throws if platform returns null', () async {
MockNativeCryptoAPI.pbkdf2Fn =
(password, salt, iterations, length, hashAlgorithm) => null;
expect(
() => Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 1,
length: 3,
hashAlgorithm: HashAlgorithm.sha256,
).derive(
Uint8List.fromList([1, 2, 3]),
),
throwsA(isA<NativeCryptoException>()),
);
});
test('derive key throws if platform returns invalid data', () async {
expect(
() async => Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 1,
length: 4,
hashAlgorithm: HashAlgorithm.sha256,
).derive(
Uint8List.fromList([1, 2, 3]),
),
throwsA(isA<NativeCryptoException>()),
);
});
test('call returns $SecretKey', () async {
final pbkdf = Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 1,
length: 3,
hashAlgorithm: HashAlgorithm.sha256,
);
final key = await pbkdf(password: 'password');
expect(key, isNotNull);
expect(key, isA<SecretKey>());
});
test('verify key returns true on the same password', () async {
final pbkdf = Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 1,
length: 3,
hashAlgorithm: HashAlgorithm.sha256,
);
final pwd = Uint8List.fromList([1, 2, 3]);
final key = await pbkdf.derive(pwd);
final sucess = await pbkdf.verify(pwd, key);
expect(sucess, true);
});
test('verify key returns true on the same password', () async {
final pbkdf = Pbkdf2(
salt: Uint8List.fromList([1, 2, 3]),
iterations: 1,
length: 3,
hashAlgorithm: HashAlgorithm.sha256,
);
final pwd = Uint8List.fromList([1, 2, 3]);
final key = Uint8List.fromList([1, 2, 3, 4, 5, 6]);
final sucess = await pbkdf.verify(pwd, key);
expect(sucess, false);
});
});
}

View File

@ -0,0 +1,63 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
import '../mocks/mock_native_crypto_api.dart';
void main() {
setUp(() {
// Mock the platform interface API
NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto(
api: MockNativeCryptoAPI(),
);
});
group('$SecureRandom', () {
test('generate random bytes correctly', () async {
final random = await const SecureRandom().generate(3);
expect(random, isNotNull);
expect(random.length, 3);
expect(
random,
Uint8List.fromList([1, 2, 3]),
);
});
test('generate random bytes with invalid length throws', () async {
expect(
() => const SecureRandom().generate(-1),
throwsA(isA<ArgumentError>()),
);
});
test('generate random bytes with 0 length returns empty list', () async {
final random = await const SecureRandom().generate(0);
expect(random, isNotNull);
expect(random.length, 0);
});
test('generate random bytes throws if platform returns null', () async {
MockNativeCryptoAPI.generateSecureRandomFn = (length) => null;
expect(
() async => const SecureRandom().generate(3),
throwsA(isA<NativeCryptoException>()),
);
});
test('generate random bytes throws if platform returns invalid data',
() async {
expect(
() async => const SecureRandom().generate(4),
throwsA(isA<NativeCryptoException>()),
);
});
});
}

View File

@ -1,125 +1,56 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: secret_key_test.dart
// Created Date: 26/05/2022 10:52:41
// Last Modified: 26/05/2022 22:38:07
// -----
// Copyright (c) 2022
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
import '../mocks/mock_native_crypto_platform.dart';
import '../mocks/mock_native_crypto_api.dart';
void main() {
final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform();
NativeCryptoPlatform.instance = mock;
setUp(() {
// Mock the platform interface API
NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto(
api: MockNativeCryptoAPI(),
);
});
group('Constructors', () {
test('handles Uint8List', () {
group('$SecretKey', () {
test('can be create from Uint8List', () {
final SecretKey key = SecretKey(Uint8List.fromList([1, 2, 3, 4, 5]));
expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5]));
});
test('handles base16', () {
test('can be create from base16', () {
final SecretKey key = SecretKey.fromBase16('0102030405');
expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5]));
});
test('handles base64', () {
test('can be create from base64', () {
final SecretKey key = SecretKey.fromBase64('AQIDBAU=');
expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5]));
});
test('handles utf8', () {
test('can be create from utf8', () {
final SecretKey key = SecretKey.fromUtf8('ABCDE');
expect(key.bytes, Uint8List.fromList([65, 66, 67, 68, 69]));
});
});
group('fromSecureRandom', () {
test('handles returning random bytes', () async {
mock
..setGenerateKeyExpectations(bitsCount: 5)
..setResponse(() => Uint8List.fromList([1, 2, 3, 4, 5]));
test('can be create from secure random', () async {
MockNativeCryptoAPI.generateSecureRandomFn =
(length) => Uint8List.fromList([1, 2, 3, 4, 5]);
final SecretKey key = await SecretKey.fromSecureRandom(5);
final SecretKey secretKey = await SecretKey.fromSecureRandom(5);
expect(
secretKey.bytes,
Uint8List.fromList([1, 2, 3, 4, 5]),
);
});
test('handles returning empty list', () async {
mock
..setGenerateKeyExpectations(bitsCount: 5)
..setResponse(() => Uint8List(0));
await expectLater(
() => SecretKey.fromSecureRandom(5),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_empty_data',
),
),
);
});
test('handles returning null', () async {
mock
..setGenerateKeyExpectations(bitsCount: 5)
..setResponse(() => null);
await expectLater(
() => SecretKey.fromSecureRandom(5),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
),
),
);
});
test('handles throwing PlatformException', () async {
mock
..setGenerateKeyExpectations(bitsCount: 5)
..setResponse(
() => throw PlatformException(
code: 'native_crypto',
message: 'dummy error',
),
);
await expectLater(
() => SecretKey.fromSecureRandom(5),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.message,
'message',
'PlatformException(native_crypto, dummy error, null, null)',
)
.having(
(e) => e.code,
'code',
'platform_throws',
),
),
);
expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5]));
});
});
}