feat(api)!: rework full api with better object oriented architecture
This commit is contained in:
parent
8044ccfa43
commit
c8ff1149d7
@ -1 +1 @@
|
||||
include: package:wyatt_analysis/analysis_options.flutter.experimental.yaml
|
||||
include: package:wyatt_analysis/analysis_options.flutter.yaml
|
@ -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';
|
||||
|
@ -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';
|
@ -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';
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
]);
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
13
packages/native_crypto/lib/src/core/constants/constants.dart
Normal file
13
packages/native_crypto/lib/src/core/constants/constants.dart
Normal 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;
|
||||
}
|
@ -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';
|
||||
|
17
packages/native_crypto/lib/src/core/enums/encoding.dart
Normal file
17
packages/native_crypto/lib/src/core/enums/encoding.dart
Normal 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,
|
||||
}
|
8
packages/native_crypto/lib/src/core/enums/enums.dart
Normal file
8
packages/native_crypto/lib/src/core/enums/enums.dart
Normal 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';
|
@ -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,
|
||||
}
|
@ -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';
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
12
packages/native_crypto/lib/src/core/utils/chunk_factory.dart
Normal file
12
packages/native_crypto/lib/src/core/utils/chunk_factory.dart
Normal 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);
|
47
packages/native_crypto/lib/src/core/utils/cipher_text.dart
Normal file
47
packages/native_crypto/lib/src/core/utils/cipher_text.dart
Normal 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;
|
||||
}
|
9
packages/native_crypto/lib/src/core/utils/platform.dart
Normal file
9
packages/native_crypto/lib/src/core/utils/platform.dart
Normal 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;
|
8
packages/native_crypto/lib/src/core/utils/utils.dart
Normal file
8
packages/native_crypto/lib/src/core/utils/utils.dart
Normal 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';
|
8
packages/native_crypto/lib/src/digest/digest.dart
Normal file
8
packages/native_crypto/lib/src/digest/digest.dart
Normal 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';
|
62
packages/native_crypto/lib/src/digest/hash.dart
Normal file
62
packages/native_crypto/lib/src/digest/hash.dart
Normal 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;
|
||||
}
|
67
packages/native_crypto/lib/src/digest/hmac.dart
Normal file
67
packages/native_crypto/lib/src/digest/hmac.dart
Normal 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;
|
||||
}
|
37
packages/native_crypto/lib/src/domain/base_key.dart
Normal file
37
packages/native_crypto/lib/src/domain/base_key.dart
Normal 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();
|
||||
}
|
@ -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];
|
||||
}
|
32
packages/native_crypto/lib/src/domain/cipher.dart
Normal file
32
packages/native_crypto/lib/src/domain/cipher.dart
Normal 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);
|
||||
}
|
31
packages/native_crypto/lib/src/domain/cipher_chunk.dart
Normal file
31
packages/native_crypto/lib/src/domain/cipher_chunk.dart
Normal 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();
|
||||
}
|
14
packages/native_crypto/lib/src/domain/domain.dart
Normal file
14
packages/native_crypto/lib/src/domain/domain.dart
Normal 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';
|
24
packages/native_crypto/lib/src/domain/hash.dart
Normal file
24
packages/native_crypto/lib/src/domain/hash.dart
Normal 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);
|
||||
}
|
25
packages/native_crypto/lib/src/domain/hmac.dart
Normal file
25
packages/native_crypto/lib/src/domain/hmac.dart
Normal 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);
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
18
packages/native_crypto/lib/src/domain/random.dart
Normal file
18
packages/native_crypto/lib/src/domain/random.dart
Normal 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);
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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';
|
@ -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();
|
||||
}
|
@ -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';
|
@ -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);
|
||||
}
|
||||
|
@ -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';
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
50
packages/native_crypto/lib/src/random/secure_random.dart
Normal file
50
packages/native_crypto/lib/src/random/secure_random.dart
Normal 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;
|
||||
}
|
||||
}
|
@ -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 }
|
@ -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 }
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 }
|
@ -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
|
||||
|
8
packages/native_crypto/pubspec_overrides.yaml
Normal file
8
packages/native_crypto/pubspec_overrides.yaml
Normal 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
|
193
packages/native_crypto/test/mocks/mock_native_crypto_api.dart
Normal file
193
packages/native_crypto/test/mocks/mock_native_crypto_api.dart
Normal 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]),
|
||||
);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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',
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
@ -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 #'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
116
packages/native_crypto/test/src/digest_test.dart
Normal file
116
packages/native_crypto/test/src/digest_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
63
packages/native_crypto/test/src/random_test.dart
Normal file
63
packages/native_crypto/test/src/random_test.dart
Normal 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>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -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]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user