v2 #12
| @ -1 +1 @@ | ||||
| include: package:wyatt_analysis/analysis_options.flutter.experimental.yaml | ||||
| include: package:wyatt_analysis/analysis_options.flutter.yaml | ||||
| @ -1,11 +1,8 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: native_crypto.dart | ||||
| // Created Date: 16/12/2021 16:28:00 | ||||
| // Last Modified: 26/05/2022 12:10:42 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| /// Fast and powerful cryptographic functions | ||||
| /// thanks to javax.crypto, CommonCrypto and CryptoKit. | ||||
| @ -13,18 +10,13 @@ | ||||
| /// Author: Hugo Pointcheval | ||||
| library native_crypto; | ||||
| 
 | ||||
| export 'package:native_crypto_platform_interface/src/utils/exception.dart'; | ||||
| export 'package:native_crypto_platform_interface/src/core/exceptions/exception.dart'; | ||||
| 
 | ||||
| export 'src/builders/builders.dart'; | ||||
| export 'src/ciphers/ciphers.dart'; | ||||
| export 'src/core/core.dart'; | ||||
| export 'src/interfaces/interfaces.dart'; | ||||
| export 'src/kdf/kdf.dart'; | ||||
| export 'src/keys/keys.dart'; | ||||
| // Utils | ||||
| export 'src/utils/cipher_algorithm.dart'; | ||||
| export 'src/utils/hash_algorithm.dart'; | ||||
| export 'src/utils/kdf_algorithm.dart'; | ||||
| 
 | ||||
| // ignore: constant_identifier_names | ||||
| const String AUTHOR = 'Hugo Pointcheval'; | ||||
| export 'src/digest/digest.dart'; | ||||
| export 'src/domain/domain.dart'; | ||||
| export 'src/kdf/pbkdf2.dart'; | ||||
| export 'src/keys/secret_key.dart'; | ||||
| export 'src/random/secure_random.dart'; | ||||
|  | ||||
| @ -1,11 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: native_crypto_ext.dart | ||||
| // Created Date: 26/05/2022 19:36:54 | ||||
| // Last Modified: 26/05/2022 19:38:44 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| export 'src/utils/encoding.dart'; | ||||
| export 'src/utils/extensions.dart'; | ||||
| @ -1,10 +1,8 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: builders.dart | ||||
| // Created Date: 23/05/2022 22:56:03 | ||||
| // Last Modified: 26/05/2022 19:22:19 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| export 'decryption_builder.dart'; | ||||
| export 'encryption_builder.dart'; | ||||
|  | ||||
| @ -1,46 +1,60 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: decryption_builder.dart | ||||
| // Created Date: 26/05/2022 19:07:52 | ||||
| // Last Modified: 26/05/2022 19:21:00 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; | ||||
| import 'package:native_crypto/src/interfaces/cipher.dart'; | ||||
| import 'package:native_crypto/src/core/utils/cipher_text.dart'; | ||||
| 
 | ||||
