chore(api): file format + update readme file
This commit is contained in:
parent
7dc07c693a
commit
01832a3b03
190
README.md
190
README.md
@ -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
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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]));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user