Compare commits

...

6 Commits

42 changed files with 1061 additions and 319 deletions

202
README.md
View File

@ -1,10 +1,3 @@
/*
* 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.
*/
<p align="center">
<img width="700px" src="resources/native_crypto.png" style="background-color: rgb(255, 255, 255)">
<h5 align="center">Fast and powerful cryptographic functions for Flutter.</h5>
@ -42,34 +35,127 @@ For comparison, on a *iPhone 13*, you can encrypt/decrypt a message of **2MiB**
In short, NativeCrypto is incomparable with PointyCastle.
## Features
* Hash functions
- SHA-256
- SHA-384
- SHA-512
* HMAC functions
- HMAC-SHA-256
- HMAC-SHA-384
- HMAC-SHA-512
* Secure random
* PBKDF2
* AES
- Uint8List encryption/decryption
- File encryption/decryption
## Quick start
```dart
import 'package:native_crypto/native_crypto.dart';
Future<void> main() async {
// Message to encrypt
final Uint8List message = 'Hello World!'.toBytes();
// Ask user for a password
final String password = await getPassword();
// Initialize a PBKDF2 object
final Pbkdf2 pbkdf2 = Pbkdf2(
length: 32, // 32 bytes
iterations: 1000,
salt: 'salt'.toBytes(),
hashAlgorithm: HashAlgorithm.sha256,
);
// Derive a secret key from the password
final SecretKey secretKey = await pbkdf2(password: password);
// Initialize an AES cipher
final AES cipher = AES(
key: secretKey,
mode: AESMode.gcm,
padding: AESPadding.none,
);
// Encrypt the message
final CipherText<AESCipherChunk> cipherText = await cipher.encrypt(message);
// Decrypt the message
final Uint8List decryptedMessage = await cipher.decrypt(cipherText);
// Verify and print the decrypted message
assert(listEquals(message, decryptedMessage));
print(decryptedMessage.toStr());
}
```
Check the [example](./native_crypto/example) for a complete example.
Please take a look a the compatibility table below to check if your target is supported.
> Note: This **Flutter** example must run on a real device or a simulator.
## Usage
#### Compatibility
First, check compatibility with your targets.
| iOS | Android | MacOS | Linux | Windows | Web |
| --- | ------- | ----- | ----- | ------- | --- |
| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
> Warning: NativeCrypto 0.2.0+ is not compatible with lower NativeCrypto versions. Especially, with NativeCrypto 0.0. X because the cipher mode is not the same. Now, NativeCrypto uses AES-GCM mode instead of AES-CBC mode. (See [Changelog](./CHANGELOG.md))
NativeCrypto ciphertexts are formatted as follow:
```
+------------------+--------------------+------------------+
| Nonce (12 bytes) | Cipher text (n-28) | Tag (16 bytes) |
+------------------+--------------------+------------------+
```
> Warning: If your data comes from another source, make sur to use the same format.
#### Hash
To digest a message, you can use the following function:
To digest a message, you'll need to initialize a Hasher object implementing `Hash` . Then, you can digest your message.
```dart
Uint8List hash = await HashAlgorithm.sha256.digest(message);
Hash hasher = Sha256();
Uint8List digest = await hasher.digest(message);
```
> In NativeCrypto, you can use the following hash functions: SHA-256, SHA-384, SHA-512
#### Keys
#### HMAC
You can build a `SecretKey` from a utf8, base64, base16 (hex) strings or raw bytes. You can also generate a SecretKey from secure random.
To generate a HMAC, you'll need to initialize a `Hmac` object. Then, you can generate a HMAC from a message and a secret key.
```dart
SecretKey secretKey = SecretKey(Uint8List.fromList([0x73, 0x65, 0x63, 0x72, 0x65, 0x74]));
Hmac hmac = HmacSha256();
Uint8List hmac = await hmac.digest(message, secretKey);
```
> In NativeCrypto, you can use the following HMAC functions: HMAC-SHA-256, HMAC-SHA-384, HMAC-SHA-512
#### Keys
You can build a `SecretKey` from utf8, utf16, base64, base16 (hex) strings, int list or raw bytes. You can also generate a SecretKey from secure random.
```dart
SecretKey secretKey = SecretKey(bytes); // bytes is a Uint8List
SecretKey secretKey = SecretKey.fromUtf8('secret');
SecretKet secretKey = SecretKey.fromUtf16('secret');
SecretKey secretKey = SecretKey.fromBase64('c2VjcmV0');
SecretKey secretKey = SecretKey.fromBase16('63657274');
SecretKey secretKey = await SecretKey.fromSecureRandom(256);
SecretKey secretKey = SecretKey.fromList([0x73, 0x65, 0x63, 0x72, 0x65, 0x74]);
SecretKey secretKey = await SecretKey.fromSecureRandom(32); // 32 bytes
```
#### Key derivation
@ -79,20 +165,21 @@ You can derive a `SecretKey` using **PBKDF2**.
First, you need to initialize a `Pbkdf2` object.
```dart
Pbkdf2 pbkdf2 = Pbkdf2(
keyBytesCount: 32,
final Pbkdf2 pbkdf2 = Pbkdf2(
length: 32, // 32 bytes
iterations: 1000,
algorithm: HashAlgorithm.sha512,
salt: salt.toBytes(),
hashAlgorithm: HashAlgorithm.sha256,
);
```
Then, you can derive a `SecretKey` from a password and salt.
Then, you can derive a `SecretKey` from a password.
```dart
SecretKey secretKey = await pbkdf2.derive(password: password, salt: 'salt');
SecretKey secretKey = await pbkdf2(password: password);
```
> In NativeCrypto, you can use the following key derivation function: PBKDF2
> Note: Pbkdf2 is a callable class. You can use it like a function.
#### Cipher
@ -101,44 +188,79 @@ And now, you can use the `SecretKey` to encrypt/decrypt a message.
First, you need to initialize a `Cipher` object.
```dart
AES cipher = AES(secretKey);
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
```
Then, you can encrypt your message.
```dart
CipherTextWrapper wrapper = await cipher.encrypt(message);
CipherText cipherText = wrapper.unwrap<CipherText>();
// same as
CipherText cipherText = wrapper.single;
// or
List<CipherText> cipherTexts = wrapper.unwrap<List<CipherText>>();
// same as
List<CipherText> cipherTexts = wrapper.list;
final CipherText<AESCipherChunk> cipherText = await cipher.encrypt(message);
```
After an encryption you obtain a `CipherTextWrapper` which contains `CipherText` or `List<CipherText>` depending on the message size. It's up to you to know how to unwrap the `CipherTextWrapper` depending the chunk size you configured.
After an encryption you obtain a `CipherText` which contains chunks. You can get the underlying bytes with `cipherText.bytes` .
Uppon receiving encrypted message, you can decrypt it.
You have to reconstruct the wrapper before decrypting.
Uppon receiving encrypted message `receivedData` , you can decrypt it.
You have to reconstruct the ciphertext and the setup the chunk factory.
```dart
CipherTextWrapper wrapper = CipherTextWrapper.fromBytes(
data,
ivLength: AESMode.gcm.ivLength,
tagLength: AESMode.gcm.tagLength,
);
final CipherText<AESCipherChunk> receivedCipherText CipherText(
receivedData,
chunkFactory: (bytes) => AESCipherChunk(
bytes,
ivLength: cipher.mode.ivLength,
tagLength: cipher.mode.tagLength,
),
),
```
Then, you can decrypt your message.
```dart
Uint8List message = await cipher.decrypt(wrapper);
Uint8List message = await cipher.decrypt(receivedCipherText);
```
#### Files
You can encrypt/decrypt files.
First, you need to initialize a `Cipher` object.
```dart
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
```
Then, you can encrypt your file.
```dart
await cipher.encryptFile(plainText, cipherText);
```
> Note: `plainText` and `cipherText` are `File` objects.
You can decrypt your file.
```dart
await cipher.decryptFile(cipherText, plainText);
```
#### Advanced
You can force the use of a specific IV. Please note that the IV must be unique for each encryption.
```dart
final CipherText<AESCipherChunk> cipherText = await cipher.encryptWithIV(message, iv);
```
⚠️ Use `encrypt(...)` instead of `encryptWithIV(...)` if you don't know what you are doing.
## Development
### Android