| class DecryptionBuilder extends StatelessWidget { | ||||
|   final Cipher cipher; | ||||
|   final CipherTextWrapper data; | ||||
|   final Widget Function(BuildContext context) onLoading; | ||||
|   final Widget Function(BuildContext context, Object error) onError; | ||||
|   final Widget Function(BuildContext context, Uint8List plainText) onSuccess; | ||||
| import 'package:native_crypto/src/domain/cipher.dart'; | ||||
| import 'package:native_crypto/src/domain/cipher_chunk.dart'; | ||||
| 
 | ||||
| /// {@template decryption_builder} | ||||
| /// A [StatelessWidget] that builds a [FutureBuilder] that will decrypt a | ||||
| /// [CipherText] using a [Cipher]. | ||||
| /// {@endtemplate} | ||||
| class DecryptionBuilder<T extends CipherChunk> extends StatelessWidget { | ||||
|   /// {@macro decryption_builder} | ||||
|   const DecryptionBuilder({ | ||||
|     super.key, | ||||
|     required this.cipher, | ||||
|     required this.data, | ||||
|     required this.cipherText, | ||||
|     required this.onLoading, | ||||
|     required this.onError, | ||||
|     required this.onSuccess, | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   /// The [Cipher] that will be used to decrypt the [CipherText]. | ||||
|   final Cipher<T> cipher; | ||||
| 
 | ||||
|   /// The [CipherText] that will be decrypted. | ||||
|   final CipherText<T> cipherText; | ||||
| 
 | ||||
|   /// The [Widget] that will be displayed while the [CipherText] is being | ||||
|   /// decrypted. | ||||
|   final Widget Function(BuildContext context) onLoading; | ||||
| 
 | ||||
|   /// The [Widget] that will be displayed if an error occurs while decrypting | ||||
|   /// the [CipherText]. | ||||
|   final Widget Function(BuildContext context, Object error) onError; | ||||
| 
 | ||||
|   /// The [Widget] that will be displayed once the [CipherText] has been | ||||
|   /// decrypted. | ||||
|   final Widget Function(BuildContext context, Uint8List plainText) onSuccess; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return FutureBuilder<Uint8List>( | ||||
|       future: cipher.decrypt(data), | ||||
|       builder: (context, snapshot) { | ||||
|         if (snapshot.hasData) { | ||||
|           return onSuccess(context, snapshot.data!); | ||||
|         } else if (snapshot.hasError) { | ||||
|           return onError(context, snapshot.error!); | ||||
|         } | ||||
|         return onLoading(context); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
|   Widget build(BuildContext context) => FutureBuilder<Uint8List>( | ||||
|         future: cipher.decrypt(cipherText), | ||||
|         builder: (context, snapshot) { | ||||
|           if (snapshot.hasData) { | ||||
|             return onSuccess(context, snapshot.data!); | ||||
|           } else if (snapshot.hasError) { | ||||
|             return onError(context, snapshot.error!); | ||||
|           } | ||||
|           return onLoading(context); | ||||
|         }, | ||||
|       ); | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,61 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:native_crypto/src/core/utils/cipher_text.dart'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/domain/cipher.dart'; | ||||
| import 'package:native_crypto/src/domain/cipher_chunk.dart'; | ||||
| 
 | ||||
| /// {@template encryption_builder} | ||||
| /// A [StatelessWidget] that builds a [FutureBuilder] that will | ||||
| /// encrypt a [Uint8List] using a [Cipher]. | ||||
| /// {@endtemplate} | ||||
| class EncryptionBuilder<T extends CipherChunk> extends StatelessWidget { | ||||
|   /// {@macro encryption_builder} | ||||
|   const EncryptionBuilder({ | ||||
|     required this.cipher, | ||||
|     required this.plainText, | ||||
|     required this.onLoading, | ||||
|     required this.onError, | ||||
|     required this.onSuccess, | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   /// The [Cipher] that will be used to encrypt the [Uint8List]. | ||||
|   final Cipher<T> cipher; | ||||
| 
 | ||||
|   /// The [Uint8List] that will be encrypted. | ||||
|   final Uint8List plainText; | ||||
| 
 | ||||
|   /// The [Widget] that will be displayed while the [Uint8List] is being | ||||
|   /// encrypted. | ||||
|   final Widget Function(BuildContext context) onLoading; | ||||
| 
 | ||||
|   /// The [Widget] that will be displayed if an error occurs while encrypting | ||||
|   /// the [Uint8List]. | ||||
|   final Widget Function(BuildContext context, Object error) onError; | ||||
| 
 | ||||
|   /// The [Widget] that will be displayed once the [Uint8List] has been | ||||
|   /// encrypted. | ||||
|   final Widget Function(BuildContext context, CipherText<T> cipherText) | ||||
|       onSuccess; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) => FutureBuilder<CipherText<T>>( | ||||
|         future: cipher.encrypt(plainText), | ||||
|         builder: (context, snapshot) { | ||||
|           if (snapshot.hasData) { | ||||
|             return onSuccess(context, snapshot.data!); | ||||
|           } else if (snapshot.hasError) { | ||||
|             return onError(context, snapshot.error!); | ||||
|           } | ||||
|           return onLoading(context); | ||||
|         }, | ||||
|       ); | ||||
| } | ||||
| @ -1,181 +1,300 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: aes.dart | ||||
| // Created Date: 16/12/2021 16:28:00 | ||||
| // Last Modified: 27/05/2022 12:13:28 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:io'; | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/ciphers/aes/aes_cipher_chunk.dart'; | ||||
| import 'package:native_crypto/src/ciphers/aes/aes_key_size.dart'; | ||||
| import 'package:native_crypto/src/ciphers/aes/aes_mode.dart'; | ||||
| import 'package:native_crypto/src/ciphers/aes/aes_padding.dart'; | ||||
| import 'package:native_crypto/src/core/cipher_text.dart'; | ||||
| import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; | ||||
| import 'package:native_crypto/src/interfaces/cipher.dart'; | ||||
| import 'package:native_crypto/src/core/constants/constants.dart'; | ||||
| import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart'; | ||||
| import 'package:native_crypto/src/core/utils/cipher_text.dart'; | ||||
| import 'package:native_crypto/src/core/utils/platform.dart'; | ||||
| import 'package:native_crypto/src/domain/cipher.dart'; | ||||
| import 'package:native_crypto/src/keys/secret_key.dart'; | ||||
| import 'package:native_crypto/src/platform.dart'; | ||||
| import 'package:native_crypto/src/utils/cipher_algorithm.dart'; | ||||
| import 'package:native_crypto/src/utils/extensions.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| export 'aes_cipher_chunk.dart'; | ||||
| export 'aes_key_size.dart'; | ||||
| export 'aes_mode.dart'; | ||||
| export 'aes_padding.dart'; | ||||
| 
 | ||||
| /// An AES cipher. | ||||
| /// {@template aes} | ||||
| /// AES cipher. | ||||
| /// | ||||
| /// [AES] is a [Cipher] that can be used to encrypt or decrypt data. | ||||
| class AES implements Cipher { | ||||
|   final SecretKey _key; | ||||
| /// [AES] is a symmetric cipher which means that the same key is used to encrypt | ||||
| /// and decrypt the data. | ||||
| /// {@endtemplate} | ||||
| class AES implements Cipher<AESCipherChunk> { | ||||
|   const AES({ | ||||
|     required this.key, | ||||
|     required this.mode, | ||||
|     required this.padding, | ||||
|     this.chunkSize = Constants.defaultChunkSize, | ||||
|   }); | ||||
| 
 | ||||
|   static const String _algorithm = 'aes'; | ||||
| 
 | ||||
|   /// The key used to encrypt and decrypt the data. | ||||
|   final SecretKey key; | ||||
| 
 | ||||
|   /// The [AESMode] used by this [AES]. | ||||
|   final AESMode mode; | ||||
| 
 | ||||
|   /// The [AESPadding] used by this [AES]. | ||||
|   final AESPadding padding; | ||||
| 
 | ||||
|   @override | ||||
|   CipherAlgorithm get algorithm => CipherAlgorithm.aes; | ||||
|   /// The size of the cipher text chunks. | ||||
|   final int chunkSize; | ||||
| 
 | ||||
|   AES(SecretKey key, [this.mode = AESMode.gcm, this.padding = AESPadding.none]) | ||||
|       : _key = key { | ||||
|     if (!AESKeySize.supportedSizes.contains(key.bitLength)) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Invalid key size! ' | ||||
|             'Expected: ${AESKeySize.supportedSizes.join(', ')} bits', | ||||
|         code: NativeCryptoExceptionCode.invalid_key_length.code, | ||||
|   @override | ||||
|   Future<Uint8List> decrypt(CipherText<AESCipherChunk> cipherText) async { | ||||
|     final BytesBuilder plainText = BytesBuilder(copy: false); | ||||
|     final chunks = cipherText.chunks; | ||||
| 
 | ||||
|     int i = 0; | ||||
|     for (final chunk in chunks) { | ||||
|       plainText.add(await _decryptChunk(chunk.bytes, count: i++)); | ||||
|     } | ||||
| 
 | ||||
|     return plainText.toBytes(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> decryptFile(File cipherTextFile, File plainTextFile) { | ||||
|     if (!cipherTextFile.existsSync()) { | ||||
|       throw ArgumentError.value( | ||||
|         cipherTextFile.path, | ||||
|         'cipherTextFile.path', | ||||
|         'File does not exist!', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (plainTextFile.existsSync()) { | ||||
|       throw ArgumentError.value( | ||||
|         plainTextFile.path, | ||||
|         'plainTextFile.path', | ||||
|         'File already exists!', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return platform.decryptFile( | ||||
|       cipherTextPath: cipherTextFile.path, | ||||
|       plainTextPath: plainTextFile.path, | ||||
|       key: key.bytes, | ||||
|       algorithm: _algorithm, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<CipherText<AESCipherChunk>> encrypt(Uint8List plainText) async { | ||||
|     final chunks = <AESCipherChunk>[]; | ||||
|     final chunkedPlainText = plainText.chunked(chunkSize); | ||||
| 
 | ||||
|     int i = 0; | ||||
|     for (final plainTextChunk in chunkedPlainText) { | ||||
|       final bytes = await _encryptChunk(plainTextChunk, count: i++); | ||||
|       chunks.add( | ||||
|         AESCipherChunk( | ||||
|           bytes, | ||||
|           ivLength: mode.ivLength, | ||||
|           tagLength: mode.tagLength, | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return CipherText.fromChunks( | ||||
|       chunks, | ||||
|       chunkFactory: (bytes) => AESCipherChunk( | ||||
|         bytes, | ||||
|         ivLength: mode.ivLength, | ||||
|         tagLength: mode.tagLength, | ||||
|       ), | ||||
|       chunkSize: chunkSize, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> encryptFile(File plainTextFile, File cipherTextFile) { | ||||
|     if (!plainTextFile.existsSync()) { | ||||
|       throw ArgumentError.value( | ||||
|         plainTextFile.path, | ||||
|         'plainTextFile.path', | ||||
|         'File does not exist!', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (cipherTextFile.existsSync()) { | ||||
|       throw ArgumentError.value( | ||||
|         cipherTextFile.path, | ||||
|         'cipherTextFile.path', | ||||
|         'File already exists!', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return platform.encryptFile( | ||||
|       plainTextPath: plainTextFile.path, | ||||
|       cipherTextPath: cipherTextFile.path, | ||||
|       key: key.bytes, | ||||
|       algorithm: _algorithm, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// Encrypts the [plainText] with the [iv] chosen by the Flutter side. | ||||
|   /// | ||||
|   /// Prefer using [encrypt] instead which will generate a | ||||
|   /// random [iv] on the native side. | ||||
|   /// | ||||
|   /// Note: this method doesn't chunk the data. It can lead to memory issues | ||||
|   /// if the [plainText] is too big. Use [encrypt] instead. | ||||
|   Future<AESCipherChunk> encryptWithIV( | ||||
|     Uint8List plainText, | ||||
|     Uint8List iv, | ||||
|   ) async { | ||||
|     // Check if the cipher is correctly initialized | ||||
|     _isCorrectlyInitialized(); | ||||
| 
 | ||||
|     if (iv.length != mode.ivLength) { | ||||
|       throw ArgumentError.value( | ||||
|         iv.length, | ||||
|         'iv.length', | ||||
|         'Invalid iv length! ' | ||||
|             'Expected: ${mode.ivLength}', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     final bytes = await platform.encryptWithIV( | ||||
|       plainText: plainText, | ||||
|       iv: iv, | ||||
|       key: key.bytes, | ||||
|       algorithm: _algorithm, | ||||
|     ); | ||||
| 
 | ||||
|     // TODO(hpcl): move these checks to the platform interface | ||||
|     if (bytes == null) { | ||||
|       throw const NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.nullError, | ||||
|         message: 'Platform returned null bytes', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (bytes.isEmpty) { | ||||
|       throw const NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.invalidData, | ||||
|         message: 'Platform returned no data', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return AESCipherChunk( | ||||
|       bytes, | ||||
|       ivLength: mode.ivLength, | ||||
|       tagLength: mode.tagLength, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// Ensures that the cipher is correctly initialized. | ||||
|   bool _isCorrectlyInitialized() { | ||||
|     final keySize = key.length * 8; | ||||
|     if (!AESKeySize.supportedSizes.contains(keySize)) { | ||||
|       throw ArgumentError.value( | ||||
|         keySize, | ||||
|         'keySize', | ||||
|         'Invalid key size! ' | ||||
|             'Expected: ${AESKeySize.supportedSizes.join(', ')}', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (!mode.supportedPaddings.contains(padding)) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Invalid padding! ' | ||||
|       throw ArgumentError.value( | ||||
|         padding, | ||||
|         'padding', | ||||
|         'Invalid padding! ' | ||||
|             'Expected: ${mode.supportedPaddings.join(', ')}', | ||||
|         code: NativeCryptoExceptionCode.invalid_padding.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   Future<Uint8List> _decrypt( | ||||
|     CipherText cipherText, { | ||||
|     int chunkCount = 0, | ||||
|   /// Encrypts the plain text chunk. | ||||
|   Future<Uint8List> _encryptChunk(Uint8List plainChunk, {int count = 0}) async { | ||||
|     // Check if the cipher is correctly initialized | ||||
|     _isCorrectlyInitialized(); | ||||
| 
 | ||||
|     Uint8List? bytes; | ||||
| 
 | ||||
|     try { | ||||
|       bytes = await platform.encrypt( | ||||
|         plainChunk, | ||||
|         key: key.bytes, | ||||
|         algorithm: _algorithm, | ||||
|       ); | ||||
|     } on NativeCryptoException catch (e) { | ||||
|       throw e.copyWith( | ||||
|         message: 'Failed to encrypt chunk #$count: ${e.message}', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     // TODO(hpcl): move these checks to the platform interface | ||||
|     if (bytes == null) { | ||||
|       throw NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.nullError, | ||||
|         message: 'Platform returned null bytes on chunk #$count', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (bytes.isEmpty) { | ||||
|       throw NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.invalidData, | ||||
|         message: 'Platform returned no data on chunk #$count', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return bytes; | ||||
|   } | ||||
| 
 | ||||
|   /// Decrypts the cipher text chunk. | ||||
|   Future<Uint8List> _decryptChunk( | ||||
|     Uint8List cipherChunk, { | ||||
|     int count = 0, | ||||
|   }) async { | ||||
|     Uint8List? decrypted; | ||||
|     // Check if the cipher is correctly initialized | ||||
|     _isCorrectlyInitialized(); | ||||
| 
 | ||||
|     Uint8List? bytes; | ||||
| 
 | ||||
|     try { | ||||
|       decrypted = await platform.decrypt( | ||||
|         cipherText.bytes, | ||||
|         _key.bytes, | ||||
|         algorithm.name, | ||||
|       bytes = await platform.decrypt( | ||||
|         cipherChunk, | ||||
|         key: key.bytes, | ||||
|         algorithm: _algorithm, | ||||
|       ); | ||||
|     } catch (e, s) { | ||||
|     } on NativeCryptoException catch (e) { | ||||
|       throw e.copyWith( | ||||
|         message: 'Failed to decrypt chunk #$count: ${e.message}', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     // TODO(hpcl): move these checks to the platform interface | ||||
|     if (bytes == null) { | ||||
|       throw NativeCryptoException( | ||||
|         message: '$e', | ||||
|         code: NativeCryptoExceptionCode.platform_throws.code, | ||||
|         stackTrace: s, | ||||
|         code: NativeCryptoExceptionCode.nullError, | ||||
|         message: 'Platform returned null bytes on chunk #$count', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (decrypted.isNull) { | ||||
|     if (bytes.isEmpty) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Platform returned null when decrypting chunk #$chunkCount', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_null.code, | ||||
|       ); | ||||
|     } else if (decrypted!.isEmpty) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Platform returned no data when decrypting chunk #$chunkCount', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_empty_data.code, | ||||
|       ); | ||||
|     } else { | ||||
|       return decrypted; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async { | ||||
|     Uint8List? encrypted; | ||||
| 
 | ||||
|     try { | ||||
|       encrypted = await platform.encrypt( | ||||
|         data, | ||||
|         _key.bytes, | ||||
|         algorithm.name, | ||||
|       ); | ||||
|     } catch (e, s) { | ||||
|       throw NativeCryptoException( | ||||
|         message: '$e on chunk #$chunkCount', | ||||
|         code: NativeCryptoExceptionCode.platform_throws.code, | ||||
|         stackTrace: s, | ||||
|         code: NativeCryptoExceptionCode.invalidData, | ||||
|         message: 'Platform returned no data on chunk #$count', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (encrypted.isNull) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Platform returned null when encrypting chunk #$chunkCount', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_null.code, | ||||
|       ); | ||||
|     } else if (encrypted!.isEmpty) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Platform returned no data when encrypting chunk #$chunkCount', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_empty_data.code, | ||||
|       ); | ||||
|     } else { | ||||
|       try { | ||||
|         return CipherText.fromBytes( | ||||
|           encrypted, | ||||
|           ivLength: 12, | ||||
|           messageLength: encrypted.length - 28, | ||||
|           tagLength: 16, | ||||
|           cipherAlgorithm: CipherAlgorithm.aes, | ||||
|         ); | ||||
|       } on NativeCryptoException catch (e, s) { | ||||
|         throw NativeCryptoException( | ||||
|           message: '${e.message} on chunk #$chunkCount', | ||||
|           code: e.code, | ||||
|           stackTrace: s, | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List> decrypt(CipherTextWrapper cipherText) async { | ||||
|     final BytesBuilder decryptedData = BytesBuilder(copy: false); | ||||
| 
 | ||||
|     if (cipherText.isList) { | ||||
|       int chunkCount = 0; | ||||
|       for (final CipherText chunk in cipherText.list) { | ||||
|         decryptedData.add(await _decrypt(chunk, chunkCount: chunkCount++)); | ||||
|       } | ||||
|     } else { | ||||
|       decryptedData.add(await _decrypt(cipherText.single)); | ||||
|     } | ||||
| 
 | ||||
|     return decryptedData.toBytes(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<CipherTextWrapper> encrypt(Uint8List data) async { | ||||
|     if (data.isEmpty) { | ||||
|       return CipherTextWrapper.empty(); | ||||
|     } | ||||
|     CipherTextWrapper cipherTextWrapper; | ||||
|     Uint8List dataToEncrypt; | ||||
|     final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil(); | ||||
| 
 | ||||
|     if (chunkNb > 1) { | ||||
|       cipherTextWrapper = CipherTextWrapper.empty(); | ||||
|       for (var i = 0; i < chunkNb; i++) { | ||||
|         dataToEncrypt = i < (chunkNb - 1) | ||||
|             ? data.sublist( | ||||
|                 i * Cipher.bytesCountPerChunk, | ||||
|                 (i + 1) * Cipher.bytesCountPerChunk, | ||||
|               ) | ||||
|             : data.sublist(i * Cipher.bytesCountPerChunk); | ||||
|         cipherTextWrapper.add(await _encrypt(dataToEncrypt, chunkCount: i)); | ||||
|       } | ||||
|     } else { | ||||
|       cipherTextWrapper = CipherTextWrapper.single(await _encrypt(data)); | ||||
|     } | ||||
| 
 | ||||
|     return cipherTextWrapper; | ||||
|     return bytes; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,67 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/domain/cipher_chunk.dart'; | ||||
| 
 | ||||
| class AESCipherChunk extends CipherChunk { | ||||
|   const AESCipherChunk( | ||||
|     super.bytes, { | ||||
|     required this.ivLength, | ||||
|     required this.tagLength, | ||||
|   }); | ||||
| 
 | ||||
|   /// Creates a [AESCipherChunk] from a [List<int>]. | ||||
|   AESCipherChunk.fromList( | ||||
|     super.list, { | ||||
|     required this.ivLength, | ||||
|     required this.tagLength, | ||||
|   }) : super.fromList(); | ||||
| 
 | ||||
|   /// Creates a [AESCipherChunk] from a [String] encoded in UTF-8. | ||||
|   AESCipherChunk.fromUtf8( | ||||
|     super.encoded, { | ||||
|     required this.ivLength, | ||||
|     required this.tagLength, | ||||
|   }) : super.fromUtf8(); | ||||
| 
 | ||||
|   /// Creates a [AESCipherChunk] from a [String] encoded in UTF-16. | ||||
|   AESCipherChunk.fromUtf16( | ||||
|     super.encoded, { | ||||
|     required this.ivLength, | ||||
|     required this.tagLength, | ||||
|   }) : super.fromUtf16(); | ||||
| 
 | ||||
|   /// Creates a [AESCipherChunk] from a [String] encoded in Hexadecimal. | ||||
|   AESCipherChunk.fromBase16( | ||||
|     super.encoded, { | ||||
|     required this.ivLength, | ||||
|     required this.tagLength, | ||||
|   }) : super.fromBase16(); | ||||
| 
 | ||||
|   /// Creates a [AESCipherChunk] from a [String] encoded in Base64. | ||||
|   AESCipherChunk.fromBase64( | ||||
|     super.encoded, { | ||||
|     required this.ivLength, | ||||
|     required this.tagLength, | ||||
|   }) : super.fromBase64(); | ||||
| 
 | ||||
|   /// Intialization vector length. | ||||
|   final int ivLength; | ||||
| 
 | ||||
|   /// Tag length. | ||||
|   final int tagLength; | ||||
| 
 | ||||
|   /// Returns the initialization vector, or nonce of the [AESCipherChunk]. | ||||
|   Uint8List get iv => bytes.sublist(0, ivLength); | ||||
| 
 | ||||
|   /// Returns the tag of the [AESCipherChunk]. | ||||
|   Uint8List get tag => bytes.sublist(bytes.length - tagLength, bytes.length); | ||||
| 
 | ||||
|   /// Returns the message of the [AESCipherChunk]. | ||||
|   Uint8List get message => bytes.sublist(ivLength, bytes.length - tagLength); | ||||
| } | ||||
| @ -1,18 +1,20 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: aes_key_size.dart | ||||
| // Created Date: 23/05/2022 22:10:07 | ||||
| // Last Modified: 26/05/2022 18:45:01 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| //  | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| /// Defines all available key sizes. | ||||
| /// {@template aes_key_size} | ||||
| /// Defines the key size of an AES cipher. | ||||
| /// {@endtemplate} | ||||
| enum AESKeySize { | ||||
|   bits128(128), | ||||
|   bits192(192), | ||||
|   bits256(256); | ||||
| 
 | ||||
|   /// {@macro aes_key_size} | ||||
|   const AESKeySize(this.bits); | ||||
| 
 | ||||
|   /// Returns the number of bits supported by an [AESKeySize]. | ||||
|   static final List<int> supportedSizes = [128, 192, 256]; | ||||
| 
 | ||||
| @ -21,6 +23,4 @@ enum AESKeySize { | ||||
| 
 | ||||
|   /// Returns the number of bytes in this [AESKeySize]. | ||||
|   int get bytes => bits ~/ 8; | ||||
| 
 | ||||
|   const AESKeySize(this.bits); | ||||
| } | ||||
|  | ||||
| @ -1,17 +1,29 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: aes_mode.dart | ||||
| // Created Date: 23/05/2022 22:09:16 | ||||
| // Last Modified: 26/05/2022 21:03:26 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'package:native_crypto/src/ciphers/aes/aes_padding.dart'; | ||||
| import 'package:native_crypto/src/core/constants/constants.dart'; | ||||
| 
 | ||||
| /// {@template aes_mode} | ||||
| /// Defines the AES modes of operation. | ||||
| /// {@endtemplate} | ||||
| enum AESMode { | ||||
|   gcm([AESPadding.none], 12, 16); | ||||
|   /// GCM mode. | ||||
|   gcm( | ||||
|     [AESPadding.none], | ||||
|     Constants.aesGcmNonceLength, | ||||
|     Constants.aesGcmTagLength, | ||||
|   ); | ||||
| 
 | ||||
|   /// {@macro aes_mode} | ||||
|   const AESMode( | ||||
|     this.supportedPaddings, [ | ||||
|     this.ivLength = 16, | ||||
|     this.tagLength = 0, | ||||
|   ]); | ||||
| 
 | ||||
|   /// Returns the list of supported [AESPadding] for this [AESMode]. | ||||
|   final List<AESPadding> supportedPaddings; | ||||
| @ -21,10 +33,4 @@ enum AESMode { | ||||
| 
 | ||||
|   /// Returns the default tag length for this [AESMode]. | ||||
|   final int tagLength; | ||||
| 
 | ||||
|   const AESMode( | ||||
|     this.supportedPaddings, [ | ||||
|     this.ivLength = 16, | ||||
|     this.tagLength = 0, | ||||
|   ]); | ||||
| } | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: aes_padding.dart | ||||
| // Created Date: 23/05/2022 22:10:17 | ||||
| // Last Modified: 25/05/2022 09:23:49 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| /// Represents different paddings. | ||||
| enum AESPadding { none } | ||||
| /// Padding used for AES encryption. | ||||
| enum AESPadding { | ||||
|   /// No padding. | ||||
|   none, | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,7 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: ciphers.dart | ||||
| // Created Date: 23/05/2022 22:56:30 | ||||
| // Last Modified: 23/05/2022 22:56:47 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| export 'aes/aes.dart'; | ||||
|  | ||||
| @ -1,117 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: cipher_text.dart | ||||
| // Created Date: 16/12/2021 16:59:53 | ||||
| // Last Modified: 27/05/2022 12:09:47 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; | ||||
| import 'package:native_crypto/src/interfaces/byte_array.dart'; | ||||
| import 'package:native_crypto/src/interfaces/cipher.dart'; | ||||
| import 'package:native_crypto/src/utils/cipher_algorithm.dart'; | ||||
| import 'package:native_crypto/src/utils/extensions.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| /// Represents a cipher text in NativeCrypto. | ||||
| /// | ||||
| /// [CipherText] is a [ByteArray] that can be used to store encrypted data. | ||||
| /// It is represented like: | ||||
| /// ```txt | ||||
| /// [IV + MESSAGE + TAG] | ||||
| /// ``` | ||||
| /// where: | ||||
| /// - IV's length is [CipherText.ivLength] bytes. | ||||
| /// - MESSAGE's length is [CipherText.messageLength] bytes. | ||||
| /// - TAG's length is [CipherText.tagLength] bytes. | ||||
| /// | ||||
| /// Check [CipherTextWrapper] for more information. | ||||
| class CipherText extends ByteArray { | ||||
|   final int _ivLength; | ||||
|   final int _messageLength; | ||||
|   final int _tagLength; | ||||
| 
 | ||||
|   final CipherAlgorithm? _cipherAlgorithm; | ||||
| 
 | ||||
|   const CipherText._( | ||||
|     this._ivLength, | ||||
|     this._messageLength, | ||||
|     this._tagLength, | ||||
|     this._cipherAlgorithm, | ||||
|     super.bytes, | ||||
|   ); | ||||
| 
 | ||||
|   factory CipherText.fromBytes( | ||||
|     Uint8List bytes, { | ||||
|     required int ivLength, | ||||
|     required int tagLength, | ||||
|     int? messageLength, | ||||
|     CipherAlgorithm? cipherAlgorithm, | ||||
|   }) { | ||||
|     messageLength ??= bytes.length - ivLength - tagLength; | ||||
| 
 | ||||
|     if (ivLength.isNegative || | ||||
|         messageLength.isNegative || | ||||
|         tagLength.isNegative) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Invalid length! Must be positive.', | ||||
|         code: NativeCryptoExceptionCode.invalid_argument.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (bytes.isEmpty) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Passed data is empty!', | ||||
|         code: NativeCryptoExceptionCode.invalid_argument.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (bytes.length != ivLength + messageLength + tagLength) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Invalid cipher text length! ' | ||||
|             'Expected: ${ivLength + messageLength + tagLength} bytes ' | ||||
|             'got: ${bytes.length} bytes.', | ||||
|         code: NativeCryptoExceptionCode.invalid_argument.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (messageLength > Cipher.bytesCountPerChunk) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Cipher text is too big! Consider using chunks.', | ||||
|         code: NativeCryptoExceptionCode.invalid_argument.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return CipherText._( | ||||
|       ivLength, | ||||
|       messageLength, | ||||
|       tagLength, | ||||
|       cipherAlgorithm, | ||||
|       bytes, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the [CipherAlgorithm] used to encrypt the [CipherText]. | ||||
|   CipherAlgorithm get cipherAlgorithm { | ||||
|     if (_cipherAlgorithm.isNotNull) { | ||||
|       return _cipherAlgorithm!; | ||||
|     } else { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Cipher algorithm is not specified', | ||||
|         code: NativeCryptoExceptionCode.invalid_cipher.code, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the length of the [CipherText]'s IV. | ||||
|   int get ivLength => _ivLength; | ||||
| 
 | ||||
|   /// Gets the length of the [CipherText]'s Message. | ||||
|   int get messageLength => _messageLength; | ||||
| 
 | ||||
|   /// Gets the length of the [CipherText]'s Tag. | ||||
|   int get tagLength => _tagLength; | ||||
| } | ||||
| @ -1,191 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: cipher_text_wrapper.dart | ||||
| // Created Date: 26/05/2022 14:27:32 | ||||
| // Last Modified: 27/05/2022 13:43:29 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| import 'package:native_crypto/src/utils/extensions.dart'; | ||||
| 
 | ||||
| /// Wrapper for [CipherText] | ||||
| /// | ||||
| /// Typically, this object is the result of an encryption operation. | ||||
| /// For decryption you have to build this before using it. | ||||
| class CipherTextWrapper { | ||||
|   final CipherText? _single; | ||||
|   final List<CipherText>? _list; | ||||
| 
 | ||||
|   CipherTextWrapper._(this._single, this._list); | ||||
| 
 | ||||
|   /// Creates a [CipherTextWrapper] from a [CipherText]. | ||||
|   factory CipherTextWrapper.single(CipherText cipherText) => | ||||
|       CipherTextWrapper._(cipherText, null); | ||||
| 
 | ||||
|   /// Creates a [CipherTextWrapper] from a [List] of [CipherText]. | ||||
|   factory CipherTextWrapper.list(List<CipherText> cipherTexts) => | ||||
|       CipherTextWrapper._(null, cipherTexts); | ||||
| 
 | ||||
|   /// Creates an empty [List] in a [CipherTextWrapper]. | ||||
|   /// | ||||
|   /// This is useful when you want to create a [CipherTextWrapper] then | ||||
|   /// fill it with data. | ||||
|   factory CipherTextWrapper.empty() => CipherTextWrapper._(null, []); | ||||
| 
 | ||||
|   /// Creates a [CipherTextWrapper] from a [Uint8List]. | ||||
|   /// | ||||
|   /// This is a convenience method to create a [CipherTextWrapper] | ||||
|   /// from a [Uint8List]. It tries to detect if the [Uint8List] is a | ||||
|   /// single [CipherText] or a list of [CipherText]. | ||||
|   /// | ||||
|   /// You can customize the chunk size by passing a [chunkSize] parameter. | ||||
|   /// The default chunk size is [Cipher.bytesCountPerChunk]. | ||||
|   /// | ||||
|   /// Throw an [NativeCryptoExceptionCode] with | ||||
|   /// [NativeCryptoExceptionCode.invalid_argument] if the [Uint8List] is | ||||
|   /// not a valid [CipherText] or a [List] of [CipherText]. | ||||
|   factory CipherTextWrapper.fromBytes( | ||||
|     Uint8List bytes, { | ||||
|     required int ivLength, | ||||
|     required int tagLength, | ||||
|     CipherAlgorithm? cipherAlgorithm, | ||||
|     int? chunkSize, | ||||
|   }) { | ||||
|     chunkSize ??= Cipher.bytesCountPerChunk; | ||||
|     Cipher.bytesCountPerChunk = chunkSize; | ||||
| 
 | ||||
|     final int messageLength = bytes.length - ivLength - tagLength; | ||||
| 
 | ||||
|     if (messageLength <= chunkSize) { | ||||
|       return CipherTextWrapper.single( | ||||
|         CipherText.fromBytes( | ||||
|           bytes, | ||||
|           ivLength: ivLength, | ||||
|           tagLength: tagLength, | ||||
|           cipherAlgorithm: cipherAlgorithm, | ||||
|         ), | ||||
|       ); | ||||
|     } else { | ||||
|       final cipherTexts = <CipherText>[]; | ||||
|       for (var i = 0; i < bytes.length; i += chunkSize + ivLength + tagLength) { | ||||
|         final chunk = bytes.trySublist(i, i + chunkSize + ivLength + tagLength); | ||||
| 
 | ||||
|         try { | ||||
|           cipherTexts.add( | ||||
|             CipherText.fromBytes( | ||||
|               chunk, | ||||
|               ivLength: ivLength, | ||||
|               tagLength: tagLength, | ||||
|               cipherAlgorithm: cipherAlgorithm, | ||||
|             ), | ||||
|           ); | ||||
|         } on NativeCryptoException catch (e, s) { | ||||
|           throw NativeCryptoException( | ||||
|             message: '${e.message} on chunk #$i', | ||||
|             code: e.code, | ||||
|             stackTrace: s, | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|       return CipherTextWrapper.list(cipherTexts); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Checks if the [CipherText] is a single [CipherText]. | ||||
|   bool get isSingle => _single.isNotNull; | ||||
| 
 | ||||
|   /// Checks if the [CipherText] is a [List] of [CipherText]. | ||||
|   bool get isList => _list.isNotNull; | ||||
| 
 | ||||
|   /// Gets the [CipherText] if it's a single one. | ||||
|   /// | ||||
|   /// Throws [NativeCryptoException] with | ||||
|   /// [NativeCryptoExceptionCode.invalid_data] if it's not a single one. | ||||
|   CipherText get single { | ||||
|     if (isSingle) { | ||||
|       return _single!; | ||||
|     } else { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'CipherTextWrapper is not single', | ||||
|         code: NativeCryptoExceptionCode.invalid_data.code, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the [List] of [CipherText] if it's a list. | ||||
|   /// | ||||
|   /// Throws [NativeCryptoException] with | ||||
|   /// [NativeCryptoExceptionCode.invalid_data] if it's not a list. | ||||
|   List<CipherText> get list { | ||||
|     if (isList) { | ||||
|       return _list!; | ||||
|     } else { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'CipherTextWrapper is not list', | ||||
|         code: NativeCryptoExceptionCode.invalid_data.code, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the raw [Uint8List] of the [CipherText] or [List] of [CipherText]. | ||||
|   Uint8List get bytes { | ||||
|     if (isSingle) { | ||||
|       return single.bytes; | ||||
|     } else { | ||||
|       return list.map((cipherText) => cipherText.bytes).toList().combine(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the number of parts of the [CipherText] or [List] of [CipherText]. | ||||
|   /// | ||||
|   /// Check [Cipher.bytesCountPerChunk] for more information. | ||||
|   int get chunkCount { | ||||
|     _single.isNull; | ||||
|     if (_single.isNotNull) { | ||||
|       return 1; | ||||
|     } else { | ||||
|       return _list?.length ?? 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the [CipherText] or the [List] of [CipherText]. | ||||
|   /// | ||||
|   /// Throws [NativeCryptoException] with | ||||
|   /// [NativeCryptoExceptionCode.invalid_data] if it's not a single or a list or | ||||
|   /// if [T] is not [CipherText] or [List] of [CipherText]. | ||||
|   T unwrap<T>() { | ||||
|     if (isSingle && T == CipherText) { | ||||
|       return single as T; | ||||
|     } else if (isList && T == List<CipherText>) { | ||||
|       return list as T; | ||||
|     } else { | ||||
|       final String type = | ||||
|           isSingle ? 'CipherText' : (isList ? 'List<CipherText>' : 'unknown'); | ||||
|       throw NativeCryptoException( | ||||
|         message: 'CipherTextWrapper is not a $T but a $type, ' | ||||
|             'you should use unwrap<$type>()', | ||||
|         code: NativeCryptoExceptionCode.invalid_data.code, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void add(CipherText cipherText) { | ||||
|     if (isSingle) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'CipherTextWrapper is already single', | ||||
|         code: NativeCryptoExceptionCode.invalid_data.code, | ||||
|       ); | ||||
|     } else if (isList) { | ||||
|       _list!.add(cipherText); | ||||
|     } else { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'CipherTextWrapper is not single or list', | ||||
|         code: NativeCryptoExceptionCode.invalid_data.code, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										13
									
								
								packages/native_crypto/lib/src/core/constants/constants.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/native_crypto/lib/src/core/constants/constants.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| abstract class Constants { | ||||
|   /// The default chunk size in bytes used for encryption and decryption. | ||||
|   static const int defaultChunkSize = 33554432; | ||||
| 
 | ||||
|   static const int aesGcmNonceLength = 12; | ||||
|   static const int aesGcmTagLength = 16; | ||||
| } | ||||
| @ -1,11 +1,11 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: core.dart | ||||
| // Created Date: 23/05/2022 23:05:26 | ||||
| // Last Modified: 26/05/2022 17:10:25 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| export 'cipher_text.dart'; | ||||
| export 'cipher_text_wrapper.dart'; | ||||
| export './constants/constants.dart'; | ||||
| export './enums/enums.dart'; | ||||
| export './extensions/extensions.dart'; | ||||
| export './utils/utils.dart'; | ||||
| export 'utils/platform.dart'; | ||||
|  | ||||
							
								
								
									
										17
									
								
								packages/native_crypto/lib/src/core/enums/encoding.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/native_crypto/lib/src/core/enums/encoding.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| /// An encoding used to convert a byte array to a string and vice-versa. | ||||
| enum Encoding { | ||||
|   /// UTF-8 encoding, as defined by the Unicode standard. | ||||
|   utf8, | ||||
|   /// UTF-16 encoding, as defined by the Unicode standard. | ||||
|   utf16, | ||||
|   /// Base64 encoding, as defined by RFC 4648. | ||||
|   base64, | ||||
|   /// Hexadecimal encoding. | ||||
|   base16, | ||||
| } | ||||
							
								
								
									
										8
									
								
								packages/native_crypto/lib/src/core/enums/enums.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/native_crypto/lib/src/core/enums/enums.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| export 'encoding.dart'; | ||||
| export 'hash_algorithm.dart'; | ||||
| @ -0,0 +1,15 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| /// The hash algorithm to use in Message Digest and HMAC. | ||||
| enum HashAlgorithm { | ||||
|   /// The SHA-256 hash algorithm. | ||||
|   sha256, | ||||
|   /// The SHA-384 hash algorithm. | ||||
|   sha384, | ||||
|   /// The SHA-512 hash algorithm. | ||||
|   sha512, | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| //  | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| export 'list_int_extension.dart'; | ||||
| export 'list_uint8_list_extension.dart'; | ||||
| export 'string_extension.dart'; | ||||
| export 'uint8_list_extension.dart'; | ||||
| @ -0,0 +1,12 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| extension ListIntExtension on List<int> { | ||||
|   /// Converts a [List] of int to a [Uint8List]. | ||||
|   Uint8List toTypedList() => Uint8List.fromList(this); | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart'; | ||||
| 
 | ||||
| extension ListUint8ListExtension on List<Uint8List> { | ||||
|   /// Reduce a [List] of [Uint8List] to a [Uint8List]. | ||||
|   Uint8List combine() { | ||||
|     if (isEmpty) { | ||||
|       return Uint8List(0); | ||||
|     } | ||||
|     return reduce((value, element) => value | element); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/enums/encoding.dart'; | ||||
| import 'package:native_crypto/src/core/extensions/list_int_extension.dart'; | ||||
| 
 | ||||
| extension StringExtension on String { | ||||
|   /// Converts a [String] to a [Uint8List] using the specified [Encoding]. | ||||
|   Uint8List toBytes({Encoding from = Encoding.utf16}) { | ||||
|     Uint8List bytes; | ||||
|     switch (from) { | ||||
|       case Encoding.utf8: | ||||
|         bytes = utf8.encode(this).toTypedList(); | ||||
|         break; | ||||
|       case Encoding.utf16: | ||||
|         bytes = runes.toList().toTypedList(); | ||||
|         break; | ||||
|       case Encoding.base64: | ||||
|         bytes = base64.decode(this); | ||||
|         break; | ||||
|       case Encoding.base16: | ||||
|         assert(length.isEven, 'String needs to be an even length.'); | ||||
|         bytes = List.generate( | ||||
|           length ~/ 2, | ||||
|           (i) => int.parse(substring(i * 2, (i * 2) + 2), radix: 16), | ||||
|         ).toList().toTypedList(); | ||||
|     } | ||||
|     return bytes; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,69 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/enums/encoding.dart'; | ||||
| import 'package:native_crypto/src/core/extensions/list_int_extension.dart'; | ||||
| 
 | ||||
| extension Uint8ListExtension on Uint8List { | ||||
|   /// Returns a concatenation of this with the other [Uint8List]. | ||||
|   Uint8List plus(Uint8List other) => [...this, ...other].toTypedList(); | ||||
| 
 | ||||
|   /// Returns a concatenation of this with the other | ||||
|   /// [Uint8List] using the `|` operator as a shortcut. | ||||
|   Uint8List operator |(Uint8List other) => plus(other); | ||||
| 
 | ||||
|   /// Returns a sublist of this from the [start] index to the [end] index. | ||||
|   /// If [end] is greater than the length of the list, it is set to the length. | ||||
|   Uint8List trySublist(int start, [int? end]) { | ||||
|     if (isEmpty) { | ||||
|       return this; | ||||
|     } | ||||
| 
 | ||||
|     int ending = end ?? length; | ||||
|     if (ending > length) { | ||||
|       ending = length; | ||||
|     } | ||||
| 
 | ||||
|     return sublist(start, ending); | ||||
|   } | ||||
| 
 | ||||
|   /// Returns a [List] of [Uint8List] of the specified [chunkSize]. | ||||
|   List<Uint8List> chunked(int chunkSize) { | ||||
|     if (isEmpty) { | ||||
|       return []; | ||||
|     } | ||||
| 
 | ||||
|     return List.generate( | ||||
|       (length / chunkSize).ceil(), | ||||
|       (i) => trySublist(i * chunkSize, (i * chunkSize) + chunkSize), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// Converts a [Uint8List] to a [String] using the specified [Encoding]. | ||||
|   String toStr({Encoding to = Encoding.utf16}) { | ||||
|     String str; | ||||
|     switch (to) { | ||||
|       case Encoding.utf8: | ||||
|         str = utf8.decode(this); | ||||
|         break; | ||||
|       case Encoding.utf16: | ||||
|         str = String.fromCharCodes(this); | ||||
|         break; | ||||
|       case Encoding.base64: | ||||
|         str = base64.encode(this); | ||||
|         break; | ||||
|       case Encoding.base16: | ||||
|         str = List.generate( | ||||
|           length, | ||||
|           (i) => this[i].toRadixString(16).padLeft(2, '0'), | ||||
|         ).join(); | ||||
|     } | ||||
|     return str; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										12
									
								
								packages/native_crypto/lib/src/core/utils/chunk_factory.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/native_crypto/lib/src/core/utils/chunk_factory.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/domain/cipher_chunk.dart'; | ||||
| 
 | ||||
| /// A factory that creates a [CipherChunk] of type [T] from a [Uint8List]. | ||||
| typedef ChunkFactory<T extends CipherChunk> = T Function(Uint8List chunk); | ||||
							
								
								
									
										47
									
								
								packages/native_crypto/lib/src/core/utils/cipher_text.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								packages/native_crypto/lib/src/core/utils/cipher_text.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/constants/constants.dart'; | ||||
| import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart'; | ||||
| import 'package:native_crypto/src/core/utils/chunk_factory.dart'; | ||||
| import 'package:native_crypto/src/domain/byte_array.dart'; | ||||
| import 'package:native_crypto/src/domain/cipher_chunk.dart'; | ||||
| 
 | ||||
| /// {@template cipher_text} | ||||
| /// A [CipherText] is a [ByteArray] that is used to store a text encrypted by a | ||||
| /// Cipher. | ||||
| /// {@endtemplate} | ||||
| class CipherText<T extends CipherChunk> extends ByteArray { | ||||
|   /// {@macro cipher_text} | ||||
|   CipherText( | ||||
|     super.bytes, { | ||||
|     required ChunkFactory<T> this.chunkFactory, | ||||
|     this.chunkSize = Constants.defaultChunkSize, | ||||
|   }) : chunks = bytes.chunked(chunkSize).map(chunkFactory).toList(); | ||||
| 
 | ||||
|   /// Creates a [CipherText] from a [List<T>] of [CipherChunk]. | ||||
|   CipherText.fromChunks( | ||||
|     this.chunks, { | ||||
|     required ChunkFactory<T> this.chunkFactory, | ||||
|     this.chunkSize = Constants.defaultChunkSize, | ||||
|   }) : super( | ||||
|           chunks.fold<Uint8List>( | ||||
|             Uint8List(0), | ||||
|             (acc, chunk) => acc | chunk.bytes, | ||||
|           ), | ||||
|         ); | ||||
| 
 | ||||
|   /// Factory used to create [CipherChunk] from an Uint8List. | ||||
|   final ChunkFactory<T>? chunkFactory; | ||||
| 
 | ||||
|   /// List of [CipherChunk] that compose the [CipherText]. | ||||
|   final List<T> chunks; | ||||
| 
 | ||||
|   /// Size of one chunk. | ||||
|   final int chunkSize; | ||||
| } | ||||
							
								
								
									
										9
									
								
								packages/native_crypto/lib/src/core/utils/platform.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/native_crypto/lib/src/core/utils/platform.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| NativeCryptoPlatform platform = NativeCryptoPlatform.instance; | ||||
							
								
								
									
										8
									
								
								packages/native_crypto/lib/src/core/utils/utils.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/native_crypto/lib/src/core/utils/utils.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| //  | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| export 'chunk_factory.dart'; | ||||
| export 'cipher_text.dart'; | ||||
							
								
								
									
										8
									
								
								packages/native_crypto/lib/src/digest/digest.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/native_crypto/lib/src/digest/digest.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| export 'hash.dart'; | ||||
| export 'hmac.dart'; | ||||
							
								
								
									
										62
									
								
								packages/native_crypto/lib/src/digest/hash.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								packages/native_crypto/lib/src/digest/hash.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/enums/hash_algorithm.dart'; | ||||
| import 'package:native_crypto/src/core/utils/platform.dart'; | ||||
| import 'package:native_crypto/src/domain/hash.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| class _Hash extends Hash { | ||||
|   const _Hash(this.algorithm); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List> digest(Uint8List data) async { | ||||
|     final hash = await platform.hash(data, algorithm: algorithm.name); | ||||
| 
 | ||||
|     // TODO(hpcl): move these checks to the platform interface | ||||
|     if (hash == null) { | ||||
|       throw const NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.nullError, | ||||
|         message: 'Platform returned null bytes', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (hash.isEmpty) { | ||||
|       throw const NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.invalidData, | ||||
|         message: 'Platform returned no data.', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return hash; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   final HashAlgorithm algorithm; | ||||
| } | ||||
| 
 | ||||
| /// A [Hash] that uses the SHA-256 algorithm. | ||||
| class Sha256 extends _Hash { | ||||
|   factory Sha256() => _instance ??= Sha256._(); | ||||
|   Sha256._() : super(HashAlgorithm.sha256); | ||||
|   static Sha256? _instance; | ||||
| } | ||||
| 
 | ||||
| /// A [Hash] that uses the SHA-384 algorithm. | ||||
| class Sha384 extends _Hash { | ||||
|   factory Sha384() => _instance ??= Sha384._(); | ||||
|   Sha384._() : super(HashAlgorithm.sha384); | ||||
|   static Sha384? _instance; | ||||
| } | ||||
| 
 | ||||
| /// A [Hash] that uses the SHA-512 algorithm. | ||||
| class Sha512 extends _Hash { | ||||
|   factory Sha512() => _instance ??= Sha512._(); | ||||
|   Sha512._() : super(HashAlgorithm.sha512); | ||||
|   static Sha512? _instance; | ||||
| } | ||||
							
								
								
									
										67
									
								
								packages/native_crypto/lib/src/digest/hmac.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								packages/native_crypto/lib/src/digest/hmac.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/enums/hash_algorithm.dart'; | ||||
| import 'package:native_crypto/src/core/utils/platform.dart'; | ||||
| import 'package:native_crypto/src/domain/hmac.dart'; | ||||
| import 'package:native_crypto/src/keys/secret_key.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| class _Hmac extends Hmac { | ||||
|   const _Hmac(this.algorithm); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List> digest(Uint8List data, SecretKey key) async { | ||||
|     final hmac = await platform.hmac( | ||||
|       data, | ||||
|       key: key.bytes, | ||||
|       algorithm: algorithm.name, | ||||
|     ); | ||||
| 
 | ||||
|     // TODO(hpcl): move these checks to the platform interface | ||||
|     if (hmac == null) { | ||||
|       throw const NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.nullError, | ||||
|         message: 'Platform returned null bytes', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (hmac.isEmpty) { | ||||
|       throw const NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.invalidData, | ||||
|         message: 'Platform returned no data.', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return hmac; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   final HashAlgorithm algorithm; | ||||
| } | ||||
| 
 | ||||
| /// A [Hmac] that uses the SHA-256 algorithm. | ||||
| class HmacSha256 extends _Hmac { | ||||
|   factory HmacSha256() => _instance ??= HmacSha256._(); | ||||
|   HmacSha256._() : super(HashAlgorithm.sha256); | ||||
|   static HmacSha256? _instance; | ||||
| } | ||||
| 
 | ||||
| /// A [Hmac] that uses the SHA-384 algorithm. | ||||
| class HmacSha384 extends _Hmac { | ||||
|   factory HmacSha384() => _instance ??= HmacSha384._(); | ||||
|   HmacSha384._() : super(HashAlgorithm.sha384); | ||||
|   static HmacSha384? _instance; | ||||
| } | ||||
| 
 | ||||
| /// A [Hmac] that uses the SHA-512 algorithm. | ||||
| class HmacSha512 extends _Hmac { | ||||
|   factory HmacSha512() => _instance ??= HmacSha512._(); | ||||
|   HmacSha512._() : super(HashAlgorithm.sha512); | ||||
|   static HmacSha512? _instance; | ||||
| } | ||||
							
								
								
									
										37
									
								
								packages/native_crypto/lib/src/domain/base_key.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								packages/native_crypto/lib/src/domain/base_key.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'package:native_crypto/src/domain/byte_array.dart'; | ||||
| 
 | ||||
| /// {@template base_key} | ||||
| /// Represents a key in NativeCrypto. | ||||
| /// | ||||
| /// [BaseKey] is a [ByteArray] that can be used to store keys. | ||||
| /// | ||||
| /// This interface is implemented by all the key classes. | ||||
| ///  | ||||
| /// Note: [BaseKey] is named [BaseKey] instead of Key to avoid conflicts with | ||||
| /// the Key class from Flutter. | ||||
| /// {@endtemplate} | ||||
| abstract class BaseKey extends ByteArray { | ||||
|   /// {@macro base_key} | ||||
|   const BaseKey(super.bytes); | ||||
| 
 | ||||
|   /// Creates a [BaseKey] from a [List<int>]. | ||||
|   BaseKey.fromList(super.list) : super.fromList(); | ||||
| 
 | ||||
|   /// Creates a [BaseKey] from a [String] encoded in UTF-8. | ||||
|   BaseKey.fromUtf8(super.encoded) : super.fromUtf8(); | ||||
| 
 | ||||
|   /// Creates a [BaseKey] from a [String] encoded in UTF-16. | ||||
|   BaseKey.fromUtf16(super.encoded) : super.fromUtf16(); | ||||
| 
 | ||||
|   /// Creates a [BaseKey] from a [String] encoded in Hexadecimal. | ||||
|   BaseKey.fromBase16(super.encoded) : super.fromBase16(); | ||||
| 
 | ||||
|   /// Creates a [BaseKey] from a [String] encoded in Base64. | ||||
|   BaseKey.fromBase64(super.encoded) : super.fromBase64(); | ||||
| } | ||||
| @ -1,48 +1,49 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: byte_array.dart | ||||
| // Created Date: 16/12/2021 17:54:16 | ||||
| // Last Modified: 26/05/2022 17:13:27 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:native_crypto/src/utils/encoding.dart'; | ||||
| import 'package:native_crypto/src/utils/extensions.dart'; | ||||
| import 'package:equatable/equatable.dart'; | ||||
| import 'package:native_crypto/src/core/enums/encoding.dart'; | ||||
| import 'package:native_crypto/src/core/extensions/list_int_extension.dart'; | ||||
| import 'package:native_crypto/src/core/extensions/string_extension.dart'; | ||||
| import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart'; | ||||
| 
 | ||||
| /// {@template byte_array} | ||||
| /// Represents a byte array. | ||||
| /// | ||||
| /// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods. | ||||
| @immutable | ||||
| abstract class ByteArray { | ||||
|   final Uint8List _bytes; | ||||
| 
 | ||||
|   /// Creates a [ByteArray] from a [Uint8List]. | ||||
| /// {@endtemplate} | ||||
| abstract class ByteArray extends Equatable { | ||||
|   /// {@macro byte_array} | ||||
|   const ByteArray(this._bytes); | ||||
| 
 | ||||
|   /// Creates a [ByteArray] object from a hexdecimal string. | ||||
|   ByteArray.fromBase16(String encoded) | ||||
|       : _bytes = encoded.toBytes(from: Encoding.base16); | ||||
|   /// Creates a [ByteArray] object from a [List] of [int]. | ||||
|   ByteArray.fromList(List<int> list) : _bytes = list.toTypedList(); | ||||
| 
 | ||||
|   /// Creates a [ByteArray] object from a Base64 string. | ||||
|   ByteArray.fromBase64(String encoded) | ||||
|       : _bytes = encoded.toBytes(from: Encoding.base64); | ||||
|   /// Creates an empty [ByteArray] object from a length. | ||||
|   ByteArray.fromLength(int length, {int fill = 0}) | ||||
|       : _bytes = Uint8List(length)..fillRange(0, length, fill); | ||||
| 
 | ||||
|   /// Creates a [ByteArray] object from an UTF-16 string. | ||||
|   ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes(); | ||||
| 
 | ||||
|   /// Creates a [ByteArray] object from an UTF-8 string. | ||||
|   ByteArray.fromUtf8(String encoded) | ||||
|       : _bytes = encoded.toBytes(from: Encoding.utf8); | ||||
| 
 | ||||
|   /// Creates a [ByteArray] object from an UTF-16 string. | ||||
|   ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes(); | ||||
|   /// Creates a [ByteArray] object from a Base64 string. | ||||
|   ByteArray.fromBase64(String encoded) | ||||
|       : _bytes = encoded.toBytes(from: Encoding.base64); | ||||
| 
 | ||||
|   /// Creates an empty [ByteArray] object from a length. | ||||
|   ByteArray.fromLength(int length) : _bytes = Uint8List(length); | ||||
|   /// Creates a [ByteArray] object from a hexdecimal string. | ||||
|   ByteArray.fromBase16(String encoded) | ||||
|       : _bytes = encoded.toBytes(from: Encoding.base16); | ||||
| 
 | ||||
|   /// Creates a [ByteArray] object from a [List] of [int]. | ||||
|   ByteArray.fromList(List<int> list) : _bytes = list.toTypedList(); | ||||
|   final Uint8List _bytes; | ||||
| 
 | ||||
|   /// Gets the [ByteArray] bytes. | ||||
|   Uint8List get bytes => _bytes; | ||||
| @ -62,31 +63,6 @@ abstract class ByteArray { | ||||
|   /// Gets the [ByteArray] length in bytes. | ||||
|   int get length => _bytes.length; | ||||
| 
 | ||||
|   /// Gets the [ByteArray] length in bits. | ||||
|   int get bitLength => _bytes.length * 8; | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     if (other is ByteArray) { | ||||
|       for (int i = 0; i < _bytes.length; i++) { | ||||
|         if (_bytes[i] != other._bytes[i]) { | ||||
|           return false; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   int get hashCode { | ||||
|     int hash = 0; | ||||
|     for (int i = 0; i < _bytes.length; i++) { | ||||
|       hash = _bytes[i] + (hash << 6) + (hash << 16) - hash; | ||||
|     } | ||||
| 
 | ||||
|     return hash; | ||||
|   } | ||||
|   List<Object?> get props => [_bytes]; | ||||
| } | ||||
							
								
								
									
										32
									
								
								packages/native_crypto/lib/src/domain/cipher.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								packages/native_crypto/lib/src/domain/cipher.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:io'; | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/utils/cipher_text.dart'; | ||||
| import 'package:native_crypto/src/domain/cipher_chunk.dart'; | ||||
| 
 | ||||
| /// {@template cipher} | ||||
| /// Abstract class that defines the behavior of a Cipher. | ||||
| /// {@endtemplate} | ||||
| abstract class Cipher<T extends CipherChunk> { | ||||
|   /// {@macro cipher} | ||||
|   const Cipher(); | ||||
|   /// Encrypts a [Uint8List] and returns a [CipherText]. | ||||
|   Future<CipherText<T>> encrypt(Uint8List plainText); | ||||
| 
 | ||||
|   /// Decrypts a [CipherText] and returns a [Uint8List]. | ||||
|   Future<Uint8List> decrypt(CipherText<T> cipherText); | ||||
| 
 | ||||
|   /// Encrypts a File located at [plainTextFile] and saves the result | ||||
|   /// at [cipherTextFile]. | ||||
|   Future<void> encryptFile(File plainTextFile, File cipherTextFile); | ||||
| 
 | ||||
|   /// Decrypts a File located at [cipherTextFile] and saves the result | ||||
|   /// at [plainTextFile]. | ||||
|   Future<void> decryptFile(File cipherTextFile, File plainTextFile); | ||||
| } | ||||
							
								
								
									
										31
									
								
								packages/native_crypto/lib/src/domain/cipher_chunk.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/native_crypto/lib/src/domain/cipher_chunk.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'package:native_crypto/src/domain/byte_array.dart'; | ||||
| 
 | ||||
| /// {@template cipher_chunk} | ||||
| /// A [CipherChunk] is a [ByteArray] that is used to store a chunk of data | ||||
| /// encrypted by a Cipher. | ||||
| /// {@endtemplate} | ||||
| abstract class CipherChunk extends ByteArray { | ||||
|   /// {@macro cipher_chunk} | ||||
|   const CipherChunk(super.bytes); | ||||
| 
 | ||||
|   /// Creates a [CipherChunk] from a [List<int>]. | ||||
|   CipherChunk.fromList(super.list) : super.fromList(); | ||||
| 
 | ||||
|   /// Creates a [CipherChunk] from a [String] encoded in UTF-8. | ||||
|   CipherChunk.fromUtf8(super.encoded) : super.fromUtf8(); | ||||
| 
 | ||||
|   /// Creates a [CipherChunk] from a [String] encoded in UTF-16. | ||||
|   CipherChunk.fromUtf16(super.encoded) : super.fromUtf16(); | ||||
| 
 | ||||
|   /// Creates a [CipherChunk] from a [String] encoded in Hexadecimal. | ||||
|   CipherChunk.fromBase16(super.encoded) : super.fromBase16(); | ||||
| 
 | ||||
|   /// Creates a [CipherChunk] from a [String] encoded in Base64. | ||||
|   CipherChunk.fromBase64(super.encoded) : super.fromBase64(); | ||||
| } | ||||
							
								
								
									
										14
									
								
								packages/native_crypto/lib/src/domain/domain.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/native_crypto/lib/src/domain/domain.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| //  | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| export 'base_key.dart'; | ||||
| export 'byte_array.dart'; | ||||
| export 'cipher.dart'; | ||||
| export 'cipher_chunk.dart'; | ||||
| export 'hash.dart'; | ||||
| export 'hmac.dart'; | ||||
| export 'key_derivation_function.dart'; | ||||
| export 'random.dart'; | ||||
							
								
								
									
										24
									
								
								packages/native_crypto/lib/src/domain/hash.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								packages/native_crypto/lib/src/domain/hash.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/enums/hash_algorithm.dart'; | ||||
| 
 | ||||
| /// {@template hash} | ||||
| /// A [Hash] is a one-way function that takes arbitrary-sized data and | ||||
| /// outputs a fixed-sized hash value. | ||||
| /// {@endtemplate} | ||||
| abstract class Hash { | ||||
|   /// {@macro hash} | ||||
|   const Hash(); | ||||
| 
 | ||||
|   /// The [HashAlgorithm] used by this [Hash]. | ||||
|   HashAlgorithm get algorithm; | ||||
| 
 | ||||
|   /// Digests the given [data] and returns the hash. | ||||
|   Future<Uint8List> digest(Uint8List data); | ||||
| } | ||||
							
								
								
									
										25
									
								
								packages/native_crypto/lib/src/domain/hmac.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								packages/native_crypto/lib/src/domain/hmac.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/enums/hash_algorithm.dart'; | ||||
| import 'package:native_crypto/src/keys/secret_key.dart'; | ||||
| 
 | ||||
| /// {@template hmac} | ||||
| /// A HMAC is a cryptographic hash that uses a key to sign a message. | ||||
| /// The receiver verifies the hash by recomputing it using the same key. | ||||
| /// {@endtemplate} | ||||
| abstract class Hmac { | ||||
|   /// {@macro hmac} | ||||
|   const Hmac(); | ||||
| 
 | ||||
|   /// The [HashAlgorithm] used by this [Hmac]. | ||||
|   HashAlgorithm get algorithm; | ||||
| 
 | ||||
|   /// Digests the given [data] and returns the hmac. | ||||
|   Future<Uint8List> digest(Uint8List data, SecretKey key); | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| /// {@template key_derivation_function} | ||||
| /// A [KeyDerivationFunction] is a function that derives a key from an | ||||
| /// [Uint8List] key material. | ||||
| /// {@endtemplate} | ||||
| abstract class KeyDerivationFunction { | ||||
|   /// {@macro key_derivation_function} | ||||
|   const KeyDerivationFunction(); | ||||
| 
 | ||||
|   /// Derives a key from a [keyMaterial]. | ||||
|   Future<Uint8List> derive( | ||||
|     Uint8List keyMaterial, | ||||
|   ); | ||||
| 
 | ||||
|   /// Verifies a [keyMaterial] against an [expected] value. | ||||
|   Future<bool> verify( | ||||
|     Uint8List keyMaterial, | ||||
|     Uint8List expected, | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										18
									
								
								packages/native_crypto/lib/src/domain/random.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/native_crypto/lib/src/domain/random.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| /// {@template random} | ||||
| /// A [Random] is a source of random bytes. | ||||
| /// {@endtemplate} | ||||
| abstract class Random { | ||||
|   /// {@macro random} | ||||
|   const Random(); | ||||
| 
 | ||||
|   /// Generates a random [Uint8List] of [length] bytes. | ||||
|   Future<Uint8List> generate(int length); | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: base_key.dart | ||||
| // Created Date: 16/12/2021 16:28:00 | ||||
| // Last Modified: 26/05/2022 17:40:38 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| 
 | ||||
| import 'package:native_crypto/src/interfaces/byte_array.dart'; | ||||
| 
 | ||||
| /// Represents a key in NativeCrypto. | ||||
| /// | ||||
| /// [BaseKey] is a [ByteArray] that can be used to store keys. | ||||
| /// | ||||
| /// This interface is implemented by all the key classes. | ||||
| abstract class BaseKey extends ByteArray { | ||||
|   const BaseKey(super.bytes); | ||||
|   BaseKey.fromBase16(super.encoded) : super.fromBase16(); | ||||
|   BaseKey.fromBase64(super.encoded) : super.fromBase64(); | ||||
|   BaseKey.fromUtf8(super.input) : super.fromUtf8(); | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: builder.dart | ||||
| // Created Date: 28/12/2021 12:02:34 | ||||
| // Last Modified: 23/05/2022 22:38:44 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| 
 | ||||
| // ignore_for_file: one_member_abstracts | ||||
| 
 | ||||
| abstract class Builder<T> { | ||||
|   Future<T> build(); | ||||
| } | ||||
| @ -1,53 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: cipher.dart | ||||
| // Created Date: 16/12/2021 16:28:00 | ||||
| // Last Modified: 26/05/2022 21:21:07 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; | ||||
| import 'package:native_crypto/src/utils/cipher_algorithm.dart'; | ||||
| 
 | ||||
| /// Represents a cipher in NativeCrypto. | ||||
| /// | ||||
| /// In cryptography, a [Cipher] is an algorithm for performing encryption | ||||
| /// or decryption - a series of well-defined steps that can | ||||
| /// be followed as a procedure. | ||||
| /// | ||||
| /// This interface is implemented by all the ciphers in NativeCrypto. | ||||
| abstract class Cipher { | ||||
|   static const int _bytesCountPerChunkDefault = 33554432; | ||||
|   static int _bytesCountPerChunk = _bytesCountPerChunkDefault; | ||||
| 
 | ||||
|   /// Returns the default number of bytes per chunk. | ||||
|   static int get defaultBytesCountPerChunk => _bytesCountPerChunkDefault; | ||||
| 
 | ||||
|   /// Returns the size of a chunk of data | ||||
|   /// that can be processed by the [Cipher]. | ||||
|   static int get bytesCountPerChunk => Cipher._bytesCountPerChunk; | ||||
| 
 | ||||
|   /// Sets the size of a chunk of data | ||||
|   /// that can be processed by the [Cipher]. | ||||
|   static set bytesCountPerChunk(int bytesCount) { | ||||
|     _bytesCountPerChunk = bytesCount; | ||||
|   } | ||||
| 
 | ||||
|   /// Returns the standard algorithm for this [Cipher]. | ||||
|   CipherAlgorithm get algorithm; | ||||
| 
 | ||||
|   /// Encrypts the [data]. | ||||
|   /// | ||||
|   /// Takes [Uint8List] data as parameter. | ||||
|   /// Returns a [CipherTextWrapper]. | ||||
|   Future<CipherTextWrapper> encrypt(Uint8List data); | ||||
| 
 | ||||
|   /// Decrypts the [cipherText] | ||||
|   /// | ||||
|   /// Takes [CipherTextWrapper] as parameter. | ||||
|   /// And returns plain text data as [Uint8List]. | ||||
|   Future<Uint8List> decrypt(CipherTextWrapper cipherText); | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: interfaces.dart | ||||
| // Created Date: 23/05/2022 23:03:47 | ||||
| // Last Modified: 26/05/2022 17:41:06 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| export 'base_key.dart'; | ||||
| export 'builder.dart'; | ||||
| export 'byte_array.dart'; | ||||
| export 'cipher.dart'; | ||||
| export 'keyderivation.dart'; | ||||
| @ -1,23 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: kdf.dart | ||||
| // Created Date: 18/12/2021 11:56:43 | ||||
| // Last Modified: 26/05/2022 18:47:15 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| 
 | ||||
| import 'package:native_crypto/src/keys/secret_key.dart'; | ||||
| import 'package:native_crypto/src/utils/kdf_algorithm.dart'; | ||||
| 
 | ||||
| /// Represents a Key Derivation Function (KDF) in NativeCrypto. | ||||
| /// | ||||
| /// [KeyDerivation] function is a function that takes some | ||||
| /// parameters and returns a [SecretKey]. | ||||
| abstract class KeyDerivation { | ||||
|   /// Returns the standard algorithm for this key derivation function | ||||
|   KdfAlgorithm get algorithm; | ||||
| 
 | ||||
|   /// Derive a [SecretKey]. | ||||
|   Future<SecretKey> derive(); | ||||
| } | ||||
| @ -1,10 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: kdf.dart | ||||
| // Created Date: 23/05/2022 22:57:11 | ||||
| // Last Modified: 23/05/2022 23:04:15 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| export 'pbkdf2.dart'; | ||||
| @ -1,115 +1,93 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: pbkdf2.dart | ||||
| // Created Date: 17/12/2021 14:50:42 | ||||
| // Last Modified: 26/05/2022 23:19:46 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/interfaces/keyderivation.dart'; | ||||
| import 'package:native_crypto/src/keys/secret_key.dart'; | ||||
| import 'package:native_crypto/src/platform.dart'; | ||||
| import 'package:native_crypto/src/utils/extensions.dart'; | ||||
| import 'package:native_crypto/src/utils/hash_algorithm.dart'; | ||||
| import 'package:native_crypto/src/utils/kdf_algorithm.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| /// Represent a PBKDF2 Key Derivation Function (KDF) in NativeCrypto. | ||||
| /// | ||||
| /// [Pbkdf2] is a function that takes password, salt, iteration count and | ||||
| /// derive a [SecretKey] of specified length. | ||||
| class Pbkdf2 extends KeyDerivation { | ||||
|   final int _keyBytesCount; | ||||
|   final int _iterations; | ||||
|   final HashAlgorithm _hash; | ||||
| /// {@template pbkdf2} | ||||
| /// A PBKDF2 is a password-based key derivation function. | ||||
| /// {@endtemplate} | ||||
| class Pbkdf2 extends KeyDerivationFunction { | ||||
|   /// {@macro pbkdf2} | ||||
|   const Pbkdf2({ | ||||
|     required this.hashAlgorithm, | ||||
|     required this.iterations, | ||||
|     required this.salt, | ||||
|     required this.length, | ||||
|   }); | ||||
| 
 | ||||
|   /// The [HashAlgorithm] used by this [Pbkdf2]. | ||||
|   final HashAlgorithm hashAlgorithm; | ||||
| 
 | ||||
|   /// The number of iterations. | ||||
|   final int iterations; | ||||
| 
 | ||||
|   /// The salt. | ||||
|   final Uint8List salt; | ||||
| 
 | ||||
|   /// The length of the derived key in bytes. | ||||
|   final int length; | ||||
| 
 | ||||
|   @override | ||||
|   KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2; | ||||
|   Future<Uint8List> derive(Uint8List keyMaterial) async { | ||||
|     if (length == 0) { | ||||
|       // If the length is 0, return an empty list | ||||
|       return Uint8List(0); | ||||
|     } | ||||
| 
 | ||||
|   Pbkdf2({ | ||||
|     required int keyBytesCount, | ||||
|     required int iterations, | ||||
|     HashAlgorithm algorithm = HashAlgorithm.sha256, | ||||
|   })  : _keyBytesCount = keyBytesCount, | ||||
|         _iterations = iterations, | ||||
|         _hash = algorithm { | ||||
|     if (keyBytesCount < 0) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'keyBytesCount must be positive.', | ||||
|         code: NativeCryptoExceptionCode.invalid_argument.code, | ||||
|       ); | ||||
|     if (length < 0) { | ||||
|       throw ArgumentError.value(length, 'length', 'must be positive'); | ||||
|     } | ||||
| 
 | ||||
|     if (iterations <= 0) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'iterations must be strictly positive.', | ||||
|         code: NativeCryptoExceptionCode.invalid_argument.code, | ||||
|       throw ArgumentError.value( | ||||
|         iterations, | ||||
|         'iterations', | ||||
|         'must greater than 0', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     // Call the platform interface to derive the key | ||||
|     final bytes = await platform.pbkdf2( | ||||
|       password: keyMaterial, | ||||
|       salt: salt, | ||||
|       iterations: iterations, | ||||
|       length: length, | ||||
|       hashAlgorithm: hashAlgorithm.name, | ||||
|     ); | ||||
| 
 | ||||
|     // TODO(hpcl): move these checks to the platform interface | ||||
|     if (bytes == null) { | ||||
|       throw const NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.nullError, | ||||
|         message: 'Platform returned null bytes', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (bytes.length != length) { | ||||
|       throw NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.invalidData, | ||||
|         message: 'Platform returned bytes of wrong length: ' | ||||
|             'expected $length, got ${bytes.length}', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return bytes; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<SecretKey> derive({String? password, String? salt}) async { | ||||
|     Uint8List? derivation; | ||||
|   Future<bool> verify(Uint8List keyMaterial, Uint8List expected) => | ||||
|       derive(keyMaterial).then((actual) { | ||||
|         if (actual.length != expected.length) { | ||||
|           return false; | ||||
|         } | ||||
|         return listEquals(actual, expected); | ||||
|       }); | ||||
| 
 | ||||
|     if (_keyBytesCount == 0) { | ||||
|       return SecretKey(Uint8List(0)); | ||||
|     } | ||||
|     if (password.isNull) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Password cannot be null.', | ||||
|         code: NativeCryptoExceptionCode.invalid_argument.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (salt.isNull) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Salt cannot be null.', | ||||
|         code: NativeCryptoExceptionCode.invalid_argument.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       derivation = await platform.pbkdf2( | ||||
|         password!, | ||||
|         salt!, | ||||
|         _keyBytesCount, | ||||
|         _iterations, | ||||
|         _hash.name, | ||||
|       ); | ||||
|     } catch (e, s) { | ||||
|       throw NativeCryptoException( | ||||
|         message: '$e', | ||||
|         code: NativeCryptoExceptionCode.platform_throws.code, | ||||
|         stackTrace: s, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (derivation.isNull) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Failed to derive a key! Platform returned null.', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_null.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (derivation!.isEmpty) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Failed to derive a key! Platform returned no data.', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_empty_data.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (derivation.length != _keyBytesCount) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Failed to derive a key! Platform returned ' | ||||
|             '${derivation.length} bytes, but expected $_keyBytesCount bytes.', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_invalid_data.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return SecretKey(derivation); | ||||
|   } | ||||
|   Future<SecretKey> call({required String password}) => | ||||
|       derive(password.toBytes()).then(SecretKey.new); | ||||
| } | ||||
|  | ||||
| @ -1,10 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: keys.dart | ||||
| // Created Date: 23/05/2022 23:04:04 | ||||
| // Last Modified: 23/05/2022 23:04:07 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| export 'secret_key.dart'; | ||||
| @ -1,60 +1,43 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: secret_key.dart | ||||
| // Created Date: 28/12/2021 13:36:54 | ||||
| // Last Modified: 26/05/2022 23:13:10 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/interfaces/base_key.dart'; | ||||
| import 'package:native_crypto/src/interfaces/cipher.dart'; | ||||
| import 'package:native_crypto/src/platform.dart'; | ||||
| import 'package:native_crypto/src/utils/extensions.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| import 'package:native_crypto/src/domain/base_key.dart'; | ||||
| import 'package:native_crypto/src/random/secure_random.dart'; | ||||
| 
 | ||||
| /// {@template secret_key} | ||||
| /// Represents a secret key in NativeCrypto. | ||||
| /// | ||||
| /// [SecretKey] is a [BaseKey] that can be used to store secret keys. | ||||
| /// A [SecretKey] is a key that can be used to encrypt or decrypt data with | ||||
| /// a symmetric [Cipher]. | ||||
| /// a symmetric Cipher. | ||||
| /// {@endtemplate} | ||||
| class SecretKey extends BaseKey { | ||||
|   /// {@macro secret_key} | ||||
|   const SecretKey(super.bytes); | ||||
| 
 | ||||
|   /// Creates a [SecretKey] from a [List<int>]. | ||||
|   SecretKey.fromList(super.list) : super.fromList(); | ||||
| 
 | ||||
|   /// Creates a [SecretKey] from a [String] encoded in UTF-8. | ||||
|   SecretKey.fromUtf8(super.encoded) : super.fromUtf8(); | ||||
| 
 | ||||
|   /// Creates a [SecretKey] from a [String] encoded in UTF-16. | ||||
|   SecretKey.fromUtf16(super.encoded) : super.fromUtf16(); | ||||
| 
 | ||||
|   /// Creates a [SecretKey] from a [String] encoded in Hexadecimal. | ||||
|   SecretKey.fromBase16(super.encoded) : super.fromBase16(); | ||||
| 
 | ||||
|   /// Creates a [SecretKey] from a [String] encoded in Base64. | ||||
|   SecretKey.fromBase64(super.encoded) : super.fromBase64(); | ||||
|   SecretKey.fromUtf8(super.input) : super.fromUtf8(); | ||||
| 
 | ||||
|   static Future<SecretKey> fromSecureRandom(int bitsCount) async { | ||||
|     Uint8List? key; | ||||
|     if (bitsCount == 0) { | ||||
|       return SecretKey(Uint8List(0)); | ||||
|     } | ||||
|   /// Generates a random [SecretKey] of the given [length] in bytes. | ||||
|   static Future<SecretKey> fromSecureRandom(int length) async { | ||||
|     const random = SecureRandom(); | ||||
|     final bytes = await random.generate(length); | ||||
| 
 | ||||
|     try { | ||||
|       key = await platform.generateSecretKey(bitsCount); | ||||
|     } catch (e, s) { | ||||
|       throw NativeCryptoException( | ||||
|         message: '$e', | ||||
|         code: NativeCryptoExceptionCode.platform_throws.code, | ||||
|         stackTrace: s, | ||||
|       ); | ||||
|     } | ||||
|     if (key.isNull) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Failed to generate a secret key! Platform returned null.', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_null.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (key!.isEmpty) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Failed to generate a secret key! ' | ||||
|             'Platform returned no data.', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_empty_data.code, | ||||
|       ); | ||||
|     } | ||||
|     return SecretKey(key); | ||||
|     return SecretKey(bytes); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,12 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: platform.dart | ||||
| // Created Date: 27/12/2021 22:03:58 | ||||
| // Last Modified: 25/05/2022 10:09:18 | ||||
| // ----- | ||||
| // Copyright (c) 2021 | ||||
| 
 | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| NativeCryptoPlatform platform = NativeCryptoPlatform.instance; | ||||
							
								
								
									
										50
									
								
								packages/native_crypto/lib/src/random/secure_random.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								packages/native_crypto/lib/src/random/secure_random.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/core/utils/platform.dart'; | ||||
| import 'package:native_crypto/src/domain/random.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| /// {@template secure_random} | ||||
| /// A [SecureRandom] is a source of secure random bytes. | ||||
| /// {@endtemplate} | ||||
| class SecureRandom extends Random { | ||||
|   /// {@macro secure_random} | ||||
|   const SecureRandom(); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List> generate(int length) async { | ||||
|     if (length < 0) { | ||||
|       throw ArgumentError.value(length, 'length', 'must be positive'); | ||||
|     } | ||||
|     if (length == 0) { | ||||
|       // If the length is 0, return an empty list | ||||
|       return Uint8List(0); | ||||
|     } | ||||
|     // Call the platform interface to generate the secure random bytes | ||||
|     final bytes = await platform.generateSecureRandom(length); | ||||
| 
 | ||||
|     // TODO(hpcl): move these checks to the platform interface | ||||
|     if (bytes == null) { | ||||
|       throw const NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.nullError, | ||||
|         message: 'Platform returned null bytes', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (bytes.length != length) { | ||||
|       throw NativeCryptoException( | ||||
|         code: NativeCryptoExceptionCode.invalidData, | ||||
|         message: 'Platform returned bytes of wrong length: ' | ||||
|             'expected $length, got ${bytes.length}', | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return bytes; | ||||
|   } | ||||
| } | ||||
| @ -1,11 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: cipher_algorithm.dart | ||||
| // Created Date: 23/05/2022 22:07:54 | ||||
| // Last Modified: 26/05/2022 18:52:32 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| /// Represents different cipher algorithms | ||||
| enum CipherAlgorithm { aes } | ||||
| @ -1,10 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: encoding.dart | ||||
| // Created Date: 26/05/2022 12:12:34 | ||||
| // Last Modified: 26/05/2022 12:18:09 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| enum Encoding { utf8, utf16, base64, base16 } | ||||
| @ -1,101 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: extensions.dart | ||||
| // Created Date: 26/05/2022 12:12:48 | ||||
| // Last Modified: 27/05/2022 12:26:55 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer' as developer; | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/utils/encoding.dart'; | ||||
| 
 | ||||
| extension ObjectX on Object? { | ||||
|   /// Returns `true` if the object is `null`. | ||||
|   bool get isNull => this == null; | ||||
| 
 | ||||
|   /// Returns `true` if the object is **not** `null`. | ||||
|   bool get isNotNull => this != null; | ||||
| 
 | ||||
|   /// Prints the object to the console. | ||||
|   void log() => developer.log(toString()); | ||||
| } | ||||
| 
 | ||||
| extension ListIntX on List<int> { | ||||
|   /// Converts a [List] of int to a [Uint8List]. | ||||
|   Uint8List toTypedList() => Uint8List.fromList(this); | ||||
| } | ||||
| 
 | ||||
| extension ListUint8ListX on List<Uint8List> { | ||||
|   /// Reduce a [List] of [Uint8List] to a [Uint8List]. | ||||
|   Uint8List combine() { | ||||
|     if (isEmpty) return Uint8List(0); | ||||
|     return reduce((value, element) => value.plus(element)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| extension StringX on String { | ||||
|   /// Converts a [String] to a [Uint8List] using the specified [Encoding]. | ||||
|   Uint8List toBytes({final Encoding from = Encoding.utf16}) { | ||||
|     Uint8List bytes; | ||||
|     switch (from) { | ||||
|       case Encoding.utf8: | ||||
|         bytes = utf8.encode(this).toTypedList(); | ||||
|         break; | ||||
|       case Encoding.utf16: | ||||
|         bytes = runes.toList().toTypedList(); | ||||
|         break; | ||||
|       case Encoding.base64: | ||||
|         bytes = base64.decode(this); | ||||
|         break; | ||||
|       case Encoding.base16: | ||||
|         assert(length.isEven, 'String needs to be an even length.'); | ||||
|         bytes = List.generate( | ||||
|           length ~/ 2, | ||||
|           (i) => int.parse(substring(i * 2, (i * 2) + 2), radix: 16), | ||||
|         ).toList().toTypedList(); | ||||
|     } | ||||
|     return bytes; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| extension Uint8ListX on Uint8List { | ||||
|   /// Converts a [Uint8List] to a [String] using the specified [Encoding]. | ||||
|   String toStr({final Encoding to = Encoding.utf16}) { | ||||
|     String str; | ||||
|     switch (to) { | ||||
|       case Encoding.utf8: | ||||
|         str = utf8.decode(this); | ||||
|         break; | ||||
|       case Encoding.utf16: | ||||
|         str = String.fromCharCodes(this); | ||||
|         break; | ||||
|       case Encoding.base64: | ||||
|         str = base64.encode(this); | ||||
|         break; | ||||
|       case Encoding.base16: | ||||
|         str = List.generate( | ||||
|           length, | ||||
|           (i) => this[i].toRadixString(16).padLeft(2, '0'), | ||||
|         ).join(); | ||||
|     } | ||||
|     return str; | ||||
|   } | ||||
| 
 | ||||
|   /// Returns a concatenation of this with the other [Uint8List]. | ||||
|   Uint8List plus(final Uint8List other) => [...this, ...other].toTypedList(); | ||||
| 
 | ||||
|   /// Returns a sublist of this from the [start] index to the [end] index. | ||||
|   /// If [end] is greater than the length of the list, it is set to the length | ||||
|   Uint8List trySublist(int start, [int? end]) { | ||||
|     if (isEmpty) return this; | ||||
| 
 | ||||
|     int ending = end ?? length; | ||||
|     if (ending > length) ending = length; | ||||
| 
 | ||||
|     return sublist(start, ending); | ||||
|   } | ||||
| } | ||||
| @ -1,51 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: hash_algorithm.dart | ||||
| // Created Date: 23/05/2022 22:01:59 | ||||
| // Last Modified: 26/05/2022 22:59:04 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto/src/platform.dart'; | ||||
| import 'package:native_crypto/src/utils/extensions.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| /// Defines the hash algorithms. | ||||
| enum HashAlgorithm { | ||||
|   sha256, | ||||
|   sha384, | ||||
|   sha512; | ||||
| 
 | ||||
|   /// Digest the [data] using this [HashAlgorithm]. | ||||
|   Future<Uint8List> digest(Uint8List data) async { | ||||
|     Uint8List? hash; | ||||
|     try { | ||||
|       hash = await platform.digest(data, name); | ||||
|     } catch (e, s) { | ||||
|       throw NativeCryptoException( | ||||
|         message: '$e', | ||||
|         code: NativeCryptoExceptionCode.platform_throws.code, | ||||
|         stackTrace: s, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (hash.isNull) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Failed to digest data! Platform returned null.', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_null.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (hash!.isEmpty) { | ||||
|       throw NativeCryptoException( | ||||
|         message: 'Failed to digest data! Platform returned no data.', | ||||
|         code: NativeCryptoExceptionCode.platform_returned_empty_data.code, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return hash; | ||||
|   } | ||||
| } | ||||
| @ -1,11 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: kdf_algorithm.dart | ||||
| // Created Date: 23/05/2022 22:36:24 | ||||
| // Last Modified: 26/05/2022 18:53:50 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| /// Represents different key derivation functions | ||||
| enum KdfAlgorithm { pbkdf2 } | ||||
| @ -2,15 +2,14 @@ name: native_crypto | ||||
| description: Fast and secure cryptography for Flutter. | ||||
| version: 0.1.1 | ||||
| 
 | ||||
| publish_to: 'none' | ||||
| publish_to: "none" | ||||
| 
 | ||||
| environment: | ||||
|   sdk: ">=2.17.0 <3.0.0" | ||||
|   flutter: ">=2.5.0" | ||||
| 
 | ||||
| dependencies: | ||||
|   flutter: | ||||
|     sdk: flutter | ||||
|   flutter: { sdk: flutter } | ||||
| 
 | ||||
|   native_crypto_android: | ||||
|     git: | ||||
| @ -29,19 +28,19 @@ dependencies: | ||||
|       url: https://github.com/hugo-pcl/native-crypto-flutter.git | ||||
|       ref: native_crypto_platform_interface-v0.1.1 | ||||
|       path: packages/native_crypto_platform_interface | ||||
|   equatable: ^2.0.5 | ||||
| 
 | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|     sdk: flutter | ||||
|   flutter_test: { sdk: flutter } | ||||
| 
 | ||||
|   mockito: ^5.2.0 | ||||
|   plugin_platform_interface: ^2.1.2 | ||||
|   mockito: ^5.3.2 | ||||
|   plugin_platform_interface: ^2.1.3 | ||||
| 
 | ||||
|   wyatt_analysis: | ||||
|     git: | ||||
|       url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages | ||||
|       ref: wyatt_analysis-v2.1.0 | ||||
|       path: packages/wyatt_analysis | ||||
|     hosted: | ||||
|       url: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ | ||||
|       name: wyatt_analysis | ||||
|     version: 2.4.0 | ||||
| 
 | ||||
| flutter: | ||||
|   plugin: | ||||
|  | ||||
							
								
								
									
										8
									
								
								packages/native_crypto/pubspec_overrides.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/native_crypto/pubspec_overrides.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| # melos_managed_dependency_overrides: native_crypto_android,native_crypto_ios,native_crypto_platform_interface,native_crypto_web | ||||
| dependency_overrides: | ||||
|   native_crypto_android: | ||||
|     path: ../native_crypto_android | ||||
|   native_crypto_ios: | ||||
|     path: ../native_crypto_ios | ||||
|   native_crypto_platform_interface: | ||||
|     path: ../native_crypto_platform_interface | ||||
							
								
								
									
										193
									
								
								packages/native_crypto/test/mocks/mock_native_crypto_api.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								packages/native_crypto/test/mocks/mock_native_crypto_api.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| class MockNativeCryptoAPI implements NativeCryptoAPI { | ||||
|   static Uint8List? Function(int length)? generateSecureRandomFn; | ||||
| 
 | ||||
|   static Uint8List? Function( | ||||
|     Uint8List data, | ||||
|     String algorithm, | ||||
|   )? hashFn; | ||||
| 
 | ||||
|   static Uint8List? Function( | ||||
|     Uint8List data, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   )? hmacFn; | ||||
| 
 | ||||
|   static Uint8List? Function( | ||||
|     Uint8List cipherText, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   )? decryptFn; | ||||
| 
 | ||||
|   static bool? Function( | ||||
|     String cipherTextPath, | ||||
|     String plainTextPath, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   )? decryptFileFn; | ||||
| 
 | ||||
|   static Uint8List? Function( | ||||
|     Uint8List plainText, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   )? encryptFn; | ||||
| 
 | ||||
|   static bool? Function( | ||||
|     String plainTextPath, | ||||
|     String cipherTextPath, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   )? encryptFileFn; | ||||
| 
 | ||||
|   static Uint8List? Function( | ||||
|     Uint8List plainText, | ||||
|     Uint8List key, | ||||
|     Uint8List iv, | ||||
|     String algorithm, | ||||
|   )? encryptWithIVFn; | ||||
| 
 | ||||
|   static Uint8List? Function( | ||||
|     Uint8List password, | ||||
|     Uint8List salt, | ||||
|     int iterations, | ||||
|     int length, | ||||
|     String algorithm, | ||||
|   )? pbkdf2Fn; | ||||
| 
 | ||||
|   @override | ||||
|   Future<DecryptResponse> decrypt(DecryptRequest argRequest) async => | ||||
|       decryptFn != null | ||||
|           ? DecryptResponse( | ||||
|               plainText: decryptFn!( | ||||
|                 argRequest.cipherText!, | ||||
|                 argRequest.key!, | ||||
|                 argRequest.algorithm!, | ||||
|               ), | ||||
|             ) | ||||
|           : DecryptResponse( | ||||
|               plainText: Uint8List.fromList([1, 2, 3]), | ||||
|             ); | ||||
| 
 | ||||
|   @override | ||||
|   Future<DecryptFileResponse> decryptFile( | ||||
|     DecryptFileRequest argRequest, | ||||
|   ) async => | ||||
|       decryptFileFn != null | ||||
|           ? DecryptFileResponse( | ||||
|               success: decryptFileFn!( | ||||
|                 argRequest.cipherTextPath!, | ||||
|                 argRequest.plainTextPath!, | ||||
|                 argRequest.key!, | ||||
|                 argRequest.algorithm!, | ||||
|               ), | ||||
|             ) | ||||
|           : DecryptFileResponse(success: true); | ||||
| 
 | ||||
|   @override | ||||
|   Future<EncryptResponse> encrypt(EncryptRequest argRequest) async => | ||||
|       encryptFn != null | ||||
|           ? EncryptResponse( | ||||
|               cipherText: encryptFn!( | ||||
|                 argRequest.plainText!, | ||||
|                 argRequest.key!, | ||||
|                 argRequest.algorithm!, | ||||
|               ), | ||||
|             ) | ||||
|           : EncryptResponse( | ||||
|               cipherText: Uint8List.fromList([1, 2, 3]), | ||||
|             ); | ||||
| 
 | ||||
|   @override | ||||
|   Future<EncryptFileResponse> encryptFile( | ||||
|     EncryptFileRequest argRequest, | ||||
|   ) async => | ||||
|       encryptFileFn != null | ||||
|           ? EncryptFileResponse( | ||||
|               success: encryptFileFn!( | ||||
|                 argRequest.plainTextPath!, | ||||
|                 argRequest.cipherTextPath!, | ||||
|                 argRequest.key!, | ||||
|                 argRequest.algorithm!, | ||||
|               ), | ||||
|             ) | ||||
|           : EncryptFileResponse(success: true); | ||||
| 
 | ||||
|   @override | ||||
|   Future<EncryptResponse> encryptWithIV( | ||||
|     EncryptWithIVRequest argRequest, | ||||
|   ) async => | ||||
|       encryptWithIVFn != null | ||||
|           ? EncryptResponse( | ||||
|               cipherText: encryptWithIVFn!( | ||||
|                 argRequest.plainText!, | ||||
|                 argRequest.key!, | ||||
|                 argRequest.iv!, | ||||
|                 argRequest.algorithm!, | ||||
|               ), | ||||
|             ) | ||||
|           : EncryptResponse( | ||||
|               cipherText: Uint8List.fromList([1, 2, 3]), | ||||
|             ); | ||||
| 
 | ||||
|   @override | ||||
|   Future<GenerateSecureRandomResponse> generateSecureRandom( | ||||
|     GenerateSecureRandomRequest argRequest, | ||||
|   ) async => | ||||
|       generateSecureRandomFn != null | ||||
|           ? GenerateSecureRandomResponse( | ||||
|               random: generateSecureRandomFn!(argRequest.length!), | ||||
|             ) | ||||
|           : GenerateSecureRandomResponse( | ||||
|               random: Uint8List.fromList([1, 2, 3]), | ||||
|             ); | ||||
| 
 | ||||
|   @override | ||||
|   Future<HashResponse> hash(HashRequest argRequest) async => hashFn != null | ||||
|       ? HashResponse( | ||||
|           hash: hashFn!( | ||||
|             argRequest.data!, | ||||
|             argRequest.algorithm!, | ||||
|           ), | ||||
|         ) | ||||
|       : HashResponse( | ||||
|           hash: Uint8List.fromList([1, 2, 3]), | ||||
|         ); | ||||
| 
 | ||||
|   @override | ||||
|   Future<HmacResponse> hmac(HmacRequest argRequest) async => hmacFn != null | ||||
|       ? HmacResponse( | ||||
|           hmac: hmacFn!( | ||||
|             argRequest.data!, | ||||
|             argRequest.key!, | ||||
|             argRequest.algorithm!, | ||||
|           ), | ||||
|         ) | ||||
|       : HmacResponse( | ||||
|           hmac: Uint8List.fromList([1, 2, 3]), | ||||
|         ); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Pbkdf2Response> pbkdf2(Pbkdf2Request argRequest) async => | ||||
|       pbkdf2Fn != null | ||||
|           ? Pbkdf2Response( | ||||
|               key: pbkdf2Fn!( | ||||
|                 argRequest.password!, | ||||
|                 argRequest.salt!, | ||||
|                 argRequest.iterations!, | ||||
|                 argRequest.length!, | ||||
|                 argRequest.hashAlgorithm!, | ||||
|               ), | ||||
|             ) | ||||
|           : Pbkdf2Response( | ||||
|               key: Uint8List.fromList([1, 2, 3]), | ||||
|             ); | ||||
| } | ||||
| @ -1,192 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: mock_native_crypto_platform.dart | ||||
| // Created Date: 25/05/2022 23:34:34 | ||||
| // Last Modified: 26/05/2022 11:40:24 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| import 'package:plugin_platform_interface/plugin_platform_interface.dart'; | ||||
| 
 | ||||
| class MockNativeCryptoPlatform extends Fake | ||||
|     with MockPlatformInterfaceMixin | ||||
|     implements NativeCryptoPlatform { | ||||
|   Uint8List? data; | ||||
|   List<Uint8List>? dataAsList; | ||||
|   Uint8List? key; | ||||
|   String? algorithm; | ||||
|   int? bitsCount; | ||||
|   String? password; | ||||
|   String? salt; | ||||
|   int? keyBytesCount; | ||||
|   int? iterations; | ||||
| 
 | ||||
|   Uint8List? Function()? response; | ||||
|   List<Uint8List>? Function()? responseAsList; | ||||
| 
 | ||||
|   // ignore: use_setters_to_change_properties | ||||
|   void setResponse(Uint8List? Function()? response) { | ||||
|     this.response = response; | ||||
|   } | ||||
| 
 | ||||
|   // ignore: use_setters_to_change_properties | ||||
|   void setResponseAsList(List<Uint8List>? Function()? responseAsList) { | ||||
|     this.responseAsList = responseAsList; | ||||
|   } | ||||
| 
 | ||||
|   void setDecryptExpectations({ | ||||
|     required Uint8List data, | ||||
|     required Uint8List key, | ||||
|     required String algorithm, | ||||
|   }) { | ||||
|     this.data = data; | ||||
|     this.key = key; | ||||
|     this.algorithm = algorithm; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List?> decrypt( | ||||
|     Uint8List data, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   ) async { | ||||
|     expect(data, this.data); | ||||
|     expect(key, this.key); | ||||
|     expect(algorithm, this.algorithm); | ||||
|     return response?.call(); | ||||
|   } | ||||
| 
 | ||||
|   void setDecryptAsListExpectations({ | ||||
|     required List<Uint8List> data, | ||||
|     required Uint8List key, | ||||
|     required String algorithm, | ||||
|   }) { | ||||
|     dataAsList = data; | ||||
|     this.key = key; | ||||
|     this.algorithm = algorithm; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List?> decryptAsList( | ||||
|     List<Uint8List> data, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   ) async { | ||||
|     expect(data, dataAsList); | ||||
|     expect(key, this.key); | ||||
|     expect(algorithm, this.algorithm); | ||||
| 
 | ||||
|     return response?.call(); | ||||
|   } | ||||
| 
 | ||||
|   void setDigestExpectations({ | ||||
|     required Uint8List data, | ||||
|     required String algorithm, | ||||
|   }) { | ||||
|     this.data = data; | ||||
|     this.algorithm = algorithm; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List?> digest(Uint8List data, String algorithm) async { | ||||
|     expect(data, this.data); | ||||
|     expect(algorithm, this.algorithm); | ||||
| 
 | ||||
|     return response?.call(); | ||||
|   } | ||||
| 
 | ||||
|   void setEncryptExpectations({ | ||||
|     required Uint8List data, | ||||
|     required Uint8List key, | ||||
|     required String algorithm, | ||||
|   }) { | ||||
|     this.data = data; | ||||
|     this.key = key; | ||||
|     this.algorithm = algorithm; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List?> encrypt( | ||||
|     Uint8List data, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   ) async { | ||||
|     expect(data, this.data); | ||||
|     expect(key, this.key); | ||||
|     expect(algorithm, this.algorithm); | ||||
| 
 | ||||
|     return response?.call(); | ||||
|   } | ||||
| 
 | ||||
|   void setEncryptAsListExpectations({ | ||||
|     required Uint8List data, | ||||
|     required Uint8List key, | ||||
|     required String algorithm, | ||||
|   }) => | ||||
|       setEncryptExpectations( | ||||
|         data: data, | ||||
|         key: key, | ||||
|         algorithm: algorithm, | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   Future<List<Uint8List>?> encryptAsList( | ||||
|     Uint8List data, | ||||
|     Uint8List key, | ||||
|     String algorithm, | ||||
|   ) async { | ||||
|     expect(data, this.data); | ||||
|     expect(key, this.key); | ||||
|     expect(algorithm, this.algorithm); | ||||
| 
 | ||||
|     return responseAsList?.call(); | ||||
|   } | ||||
| 
 | ||||
|   // ignore: use_setters_to_change_properties | ||||
|   void setGenerateKeyExpectations({required int bitsCount}) { | ||||
|     this.bitsCount = bitsCount; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List?> generateSecretKey(int bitsCount) async { | ||||
|     expect(bitsCount, this.bitsCount); | ||||
| 
 | ||||
|     return response?.call(); | ||||
|   } | ||||
| 
 | ||||
|   void setPbkdf2Expectations({ | ||||
|     required String password, | ||||
|     required String salt, | ||||
|     required int keyBytesCount, | ||||
|     required int iterations, | ||||
|     required String algorithm, | ||||
|   }) { | ||||
|     this.password = password; | ||||
|     this.salt = salt; | ||||
|     this.iterations = iterations; | ||||
|     this.keyBytesCount = keyBytesCount; | ||||
|     this.algorithm = algorithm; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Uint8List?> pbkdf2( | ||||
|     String password, | ||||
|     String salt, | ||||
|     int keyBytesCount, | ||||
|     int iterations, | ||||
|     String algorithm, | ||||
|   ) async { | ||||
|     expect(password, this.password); | ||||
|     expect(salt, this.salt); | ||||
|     expect(keyBytesCount, this.keyBytesCount); | ||||
|     expect(iterations, this.iterations); | ||||
|     expect(algorithm, this.algorithm); | ||||
| 
 | ||||
|     return response?.call(); | ||||
|   } | ||||
| } | ||||
| @ -1,323 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: aes_cipher_test.dart | ||||
| // Created Date: 26/05/2022 23:20:53 | ||||
| // Last Modified: 27/05/2022 16:39:44 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| import '../mocks/mock_native_crypto_platform.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); | ||||
|   NativeCryptoPlatform.instance = mock; | ||||
| 
 | ||||
|   setUp(() { | ||||
|     Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk; | ||||
|   }); | ||||
| 
 | ||||
|   group('Constructor', () { | ||||
|     test('throws on invalid key length', () { | ||||
|       expect( | ||||
|         () => AES(SecretKey(Uint8List(0))), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_key_length', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('Invalid key'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('creates a valid instance', () { | ||||
|       expect( | ||||
|         AES( | ||||
|           SecretKey(Uint8List(16)), | ||||
|         ), | ||||
|         isA<AES>(), | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('encrypt', () { | ||||
|     test('returns a valid cipher text wrapper', () async { | ||||
|       mock | ||||
|         ..setEncryptExpectations( | ||||
|           data: Uint8List(16), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(16 + 28)); | ||||
| 
 | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
| 
 | ||||
|       expect( | ||||
|         await aes.encrypt(Uint8List(16)), | ||||
|         isA<CipherTextWrapper>().having((e) => e.isSingle, 'is single', isTrue), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('returns a valid cipher text with multiple chunks', () async { | ||||
|       mock | ||||
|         ..setEncryptExpectations( | ||||
|           data: Uint8List(16), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(16 + 28)); // Returns 1 encrypted chunk | ||||
|       Cipher.bytesCountPerChunk = 16; | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
| 
 | ||||
|       expect( | ||||
|         await aes.encrypt(Uint8List(16 * 3)), | ||||
|         isA<CipherTextWrapper>().having((e) => e.isList, 'is list', isTrue), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning empty list', () async { | ||||
|       mock | ||||
|         ..setEncryptExpectations( | ||||
|           data: Uint8List(16), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(0)); | ||||
| 
 | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => aes.encrypt(Uint8List(16)), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_empty_data', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning null', () async { | ||||
|       mock | ||||
|         ..setEncryptExpectations( | ||||
|           data: Uint8List(16), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse(() => null); | ||||
| 
 | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => aes.encrypt(Uint8List(16)), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_null', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles throwing PlatformException', () async { | ||||
|       mock | ||||
|         ..setEncryptExpectations( | ||||
|           data: Uint8List(16), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse( | ||||
|           () => throw PlatformException( | ||||
|             code: 'native_crypto', | ||||
|             message: 'dummy error', | ||||
|           ), | ||||
|         ); | ||||
| 
 | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => aes.encrypt(Uint8List(16)), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains( | ||||
|                   'PlatformException(native_crypto, dummy error, null, null)', | ||||
|                 ), | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'platform_throws', | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('decrypt', () { | ||||
|     test('returns a valid Uint8List', () async { | ||||
|       mock | ||||
|         ..setDecryptExpectations( | ||||
|           data: Uint8List(16 + 28), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(16)); | ||||
| 
 | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
|       final bytes = Uint8List(16 + 28); | ||||
|       final wrapper = CipherTextWrapper.fromBytes( | ||||
|         bytes, | ||||
|         ivLength: 12, | ||||
|         tagLength: 16, | ||||
|       ); | ||||
| 
 | ||||
|       expect( | ||||
|         await aes.decrypt(wrapper), | ||||
|         isA<Uint8List>().having((e) => e.length, 'length', 16), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('returns a valid Uint8List on decrypting multiple chunks', () async { | ||||
|       const int chunkSize = 8; | ||||
|       mock | ||||
|         ..setDecryptExpectations( | ||||
|           data: Uint8List(chunkSize + 28), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(chunkSize)); | ||||
|       Cipher.bytesCountPerChunk = chunkSize; | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
|       final bytes = Uint8List((chunkSize + 28) * 3); | ||||
|       final wrapper = CipherTextWrapper.fromBytes( | ||||
|         bytes, | ||||
|         ivLength: 12, | ||||
|         tagLength: 16, | ||||
|       ); | ||||
| 
 | ||||
|       expect( | ||||
|         await aes.decrypt(wrapper), | ||||
|         isA<Uint8List>().having((e) => e.length, 'length', chunkSize * 3), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning empty list', () async { | ||||
|       mock | ||||
|         ..setDecryptExpectations( | ||||
|           data: Uint8List(16 + 28), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(0)); | ||||
| 
 | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
|       final bytes = Uint8List(16 + 28); | ||||
|       final wrapper = CipherTextWrapper.fromBytes( | ||||
|         bytes, | ||||
|         ivLength: 12, | ||||
|         tagLength: 16, | ||||
|       ); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => aes.decrypt(wrapper), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_empty_data', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning null', () async { | ||||
|       mock | ||||
|         ..setDecryptExpectations( | ||||
|           data: Uint8List(16 + 28), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse(() => null); | ||||
| 
 | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
|       final bytes = Uint8List(16 + 28); | ||||
|       final wrapper = CipherTextWrapper.fromBytes( | ||||
|         bytes, | ||||
|         ivLength: 12, | ||||
|         tagLength: 16, | ||||
|       ); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => aes.decrypt(wrapper), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_null', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles throwing PlatformException', () async { | ||||
|       mock | ||||
|         ..setDecryptExpectations( | ||||
|           data: Uint8List(16 + 28), | ||||
|           key: Uint8List(16), | ||||
|           algorithm: 'aes', | ||||
|         ) | ||||
|         ..setResponse( | ||||
|           () => throw PlatformException( | ||||
|             code: 'native_crypto', | ||||
|             message: 'dummy error', | ||||
|           ), | ||||
|         ); | ||||
| 
 | ||||
|       final aes = AES(SecretKey(Uint8List(16))); | ||||
|       final bytes = Uint8List(16 + 28); | ||||
|       final wrapper = CipherTextWrapper.fromBytes( | ||||
|         bytes, | ||||
|         ivLength: 12, | ||||
|         tagLength: 16, | ||||
|       ); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => aes.decrypt(wrapper), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains( | ||||
|                   'PlatformException(native_crypto, dummy error, null, null)', | ||||
|                 ), | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'platform_throws', | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| @ -1,192 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: cipher_text_test.dart | ||||
| // Created Date: 26/05/2022 20:45:38 | ||||
| // Last Modified: 26/05/2022 21:29:51 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   setUp(() { | ||||
|     Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk; | ||||
|   }); | ||||
| 
 | ||||
|   group('fromBytes', () { | ||||
|     test('throws if length is not the one expected', () { | ||||
|       final Uint8List bytes = Uint8List.fromList([1, 2, 3, 4, 5]); | ||||
|       expect( | ||||
|         () => CipherText.fromBytes( | ||||
|           bytes, | ||||
|           ivLength: 1, | ||||
|           messageLength: 1, | ||||
|           tagLength: 1, | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('Invalid cipher text length'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws if length is bigger than expected', () { | ||||
|       final Uint8List bytes = Uint8List.fromList([1, 3, 3, 3, 1]); | ||||
|       Cipher.bytesCountPerChunk = 2; | ||||
|       expect( | ||||
|         () => CipherText.fromBytes( | ||||
|           bytes, | ||||
|           ivLength: 1, | ||||
|           messageLength: 3, | ||||
|           tagLength: 1, | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('Cipher text is too big'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws if data is empty', () { | ||||
|       final Uint8List bytes = Uint8List(0); | ||||
|       expect( | ||||
|         () => CipherText.fromBytes( | ||||
|           bytes, | ||||
|           ivLength: 1, | ||||
|           messageLength: 3, | ||||
|           tagLength: 1, | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('Passed data is empty'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws if one of the length is negative', () { | ||||
|       final Uint8List bytes = Uint8List(0); | ||||
|       expect( | ||||
|         () => CipherText.fromBytes( | ||||
|           bytes, | ||||
|           ivLength: -1, | ||||
|           messageLength: 1, | ||||
|           tagLength: 1, | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('Invalid length'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('get.cipherAlgorithm', () { | ||||
|     test('throws if not set', () { | ||||
|       final CipherText cipherText = CipherText.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|         ivLength: 1, | ||||
|         messageLength: 1, | ||||
|         tagLength: 1, | ||||
|       ); | ||||
|       expect( | ||||
|         () => cipherText.cipherAlgorithm, | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_cipher', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('Cipher algorithm is not specified'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('returns the expected value', () { | ||||
|       final CipherText cipherText = CipherText.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|         ivLength: 1, | ||||
|         messageLength: 1, | ||||
|         tagLength: 1, | ||||
|         cipherAlgorithm: CipherAlgorithm.aes, | ||||
|       ); | ||||
|       expect(cipherText.cipherAlgorithm, CipherAlgorithm.aes); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('Lengths', () { | ||||
|     test('get.ivLength returns the expected value', () { | ||||
|       final CipherText cipherText = CipherText.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|         ivLength: 1, | ||||
|         messageLength: 1, | ||||
|         tagLength: 1, | ||||
|       ); | ||||
|       expect(cipherText.ivLength, 1); | ||||
|     }); | ||||
| 
 | ||||
|     test('get.messageLength returns the expected value', () { | ||||
|       final CipherText cipherText = CipherText.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|         ivLength: 1, | ||||
|         messageLength: 1, | ||||
|         tagLength: 1, | ||||
|       ); | ||||
|       expect(cipherText.messageLength, 1); | ||||
|     }); | ||||
| 
 | ||||
|     test('get.tagLength returns the expected value', () { | ||||
|       final CipherText cipherText = CipherText.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|         ivLength: 1, | ||||
|         messageLength: 1, | ||||
|         tagLength: 1, | ||||
|       ); | ||||
|       expect(cipherText.tagLength, 1); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| @ -1,349 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: cipher_text_wrapper_test.dart | ||||
| // Created Date: 26/05/2022 21:35:41 | ||||
| // Last Modified: 27/05/2022 13:46:54 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   late CipherText single; | ||||
|   late List<CipherText> list; | ||||
| 
 | ||||
|   setUp(() { | ||||
|     Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk; | ||||
|     single = CipherText.fromBytes( | ||||
|       Uint8List.fromList([1, 2, 3]), | ||||
|       ivLength: 1, | ||||
|       messageLength: 1, | ||||
|       tagLength: 1, | ||||
|     ); | ||||
|     list = [ | ||||
|       CipherText.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|         ivLength: 1, | ||||
|         messageLength: 1, | ||||
|         tagLength: 1, | ||||
|       ), | ||||
|       CipherText.fromBytes( | ||||
|         Uint8List.fromList([4, 5, 6]), | ||||
|         ivLength: 1, | ||||
|         messageLength: 1, | ||||
|         tagLength: 1, | ||||
|       ), | ||||
|     ]; | ||||
|   }); | ||||
| 
 | ||||
|   group('single', () { | ||||
|     test('makes isSingle true', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect(wrapper.isSingle, isTrue); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes isList false', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect(wrapper.isList, isFalse); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes CipherText the single value', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect(wrapper.single, single); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws when trying to get list', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect( | ||||
|         () => wrapper.list, | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_data', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('is not list'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes wrapper returns bytes of CipherText', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect(wrapper.bytes, single.bytes); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes chunkCount = 1', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect(wrapper.chunkCount, 1); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes unwrap() returns only CipherText', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect(wrapper.unwrap<CipherText>(), single); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes unwrap() throws when trying to unwrap List', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect( | ||||
|         () => wrapper.unwrap<List<CipherText>>(), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_data', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('you should use unwrap'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes adding is not supported', () { | ||||
|       final wrapper = CipherTextWrapper.single(single); | ||||
|       expect( | ||||
|         () => wrapper.add(single), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_data', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('is already single'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('list', () { | ||||
|     test('makes isList true', () { | ||||
|       final wrapper = CipherTextWrapper.list(list); | ||||
|       expect(wrapper.isList, isTrue); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes isSingle false', () { | ||||
|       final wrapper = CipherTextWrapper.list(list); | ||||
|       expect(wrapper.isSingle, isFalse); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes List<CipherText> the list value', () { | ||||
|       final wrapper = CipherTextWrapper.list(list); | ||||
|       expect(wrapper.list, list); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws when trying to get single', () { | ||||
|       final wrapper = CipherTextWrapper.list(list); | ||||
|       expect( | ||||
|         () => wrapper.single, | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_data', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('is not single'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes wrapper returns bytes of all CipherText joined', () { | ||||
|       final wrapper = CipherTextWrapper.list(list); | ||||
|       expect(wrapper.bytes, Uint8List.fromList([1, 2, 3, 4, 5, 6])); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes chunkCount = 2', () { | ||||
|       final wrapper = CipherTextWrapper.list(list); | ||||
|       expect(wrapper.chunkCount, 2); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes unwrap() returns List<CipherText>', () { | ||||
|       final wrapper = CipherTextWrapper.list(list); | ||||
|       expect(wrapper.unwrap<List<CipherText>>(), list); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes unwrap() throws when trying to unwrap single', () { | ||||
|       final wrapper = CipherTextWrapper.list(list); | ||||
|       expect( | ||||
|         () => wrapper.unwrap<CipherText>(), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_data', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('you should use unwrap'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes adding is supported', () { | ||||
|       final originalList = List<CipherText>.from(list); | ||||
|       final wrapper = CipherTextWrapper.list(list)..add(single); | ||||
|       printOnFailure(list.length.toString()); | ||||
|       expect(wrapper.list, [...originalList, single]); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('empty', () { | ||||
|     test('makes isList true', () { | ||||
|       final wrapper = CipherTextWrapper.empty(); | ||||
|       expect(wrapper.isList, isTrue); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes isSingle false', () { | ||||
|       final wrapper = CipherTextWrapper.empty(); | ||||
|       expect(wrapper.isSingle, isFalse); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes List<CipherText> the list value', () { | ||||
|       final wrapper = CipherTextWrapper.empty(); | ||||
|       expect(wrapper.list, <CipherText>[]); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws when trying to get single', () { | ||||
|       final wrapper = CipherTextWrapper.empty(); | ||||
|       expect( | ||||
|         () => wrapper.single, | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_data', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('is not single'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes wrapper returns empty bytes', () { | ||||
|       final wrapper = CipherTextWrapper.empty(); | ||||
|       expect(wrapper.bytes, Uint8List.fromList([])); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes chunkCount = 0', () { | ||||
|       final wrapper = CipherTextWrapper.empty(); | ||||
|       expect(wrapper.chunkCount, 0); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes unwrap() returns empty List<CipherText>', () { | ||||
|       final wrapper = CipherTextWrapper.empty(); | ||||
|       expect(wrapper.unwrap<List<CipherText>>(), <CipherText>[]); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes unwrap() throws when trying to unwrap single', () { | ||||
|       final wrapper = CipherTextWrapper.empty(); | ||||
|       expect( | ||||
|         () => wrapper.unwrap<CipherText>(), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_data', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('you should use unwrap'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('makes adding is supported', () { | ||||
|       final wrapper = CipherTextWrapper.empty()..add(single); | ||||
|       expect(wrapper.list, [single]); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('fromBytes', () { | ||||
|     test('creates single from bytes when no too big', () { | ||||
|       final wrapper = CipherTextWrapper.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|         ivLength: 1, | ||||
|         tagLength: 1, | ||||
|       ); | ||||
|       expect(wrapper.isSingle, isTrue); | ||||
|       expect(wrapper.single, single); | ||||
|     }); | ||||
| 
 | ||||
|     test('creates list from bytes when too big', () { | ||||
|       Cipher.bytesCountPerChunk = 1; | ||||
|       final wrapper = CipherTextWrapper.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3, 4, 5, 6]), | ||||
|         ivLength: 1, | ||||
|         tagLength: 1, | ||||
|       ); | ||||
|       expect(wrapper.isList, isTrue); | ||||
|       expect(wrapper.list, list); | ||||
|     }); | ||||
| 
 | ||||
|     test('modifies Cipher.bytesCountPerChunk', () { | ||||
|       expect(Cipher.bytesCountPerChunk, Cipher.defaultBytesCountPerChunk); | ||||
|       CipherTextWrapper.fromBytes( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|         ivLength: 1, | ||||
|         tagLength: 1, | ||||
|         chunkSize: 3, | ||||
|       ); | ||||
|       expect(Cipher.bytesCountPerChunk, 3); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws if trying to build list with bad parameters', () { | ||||
|       Cipher.bytesCountPerChunk = 1; // length of a message | ||||
| 
 | ||||
|       expect( | ||||
|         () => CipherTextWrapper.fromBytes( | ||||
|           Uint8List.fromList([1, 2, 3, 4, 5, 6]), | ||||
|           ivLength: 2, | ||||
|           tagLength: 1, | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('on chunk #'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										116
									
								
								packages/native_crypto/test/src/digest_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								packages/native_crypto/test/src/digest_test.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| import '../mocks/mock_native_crypto_api.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   setUp(() { | ||||
|     // Mock the platform interface API | ||||
|     NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto( | ||||
|       api: MockNativeCryptoAPI(), | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   group('Hash', () { | ||||
|     test('$Sha256 digest correctly', () async { | ||||
|       final hash = await Sha256().digest('abc'.toBytes()); | ||||
|       expect(hash, isNotNull); | ||||
|       expect( | ||||
|         hash, | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('$Sha256 digest throws if platform returns null', () async { | ||||
|       MockNativeCryptoAPI.hashFn = (input, algorithm) => null; | ||||
|       expect( | ||||
|         () async => Sha256().digest('abc'.toBytes()), | ||||
|         throwsA(isA<NativeCryptoException>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('$Sha256 digest throws if platform returns invalid data', () async { | ||||
|       MockNativeCryptoAPI.hashFn = (input, algorithm) => Uint8List(0); | ||||
|       expect( | ||||
|         () async => Sha256().digest('abcd'.toBytes()), | ||||
|         throwsA(isA<NativeCryptoException>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('$Sha256 returns correct $HashAlgorithm', () async { | ||||
|       final hash = Sha256(); | ||||
| 
 | ||||
|       expect(hash.algorithm, HashAlgorithm.sha256); | ||||
|     }); | ||||
| 
 | ||||
|     test('$Sha384 returns correct $HashAlgorithm', () async { | ||||
|       final hash = Sha384(); | ||||
| 
 | ||||
|       expect(hash.algorithm, HashAlgorithm.sha384); | ||||
|     }); | ||||
| 
 | ||||
|     test('$Sha512 returns correct $HashAlgorithm', () async { | ||||
|       final hash = Sha512(); | ||||
| 
 | ||||
|       expect(hash.algorithm, HashAlgorithm.sha512); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('Hmac', () { | ||||
|     test('$HmacSha256 digest correctly', () async { | ||||
|       final hash = await HmacSha256() | ||||
|           .digest('abc'.toBytes(), SecretKey.fromUtf16('key')); | ||||
|       expect(hash, isNotNull); | ||||
|       expect( | ||||
|         hash, | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('$HmacSha256 digest throws if platform returns null', () async { | ||||
|       MockNativeCryptoAPI.hmacFn = (input, key, algorithm) => null; | ||||
|       expect( | ||||
|         () async => | ||||
|             HmacSha256().digest('abc'.toBytes(), SecretKey.fromUtf16('key')), | ||||
|         throwsA(isA<NativeCryptoException>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('$HmacSha256 digest throws if platform returns invalid data', | ||||
|         () async { | ||||
|       MockNativeCryptoAPI.hmacFn = (input, key, algorithm) => Uint8List(0); | ||||
|       expect( | ||||
|         () async => | ||||
|             HmacSha256().digest('abc'.toBytes(), SecretKey.fromUtf16('key')), | ||||
|         throwsA(isA<NativeCryptoException>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('$HmacSha256 returns correct $HashAlgorithm', () async { | ||||
|       final hash = HmacSha256(); | ||||
| 
 | ||||
|       expect(hash.algorithm, HashAlgorithm.sha256); | ||||
|     }); | ||||
| 
 | ||||
|     test('$HmacSha384 returns correct $HashAlgorithm', () async { | ||||
|       final hash = HmacSha384(); | ||||
| 
 | ||||
|       expect(hash.algorithm, HashAlgorithm.sha384); | ||||
|     }); | ||||
| 
 | ||||
|     test('$HmacSha512 returns correct $HashAlgorithm', () async { | ||||
|       final hash = HmacSha512(); | ||||
| 
 | ||||
|       expect(hash.algorithm, HashAlgorithm.sha512); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| @ -1,128 +0,0 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: hash_algorithm_test.dart | ||||
| // Created Date: 26/05/2022 22:28:53 | ||||
| // Last Modified: 26/05/2022 23:03:03 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto/src/utils/hash_algorithm.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| import '../mocks/mock_native_crypto_platform.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); | ||||
|   NativeCryptoPlatform.instance = mock; | ||||
| 
 | ||||
|   group('name', () { | ||||
|     test('is sha256 for HashAlgorithm.sha256', () { | ||||
|       expect(HashAlgorithm.sha256.name, 'sha256'); | ||||
|     }); | ||||
|     test('is sha384 for HashAlgorithm.sha384', () { | ||||
|       expect(HashAlgorithm.sha384.name, 'sha384'); | ||||
|     }); | ||||
|     test('is sha512 for HashAlgorithm.sha512', () { | ||||
|       expect(HashAlgorithm.sha512.name, 'sha512'); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('digest', () { | ||||
|     test('handles returning empty list', () async { | ||||
|       mock | ||||
|         ..setDigestExpectations( | ||||
|           data: Uint8List.fromList([1, 2, 3]), | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(0)); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_empty_data', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning null', () async { | ||||
|       mock | ||||
|         ..setDigestExpectations( | ||||
|           data: Uint8List.fromList([1, 2, 3]), | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse(() => null); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_null', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles throwing PlatformException', () async { | ||||
|       mock | ||||
|         ..setDigestExpectations( | ||||
|           data: Uint8List.fromList([1, 2, 3]), | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse( | ||||
|           () => throw PlatformException( | ||||
|             code: 'native_crypto', | ||||
|             message: 'dummy error', | ||||
|           ), | ||||
|         ); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 'PlatformException(native_crypto, dummy error, null, null)', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'platform_throws', | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('returns data on success', () async { | ||||
|       final hash = Uint8List.fromList([4, 5, 6]); | ||||
|       mock | ||||
|         ..setDigestExpectations( | ||||
|           data: Uint8List.fromList([1, 2, 3]), | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse(() => hash); | ||||
| 
 | ||||
|       final result = await HashAlgorithm.sha256.digest( | ||||
|         Uint8List.fromList( | ||||
|           [1, 2, 3], | ||||
|         ), | ||||
|       ); | ||||
| 
 | ||||
|       expect( | ||||
|         result, | ||||
|         hash, | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| @ -1,280 +1,155 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: pbkdf2_test.dart | ||||
| // Created Date: 26/05/2022 22:37:27 | ||||
| // Last Modified: 26/05/2022 23:20:11 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| import '../mocks/mock_native_crypto_platform.dart'; | ||||
| import '../mocks/mock_native_crypto_api.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); | ||||
|   NativeCryptoPlatform.instance = mock; | ||||
|   setUp(() { | ||||
|     // Mock the platform interface API | ||||
|     NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto( | ||||
|       api: MockNativeCryptoAPI(), | ||||
|     ); | ||||
| 
 | ||||
|   group('Constructor', () { | ||||
|     test('throws if keyBytesCount is negative', () { | ||||
|       expect( | ||||
|         () => Pbkdf2(keyBytesCount: -1, iterations: 10000), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('must be positive'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws if iterations is negative or 0', () { | ||||
|       expect( | ||||
|         () => Pbkdf2(keyBytesCount: 32, iterations: -1), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('must be strictly positive'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
|     MockNativeCryptoAPI.pbkdf2Fn = null; | ||||
|   }); | ||||
| 
 | ||||
|   group('derive', () { | ||||
|     test('throws if password is null', () async { | ||||
|       final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); | ||||
|       await expectLater( | ||||
|         () => pbkdf2.derive( | ||||
|           salt: 'salt', | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('cannot be null'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('throws if salt is null', () async { | ||||
|       final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); | ||||
|       await expectLater( | ||||
|         () => pbkdf2.derive( | ||||
|           password: 'password', | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'invalid_argument', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 contains('cannot be null'), | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning empty list', () async { | ||||
|       mock | ||||
|         ..setPbkdf2Expectations( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|           keyBytesCount: 32, | ||||
|           iterations: 10000, | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(0)); | ||||
| 
 | ||||
|       final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => pbkdf2.derive( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_empty_data', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning null', () async { | ||||
|       mock | ||||
|         ..setPbkdf2Expectations( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|           keyBytesCount: 32, | ||||
|           iterations: 10000, | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse(() => null); | ||||
| 
 | ||||
|       final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => pbkdf2.derive( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_null', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning data with wrong length', () async { | ||||
|       mock | ||||
|         ..setPbkdf2Expectations( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|           keyBytesCount: 32, | ||||
|           iterations: 10000, | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(33)); | ||||
| 
 | ||||
|       final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => pbkdf2.derive( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_invalid_data', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles throwing PlatformException', () async { | ||||
|       mock | ||||
|         ..setPbkdf2Expectations( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|           keyBytesCount: 32, | ||||
|           iterations: 10000, | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse( | ||||
|           () => throw PlatformException( | ||||
|             code: 'native_crypto', | ||||
|             message: 'dummy error', | ||||
|           ), | ||||
|         ); | ||||
| 
 | ||||
|       final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => pbkdf2.derive( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|         ), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 'PlatformException(native_crypto, dummy error, null, null)', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'platform_throws', | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('returns SecretKey on success', () async { | ||||
|       final data = Uint8List.fromList([1, 2, 3, 4, 5, 6]); | ||||
|       final sk = SecretKey(data); | ||||
|       mock | ||||
|         ..setPbkdf2Expectations( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|           keyBytesCount: 6, | ||||
|           iterations: 10000, | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse(() => data); | ||||
| 
 | ||||
|       final pbkdf = Pbkdf2(keyBytesCount: 6, iterations: 10000); | ||||
|       final result = await pbkdf.derive( | ||||
|         password: 'password', | ||||
|         salt: 'salt', | ||||
|   group('$Pbkdf2', () { | ||||
|     test('derive key correctly', () async { | ||||
|       final key = await Pbkdf2( | ||||
|         salt: Uint8List.fromList([1, 2, 3]), | ||||
|         iterations: 1, | ||||
|         length: 3, | ||||
|         hashAlgorithm: HashAlgorithm.sha256, | ||||
|       ).derive( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|       ); | ||||
| 
 | ||||
|       expect(key, isNotNull); | ||||
|       expect(key.length, 3); | ||||
|       expect( | ||||
|         result, | ||||
|         sk, | ||||
|         key, | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('return empty SecretKey when keyBytesCount is set to 0', () async { | ||||
|       final sk = SecretKey(Uint8List(0)); | ||||
|       mock | ||||
|         ..setPbkdf2Expectations( | ||||
|           password: 'password', | ||||
|           salt: 'salt', | ||||
|           keyBytesCount: 0, | ||||
|           iterations: 10000, | ||||
|           algorithm: 'sha256', | ||||
|         ) | ||||
|         ..setResponse(() => Uint8List(0)); | ||||
| 
 | ||||
|       final pbkdf = Pbkdf2(keyBytesCount: 0, iterations: 10000); | ||||
|       final result = await pbkdf.derive( | ||||
|         password: 'password', | ||||
|         salt: 'salt', | ||||
|       ); | ||||
| 
 | ||||
|     test('derive key with invalid length throws', () async { | ||||
|       expect( | ||||
|         result, | ||||
|         sk, | ||||
|         () => Pbkdf2( | ||||
|           salt: Uint8List.fromList([1, 2, 3]), | ||||
|           iterations: 1, | ||||
|           length: -1, | ||||
|           hashAlgorithm: HashAlgorithm.sha256, | ||||
|         ).derive( | ||||
|           Uint8List.fromList([1, 2, 3]), | ||||
|         ), | ||||
|         throwsA(isA<ArgumentError>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('derive key with invalid iterations throws', () async { | ||||
|       expect( | ||||
|         () => Pbkdf2( | ||||
|           salt: Uint8List.fromList([1, 2, 3]), | ||||
|           iterations: 0, | ||||
|           length: 3, | ||||
|           hashAlgorithm: HashAlgorithm.sha256, | ||||
|         ).derive( | ||||
|           Uint8List.fromList([1, 2, 3]), | ||||
|         ), | ||||
|         throwsA(isA<ArgumentError>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('derive key with 0 length returns empty list', () async { | ||||
|       final key = await Pbkdf2( | ||||
|         salt: Uint8List.fromList([1, 2, 3]), | ||||
|         iterations: 1, | ||||
|         length: 0, | ||||
|         hashAlgorithm: HashAlgorithm.sha256, | ||||
|       ).derive( | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|       ); | ||||
|       expect(key, isNotNull); | ||||
|       expect(key.length, 0); | ||||
|     }); | ||||
| 
 | ||||
|     test('derive key throws if platform returns null', () async { | ||||
|       MockNativeCryptoAPI.pbkdf2Fn = | ||||
|           (password, salt, iterations, length, hashAlgorithm) => null; | ||||
|       expect( | ||||
|         () => Pbkdf2( | ||||
|           salt: Uint8List.fromList([1, 2, 3]), | ||||
|           iterations: 1, | ||||
|           length: 3, | ||||
|           hashAlgorithm: HashAlgorithm.sha256, | ||||
|         ).derive( | ||||
|           Uint8List.fromList([1, 2, 3]), | ||||
|         ), | ||||
|         throwsA(isA<NativeCryptoException>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('derive key throws if platform returns invalid data', () async { | ||||
|       expect( | ||||
|         () async => Pbkdf2( | ||||
|           salt: Uint8List.fromList([1, 2, 3]), | ||||
|           iterations: 1, | ||||
|           length: 4, | ||||
|           hashAlgorithm: HashAlgorithm.sha256, | ||||
|         ).derive( | ||||
|           Uint8List.fromList([1, 2, 3]), | ||||
|         ), | ||||
|         throwsA(isA<NativeCryptoException>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('call returns $SecretKey', () async { | ||||
|       final pbkdf = Pbkdf2( | ||||
|         salt: Uint8List.fromList([1, 2, 3]), | ||||
|         iterations: 1, | ||||
|         length: 3, | ||||
|         hashAlgorithm: HashAlgorithm.sha256, | ||||
|       ); | ||||
| 
 | ||||
|       final key = await pbkdf(password: 'password'); | ||||
| 
 | ||||
|       expect(key, isNotNull); | ||||
|       expect(key, isA<SecretKey>()); | ||||
|     }); | ||||
| 
 | ||||
|     test('verify key returns true on the same password', () async { | ||||
|       final pbkdf = Pbkdf2( | ||||
|         salt: Uint8List.fromList([1, 2, 3]), | ||||
|         iterations: 1, | ||||
|         length: 3, | ||||
|         hashAlgorithm: HashAlgorithm.sha256, | ||||
|       ); | ||||
|       final pwd = Uint8List.fromList([1, 2, 3]); | ||||
|       final key = await pbkdf.derive(pwd); | ||||
|       final sucess = await pbkdf.verify(pwd, key); | ||||
|       expect(sucess, true); | ||||
|     }); | ||||
| 
 | ||||
|     test('verify key returns true on the same password', () async { | ||||
|       final pbkdf = Pbkdf2( | ||||
|         salt: Uint8List.fromList([1, 2, 3]), | ||||
|         iterations: 1, | ||||
|         length: 3, | ||||
|         hashAlgorithm: HashAlgorithm.sha256, | ||||
|       ); | ||||
|       final pwd = Uint8List.fromList([1, 2, 3]); | ||||
|       final key = Uint8List.fromList([1, 2, 3, 4, 5, 6]); | ||||
|       final sucess = await pbkdf.verify(pwd, key); | ||||
|       expect(sucess, false); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
							
								
								
									
										63
									
								
								packages/native_crypto/test/src/random_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								packages/native_crypto/test/src/random_test.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| import '../mocks/mock_native_crypto_api.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   setUp(() { | ||||
|     // Mock the platform interface API | ||||
|     NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto( | ||||
|       api: MockNativeCryptoAPI(), | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   group('$SecureRandom', () { | ||||
|     test('generate random bytes correctly', () async { | ||||
|       final random = await const SecureRandom().generate(3); | ||||
|       expect(random, isNotNull); | ||||
|       expect(random.length, 3); | ||||
|       expect( | ||||
|         random, | ||||
|         Uint8List.fromList([1, 2, 3]), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('generate random bytes with invalid length throws', () async { | ||||
|       expect( | ||||
|         () => const SecureRandom().generate(-1), | ||||
|         throwsA(isA<ArgumentError>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('generate random bytes with 0 length returns empty list', () async { | ||||
|       final random = await const SecureRandom().generate(0); | ||||
|       expect(random, isNotNull); | ||||
|       expect(random.length, 0); | ||||
|     }); | ||||
| 
 | ||||
|     test('generate random bytes throws if platform returns null', () async { | ||||
|       MockNativeCryptoAPI.generateSecureRandomFn = (length) => null; | ||||
|       expect( | ||||
|         () async => const SecureRandom().generate(3), | ||||
|         throwsA(isA<NativeCryptoException>()), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('generate random bytes throws if platform returns invalid data', | ||||
|         () async { | ||||
|       expect( | ||||
|         () async => const SecureRandom().generate(4), | ||||
|         throwsA(isA<NativeCryptoException>()), | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| @ -1,125 +1,56 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: secret_key_test.dart | ||||
| // Created Date: 26/05/2022 10:52:41 | ||||
| // Last Modified: 26/05/2022 22:38:07 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| // | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| 
 | ||||
| import 'dart:typed_data'; | ||||
| 
 | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:native_crypto/src/keys/secret_key.dart'; | ||||
| import 'package:native_crypto/native_crypto.dart'; | ||||
| import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; | ||||
| 
 | ||||
| import '../mocks/mock_native_crypto_platform.dart'; | ||||
| import '../mocks/mock_native_crypto_api.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); | ||||
|   NativeCryptoPlatform.instance = mock; | ||||
|   setUp(() { | ||||
|     // Mock the platform interface API | ||||
|     NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto( | ||||
|       api: MockNativeCryptoAPI(), | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   group('Constructors', () { | ||||
|     test('handles Uint8List', () { | ||||
|   group('$SecretKey', () { | ||||
|     test('can be create from Uint8List', () { | ||||
|       final SecretKey key = SecretKey(Uint8List.fromList([1, 2, 3, 4, 5])); | ||||
| 
 | ||||
|       expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles base16', () { | ||||
|     test('can be create from base16', () { | ||||
|       final SecretKey key = SecretKey.fromBase16('0102030405'); | ||||
| 
 | ||||
|       expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles base64', () { | ||||
|     test('can be create from base64', () { | ||||
|       final SecretKey key = SecretKey.fromBase64('AQIDBAU='); | ||||
| 
 | ||||
|       expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles utf8', () { | ||||
|     test('can be create from utf8', () { | ||||
|       final SecretKey key = SecretKey.fromUtf8('ABCDE'); | ||||
| 
 | ||||
|       expect(key.bytes, Uint8List.fromList([65, 66, 67, 68, 69])); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   group('fromSecureRandom', () { | ||||
|     test('handles returning random bytes', () async { | ||||
|       mock | ||||
|         ..setGenerateKeyExpectations(bitsCount: 5) | ||||
|         ..setResponse(() => Uint8List.fromList([1, 2, 3, 4, 5])); | ||||
|     test('can be create from secure random', () async { | ||||
|       MockNativeCryptoAPI.generateSecureRandomFn = | ||||
|           (length) => Uint8List.fromList([1, 2, 3, 4, 5]); | ||||
|       final SecretKey key = await SecretKey.fromSecureRandom(5); | ||||
| 
 | ||||
|       final SecretKey secretKey = await SecretKey.fromSecureRandom(5); | ||||
| 
 | ||||
|       expect( | ||||
|         secretKey.bytes, | ||||
|         Uint8List.fromList([1, 2, 3, 4, 5]), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning empty list', () async { | ||||
|       mock | ||||
|         ..setGenerateKeyExpectations(bitsCount: 5) | ||||
|         ..setResponse(() => Uint8List(0)); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => SecretKey.fromSecureRandom(5), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_empty_data', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles returning null', () async { | ||||
|       mock | ||||
|         ..setGenerateKeyExpectations(bitsCount: 5) | ||||
|         ..setResponse(() => null); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => SecretKey.fromSecureRandom(5), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>().having( | ||||
|             (e) => e.code, | ||||
|             'code', | ||||
|             'platform_returned_null', | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     test('handles throwing PlatformException', () async { | ||||
|       mock | ||||
|         ..setGenerateKeyExpectations(bitsCount: 5) | ||||
|         ..setResponse( | ||||
|           () => throw PlatformException( | ||||
|             code: 'native_crypto', | ||||
|             message: 'dummy error', | ||||
|           ), | ||||
|         ); | ||||
| 
 | ||||
|       await expectLater( | ||||
|         () => SecretKey.fromSecureRandom(5), | ||||
|         throwsA( | ||||
|           isA<NativeCryptoException>() | ||||
|               .having( | ||||
|                 (e) => e.message, | ||||
|                 'message', | ||||
|                 'PlatformException(native_crypto, dummy error, null, null)', | ||||
|               ) | ||||
|               .having( | ||||
|                 (e) => e.code, | ||||
|                 'code', | ||||
|                 'platform_throws', | ||||
|               ), | ||||
|         ), | ||||
|       ); | ||||
|       expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user