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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: native_crypto.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 16/12/2021 16:28:00
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 12:10:42
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2021
|
|
||||||
|
|
||||||
/// Fast and powerful cryptographic functions
|
/// Fast and powerful cryptographic functions
|
||||||
/// thanks to javax.crypto, CommonCrypto and CryptoKit.
|
/// thanks to javax.crypto, CommonCrypto and CryptoKit.
|
||||||
@ -13,18 +10,13 @@
|
|||||||
/// Author: Hugo Pointcheval
|
/// Author: Hugo Pointcheval
|
||||||
library native_crypto;
|
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/builders/builders.dart';
|
||||||
export 'src/ciphers/ciphers.dart';
|
export 'src/ciphers/ciphers.dart';
|
||||||
export 'src/core/core.dart';
|
export 'src/core/core.dart';
|
||||||
export 'src/interfaces/interfaces.dart';
|
export 'src/digest/digest.dart';
|
||||||
export 'src/kdf/kdf.dart';
|
export 'src/domain/domain.dart';
|
||||||
export 'src/keys/keys.dart';
|
export 'src/kdf/pbkdf2.dart';
|
||||||
// Utils
|
export 'src/keys/secret_key.dart';
|
||||||
export 'src/utils/cipher_algorithm.dart';
|
export 'src/random/secure_random.dart';
|
||||||
export 'src/utils/hash_algorithm.dart';
|
|
||||||
export 'src/utils/kdf_algorithm.dart';
|
|
||||||
|
|
||||||
// ignore: constant_identifier_names
|
|
||||||
const String AUTHOR = 'Hugo Pointcheval';
|
|
||||||
|
@ -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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: builders.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 23/05/2022 22:56:03
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 19:22:19
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
export 'decryption_builder.dart';
|
export 'decryption_builder.dart';
|
||||||
|
export 'encryption_builder.dart';
|
||||||
|
@ -1,38 +1,53 @@
|
|||||||
// Author: Hugo Pointcheval
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: decryption_builder.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 26/05/2022 19:07:52
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 19:21:00
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
|
import 'package:native_crypto/src/core/utils/cipher_text.dart';
|
||||||
import 'package:native_crypto/src/interfaces/cipher.dart';
|
|
||||||
|
|
||||||
class DecryptionBuilder extends StatelessWidget {
|
import 'package:native_crypto/src/domain/cipher.dart';
|
||||||
final Cipher cipher;
|
import 'package:native_crypto/src/domain/cipher_chunk.dart';
|
||||||
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;
|
|
||||||
|
|
||||||
|
/// {@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({
|
const DecryptionBuilder({
|
||||||
super.key,
|
|
||||||
required this.cipher,
|
required this.cipher,
|
||||||
required this.data,
|
required this.cipherText,
|
||||||
required this.onLoading,
|
required this.onLoading,
|
||||||
required this.onError,
|
required this.onError,
|
||||||
required this.onSuccess,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) => FutureBuilder<Uint8List>(
|
||||||
return FutureBuilder<Uint8List>(
|
future: cipher.decrypt(cipherText),
|
||||||
future: cipher.decrypt(data),
|
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
return onSuccess(context, snapshot.data!);
|
return onSuccess(context, snapshot.data!);
|
||||||
@ -42,5 +57,4 @@ class DecryptionBuilder extends StatelessWidget {
|
|||||||
return onLoading(context);
|
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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: aes.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 16/12/2021 16:28:00
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 27/05/2022 12:13:28
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
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_key_size.dart';
|
||||||
import 'package:native_crypto/src/ciphers/aes/aes_mode.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/ciphers/aes/aes_padding.dart';
|
||||||
import 'package:native_crypto/src/core/cipher_text.dart';
|
import 'package:native_crypto/src/core/constants/constants.dart';
|
||||||
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
|
import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart';
|
||||||
import 'package:native_crypto/src/interfaces/cipher.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/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';
|
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
||||||
|
|
||||||
|
export 'aes_cipher_chunk.dart';
|
||||||
export 'aes_key_size.dart';
|
export 'aes_key_size.dart';
|
||||||
export 'aes_mode.dart';
|
export 'aes_mode.dart';
|
||||||
export 'aes_padding.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.
|
/// [AES] is a symmetric cipher which means that the same key is used to encrypt
|
||||||
class AES implements Cipher {
|
/// and decrypt the data.
|
||||||
final SecretKey _key;
|
/// {@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;
|
final AESMode mode;
|
||||||
|
|
||||||
|
/// The [AESPadding] used by this [AES].
|
||||||
final AESPadding padding;
|
final AESPadding padding;
|
||||||
|
|
||||||
@override
|
/// The size of the cipher text chunks.
|
||||||
CipherAlgorithm get algorithm => CipherAlgorithm.aes;
|
final int chunkSize;
|
||||||
|
|
||||||
AES(SecretKey key, [this.mode = AESMode.gcm, this.padding = AESPadding.none])
|
@override
|
||||||
: _key = key {
|
Future<Uint8List> decrypt(CipherText<AESCipherChunk> cipherText) async {
|
||||||
if (!AESKeySize.supportedSizes.contains(key.bitLength)) {
|
final BytesBuilder plainText = BytesBuilder(copy: false);
|
||||||
throw NativeCryptoException(
|
final chunks = cipherText.chunks;
|
||||||
message: 'Invalid key size! '
|
|
||||||
'Expected: ${AESKeySize.supportedSizes.join(', ')} bits',
|
int i = 0;
|
||||||
code: NativeCryptoExceptionCode.invalid_key_length.code,
|
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)) {
|
if (!mode.supportedPaddings.contains(padding)) {
|
||||||
throw NativeCryptoException(
|
throw ArgumentError.value(
|
||||||
message: 'Invalid padding! '
|
padding,
|
||||||
|
'padding',
|
||||||
|
'Invalid padding! '
|
||||||
'Expected: ${mode.supportedPaddings.join(', ')}',
|
'Expected: ${mode.supportedPaddings.join(', ')}',
|
||||||
code: NativeCryptoExceptionCode.invalid_padding.code,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> _decrypt(
|
/// Encrypts the plain text chunk.
|
||||||
CipherText cipherText, {
|
Future<Uint8List> _encryptChunk(Uint8List plainChunk, {int count = 0}) async {
|
||||||
int chunkCount = 0,
|
// 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 {
|
}) async {
|
||||||
Uint8List? decrypted;
|
// Check if the cipher is correctly initialized
|
||||||
|
_isCorrectlyInitialized();
|
||||||
|
|
||||||
|
Uint8List? bytes;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
decrypted = await platform.decrypt(
|
bytes = await platform.decrypt(
|
||||||
cipherText.bytes,
|
cipherChunk,
|
||||||
_key.bytes,
|
key: key.bytes,
|
||||||
algorithm.name,
|
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(
|
throw NativeCryptoException(
|
||||||
message: '$e',
|
code: NativeCryptoExceptionCode.nullError,
|
||||||
code: NativeCryptoExceptionCode.platform_throws.code,
|
message: 'Platform returned null bytes on chunk #$count',
|
||||||
stackTrace: s,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decrypted.isNull) {
|
if (bytes.isEmpty) {
|
||||||
throw NativeCryptoException(
|
throw NativeCryptoException(
|
||||||
message: 'Platform returned null when decrypting chunk #$chunkCount',
|
code: NativeCryptoExceptionCode.invalidData,
|
||||||
code: NativeCryptoExceptionCode.platform_returned_null.code,
|
message: 'Platform returned no data on chunk #$count',
|
||||||
);
|
|
||||||
} 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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encrypted.isNull) {
|
return bytes;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: aes_key_size.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 23/05/2022 22:10:07
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 18:45:01
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
/// Defines all available key sizes.
|
/// {@template aes_key_size}
|
||||||
|
/// Defines the key size of an AES cipher.
|
||||||
|
/// {@endtemplate}
|
||||||
enum AESKeySize {
|
enum AESKeySize {
|
||||||
bits128(128),
|
bits128(128),
|
||||||
bits192(192),
|
bits192(192),
|
||||||
bits256(256);
|
bits256(256);
|
||||||
|
|
||||||
|
/// {@macro aes_key_size}
|
||||||
|
const AESKeySize(this.bits);
|
||||||
|
|
||||||
/// Returns the number of bits supported by an [AESKeySize].
|
/// Returns the number of bits supported by an [AESKeySize].
|
||||||
static final List<int> supportedSizes = [128, 192, 256];
|
static final List<int> supportedSizes = [128, 192, 256];
|
||||||
|
|
||||||
@ -21,6 +23,4 @@ enum AESKeySize {
|
|||||||
|
|
||||||
/// Returns the number of bytes in this [AESKeySize].
|
/// Returns the number of bytes in this [AESKeySize].
|
||||||
int get bytes => bits ~/ 8;
|
int get bytes => bits ~/ 8;
|
||||||
|
|
||||||
const AESKeySize(this.bits);
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,29 @@
|
|||||||
// Author: Hugo Pointcheval
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: aes_mode.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 23/05/2022 22:09:16
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 21:03:26
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
|
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.
|
/// Defines the AES modes of operation.
|
||||||
|
/// {@endtemplate}
|
||||||
enum AESMode {
|
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].
|
/// Returns the list of supported [AESPadding] for this [AESMode].
|
||||||
final List<AESPadding> supportedPaddings;
|
final List<AESPadding> supportedPaddings;
|
||||||
@ -21,10 +33,4 @@ enum AESMode {
|
|||||||
|
|
||||||
/// Returns the default tag length for this [AESMode].
|
/// Returns the default tag length for this [AESMode].
|
||||||
final int tagLength;
|
final int tagLength;
|
||||||
|
|
||||||
const AESMode(
|
|
||||||
this.supportedPaddings, [
|
|
||||||
this.ivLength = 16,
|
|
||||||
this.tagLength = 0,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
// Author: Hugo Pointcheval
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: aes_padding.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 23/05/2022 22:10:17
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 25/05/2022 09:23:49
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
/// Represents different paddings.
|
/// Padding used for AES encryption.
|
||||||
enum AESPadding { none }
|
enum AESPadding {
|
||||||
|
/// No padding.
|
||||||
|
none,
|
||||||
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
// Author: Hugo Pointcheval
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: ciphers.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 23/05/2022 22:56:30
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 23/05/2022 22:56:47
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
export 'aes/aes.dart';
|
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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: core.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 23/05/2022 23:05:26
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 17:10:25
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
export 'cipher_text.dart';
|
export './constants/constants.dart';
|
||||||
export 'cipher_text_wrapper.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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: byte_array.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 16/12/2021 17:54:16
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 17:13:27
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2021
|
|
||||||
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:native_crypto/src/utils/encoding.dart';
|
import 'package:native_crypto/src/core/enums/encoding.dart';
|
||||||
import 'package:native_crypto/src/utils/extensions.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.
|
/// Represents a byte array.
|
||||||
///
|
///
|
||||||
/// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods.
|
/// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods.
|
||||||
@immutable
|
/// {@endtemplate}
|
||||||
abstract class ByteArray {
|
abstract class ByteArray extends Equatable {
|
||||||
final Uint8List _bytes;
|
/// {@macro byte_array}
|
||||||
|
|
||||||
/// Creates a [ByteArray] from a [Uint8List].
|
|
||||||
const ByteArray(this._bytes);
|
const ByteArray(this._bytes);
|
||||||
|
|
||||||
/// Creates a [ByteArray] object from a hexdecimal string.
|
/// Creates a [ByteArray] object from a [List] of [int].
|
||||||
ByteArray.fromBase16(String encoded)
|
ByteArray.fromList(List<int> list) : _bytes = list.toTypedList();
|
||||||
: _bytes = encoded.toBytes(from: Encoding.base16);
|
|
||||||
|
|
||||||
/// Creates a [ByteArray] object from a Base64 string.
|
/// Creates an empty [ByteArray] object from a length.
|
||||||
ByteArray.fromBase64(String encoded)
|
ByteArray.fromLength(int length, {int fill = 0})
|
||||||
: _bytes = encoded.toBytes(from: Encoding.base64);
|
: _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.
|
/// Creates a [ByteArray] object from an UTF-8 string.
|
||||||
ByteArray.fromUtf8(String encoded)
|
ByteArray.fromUtf8(String encoded)
|
||||||
: _bytes = encoded.toBytes(from: Encoding.utf8);
|
: _bytes = encoded.toBytes(from: Encoding.utf8);
|
||||||
|
|
||||||
/// Creates a [ByteArray] object from an UTF-16 string.
|
/// Creates a [ByteArray] object from a Base64 string.
|
||||||
ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes();
|
ByteArray.fromBase64(String encoded)
|
||||||
|
: _bytes = encoded.toBytes(from: Encoding.base64);
|
||||||
|
|
||||||
/// Creates an empty [ByteArray] object from a length.
|
/// Creates a [ByteArray] object from a hexdecimal string.
|
||||||
ByteArray.fromLength(int length) : _bytes = Uint8List(length);
|
ByteArray.fromBase16(String encoded)
|
||||||
|
: _bytes = encoded.toBytes(from: Encoding.base16);
|
||||||
|
|
||||||
/// Creates a [ByteArray] object from a [List] of [int].
|
final Uint8List _bytes;
|
||||||
ByteArray.fromList(List<int> list) : _bytes = list.toTypedList();
|
|
||||||
|
|
||||||
/// Gets the [ByteArray] bytes.
|
/// Gets the [ByteArray] bytes.
|
||||||
Uint8List get bytes => _bytes;
|
Uint8List get bytes => _bytes;
|
||||||
@ -62,31 +63,6 @@ abstract class ByteArray {
|
|||||||
/// Gets the [ByteArray] length in bytes.
|
/// Gets the [ByteArray] length in bytes.
|
||||||
int get length => _bytes.length;
|
int get length => _bytes.length;
|
||||||
|
|
||||||
/// Gets the [ByteArray] length in bits.
|
|
||||||
int get bitLength => _bytes.length * 8;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
List<Object?> get props => [_bytes];
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: pbkdf2.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 17/12/2021 14:50:42
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 23:19:46
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2021
|
|
||||||
|
|
||||||
import 'dart:typed_data';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:native_crypto/native_crypto.dart';
|
||||||
import 'package:native_crypto/src/interfaces/keyderivation.dart';
|
|
||||||
import 'package:native_crypto/src/keys/secret_key.dart';
|
|
||||||
import 'package:native_crypto/src/platform.dart';
|
|
||||||
import 'package:native_crypto/src/utils/extensions.dart';
|
|
||||||
import 'package:native_crypto/src/utils/hash_algorithm.dart';
|
|
||||||
import 'package:native_crypto/src/utils/kdf_algorithm.dart';
|
|
||||||
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
||||||
|
|
||||||
/// Represent a PBKDF2 Key Derivation Function (KDF) in NativeCrypto.
|
/// {@template pbkdf2}
|
||||||
///
|
/// A PBKDF2 is a password-based key derivation function.
|
||||||
/// [Pbkdf2] is a function that takes password, salt, iteration count and
|
/// {@endtemplate}
|
||||||
/// derive a [SecretKey] of specified length.
|
class Pbkdf2 extends KeyDerivationFunction {
|
||||||
class Pbkdf2 extends KeyDerivation {
|
/// {@macro pbkdf2}
|
||||||
final int _keyBytesCount;
|
const Pbkdf2({
|
||||||
final int _iterations;
|
required this.hashAlgorithm,
|
||||||
final HashAlgorithm _hash;
|
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
|
@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({
|
if (length < 0) {
|
||||||
required int keyBytesCount,
|
throw ArgumentError.value(length, 'length', 'must be positive');
|
||||||
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 (iterations <= 0) {
|
if (iterations <= 0) {
|
||||||
throw NativeCryptoException(
|
throw ArgumentError.value(
|
||||||
message: 'iterations must be strictly positive.',
|
iterations,
|
||||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
'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
|
@override
|
||||||
Future<SecretKey> derive({String? password, String? salt}) async {
|
Future<bool> verify(Uint8List keyMaterial, Uint8List expected) =>
|
||||||
Uint8List? derivation;
|
derive(keyMaterial).then((actual) {
|
||||||
|
if (actual.length != expected.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return listEquals(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
if (_keyBytesCount == 0) {
|
Future<SecretKey> call({required String password}) =>
|
||||||
return SecretKey(Uint8List(0));
|
derive(password.toBytes()).then(SecretKey.new);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: secret_key.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 28/12/2021 13:36:54
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 23:13:10
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2021
|
|
||||||
|
|
||||||
import 'dart:typed_data';
|
import 'package:native_crypto/src/domain/base_key.dart';
|
||||||
|
import 'package:native_crypto/src/random/secure_random.dart';
|
||||||
import 'package:native_crypto/src/interfaces/base_key.dart';
|
|
||||||
import 'package:native_crypto/src/interfaces/cipher.dart';
|
|
||||||
import 'package:native_crypto/src/platform.dart';
|
|
||||||
import 'package:native_crypto/src/utils/extensions.dart';
|
|
||||||
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
|
|
||||||
|
|
||||||
|
/// {@template secret_key}
|
||||||
/// Represents a secret key in NativeCrypto.
|
/// Represents a secret key in NativeCrypto.
|
||||||
///
|
///
|
||||||
/// [SecretKey] is a [BaseKey] that can be used to store secret keys.
|
/// [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 [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 {
|
class SecretKey extends BaseKey {
|
||||||
|
/// {@macro secret_key}
|
||||||
const SecretKey(super.bytes);
|
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();
|
SecretKey.fromBase16(super.encoded) : super.fromBase16();
|
||||||
|
|
||||||
|
/// Creates a [SecretKey] from a [String] encoded in Base64.
|
||||||
SecretKey.fromBase64(super.encoded) : super.fromBase64();
|
SecretKey.fromBase64(super.encoded) : super.fromBase64();
|
||||||
SecretKey.fromUtf8(super.input) : super.fromUtf8();
|
|
||||||
|
|
||||||
static Future<SecretKey> fromSecureRandom(int bitsCount) async {
|
/// Generates a random [SecretKey] of the given [length] in bytes.
|
||||||
Uint8List? key;
|
static Future<SecretKey> fromSecureRandom(int length) async {
|
||||||
if (bitsCount == 0) {
|
const random = SecureRandom();
|
||||||
return SecretKey(Uint8List(0));
|
final bytes = await random.generate(length);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
return SecretKey(bytes);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,15 +2,14 @@ name: native_crypto
|
|||||||
description: Fast and secure cryptography for Flutter.
|
description: Fast and secure cryptography for Flutter.
|
||||||
version: 0.1.1
|
version: 0.1.1
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: "none"
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
flutter: ">=2.5.0"
|
flutter: ">=2.5.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter: { sdk: flutter }
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
native_crypto_android:
|
native_crypto_android:
|
||||||
git:
|
git:
|
||||||
@ -29,19 +28,19 @@ dependencies:
|
|||||||
url: https://github.com/hugo-pcl/native-crypto-flutter.git
|
url: https://github.com/hugo-pcl/native-crypto-flutter.git
|
||||||
ref: native_crypto_platform_interface-v0.1.1
|
ref: native_crypto_platform_interface-v0.1.1
|
||||||
path: packages/native_crypto_platform_interface
|
path: packages/native_crypto_platform_interface
|
||||||
|
equatable: ^2.0.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test: { sdk: flutter }
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
mockito: ^5.2.0
|
mockito: ^5.3.2
|
||||||
plugin_platform_interface: ^2.1.2
|
plugin_platform_interface: ^2.1.3
|
||||||
|
|
||||||
wyatt_analysis:
|
wyatt_analysis:
|
||||||
git:
|
hosted:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
ref: wyatt_analysis-v2.1.0
|
name: wyatt_analysis
|
||||||
path: packages/wyatt_analysis
|
version: 2.4.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
plugin:
|
plugin:
|
||||||
|
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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: pbkdf2_test.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 26/05/2022 22:37:27
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 23:20:11
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:native_crypto/native_crypto.dart';
|
import 'package:native_crypto/native_crypto.dart';
|
||||||
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.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() {
|
void main() {
|
||||||
final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform();
|
setUp(() {
|
||||||
NativeCryptoPlatform.instance = mock;
|
// Mock the platform interface API
|
||||||
|
NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto(
|
||||||
|
api: MockNativeCryptoAPI(),
|
||||||
|
);
|
||||||
|
|
||||||
group('Constructor', () {
|
MockNativeCryptoAPI.pbkdf2Fn = null;
|
||||||
test('throws if keyBytesCount is negative', () {
|
});
|
||||||
|
|
||||||
|
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(
|
expect(
|
||||||
() => Pbkdf2(keyBytesCount: -1, iterations: 10000),
|
key,
|
||||||
throwsA(
|
Uint8List.fromList([1, 2, 3]),
|
||||||
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', () {
|
test('derive key with invalid length throws', () async {
|
||||||
expect(
|
expect(
|
||||||
() => Pbkdf2(keyBytesCount: 32, iterations: -1),
|
() => Pbkdf2(
|
||||||
throwsA(
|
salt: Uint8List.fromList([1, 2, 3]),
|
||||||
isA<NativeCryptoException>()
|
iterations: 1,
|
||||||
.having(
|
length: -1,
|
||||||
(e) => e.code,
|
hashAlgorithm: HashAlgorithm.sha256,
|
||||||
'code',
|
).derive(
|
||||||
'invalid_argument',
|
Uint8List.fromList([1, 2, 3]),
|
||||||
)
|
|
||||||
.having(
|
|
||||||
(e) => e.message,
|
|
||||||
'message',
|
|
||||||
contains('must be strictly positive'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
throwsA(isA<ArgumentError>()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('throws if salt is null', () async {
|
test('derive key with invalid iterations throws', () 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',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
result,
|
() => Pbkdf2(
|
||||||
sk,
|
salt: Uint8List.fromList([1, 2, 3]),
|
||||||
|
iterations: 0,
|
||||||
|
length: 3,
|
||||||
|
hashAlgorithm: HashAlgorithm.sha256,
|
||||||
|
).derive(
|
||||||
|
Uint8List.fromList([1, 2, 3]),
|
||||||
|
),
|
||||||
|
throwsA(isA<ArgumentError>()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('return empty SecretKey when keyBytesCount is set to 0', () async {
|
test('derive key with 0 length returns empty list', () async {
|
||||||
final sk = SecretKey(Uint8List(0));
|
final key = await Pbkdf2(
|
||||||
mock
|
salt: Uint8List.fromList([1, 2, 3]),
|
||||||
..setPbkdf2Expectations(
|
iterations: 1,
|
||||||
password: 'password',
|
length: 0,
|
||||||
salt: 'salt',
|
hashAlgorithm: HashAlgorithm.sha256,
|
||||||
keyBytesCount: 0,
|
).derive(
|
||||||
iterations: 10000,
|
Uint8List.fromList([1, 2, 3]),
|
||||||
algorithm: 'sha256',
|
|
||||||
)
|
|
||||||
..setResponse(() => Uint8List(0));
|
|
||||||
|
|
||||||
final pbkdf = Pbkdf2(keyBytesCount: 0, iterations: 10000);
|
|
||||||
final result = await pbkdf.derive(
|
|
||||||
password: 'password',
|
|
||||||
salt: 'salt',
|
|
||||||
);
|
);
|
||||||
|
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(
|
expect(
|
||||||
result,
|
() => Pbkdf2(
|
||||||
sk,
|
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
|
// Copyright 2019-2023 Hugo Pointcheval
|
||||||
// Email: git@pcl.ovh
|
//
|
||||||
// -----
|
// Use of this source code is governed by an MIT-style
|
||||||
// File: secret_key_test.dart
|
// license that can be found in the LICENSE file or at
|
||||||
// Created Date: 26/05/2022 10:52:41
|
// https://opensource.org/licenses/MIT.
|
||||||
// Last Modified: 26/05/2022 22:38:07
|
|
||||||
// -----
|
|
||||||
// Copyright (c) 2022
|
|
||||||
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.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 '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() {
|
void main() {
|
||||||
final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform();
|
setUp(() {
|
||||||
NativeCryptoPlatform.instance = mock;
|
// Mock the platform interface API
|
||||||
|
NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto(
|
||||||
|
api: MockNativeCryptoAPI(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
group('Constructors', () {
|
group('$SecretKey', () {
|
||||||
test('handles Uint8List', () {
|
test('can be create from Uint8List', () {
|
||||||
final SecretKey key = SecretKey(Uint8List.fromList([1, 2, 3, 4, 5]));
|
final SecretKey key = SecretKey(Uint8List.fromList([1, 2, 3, 4, 5]));
|
||||||
|
|
||||||
expect(key.bytes, 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');
|
final SecretKey key = SecretKey.fromBase16('0102030405');
|
||||||
|
|
||||||
expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5]));
|
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=');
|
final SecretKey key = SecretKey.fromBase64('AQIDBAU=');
|
||||||
|
|
||||||
expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5]));
|
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');
|
final SecretKey key = SecretKey.fromUtf8('ABCDE');
|
||||||
|
|
||||||
expect(key.bytes, Uint8List.fromList([65, 66, 67, 68, 69]));
|
expect(key.bytes, Uint8List.fromList([65, 66, 67, 68, 69]));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
group('fromSecureRandom', () {
|
test('can be create from secure random', () async {
|
||||||
test('handles returning random bytes', () async {
|
MockNativeCryptoAPI.generateSecureRandomFn =
|
||||||
mock
|
(length) => Uint8List.fromList([1, 2, 3, 4, 5]);
|
||||||
..setGenerateKeyExpectations(bitsCount: 5)
|
final SecretKey key = await SecretKey.fromSecureRandom(5);
|
||||||
..setResponse(() => Uint8List.fromList([1, 2, 3, 4, 5]));
|
|
||||||
|
|
||||||
final SecretKey secretKey = await SecretKey.fromSecureRandom(5);
|
expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 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',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user