View File

@ -2,7 +2,7 @@ NativeCrypto
MIT License
Copyright (c) 2019 - 2022 Hugo Pointcheval
Copyright (c) 2019 - 2023 Hugo Pointcheval
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View File

@ -1,133 +1,3 @@
<p align="center">
<img width="700px" src="resources/native_crypto.png" style="background-color: rgb(255,255,255)">
<h5 align="center">Fast and powerful cryptographic functions for Flutter.</h5>
</p>
# NativeCrypto
<p align="center">
<a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis">
<img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" />
</a>
<a href="https://github.com/invertase/melos">
<img src="https://img.shields.io/badge/Maintained%20with-melos-f700ff.svg?style=flat-square" alt="Maintained with Melos" />
</a>
<a href="https://drone.wyatt-studio.fr/hugo/native-crypto">
<img src="https://drone.wyatt-studio.fr/api/badges/hugo/native-crypto/status.svg" alt="Build Status" />
</a>
</p>
---
[[Changelog]](./CHANGELOG.md) | [[License]](./LICENSE)
---
## About
The goal of this plugin is to provide a fast and powerful cryptographic functions by calling native libraries. On Android, it uses [javax.cypto](https://developer.android.com/reference/javax/crypto/package-summary), and on iOS, it uses [CommonCrypto](https://opensource.apple.com/source/CommonCrypto/) and [CryptoKit](https://developer.apple.com/documentation/cryptokit/)
I started this projet because I wanted to add cryptographic functions on a Flutter app. But I faced a problem with the well-known [Pointy Castle](https://pub.dev/packages/pointycastle) library: the performance was very poor. Here some benchmarks and comparison:
![](resources/benchmarks.png)
For comparison, on a *iPhone 13*, you can encrypt/decrypt a message of **2MiB** in **~5.6s** with PointyCastle and in **~40ms** with NativeCrypto. And on an *OnePlus 5*, you can encrypt/decrypt a message of **50MiB** in **~6min30** with PointyCastle and in less than **~1s** with NativeCrypto.
In short, NativeCrypto is incomparable with PointyCastle.
## Usage
First, check compatibility with your targets.
| iOS | Android | MacOS | Linux | Windows | Web |
| --- | ------- | ----- | ----- | ------- | --- |
| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
#### Hash
To digest a message, you can use the following function:
```dart
Uint8List hash = await HashAlgorithm.sha256.digest(message);
```
> In NativeCrypto, you can use the following hash functions: SHA-256, SHA-384, SHA-512
#### Keys
You can build a `SecretKey` from a utf8, base64, base16 (hex) strings or raw bytes. You can also generate a SecretKey from secure random.
```dart
SecretKey secretKey = SecretKey(Uint8List.fromList([0x73, 0x65, 0x63, 0x72, 0x65, 0x74]));
SecretKey secretKey = SecretKey.fromUtf8('secret');
SecretKey secretKey = SecretKey.fromBase64('c2VjcmV0');
SecretKey secretKey = SecretKey.fromBase16('63657274');
SecretKey secretKey = await SecretKey.fromSecureRandom(256);
```
#### Key derivation
You can derive a `SecretKey` using **PBKDF2**.
First, you need to initialize a `Pbkdf2` object.
```dart
Pbkdf2 pbkdf2 = Pbkdf2(
keyBytesCount: 32,
iterations: 1000,
algorithm: HashAlgorithm.sha512,
);
```
Then, you can derive a `SecretKey` from a password and salt.
```dart
SecretKey secretKey = await pbkdf2.derive(password: password, salt: 'salt');
```
> In NativeCrypto, you can use the following key derivation function: PBKDF2
#### Cipher
And now, you can use the `SecretKey` to encrypt/decrypt a message.
First, you need to initialize a `Cipher` object.
```dart
AES cipher = AES(secretKey);
```
Then, you can encrypt your message.
```dart
CipherTextWrapper wrapper = await cipher.encrypt(message);
CipherText cipherText = wrapper.unwrap<CipherText>();
// same as
CipherText cipherText = wrapper.single;
// or
List<CipherText> cipherTexts = wrapper.unwrap<List<CipherText>>();
// same as
List<CipherText> cipherTexts = wrapper.list;
```
After an encryption you obtain a `CipherTextWrapper` which contains `CipherText` or `List<CipherText>` depending on the message size. It's up to you to know how to unwrap the `CipherTextWrapper` depending the chunk size you configured.
Uppon receiving encrypted message, you can decrypt it.
You have to reconstruct the wrapper before decrypting.
```dart
CipherTextWrapper wrapper = CipherTextWrapper.fromBytes(
data,
ivLength: AESMode.gcm.ivLength,
tagLength: AESMode.gcm.tagLength,
);
```
Then, you can decrypt your message.
```dart
Uint8List message = await cipher.decrypt(wrapper);
```
Readme available at [project root](../../README.md).

View File

@ -1,21 +1,74 @@
PODS:
- DKImagePickerController/Core (4.3.4):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.4)
- DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.4)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.17):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- native_crypto_ios (0.0.1):
- Flutter
- SDWebImage (5.15.5):
- SDWebImage/Core (= 5.15.5)
- SDWebImage/Core (5.15.5)
- SwiftyGif (5.4.4)
DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- native_crypto_ios (from `.symlinks/plugins/native_crypto_ios/ios`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- SDWebImage
- SwiftyGif
EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
native_crypto_ios:
:path: ".symlinks/plugins/native_crypto_ios/ios"
SPEC CHECKSUMS:
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
native_crypto_ios: de03ec2f594e8d41bcba2341b7ad57fd926ada5d
SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b

View File

@ -47,5 +47,9 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
</dict>
</plist>

View File

@ -4,6 +4,8 @@
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/domain/data_sources/crypto_data_source.dart';
@ -30,6 +32,29 @@ class NativeCryptoDataSourceImpl extends CryptoDataSource {
return plainText;
}
@override
Future<void> decryptFile(
File cipherText,
Uri folderResult,
SecretKey key,
) async {
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
final plainText = File.fromUri(
Uri.parse(
'${folderResult.path}/${cipherText.path.split('/').last.replaceAll('.enc', '')}',
),
);
await cipher.decryptFile(
cipherText,
plainText,
);
}
@override
Future<SecretKey> deriveKeyFromPassword(
String password, {
@ -95,6 +120,27 @@ class NativeCryptoDataSourceImpl extends CryptoDataSource {
return cipherText.bytes;
}
@override
Future<void> encryptFile(
File plainText,
Uri folderResult,
SecretKey key,
) async {
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
final cipherText = File.fromUri(
Uri.parse(
'${folderResult.path}/${plainText.path.split('/').last}.enc',
),
);
await cipher.encryptFile(plainText, cipherText);
}
@override
Future<SecretKey> generateSecureRandom(int length) async {
final SecretKey sk = await SecretKey.fromSecureRandom(length);

View File

@ -6,6 +6,8 @@
// ignore_for_file: implementation_imports
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/domain/data_sources/crypto_data_source.dart';
@ -37,6 +39,15 @@ class PointyCastleDataSourceImpl extends CryptoDataSource {
return paddedPlainText;
}
@override
Future<void> decryptFile(
File cipherText,
Uri folderResult,
SecretKey key,
) async {
throw UnimplementedError();
}
@override
Future<SecretKey> deriveKeyFromPassword(
String password, {
@ -125,7 +136,10 @@ class PointyCastleDataSourceImpl extends CryptoDataSource {
@override
Future<Uint8List> encryptWithIV(
Uint8List data, SecretKey key, Uint8List iv,) async {
Uint8List data,
SecretKey key,
Uint8List iv,
) async {
final gcm = GCMBlockCipher(AESEngine())
..init(
true,
@ -144,6 +158,15 @@ class PointyCastleDataSourceImpl extends CryptoDataSource {
);
}
@override
Future<void> encryptFile(
File plainText,
Uri folderResult,
SecretKey key,
) async {
throw UnimplementedError();
}
@override
Future<SecretKey> generateSecureRandom(int length) async {
if (_secureRandom == null) {

View File

@ -4,6 +4,7 @@
// 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/native_crypto.dart';
@ -30,6 +31,22 @@ class CryptoRepositoryImpl extends CryptoRepository {
},
);
@override
FutureOrResult<void> decryptFile(
File cipherText,
Uri folderResult,
SecretKey key,
) =>
Result.tryCatchAsync(
() async => cryptoDataSource.decryptFile(cipherText, folderResult, key),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<SecretKey> deriveKeyFromPassword(
String password, {
@ -86,7 +103,10 @@ class CryptoRepositoryImpl extends CryptoRepository {
@override
FutureOrResult<Uint8List> encryptWithIV(
Uint8List data, SecretKey key, Uint8List iv,) =>
Uint8List data,
SecretKey key,
Uint8List iv,
) =>
Result.tryCatchAsync(
() async => cryptoDataSource.encryptWithIV(data, key, iv),
(error) {
@ -97,6 +117,22 @@ class CryptoRepositoryImpl extends CryptoRepository {
},
);
@override
FutureOrResult<void> encryptFile(
File plainText,
Uri folderResult,
SecretKey key,
) =>
Result.tryCatchAsync(
() async => cryptoDataSource.encryptFile(plainText, folderResult, key),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<SecretKey> generateSecureRandom(int length) =>
Result.tryCatchAsync(

View File

@ -0,0 +1,167 @@
// 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/native_crypto.dart';
import 'package:native_crypto_example/domain/data_sources/crypto_data_source.dart';
import 'package:native_crypto_example/domain/entities/mode.dart';
import 'package:native_crypto_example/domain/repositories/crypto_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class CryptoRepositorySwitchableImpl extends CryptoRepository {
CryptoRepositorySwitchableImpl({
required this.nativeCryptoDataSource,
required this.pointyCastleDataSource,
required this.currentMode,
});
CryptoDataSource nativeCryptoDataSource;
CryptoDataSource pointyCastleDataSource;
Mode currentMode;
set mode(Mode mode) {
currentMode = mode;
}
CryptoDataSource get cryptoDataSource {
if (currentMode is NativeCryptoMode) {
return nativeCryptoDataSource;
} else if (currentMode is PointyCastleMode) {
return pointyCastleDataSource;
} else {
throw Exception('Unknown mode');
}
}
@override
FutureOrResult<Uint8List> decrypt(Uint8List data, SecretKey key) =>
Result.tryCatchAsync(
() async => cryptoDataSource.decrypt(data, key),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<void> decryptFile(
File cipherText,
Uri folderResult,
SecretKey key,
) =>
Result.tryCatchAsync(
() async => cryptoDataSource.decryptFile(cipherText, folderResult, key),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<SecretKey> deriveKeyFromPassword(
String password, {
required String salt,
}) =>
Result.tryCatchAsync(
() async => cryptoDataSource.deriveKeyFromPassword(
password,
salt: salt,
),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<Uint8List> hash(Hash hasher, Uint8List data) =>
Result.tryCatchAsync(
() async => cryptoDataSource.hash(hasher, data),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<Uint8List> hmac(Hmac hmac, Uint8List data, SecretKey key) =>
Result.tryCatchAsync(
() async => cryptoDataSource.hmac(hmac, data, key),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<Uint8List> encrypt(Uint8List data, SecretKey key) =>
Result.tryCatchAsync(
() async => cryptoDataSource.encrypt(data, key),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<Uint8List> encryptWithIV(
Uint8List data,
SecretKey key,
Uint8List iv,
) =>
Result.tryCatchAsync(
() async => cryptoDataSource.encryptWithIV(data, key, iv),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<void> encryptFile(
File plainText,
Uri folderResult,
SecretKey key,
) =>
Result.tryCatchAsync(
() async => cryptoDataSource.encryptFile(plainText, folderResult, key),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
@override
FutureOrResult<SecretKey> generateSecureRandom(int length) =>
Result.tryCatchAsync(
() async => cryptoDataSource.generateSecureRandom(length),
(error) {
if (error is NativeCryptoException) {
return ClientException('${error.message}');
}
return ClientException(error.toString());
},
);
}

View File

@ -4,6 +4,8 @@
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
@ -15,12 +17,22 @@ abstract class CryptoDataSource extends BaseDataSource {
required String salt,
});
Future<Uint8List> encrypt(Uint8List data, SecretKey key);
Future<void> encryptFile(
File plainText,
Uri folderResult,
SecretKey key,
);
Future<Uint8List> encryptWithIV(
Uint8List data,
SecretKey key,
Uint8List iv,
);
Future<Uint8List> decrypt(Uint8List data, SecretKey key);
Future<void> decryptFile(
File cipherText,
Uri folderResult,
SecretKey key,
);
Future<Uint8List> hash(Hash hasher, Uint8List data);
Future<Uint8List> hmac(Hmac hmac, Uint8List data, SecretKey key);
}

View File

@ -4,6 +4,8 @@
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
@ -15,12 +17,22 @@ abstract class CryptoRepository extends BaseRepository {
required String salt,
});
FutureOrResult<Uint8List> encrypt(Uint8List data, SecretKey key);
FutureOrResult<void> encryptFile(
File plainText,
Uri folderResult,
SecretKey key,
);
FutureOrResult<Uint8List> encryptWithIV(
Uint8List data,
SecretKey key,
Uint8List iv,
);
FutureOrResult<Uint8List> decrypt(Uint8List data, SecretKey key);
FutureOrResult<void> decryptFile(
File cipherText,
Uri folderResult,
SecretKey key,
);
FutureOrResult<Uint8List> hash(Hash hasher, Uint8List data);
FutureOrResult<Uint8List> hmac(Hmac hmac, Uint8List data, SecretKey key);

View File

@ -8,9 +8,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:native_crypto_example/core/get_it.dart';
import 'package:native_crypto_example/data/data_sources/native_crypto_data_source_impl.dart';
import 'package:native_crypto_example/data/repositories/crypto_repository_impl.dart';
import 'package:native_crypto_example/data/data_sources/pointy_castle_data_source_impl.dart';
import 'package:native_crypto_example/data/repositories/crypto_repository_switchable_impl.dart';
import 'package:native_crypto_example/data/repositories/logger_repository_impl.dart';
import 'package:native_crypto_example/data/repositories/session_repository_impl.dart';
import 'package:native_crypto_example/domain/entities/mode.dart';
import 'package:native_crypto_example/domain/repositories/crypto_repository.dart';
import 'package:native_crypto_example/domain/repositories/logger_repository.dart';
import 'package:native_crypto_example/domain/repositories/session_repository.dart';
@ -28,6 +30,12 @@ class App extends StatelessWidget {
final SessionRepository _sessionRepository =
SessionRepositoryImpl(sessionDataSource: getIt());
final CryptoRepository _cryptoRepository = CryptoRepositorySwitchableImpl(
nativeCryptoDataSource: getIt<NativeCryptoDataSourceImpl>(),
pointyCastleDataSource: getIt<PointyCastleDataSourceImpl>(),
currentMode: const NativeCryptoMode(),
);
@override
Widget build(BuildContext context) => MultiProvider(
repositoryProviders: [
@ -35,18 +43,17 @@ class App extends StatelessWidget {
RepositoryProvider<SessionRepository>.value(
value: _sessionRepository,
),
RepositoryProvider<CryptoRepository>(
create: (_) => CryptoRepositoryImpl(
cryptoDataSource: getIt<NativeCryptoDataSourceImpl>(),
),
),
RepositoryProvider<CryptoRepository>.value(value: _cryptoRepository),
],
blocProviders: [
BlocProvider<OutputCubit>(
create: (_) => OutputCubit(_loggerRepository),
),
BlocProvider<ModeSwitcherCubit>(
create: (_) => ModeSwitcherCubit(_sessionRepository),
create: (_) => ModeSwitcherCubit(
_sessionRepository,
_cryptoRepository,
),
)
],
child: MaterialApp(

View File

@ -0,0 +1,132 @@
// 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:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:native_crypto_example/domain/entities/log_message.dart';
import 'package:native_crypto_example/domain/entities/states.dart';
import 'package:native_crypto_example/domain/repositories/crypto_repository.dart';
import 'package:native_crypto_example/domain/repositories/logger_repository.dart';
import 'package:native_crypto_example/domain/repositories/session_repository.dart';
part 'benchmark_state.dart';
class BenchmarkCubit extends Cubit<BenchmarkState> {
BenchmarkCubit({
required this.sessionRepository,
required this.loggerRepository,
required this.cryptoRepository,
}) : super(const BenchmarkState.initial());
final SessionRepository sessionRepository;
final LoggerRepository loggerRepository;
final CryptoRepository cryptoRepository;
List<int> testedSizes = [
2097152,
6291456,
10485760,
14680064,
18874368,
23068672,
27262976,
31457280,
35651584,
39845888,
44040192,
48234496,
52428800,
];
FutureOr<void> launchBenchmark() async {
emit(const BenchmarkState.loading());
final sk = await sessionRepository.getSessionKey();
if (sk.isErr) {
await loggerRepository.addLog(
const LogError('No SecretKey!\n'
'Go in Key tab and generate or derive one.'),
);
emit(
BenchmarkState.failure(
sk.err?.message,
),
);
}
int run = 0;
final csv = StringBuffer(
'Run;Size (B);Encryption Time (ms);Decryption Time (ms)\n',
);
for (final size in testedSizes) {
run++;
final StringBuffer csvLine = StringBuffer();
final dummyBytes = Uint8List(size);
csvLine.write('$run;$size;');
// Encryption
final beforeEncryption = DateTime.now();
final encryptedBigFileResult = await cryptoRepository.encrypt(
dummyBytes,
sk.ok!,
);
final afterEncryption = DateTime.now();
final benchmarkEncryption = afterEncryption.millisecondsSinceEpoch -
beforeEncryption.millisecondsSinceEpoch;
await loggerRepository.addLog(
LogInfo(
'[Benchmark] ${size ~/ 1000000}MB => Encryption took $benchmarkEncryption ms',
),
);
csvLine.write('$benchmarkEncryption');
if (encryptedBigFileResult.isErr) {
await loggerRepository.addLog(
LogError(
'Encryption failed: ${encryptedBigFileResult.err?.message}',
),
);
emit(
BenchmarkState.failure(
encryptedBigFileResult.err?.message,
),
);
return;
}
// Decryption
final beforeDecryption = DateTime.now();
await cryptoRepository.decrypt(
encryptedBigFileResult.ok!,
sk.ok!,
);
final afterDecryption = DateTime.now();
final benchmarkDecryption = afterDecryption.millisecondsSinceEpoch -
beforeDecryption.millisecondsSinceEpoch;
await loggerRepository.addLog(
LogInfo(
'[Benchmark] ${size ~/ 1000000}MB => Decryption took $benchmarkDecryption ms',
),
);
csvLine.write(';$benchmarkDecryption');
csv.writeln(csvLine);
}
debugPrint(csv.toString());
emit(
const BenchmarkState.success(),
);
return;
}
}

View File

@ -0,0 +1,27 @@
// Copyright 2019-2023 Hugo Pointcheval
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
part of 'benchmark_cubit.dart';
@immutable
class BenchmarkState {
const BenchmarkState.initial()
: state = State.initial,
error = null;
const BenchmarkState.loading()
: state = State.loading,
error = null;
const BenchmarkState.failure(this.error) : state = State.failure;
const BenchmarkState.success()
: state = State.success,
error = null;
final State state;
final String? error;
}

View File

@ -0,0 +1,55 @@
// 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:flutter/material.dart';
import 'package:native_crypto_example/core/typography.dart';
import 'package:native_crypto_example/domain/repositories/crypto_repository.dart';
import 'package:native_crypto_example/domain/repositories/logger_repository.dart';
import 'package:native_crypto_example/domain/repositories/session_repository.dart';
import 'package:native_crypto_example/presentation/benchmark/blocs/benchmark_cubit.dart';
import 'package:native_crypto_example/presentation/home/state_management/widgets/button_state_management.dart';
import 'package:native_crypto_example/presentation/output/widgets/logs.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class BenchmarkStateManagement
extends CubitScreen<BenchmarkCubit, BenchmarkState> {
const BenchmarkStateManagement({super.key});
@override
BenchmarkCubit create(BuildContext context) => BenchmarkCubit(
sessionRepository: repo<SessionRepository>(context),
loggerRepository: repo<LoggerRepository>(context),
cryptoRepository: repo<CryptoRepository>(context),
);
@override
Widget onBuild(BuildContext context, BenchmarkState state) => ListView(
children: [
const Logs(),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'Benchmark',
style: AppTypography.title,
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'''In computer science, a benchmark is a standardized way to measure the performance of a software program or hardware device. A benchmark is typically a set of tests or tasks designed to measure how quickly a program can complete a given set of operations or how efficiently a hardware device can perform a specific task.''',
style: AppTypography.body,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Launch',
onPressed: () => bloc(context).launchBenchmark(),
),
),
],
);
}

View File

@ -5,7 +5,9 @@
// https://opensource.org/licenses/MIT.
import 'dart:async';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:native_crypto/native_crypto.dart';
@ -232,4 +234,184 @@ class AESCubit extends Cubit<AESState> {
return;
}
FutureOr<void> encryptFile() async {
emit(state.copyWith(state: State.loading));
final sk = await sessionRepository.getSessionKey();
if (sk.isErr) {
await loggerRepository.addLog(
const LogError('No SecretKey!\n'
'Go in Key tab and generate or derive one.'),
);
emit(
state.copyWith(
state: State.failure,
error: sk.err?.message,
),
);
return;
}
// Pick file to encrypt
final pickFileResult = await FilePicker.platform.pickFiles();
if (pickFileResult == null) {
await loggerRepository.addLog(
const LogError('No file selected.'),
);
emit(
state.copyWith(
state: State.failure,
error: 'No file selected.',
),
);
return;
}
final file = File(pickFileResult.files.single.path!);
// Pick folder to store the encrypted file
final resultFolder = await FilePicker.platform.getDirectoryPath();
if (resultFolder == null) {
await loggerRepository.addLog(
const LogError('No folder selected.'),
);
emit(
state.copyWith(
state: State.failure,
error: 'No folder selected.',
),
);
return;
}
final folder = Directory(resultFolder);
final encryption = await cryptoRepository.encryptFile(
file,
folder.uri,
sk.ok!,
);
emit(
await encryption.foldAsync(
(_) async {
await loggerRepository.addLog(
const LogInfo('File successfully encrypted.\n'),
);
return state.copyWith(
state: State.success,
plainTextFile: '',
cipherTextFile: '',
);
},
(error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during encryption.'),
);
return state.copyWith(
state: State.failure,
error: error.message,
);
},
),
);
return;
}
FutureOr<void> decryptFile() async {
emit(state.copyWith(state: State.loading));
final sk = await sessionRepository.getSessionKey();
if (sk.isErr) {
await loggerRepository.addLog(
const LogError('No SecretKey!\n'
'Go in Key tab and generate or derive one.'),
);
emit(
state.copyWith(
state: State.failure,
error: sk.err?.message,
),
);
return;
}
await FilePicker.platform.clearTemporaryFiles();
final resultPickFile = await FilePicker.platform.pickFiles();
if (resultPickFile == null) {
await loggerRepository.addLog(
const LogError('No file selected.'),
);
emit(
state.copyWith(
state: State.failure,
error: 'No file selected.',
),
);
return;
}
final file = File(resultPickFile.files.single.path!);
// Pick folder to store the encrypted file
final resultFolder = await FilePicker.platform.getDirectoryPath();
if (resultFolder == null) {
await loggerRepository.addLog(
const LogError('No folder selected.'),
);
emit(
state.copyWith(
state: State.failure,
error: 'No folder selected.',
),
);
return;
}
final folder = Directory(resultFolder);
final decryption =
await cryptoRepository.decryptFile(file, folder.uri, sk.ok!);
emit(
await decryption.foldAsync(
(_) async {
await loggerRepository.addLog(
const LogInfo('File successfully decrypted.\n'),
);
return state.copyWith(
state: State.success,
plainTextFile: '',
cipherTextFile: '',
);
},
(error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during decryption.'),
);
return state.copyWith(
state: State.failure,
error: error.message,
);
},
),
);
return;
}
}

View File

@ -79,6 +79,27 @@ class AESStateManagement extends CubitScreen<AESCubit, AESState> {
onPressed: () => bloc(context).decryptFromMemory(),
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'File',
style: AppTypography.title,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Encrypt file',
onPressed: () => bloc(context).encryptFile(),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Decrypt file',
onPressed: () => bloc(context).decryptFile(),
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(

View File

@ -8,7 +8,9 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:native_crypto_example/data/repositories/crypto_repository_switchable_impl.dart';
import 'package:native_crypto_example/domain/entities/mode.dart';
import 'package:native_crypto_example/domain/repositories/crypto_repository.dart';
import 'package:native_crypto_example/domain/repositories/session_repository.dart';
part 'mode_switcher_state.dart';
@ -16,8 +18,11 @@ part 'mode_switcher_state.dart';
class ModeSwitcherCubit extends Cubit<ModeSwitcherState> {
ModeSwitcherCubit(
this.sessionRepository,
this.cryptoRepository,
) : super(const ModeSwitcherState(NativeCryptoMode()));
SessionRepository sessionRepository;
CryptoRepository cryptoRepository;
FutureOr<void> switchMode() async {
final currentMode = await sessionRepository.getCurrentMode();
@ -31,9 +36,16 @@ class ModeSwitcherCubit extends Cubit<ModeSwitcherState> {
}
sessionRepository.setCurrentMode(newMode);
if (cryptoRepository is CryptoRepositorySwitchableImpl) {
(cryptoRepository as CryptoRepositorySwitchableImpl).mode = newMode;
}
} else {
newMode = const NativeCryptoMode();
sessionRepository.setCurrentMode(newMode);
if (cryptoRepository is CryptoRepositorySwitchableImpl) {
(cryptoRepository as CryptoRepositorySwitchableImpl).mode = newMode;
}
}
emit(ModeSwitcherState(newMode));

View File

@ -5,12 +5,12 @@
// https://opensource.org/licenses/MIT.
import 'package:flutter/material.dart';
import 'package:native_crypto_example/presentation/benchmark/state_management/benchmark_state_management.dart';
import 'package:native_crypto_example/presentation/cipher/state_management/aes_state_management.dart';
import 'package:native_crypto_example/presentation/hash/state_management/hash_state_management.dart';
import 'package:native_crypto_example/presentation/home/blocs/navigation_bar/navigation_bar_cubit.dart';
import 'package:native_crypto_example/presentation/home/state_management/widgets/app_bar_state_management.dart';
import 'package:native_crypto_example/presentation/home/state_management/widgets/bottom_navigation_bar_state_management.dart';
import 'package:native_crypto_example/presentation/home/widgets/blank.dart';
import 'package:native_crypto_example/presentation/kdf/state_management/key_derivation_state_management.dart';
import 'package:native_crypto_example/presentation/test_vectors/state_management/test_vectors_state_management.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
@ -24,7 +24,7 @@ class HomeStateManagement
HashStateManagement(),
AESStateManagement(),
TestVectorsStateManagement(),
const Blank()
const BenchmarkStateManagement(),
];
@override

View File

@ -22,12 +22,12 @@ class AppBarStateManagement
: 'PointyCastle',
),
backgroundColor: state.currentMode.primaryColor,
// TODO(hpcl): enable mode switcher
// actions: [
// Switch(
// value: state.currentMode == const NativeCryptoMode(),
// onChanged: (_) => bloc(context).switchMode(),
// )
// ],
actions: [
Switch(
activeColor: Colors.white,
value: state.currentMode == const NativeCryptoMode(),
onChanged: (_) => bloc(context).switchMode(),
)
],
);
}

View File

@ -32,6 +32,7 @@ dependencies:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 2.0.0
get_it: ^7.2.0
file_picker: ^5.2.7
dev_dependencies:
flutter_test: { sdk: flutter }

View File

@ -1,5 +1,5 @@
// 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.

View File

@ -6,7 +6,7 @@
abstract class Constants {
/// The default chunk size in bytes used for encryption and decryption.
///
///
/// ~32MB
static const int defaultChunkSize = 33554432;

View File

@ -8,10 +8,13 @@
enum Encoding {
/// UTF-8 encoding, as defined by the Unicode standard.
utf8,
/// UTF-16 encoding, as defined by the Unicode standard.
utf16,
/// Base64 encoding, as defined by RFC 4648.
base64,
/// Hexadecimal encoding.
base16,
}

View File

@ -8,8 +8,10 @@
enum HashAlgorithm {
/// The SHA-256 hash algorithm.
sha256,
/// The SHA-384 hash algorithm.
sha384,
/// The SHA-512 hash algorithm.
sha512,
}

View File

@ -1,5 +1,5 @@
// 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.

View File

@ -1,5 +1,5 @@
// 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.

View File

@ -12,7 +12,7 @@ import 'package:native_crypto/src/domain/byte_array.dart';
/// [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}

View File

@ -16,6 +16,7 @@ import 'package:native_crypto/src/domain/cipher_chunk.dart';
abstract class Cipher<T extends CipherChunk> {
/// {@macro cipher}
const Cipher();
/// Encrypts a [Uint8List] and returns a [CipherText].
Future<CipherText<T>> encrypt(Uint8List plainText);

View File

@ -1,5 +1,5 @@
// 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.

View File

@ -215,13 +215,15 @@ class MockNativeCryptoAPI implements NativeCryptoAPI {
HashAlgorithm argAlgorithm,
) {
if (pbkdf2Fn != null) {
return Future.value(pbkdf2Fn!(
argPassword,
argSalt,
argIterations,
argLength,
argAlgorithm.toString(),
),);
return Future.value(
pbkdf2Fn!(
argPassword,
argSalt,
argIterations,
argLength,
argAlgorithm.toString(),
),
);
} else {
return Future.value(Uint8List.fromList([1, 2, 3]));
}

View File

@ -2,7 +2,7 @@ NativeCrypto - Android Implementation
MIT License
Copyright (c) 2019 - 2022 Hugo Pointcheval
Copyright (c) 2019 - 2023 Hugo Pointcheval
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View File

@ -1,15 +1,13 @@
# native_crypto_android
# NativeCrypto - Android Implementation
A new flutter plugin project.
Android Implementation of [NativeCrypto][1] Plugin.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
This project is a starting point for a Flutter [plug-in package][2], a specialized package that includes platform-specific implementation code for Android and/or iOS.
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
For help getting started with Flutter, view our [online documentation][3], which offers tutorials, samples, guidance on mobile development, and a full API reference.
[1]: ../../README.md
[2]: https://flutter.dev/developing-packages/
[3]: https://flutter.dev/docs

View File

@ -111,7 +111,7 @@ class NativeCrypto(private val context: Context) : NativeCryptoAPI {
): Boolean {
// For now, only AES is supported
val aes = AES()
val params = FileParameters(context, plainTextPath, cipherTextPath)
val params = FileParameters(context, cipherTextPath, plainTextPath)
return aes.decryptFile(params, key)
}

View File

@ -2,6 +2,9 @@ package fr.pointcheval.native_crypto_android.ciphers
import fr.pointcheval.native_crypto_android.interfaces.Cipher
import fr.pointcheval.native_crypto_android.utils.FileParameters
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
@ -60,28 +63,32 @@ class AES : Cipher {
cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk)
}
var len: Int?
val buffer = ByteArray(8192)
val inputFile = fileParameters.getFileInputStream()
val outputFile = fileParameters.getFileOutputStream()
val iv: ByteArray? = cipherInstance!!.iv
val input = BufferedInputStream(fileParameters.getFileInputStream())
outputFile?.write(iv)
outputFile?.flush()
var outputBuffered = BufferedOutputStream(fileParameters.getFileOutputStream(false))
val iv: ByteArray = cipherInstance!!.iv
// Prepend the IV to the cipherText file
outputBuffered.write(iv)
outputBuffered.flush()
outputBuffered.close()
// Reopen the file and append the cipherText
outputBuffered = BufferedOutputStream(fileParameters.getFileOutputStream(true))
val output = CipherOutputStream(outputBuffered, cipherInstance)
var i: Int
do {
i = input.read()
if (i != -1) output.write(i)
} while (i != -1)
output.flush()
output.close()
input.close()
outputBuffered.close()
val encryptedStream = CipherOutputStream(outputFile!!, cipherInstance)
while(true) {
len = inputFile?.read(buffer)
if (len != null && len > 0) {
encryptedStream.write(buffer,0,len)
} else {
break
}
}
encryptedStream.flush()
encryptedStream.close()
inputFile?.close()
outputFile.close()
return fileParameters.outputExists()
}
@ -110,9 +117,7 @@ class AES : Cipher {
val inputFile = fileParameters.getFileInputStream() ?: throw Exception("Error while reading IV")
// Read the first 12 bytes from the file
for (i in 0 until 12) {
iv[i] = inputFile.read().toByte()
}
inputFile.read(iv)
// Initialize secret key spec
val sk = SecretKeySpec(key, "AES")
@ -125,21 +130,19 @@ class AES : Cipher {
cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, gcmParameterSpec)
var len: Int?
val buffer = ByteArray(8192)
val outputFile = fileParameters.getFileOutputStream()
val decryptedStream = CipherOutputStream(outputFile!!, cipherInstance)
while (true) {
len = inputFile.read(buffer)
if(len > 0){
decryptedStream.write(buffer,0, len)
} else {
break
}
}
decryptedStream.flush()
decryptedStream.close()
inputFile.close()
val input = CipherInputStream(BufferedInputStream(inputFile), cipherInstance)
val output = BufferedOutputStream(fileParameters.getFileOutputStream(false))
var i: Int
do {
i = input.read()
if (i != -1) output.write(i)
} while (i != -1)
output.flush()
output.close()
input.close()
return fileParameters.outputExists()
}

View File

@ -43,14 +43,15 @@ class FileParameters(ctx: Context, input: String, output: String) {
}
}
fun getFileOutputStream(): OutputStream? {
fun getFileOutputStream(append: Boolean): OutputStream? {
val path = outputPath
return try{
FileOutputStream(path)
FileOutputStream(path, append)
} catch(e: IOException){
val documentFile: DocumentFile = this.getDocumentFileByPath(path)
val documentUri = documentFile.uri
context.contentResolver.openOutputStream(documentUri)
val mode = if(append) "wa" else "w"
context.contentResolver.openOutputStream(documentUri, mode)
}
}

View File

@ -2,7 +2,7 @@ NativeCrypto - iOS Implementation
MIT License
Copyright (c) 2019 - 2022 Hugo Pointcheval
Copyright (c) 2019 - 2023 Hugo Pointcheval
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View File

@ -1,15 +1,13 @@
# NativeCrypto - iOS Implementation
iOS Implementation of NativeCrypto Plugin.
iOS Implementation of [NativeCrypto][1] Plugin.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
This project is a starting point for a Flutter [plug-in package][2], a specialized package that includes platform-specific implementation code for Android and/or iOS.
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
For help getting started with Flutter, view our [online documentation][3], which offers tutorials, samples, guidance on mobile development, and a full API reference.
[1]: ../../README.md
[2]: https://flutter.dev/developing-packages/
[3]: https://flutter.dev/docs

View File

@ -25,7 +25,6 @@ public class NativeCrypto: NSObject, NativeCryptoAPI {
case .sha256: return FlutterStandardTypedData(bytes: Data(HMAC<SHA256>(key: symmetricKey).finalize()))
case .sha384: return FlutterStandardTypedData(bytes: Data(HMAC<SHA384>(key: symmetricKey).finalize()))
case .sha512: return FlutterStandardTypedData(bytes: Data(HMAC<SHA512>(key: symmetricKey).finalize()))
@unknown default: fatalError("Unknown algorithm")
}
}

View File

@ -15,7 +15,6 @@ public class HashAlgorithmParser {
case .sha256: return SHA256.init()
case .sha384: return SHA384.init()
case .sha512: return SHA512.init()
@unknown default: fatalError("Unknown algorithm")
}
}
@ -24,7 +23,6 @@ public class HashAlgorithmParser {
case .sha256: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256)
case .sha384: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA384)
case .sha512: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512)
@unknown default: fatalError("Unknown algorithm")
}
}

View File

@ -1,45 +0,0 @@
import Foundation
class Task<T> {
var task: () throws -> T
private var successful: Bool = false
private var result: T? = nil
private var exception: Error? = nil
init(task: @escaping () throws -> T) {
self.task = task
}
func isSuccessful() -> Bool {
return successful
}
func getResult() -> T? {
return result
}
func getException() -> Error {
if (exception != nil) {
return exception!
} else {
return NativeCryptoError.unknownError()
}
}
func call() {
do {
result = try task()
exception = nil
successful = true
} catch {
exception = error
result = nil
successful = false
}
}
func finalize(callback: (_ task: Task<T>) -> Void) {
callback(self)
}
}

View File

@ -1,12 +1,12 @@
# NativeCrypto - Platform Interface
A common platform interface for the [`native_crypto`][1] plugin.
A common platform interface for the [NativeCrypto][1] plugin.
This interface allows platform-specific implementations of the `native_crypto` plugin, as well as the plugin itself, to ensure they are supporting the same interface.
## Usage
To implement a new platform-specific implementation of `native_crypto`, extend [`NativeCryptoPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `NativeCryptoPlatform` by calling `NativeCryptoPlatform.instance = MyNativeCryptoPlatform()`.
To implement a new platform-specific implementation of `native_crypto`, extend [`NativeCryptoPlatform`][1] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `NativeCryptoPlatform` by calling `NativeCryptoPlatform.instance = MyNativeCryptoPlatform()`.
## Pigeon
@ -16,5 +16,5 @@ Run generator with `flutter pub run pigeon --input pigeons/messages.dart`.
> Note: Make sure the `lib/src/gen` folder exists before running the generator.
[1]: ../native_crypto
[1]: ../../README.md
[2]: lib/native_crypto_platform_interface.dart