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