chore(api): file format + update readme file

This commit is contained in:
Hugo Pointcheval 2023-04-05 16:46:35 +02:00
parent 7dc07c693a
commit 01832a3b03
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
11 changed files with 171 additions and 53 deletions

190
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"> <p align="center">
<img width="700px" src="resources/native_crypto.png" style="background-color: rgb(255, 255, 255)"> <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> <h5 align="center">Fast and powerful cryptographic functions for Flutter.</h5>
@ -42,6 +35,71 @@ For comparison, on a *iPhone 13*, you can encrypt/decrypt a message of **2MiB**
In short, NativeCrypto is incomparable with PointyCastle. 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 ## Usage
First, check compatibility with your targets. First, check compatibility with your targets.
@ -50,26 +108,42 @@ First, check compatibility with your targets.
| --- | ------- | ----- | ----- | ------- | --- | | --- | ------- | ----- | ----- | ------- | --- |
| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
> 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))
#### Hash #### 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 ```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 > 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 ```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'); SecretKey secretKey = SecretKey.fromUtf8('secret');
SecretKet secretKey = SecretKey.fromUtf16('secret');
SecretKey secretKey = SecretKey.fromBase64('c2VjcmV0'); SecretKey secretKey = SecretKey.fromBase64('c2VjcmV0');
SecretKey secretKey = SecretKey.fromBase16('63657274'); 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 #### Key derivation
@ -79,20 +153,21 @@ You can derive a `SecretKey` using **PBKDF2**.
First, you need to initialize a `Pbkdf2` object. First, you need to initialize a `Pbkdf2` object.
```dart ```dart
Pbkdf2 pbkdf2 = Pbkdf2( final Pbkdf2 pbkdf2 = Pbkdf2(
keyBytesCount: 32, length: 32, // 32 bytes
iterations: 1000, 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 ```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 #### Cipher
@ -101,44 +176,79 @@ And now, you can use the `SecretKey` to encrypt/decrypt a message.
First, you need to initialize a `Cipher` object. First, you need to initialize a `Cipher` object.
```dart ```dart
AES cipher = AES(secretKey); final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
``` ```
Then, you can encrypt your message. Then, you can encrypt your message.
```dart ```dart
CipherTextWrapper wrapper = await cipher.encrypt(message); final CipherText<AESCipherChunk> cipherText = 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. 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. Uppon receiving encrypted message `receivedData` , you can decrypt it.
You have to reconstruct the wrapper before decrypting. You have to reconstruct the ciphertext and the setup the chunk factory.
```dart ```dart
CipherTextWrapper wrapper = CipherTextWrapper.fromBytes( final CipherText<AESCipherChunk> receivedCipherText CipherText(
data, receivedData,
ivLength: AESMode.gcm.ivLength, chunkFactory: (bytes) => AESCipherChunk(
tagLength: AESMode.gcm.tagLength, bytes,
); ivLength: cipher.mode.ivLength,
tagLength: cipher.mode.tagLength,
),
),
``` ```
Then, you can decrypt your message. Then, you can decrypt your message.
```dart ```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 ## Development
### Android ### Android

View File

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

View File

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

View File

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

View File

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