Compare commits

...

47 Commits

Author SHA1 Message Date
cab2843706
chore(release): publish packages
All checks were successful
continuous-integration/drone/push Build is passing
- native_crypto@0.2.0
 - native_crypto_example@0.0.1
2023-04-05 17:11:43 +02:00
3cab93ff71
chore(release): publish packages
All checks were successful
continuous-integration/drone/push Build is passing
- native_crypto_android@0.1.2
 - native_crypto_ios@0.1.2
 - native_crypto_platform_interface@0.2.0
2023-04-05 17:07:07 +02:00
c98b9947b4
docs: update readmes/licences
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-05 17:02:38 +02:00
01832a3b03
chore(api): file format + update readme file 2023-04-05 16:46:35 +02:00
7dc07c693a
feat(api): update example for ios file encryption 2023-04-05 16:42:16 +02:00
550fe8b73e
refactor(ios): remove useless lines/classes 2023-04-05 16:41:54 +02:00
7c8f7206f0
feat(api): update example with benchmark + file encryption 2023-04-05 15:17:56 +02:00
0bf72447a0
fix(android): file encryption 2023-04-05 15:16:45 +02:00
108c394a25
test(api): update mocks with new interface 2023-04-04 23:24:20 +02:00
e47004e2d0
feat(api): update example 2023-04-04 23:23:47 +02:00
2f22cc549d
fix(api): accept empty decrypted plaintext 2023-04-04 23:22:43 +02:00
5be6296829
fix(ios): key length in bits 2023-04-04 23:21:39 +02:00
68217ac4b9
feat(ios): use swift pigeon generator 2023-04-04 22:37:12 +02:00
560f5b4942
feat(android): use kotlin pigeon generator 2023-04-04 22:36:50 +02:00
f570ed076a
feat(interface)!: set pigeon as default implementation 2023-04-04 22:36:10 +02:00
c8ff1149d7
feat(api)!: rework full api with better object oriented architecture 2023-02-22 20:16:40 +01:00
8044ccfa43
feat(interface): make api injectable for test 2023-02-22 20:13:51 +01:00
d8cf8dddc4
feat(android): generate pigeon messages 2023-02-22 17:31:58 +01:00
ccb51adbc4
feat(ios): generate pigeon messages 2023-02-22 17:31:12 +01:00
0a040d2971
feat(interface)!: add pigeon + add hmac + remove useless decryption method 2023-02-22 17:27:58 +01:00
ff981b2361
docs: add android/ios development instructions 2023-02-22 17:25:58 +01:00
f47c352efb
docs: add uml models 2023-02-22 17:25:20 +01:00
38cb0a5988
build: add vscode settings 2023-02-22 17:24:46 +01:00
39badb5613
chore(interface): update deps
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-07 17:07:36 +01:00
3b03e05efe
ci: update drone, melos and pre-commit config 2023-01-07 17:05:06 +01:00
8f4041d7bf Merge pull request 'chore(deps): update dependency flutter_lints to v2' (#5) from renovate/flutter_lints-2.x into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #5
2022-06-01 14:21:03 +00:00
Renovate
897b97f5d5 chore(deps): update dependency flutter_lints to v2
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-01 14:11:26 +00:00
bee0b3e38e Merge pull request 'Fix/Update' (#1) from Fix/Update into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #1
2022-06-01 11:51:15 +00:00
4fde7d0aa2
ci: add badge
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-01 13:47:38 +02:00
d28a49a67b
docs: update readme
All checks were successful
continuous-integration/drone Build is passing
2022-06-01 12:21:49 +02:00
cf4227fb58
ci: add drone config file 2022-05-27 18:18:58 +02:00
ac35cd89ca
style: format all + melos test command 2022-05-27 17:34:53 +02:00
1b00d20ec5
fix: update code to pass tests 2022-05-27 16:41:43 +02:00
cefa73ec3d
test: add cipher tests 2022-05-27 16:41:25 +02:00
ebdcf00c15
fix: update code to pass all tests 2022-05-26 23:23:13 +02:00
96f9aad1b3
test: (WIP) add some tests 2022-05-26 23:22:14 +02:00
c5d42feef4
feat(api): add exception code for platform throw 2022-05-26 20:43:43 +02:00
48ebabb54c
feat: rework bytearray and memory optimization, simplify API 2022-05-26 20:42:53 +02:00
6939a8df7e
refactor: (WIP) optimize exceptions and bytearray 2022-05-26 16:26:16 +02:00
9bfe969c7d
test: (WIP) add mocks and tests for secret key 2022-05-26 16:25:35 +02:00
81335dc350
test(platform): add tests for platform and method channel 2022-05-25 23:31:01 +02:00
9aa4eeb567
fix: update verify function 2022-05-25 23:30:13 +02:00
e016d640c4
doc: copy readme 2022-05-25 23:29:13 +02:00
39a0a44730
fix: change tag length in aes gcm cipher 2022-05-25 23:29:02 +02:00
5729fff09b
refactor(example): update benchmark page 2022-05-25 23:27:22 +02:00
f592799970
docs: update readme 2022-05-25 21:33:51 +02:00
ff6af2491a
chore(release): publish packages
- native_crypto@0.1.1
2022-05-25 16:26:10 +02:00
240 changed files with 10899 additions and 2694 deletions

20
.drone.yml Normal file
View File

@ -0,0 +1,20 @@
kind: pipeline
type: docker
name: default
steps:
- name: quality-check
image: git.wyatt-studio.fr/wyatt-foss/flutter-melos:2.9.0
commands:
- melos run quality-check
- melos run publish:validate
- name: publish
image: git.wyatt-studio.fr/wyatt-foss/flutter-melos:2.9.0
commands:
- melos run publish:validate
trigger:
branch:
- master
event:
- push

7
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,7 @@
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v2.1.1
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
args: [build, ci, docs, feat, fix, perf, refactor, style, test, chore]

19
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"bloc.newCubitTemplate.type": "equatable",
"psi-header.config": {
"blankLinesAfter": 0,
"forceToTop": true,
},
"psi-header.templates": [
{
"language": "*",
"template": [
"Copyright 2019-<<year>> <<author>>",
"",
"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

@ -3,6 +3,124 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## 2023-04-05
### Changes
---
Packages with breaking changes:
- [`native_crypto` - `v0.2.0`](#native_crypto---v020)
Packages with other changes:
- [`native_crypto_example` - `v0.0.1`](#native_crypto_example---v001)
---
#### `native_crypto` - `v0.2.0`
- **REFACTOR**: (WIP) optimize exceptions and bytearray.
- **REFACTOR**: update benchmark page.
- **FIX**: accept empty decrypted plaintext.
- **FIX**: update code to pass tests.
- **FIX**: update code to pass all tests.
- **FIX**: change tag length in aes gcm cipher.
- **FEAT**: update example for ios file encryption.
- **FEAT**: update example with benchmark + file encryption.
- **FEAT**: update example.
- **FEAT**: rework bytearray and memory optimization, simplify API.
- **DOCS**: update readmes/licences.
- **DOCS**: update readme.
- **BREAKING** **FEAT**: rework full api with better object oriented architecture.
#### `native_crypto_example` - `v0.0.1`
- **REFACTOR**: update benchmark page.
- **REFACTOR**: change file organization.
- **PERF**: x10 perfomance improvement on android with better list management.
- **FIX**: update code to pass all tests.
- **FIX**: benchmark output.
- **FIX**: update and fix code.
- **FEAT**: update example for ios file encryption.
- **FEAT**: update example with benchmark + file encryption.
- **FEAT**: update example.
- **FEAT**: rework bytearray and memory optimization, simplify API.
- **FEAT**: export new exceptions.
- **FEAT**: add PointyCastle benchmark.
## 2023-04-05
### Changes
---
Packages with breaking changes:
- [`native_crypto_platform_interface` - `v0.2.0`](#native_crypto_platform_interface---v020)
Packages with other changes:
- [`native_crypto_android` - `v0.1.2`](#native_crypto_android---v012)
- [`native_crypto_ios` - `v0.1.2`](#native_crypto_ios---v012)
---
#### `native_crypto_platform_interface` - `v0.2.0`
- **REFACTOR**: (WIP) optimize exceptions and bytearray.
- **FIX**: update verify function.
- **FEAT**: make api injectable for test.
- **FEAT**: add exception code for platform throw.
- **DOCS**: update readmes/licences.
- **BREAKING** **FEAT**: set pigeon as default implementation.
- **BREAKING** **FEAT**: add pigeon + add hmac + remove useless decryption method.
#### `native_crypto_android` - `v0.1.2`
- **FIX**: file encryption.
- **FEAT**: use kotlin pigeon generator.
- **FEAT**: generate pigeon messages.
- **DOCS**: update readmes/licences.
#### `native_crypto_ios` - `v0.1.2`
- **REFACTOR**: remove useless lines/classes.
- **FIX**: key length in bits.
- **FEAT**: use swift pigeon generator.
- **FEAT**: generate pigeon messages.
- **DOCS**: update readmes/licences.
## 2022-05-25
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`native_crypto` - `v0.1.1`](#native_crypto---v011)
---
#### `native_crypto` - `v0.1.1`
- **REFACTOR**: change file organization.
- **PERF**: x10 perfomance improvement on android with better list management.
- **FIX**: benchmark output.
- **FIX**: update and fix code.
- **FEAT**: export new exceptions.
- **FEAT**: add PointyCastle benchmark.
- **DOCS**: add link to readme file.
## 2022-05-25 ## 2022-05-25
### Changes ### Changes

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2022 Hugo Pointcheval Copyright (c) 2020-2023 Hugo Pointcheval
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

281
README.md
View File

@ -1,3 +1,280 @@
# NativeCrypto <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>
Fast and powerful cryptographic functions thanks to **javax.crypto** , **CommonCrypto** and **CryptoKit**. <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.
## 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'll need to initialize a Hasher object implementing `Hash` . Then, you can digest your message.
```dart
Hash hasher = Sha256();
Uint8List digest = await hasher.digest(message);
```
> In NativeCrypto, you can use the following hash functions: SHA-256, SHA-384, SHA-512
#### HMAC
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
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 = SecretKey.fromList([0x73, 0x65, 0x63, 0x72, 0x65, 0x74]);
SecretKey secretKey = await SecretKey.fromSecureRandom(32); // 32 bytes
```
#### Key derivation
You can derive a `SecretKey` using **PBKDF2**.
First, you need to initialize a `Pbkdf2` object.
```dart
final Pbkdf2 pbkdf2 = Pbkdf2(
length: 32, // 32 bytes
iterations: 1000,
salt: salt.toBytes(),
hashAlgorithm: HashAlgorithm.sha256,
);
```
Then, you can derive a `SecretKey` from a password.
```dart
SecretKey secretKey = await pbkdf2(password: password);
```
> Note: Pbkdf2 is a callable class. You can use it like a function.
#### Cipher
And now, you can use the `SecretKey` to encrypt/decrypt a message.
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 message.
```dart
final CipherText<AESCipherChunk> cipherText = await cipher.encrypt(message);
```
After an encryption you obtain a `CipherText` which contains chunks. You can get the underlying bytes with `cipherText.bytes` .
Uppon receiving encrypted message `receivedData` , you can decrypt it.
You have to reconstruct the ciphertext and the setup the chunk factory.
```dart
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(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
> https://docs.flutter.dev/development/packages-and-plugins/developing-packages#step-2b-add-android-platform-code-ktjava
* Launch Android Studio.
* Select Open an existing Android Studio Project in the Welcome to Android Studio dialog, or select File > Open from the menu, and select the `packages/native_crypto/example/android/build.gradle` file.
* In the Gradle Sync dialog, select OK.
* In the Android Gradle Plugin Update dialog, select Dont remind me again for this project.
### iOS
> https://docs.flutter.dev/development/packages-and-plugins/developing-packages#step-2c-add-ios-platform-code-swifthm
* Launch Xcode.
* Select File > Open, and select the `packages/native_crypto/example/ios/Runner.xcworkspace` file.

View File

@ -1,15 +1,17 @@
name: NativeCrypto name: NativeCrypto
# repository: https://git.pointcheval.fr/NativeCrypto/native-crypto-flutter
packages: packages:
- packages/** - packages/**
command: command:
bootstrap:
usePubspecOverrides: true
version: version:
updateGitTagRefs: true updateGitTagRefs: true
linkToCommits: false # Gitea not support this linkToCommits: false # Gitea not yet supported
workspaceChangelog: true workspaceChangelog: true
# branch: master branch: master
scripts: scripts:
lint:all: lint:all:
@ -18,8 +20,7 @@ scripts:
analyze: analyze:
run: | run: |
melos exec -c 10 -- \ melos exec -c 1 -- flutter analyze --fatal-infos
flutter analyze --fatal-infos
description: Run `flutter analyze` for all packages. description: Run `flutter analyze` for all packages.
format: format:
@ -34,6 +35,32 @@ scripts:
run: git clean -x -d -f -q run: git clean -x -d -f -q
description: Clean things very deeply with `git clean`. description: Clean things very deeply with `git clean`.
test:selective_unit_test:
run: melos exec -- flutter test --no-pub --coverage
description: Run Flutter tests for a specific package in this project.
select-package:
dir-exists:
- test
ignore:
- '*example*'
test:all:
run: melos run test:selective_unit_test --no-select
description: Run all Flutter tests in this project.
quality-check:
run: |
melos clean && \
melos bootstrap && \
melos run test:all
description: Run all targets generally expected in CI for a full local quality check.
publish:validate:
run: melos publish --diff="origin/$DRONE_COMMIT_BRANCH...HEAD" --yes
# publish:
# run: melos publish --diff="origin/$DRONE_COMMIT_BRANCH...HEAD" --no-dry-run --yes
# Additional cleanup lifecycle script, executed when `melos clean` is run. # Additional cleanup lifecycle script, executed when `melos clean` is run.
postclean: > postclean: >
melos exec -c 6 -- "flutter clean" melos exec -c 6 -- "flutter clean"

View File

@ -1,3 +1,31 @@
## 0.2.0
> Note: This release has breaking changes.
- **REFACTOR**: (WIP) optimize exceptions and bytearray.
- **REFACTOR**: update benchmark page.
- **FIX**: accept empty decrypted plaintext.
- **FIX**: update code to pass tests.
- **FIX**: update code to pass all tests.
- **FIX**: change tag length in aes gcm cipher.
- **FEAT**: update example for ios file encryption.
- **FEAT**: update example with benchmark + file encryption.
- **FEAT**: update example.
- **FEAT**: rework bytearray and memory optimization, simplify API.
- **DOCS**: update readmes/licences.
- **DOCS**: update readme.
- **BREAKING** **FEAT**: rework full api with better object oriented architecture.
## 0.1.1
- **REFACTOR**: change file organization.
- **PERF**: x10 perfomance improvement on android with better list management.
- **FIX**: benchmark output.
- **FIX**: update and fix code.
- **FEAT**: export new exceptions.
- **FEAT**: add PointyCastle benchmark.
- **DOCS**: add link to readme file.
## 0.1.0 ## 0.1.0
> Breaking changes ! > Breaking changes !

View File

@ -2,7 +2,7 @@ NativeCrypto
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,3 +1,3 @@
# NativeCrypto # NativeCrypto
Fast and powerful cryptographic functions thanks to **javax.crypto** , **CommonCrypto** and **CryptoKit**. Readme available at [project root](../../README.md).

View File

@ -1 +1 @@
include: package:wyatt_analysis/analysis_options.flutter.experimental.yaml include: package:wyatt_analysis/analysis_options.flutter.yaml

View File

@ -1,10 +1,30 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled and should not be manually edited. # This file should be version controlled.
version: version:
revision: cf4400006550b70f28e4b4af815151d1e74846c6 revision: cd41fdd495f6944ecd3506c21e94c6567b073278
channel: stable channel: stable
project_type: app project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: web
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@ -0,0 +1,15 @@
## 0.0.1
- **REFACTOR**: update benchmark page.
- **REFACTOR**: change file organization.
- **PERF**: x10 perfomance improvement on android with better list management.
- **FIX**: update code to pass all tests.
- **FIX**: benchmark output.
- **FIX**: update and fix code.
- **FEAT**: update example for ios file encryption.
- **FEAT**: update example with benchmark + file encryption.
- **FEAT**: update example.
- **FEAT**: rework bytearray and memory optimization, simplify API.
- **FEAT**: export new exceptions.
- **FEAT**: add PointyCastle benchmark.

View File

@ -1,29 +1 @@
# This file configures the analyzer, which statically analyzes Dart code to include: package:wyatt_analysis/analysis_options.flutter.yaml
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -57,6 +57,7 @@ android {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
} }
namespace 'fr.pointcheval.native_crypto_example'
} }
flutter { flutter {

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="fr.pointcheval.native_crypto_example">
<!-- Flutter needs it to communicate with the running application <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="fr.pointcheval.native_crypto_example">
<application <application
android:label="native_crypto_example" android:label="native_crypto_example"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="fr.pointcheval.native_crypto_example">
<!-- Flutter needs it to communicate with the running application <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->

View File

@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.0' classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>9.0</string> <string>11.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,21 +1,74 @@
PODS: 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) - Flutter (1.0.0)
- native_crypto_ios (0.0.1): - native_crypto_ios (0.0.1):
- Flutter - Flutter
- SDWebImage (5.15.5):
- SDWebImage/Core (= 5.15.5)
- SDWebImage/Core (5.15.5)
- SwiftyGif (5.4.4)
DEPENDENCIES: DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- native_crypto_ios (from `.symlinks/plugins/native_crypto_ios/ios`) - native_crypto_ios (from `.symlinks/plugins/native_crypto_ios/ios`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- SDWebImage
- SwiftyGif
EXTERNAL SOURCES: EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
native_crypto_ios: native_crypto_ios:
:path: ".symlinks/plugins/native_crypto_ios/ios" :path: ".symlinks/plugins/native_crypto_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
native_crypto_ios: de03ec2f594e8d41bcba2341b7ad57fd926ada5d native_crypto_ios: de03ec2f594e8d41bcba2341b7ad57fd926ada5d
SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 50; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -200,6 +200,7 @@
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
@ -214,6 +215,7 @@
}; };
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
@ -340,7 +342,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -418,7 +420,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -467,7 +469,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;

View File

@ -45,5 +45,11 @@
<true/> <true/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,35 @@
// 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:get_it/get_it.dart';
import 'package:native_crypto_example/data/data_sources/logger_data_source_impl.dart';
import 'package:native_crypto_example/data/data_sources/native_crypto_data_source_impl.dart';
import 'package:native_crypto_example/data/data_sources/pointy_castle_data_source_impl.dart';
import 'package:native_crypto_example/data/data_sources/session_data_source_impl.dart';
import 'package:native_crypto_example/domain/data_sources/logger_data_source.dart';
import 'package:native_crypto_example/domain/data_sources/session_data_source.dart';
final getIt = GetIt.I;
abstract class GetItInitializer {
static Future<void> init() async {
getIt
..registerLazySingleton<SessionDataSource>(
SessionDataSourceImpl.new,
)
..registerLazySingleton<LoggerDataSource>(
LoggerDataSourceImpl.new,
)
..registerLazySingleton<NativeCryptoDataSourceImpl>(
NativeCryptoDataSourceImpl.new,
)
..registerLazySingleton<PointyCastleDataSourceImpl>(
PointyCastleDataSourceImpl.new,
);
await getIt.allReady();
}
}

View 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 'package:flutter/material.dart';
abstract class AppTypography {
static const title = TextStyle(
color: Colors.black,
fontSize: 24,
);
static const body = TextStyle(
fontSize: 16,
);
}

View File

@ -0,0 +1,34 @@
// 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:native_crypto_example/domain/data_sources/logger_data_source.dart';
import 'package:native_crypto_example/domain/entities/log_message.dart';
class LoggerDataSourceImpl extends LoggerDataSource {
final Map<DateTime, LogMessage> _logs = {};
final StreamController<Map<DateTime, LogMessage>> _streamController =
StreamController.broadcast();
@override
Future<void> addLog(LogMessage message) async {
_logs[DateTime.now()] = message;
_streamController.add(Map.from(_logs));
}
@override
Future<void> clearLog() async {
_logs.clear();
_streamController.add(Map.from(_logs));
}
@override
Future<Map<DateTime, LogMessage>> getLogs() async => _logs;
@override
Stream<Map<DateTime, LogMessage>> streamLogs() => _streamController.stream;
}

View File

@ -0,0 +1,150 @@
// 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 'package:flutter/foundation.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/domain/data_sources/crypto_data_source.dart';
class NativeCryptoDataSourceImpl extends CryptoDataSource {
@override
Future<Uint8List> decrypt(Uint8List data, SecretKey key) async {
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
final Uint8List plainText = await cipher.decrypt(
CipherText(
data,
chunkFactory: (bytes) => AESCipherChunk(
bytes,
ivLength: cipher.mode.ivLength,
tagLength: cipher.mode.tagLength,
),
),
);
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, {
required String salt,
}) async {
final Pbkdf2 pbkdf2 = Pbkdf2(
length: 32,
iterations: 1000,
salt: salt.toBytes(),
hashAlgorithm: HashAlgorithm.sha256,
);
return pbkdf2(password: password);
}
@override
Future<Uint8List> hash(Hash hasher, Uint8List data) async {
final Uint8List digestMessage = await hasher.digest(data);
return digestMessage;
}
@override
Future<Uint8List> hmac(Hmac hmac, Uint8List data, SecretKey key) async {
final Uint8List digestMessage = await hmac.digest(data, key);
return digestMessage;
}
@override
Future<Uint8List> encrypt(Uint8List data, SecretKey key) async {
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
final CipherText<AESCipherChunk> cipherText = await cipher.encrypt(data);
return cipherText.bytes;
}
@override
Future<Uint8List> encryptWithIV(
Uint8List data,
SecretKey key,
Uint8List iv,
) async {
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
final AESCipherChunk chunk = await cipher.encryptWithIV(data, iv);
final CipherText<AESCipherChunk> cipherText = CipherText.fromChunks(
[chunk],
chunkFactory: (bytes) => AESCipherChunk(
bytes,
ivLength: cipher.mode.ivLength,
tagLength: cipher.mode.tagLength,
),
);
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);
return sk;
}
}

View File

@ -0,0 +1,183 @@
// 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.
// 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';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/src/platform_check/platform_check.dart';
class PointyCastleDataSourceImpl extends CryptoDataSource {
FortunaRandom? _secureRandom;
@override
Future<Uint8List> decrypt(Uint8List data, SecretKey key) async {
final iv = Uint8List.fromList(data.sublist(0, 12));
final cipherTextWithoutIv = Uint8List.fromList(
data.sublist(12),
);
final gcm = GCMBlockCipher(AESEngine())
..init(
false,
AEADParameters(
KeyParameter(key.bytes),
16 * 8,
iv,
Uint8List(0),
),
);
final paddedPlainText = gcm.process(cipherTextWithoutIv);
return paddedPlainText;
}
@override
Future<void> decryptFile(
File cipherText,
Uri folderResult,
SecretKey key,
) async {
throw UnimplementedError();
}
@override
Future<SecretKey> deriveKeyFromPassword(
String password, {
required String salt,
}) async {
final derivator = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64))
..init(
Pbkdf2Parameters(salt.toBytes(), 1000, 32),
);
final Uint8List sk = derivator.process(password.toBytes());
return SecretKey(sk);
}
@override
Future<Uint8List> hash(Hash hasher, Uint8List data) async {
final Digest? digest;
switch (hasher.runtimeType) {
case Sha256:
digest = SHA256Digest();
break;
case Sha384:
digest = SHA384Digest();
break;
case Sha512:
digest = SHA512Digest();
break;
default:
throw UnsupportedError(
'Unsupported hash algorithm: ${hasher.runtimeType}',
);
}
return digest.process(data);
}
@override
Future<Uint8List> hmac(Hmac hmac, Uint8List data, SecretKey key) async {
final HMac? digest;
switch (hmac.runtimeType) {
case HmacSha256:
digest = HMac.withDigest(SHA256Digest());
digest.init(KeyParameter(key.bytes));
break;
case Sha384:
digest = HMac.withDigest(SHA384Digest());
digest.init(KeyParameter(key.bytes));
break;
case Sha512:
digest = HMac.withDigest(SHA512Digest());
digest.init(KeyParameter(key.bytes));
break;
default:
throw UnsupportedError(
'Unsupported hmac algorithm: ${hmac.runtimeType}',
);
}
return digest.process(data);
}
@override
Future<Uint8List> encrypt(Uint8List data, SecretKey key) async {
final iv = (await generateSecureRandom(12 * 8)).bytes;
final gcm = GCMBlockCipher(AESEngine())
..init(
true,
AEADParameters(
KeyParameter(key.bytes),
16 * 8,
iv,
Uint8List(0),
),
);
final cipherText = gcm.process(data);
return Uint8List.fromList(
iv + cipherText,
);
}
@override
Future<Uint8List> encryptWithIV(
Uint8List data,
SecretKey key,
Uint8List iv,
) async {
final gcm = GCMBlockCipher(AESEngine())
..init(
true,
AEADParameters(
KeyParameter(key.bytes),
16 * 8,
iv,
Uint8List(0),
),
);
final cipherText = gcm.process(data);
return Uint8List.fromList(
iv + cipherText,
);
}
@override
Future<void> encryptFile(
File plainText,
Uri folderResult,
SecretKey key,
) async {
throw UnimplementedError();
}
@override
Future<SecretKey> generateSecureRandom(int length) async {
if (_secureRandom == null) {
_secureRandom = FortunaRandom();
_secureRandom!.seed(
KeyParameter(Platform.instance.platformEntropySource().getBytes(32)),
);
}
final sk = _secureRandom!.nextBytes(length);
return SecretKey(sk);
}
}

View File

@ -0,0 +1,45 @@
// 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/native_crypto.dart';
import 'package:native_crypto_example/domain/data_sources/session_data_source.dart';
import 'package:native_crypto_example/domain/entities/mode.dart';
class SessionDataSourceImpl extends SessionDataSource {
SecretKey? _sk;
Mode? _mode = const NativeCryptoMode();
@override
Future<SecretKey> getSessionKey() async {
if (_sk == null) {
throw Exception('Session key is not ready');
}
return _sk!;
}
@override
Future<bool> isSessionKeyReady() async => _sk != null;
@override
Future<void> setSessionKey(SecretKey key) async {
_sk = key;
}
@override
Future<Mode> getCurrentMode() async {
if (_mode == null) {
throw Exception('Mode is not set');
}
return _mode!;
}
@override
Future<void> setCurrentMode(Mode mode) async {
_mode = mode;
}
}

View File

@ -0,0 +1,147 @@
// 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/repositories/crypto_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class CryptoRepositoryImpl extends CryptoRepository {
CryptoRepositoryImpl({
required this.cryptoDataSource,
});
CryptoDataSource cryptoDataSource;
@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

@ -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

@ -0,0 +1,41 @@
// 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_example/domain/data_sources/logger_data_source.dart';
import 'package:native_crypto_example/domain/entities/log_message.dart';
import 'package:native_crypto_example/domain/repositories/logger_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class LoggerRepositoryImpl extends LoggerRepository {
LoggerRepositoryImpl({
required this.loggerDataSource,
});
final LoggerDataSource loggerDataSource;
@override
FutureOrResult<void> addLog(LogMessage message) => Result.tryCatchAsync(
() async => loggerDataSource.addLog(message),
(error) => ClientException(error.toString()),
);
@override
FutureOrResult<void> clearLog() => Result.tryCatchAsync(
() async => loggerDataSource.clearLog(),
(error) => ClientException(error.toString()),
);
@override
FutureOrResult<Map<DateTime, LogMessage>> getLogs() => Result.tryCatchAsync(
() async => loggerDataSource.getLogs(),
(error) => ClientException(error.toString()),
);
@override
StreamResult<Map<DateTime, LogMessage>> streamLogs() =>
loggerDataSource.streamLogs().map(Ok.new);
}

View File

@ -0,0 +1,49 @@
// 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/native_crypto.dart';
import 'package:native_crypto_example/domain/data_sources/session_data_source.dart';
import 'package:native_crypto_example/domain/entities/mode.dart';
import 'package:native_crypto_example/domain/repositories/session_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class SessionRepositoryImpl extends SessionRepository {
SessionRepositoryImpl({
required this.sessionDataSource,
});
SessionDataSource sessionDataSource;
@override
FutureOrResult<SecretKey> getSessionKey() => Result.tryCatchAsync(
() async => sessionDataSource.getSessionKey(),
(error) => ClientException(error.toString()),
);
@override
FutureOrResult<bool> isSessionKeyReady() => Result.tryCatchAsync(
() async => sessionDataSource.isSessionKeyReady(),
(error) => ClientException(error.toString()),
);
@override
FutureOrResult<void> setSessionKey(SecretKey key) => Result.tryCatchAsync(
() async => sessionDataSource.setSessionKey(key),
(error) => ClientException(error.toString()),
);
@override
FutureOrResult<Mode> getCurrentMode() => Result.tryCatchAsync(
() async => sessionDataSource.getCurrentMode(),
(error) => ClientException(error.toString()),
);
@override
FutureOrResult<void> setCurrentMode(Mode mode) => Result.tryCatchAsync(
() async => sessionDataSource.setCurrentMode(mode),
(error) => ClientException(error.toString()),
);
}

View File

@ -0,0 +1,38 @@
// 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 'package:flutter/foundation.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
abstract class CryptoDataSource extends BaseDataSource {
Future<SecretKey> generateSecureRandom(int length);
Future<SecretKey> deriveKeyFromPassword(
String password, {
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

@ -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.
import 'package:native_crypto_example/domain/entities/log_message.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
abstract class LoggerDataSource extends BaseDataSource {
Future<void> addLog(LogMessage message);
Future<void> clearLog();
Future<Map<DateTime, LogMessage>> getLogs();
Stream<Map<DateTime, LogMessage>> streamLogs();
}

View 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.
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/domain/entities/mode.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
abstract class SessionDataSource extends BaseDataSource {
Future<void> setSessionKey(SecretKey key);
Future<bool> isSessionKeyReady();
Future<SecretKey> getSessionKey();
Future<Mode> getCurrentMode();
Future<void> setCurrentMode(Mode mode);
}

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.
import 'package:flutter/material.dart';
class LogMessage {
const LogMessage(this.prefix, this.color, this.message);
final String prefix;
final Color color;
final String message;
}
class LogInfo extends LogMessage {
const LogInfo(String message) : super('info', Colors.black, message);
}
class LogWarning extends LogMessage {
const LogWarning(String message) : super('warn', Colors.orange, message);
}
class LogError extends LogMessage {
const LogError(String message) : super('fail', Colors.red, message);
}

View File

@ -0,0 +1,34 @@
// 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:wyatt_architecture/wyatt_architecture.dart';
class Mode extends Entity {
const Mode(
this.primaryColor,
this.secondaryColor,
);
final Color primaryColor;
final Color secondaryColor;
}
class NativeCryptoMode extends Mode {
const NativeCryptoMode()
: super(
Colors.blue,
Colors.black,
);
}
class PointyCastleMode extends Mode {
const PointyCastleMode()
: super(
Colors.red,
Colors.white,
);
}

View File

@ -0,0 +1,7 @@
// 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.
enum State { initial, loading, success, failure }

View File

@ -0,0 +1,54 @@
// 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/foundation.dart';
import 'package:native_crypto/native_crypto.dart';
class TestVector {
TestVector({
required this.key,
required this.nonce,
required this.plainText,
required this.cipherText,
required this.tag,
});
final String key;
final String nonce;
final String plainText;
final String cipherText;
final String tag;
Uint8List get keyBytes => key.toBytes(from: Encoding.base16);
Uint8List get nonceBytes => nonce.toBytes(from: Encoding.base16);
Uint8List get tagBytes => tag.toBytes(from: Encoding.base16);
Uint8List get plainTextBytes => plainText.toBytes(from: Encoding.base16);
Uint8List get cipherTextBytes {
final iv = nonceBytes;
final data = cipherText.toBytes(from: Encoding.base16);
final tag = tagBytes;
return Uint8List.fromList(iv + data + tag);
}
bool validateCipherText(Uint8List? testCipherText) {
final result = listEquals(testCipherText, cipherTextBytes);
if (!result) {
debugPrint('Cipher texts differs:\n$cipherTextBytes\n$testCipherText');
}
return result;
}
bool validatePlainText(Uint8List? testPlainText) {
final result = listEquals(testPlainText, plainTextBytes);
if (!result) {
debugPrint('Plain texts differs:\n$plainTextBytes\n$testPlainText');
}
return result;
}
bool validate(Uint8List? testPlainText, Uint8List? testCipherText) =>
validateCipherText(testCipherText) && validatePlainText(testPlainText);
}

View File

@ -0,0 +1,39 @@
// 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 'package:flutter/foundation.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
abstract class CryptoRepository extends BaseRepository {
FutureOrResult<SecretKey> generateSecureRandom(int length);
FutureOrResult<SecretKey> deriveKeyFromPassword(
String password, {
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

@ -0,0 +1,18 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: logger_repository.dart
// Created Date: 09/01/2023 22:12:56
// Last Modified: 09/01/2023 23:03:51
// -----
// Copyright (c) 2023
import 'package:native_crypto_example/domain/entities/log_message.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
abstract class LoggerRepository extends BaseRepository {
FutureOrResult<void> addLog(LogMessage message);
FutureOrResult<void> clearLog();
FutureOrResult<Map<DateTime, LogMessage>> getLogs();
StreamResult<Map<DateTime, LogMessage>> streamLogs();
}

View File

@ -0,0 +1,20 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: session_repository.dart
// Created Date: 08/01/2023 17:35:46
// Last Modified: 09/01/2023 22:13:21
// -----
// Copyright (c) 2023
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/domain/entities/mode.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
abstract class SessionRepository extends BaseRepository {
FutureOrResult<void> setSessionKey(SecretKey key);
FutureOrResult<bool> isSessionKeyReady();
FutureOrResult<SecretKey> getSessionKey();
FutureOrResult<Mode> getCurrentMode();
FutureOrResult<void> setCurrentMode(Mode mode);
}

View File

@ -1,64 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: home.dart
// Created Date: 28/12/2021 13:48:36
// Last Modified: 28/12/2021 15:18:03
// -----
// Copyright (c) 2021
import 'package:flutter/material.dart';
import 'package:native_crypto_example/pages/benchmark_page.dart';
import 'pages/cipher_page.dart';
import 'pages/kdf_page.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
final List<Widget> _children = [KdfPage(), CipherPage(), BenchmarkPage()];
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Native Crypto'),
),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.black,
showUnselectedLabels: true,
onTap: onTabTapped, // new
currentIndex: _currentIndex, // new
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.vpn_key),
label: 'Key',
),
BottomNavigationBarItem(
icon: Icon(Icons.lock),
label: 'Encryption',
),
BottomNavigationBarItem(
icon: Icon(Icons.timer),
label: 'Benchmark',
),
],
),
);
}
}

View File

@ -3,23 +3,16 @@
// ----- // -----
// File: main.dart // File: main.dart
// Created Date: 27/12/2021 21:15:12 // Created Date: 27/12/2021 21:15:12
// Last Modified: 28/12/2021 13:51:36 // Last Modified: 10/01/2023 14:59:05
// ----- // -----
// Copyright (c) 2021 // Copyright (c) 2021
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:native_crypto_example/core/get_it.dart';
import 'package:native_crypto_example/home.dart'; import 'package:native_crypto_example/presentation/app/app.dart';
void main() { Future<void> main() async {
runApp(const ProviderScope(child: MyApp())); await GetItInitializer.init();
}
runApp(App());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: Home());
}
} }

View File

@ -1,214 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: benchmark_page.dart
// Created Date: 28/12/2021 15:12:39
// Last Modified: 25/05/2022 15:26:42
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/pointycastle/aes_gcm.dart';
import 'package:native_crypto_example/widgets/button.dart';
import '../session.dart';
import '../widgets/output.dart';
class BenchmarkPage extends ConsumerWidget {
BenchmarkPage({Key? key}) : super(key: key);
final Output keyContent = Output();
final Output benchmarkStatus = Output(large: true);
Future<void> _benchmarkEncryptionOnly(
WidgetRef ref,
Cipher cipher,
) async {
Session state = ref.read(sessionProvider.state).state;
AesGcm pc = AesGcm();
if (state.secretKey.bytes.isEmpty) {
benchmarkStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
return;
}
benchmarkStatus.print("Benchmark 2/4/8/16/32/64/128/256MB\n");
List<int> testedSizes = [2, 4, 8, 16, 32, 64, 128, 256];
String csv = "size;encryption time\n";
var beforeBench = DateTime.now();
Cipher.bytesCountPerChunk = Cipher.bytesCountPerChunk;
benchmarkStatus
.append('[Benchmark] ${Cipher.bytesCountPerChunk} bytes/chunk \n');
for (int size in testedSizes) {
var b = Uint8List(size * 1000000);
csv += "${size * 1000000};";
// Encryption
var before = DateTime.now();
var encryptedBigFile = await cipher.encrypt(b);
var after = DateTime.now();
var benchmark =
after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
benchmarkStatus.append('[$size MB] Encryption took $benchmark ms\n');
csv += "$benchmark\n";
}
var afterBench = DateTime.now();
var benchmark =
afterBench.millisecondsSinceEpoch - beforeBench.millisecondsSinceEpoch;
var sum = testedSizes.reduce((a, b) => a + b);
benchmarkStatus.append(
'Benchmark finished.\nGenerated, and encrypted $sum MB in $benchmark ms');
debugPrint("[Benchmark cvs]\n$csv");
}
Future<void> _benchmark(WidgetRef ref, Cipher cipher,
{bool usePc = false}) async {
Session state = ref.read(sessionProvider.state).state;
AesGcm pc = AesGcm();
if (state.secretKey.bytes.isEmpty) {
benchmarkStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
return;
}
benchmarkStatus.print("Benchmark 2/4/8/16/32/64/128/256MB\n");
List<int> testedSizes = [2, 4, 8, 16, 32, 64, 128, 256];
String csv = "size;encryption time;decryption time;crypto time\n";
var beforeBench = DateTime.now();
Cipher.bytesCountPerChunk = Cipher.bytesCountPerChunk;
benchmarkStatus
.append('[Benchmark] ${Cipher.bytesCountPerChunk} bytes/chunk \n');
for (int size in testedSizes) {
var b = Uint8List(size * 1000000);
csv += "${size * 1000000};";
var cryptoTime = 0;
// Encryption
var before = DateTime.now();
Object encryptedBigFile;
if (usePc) {
encryptedBigFile = pc.encrypt(b, state.secretKey.bytes);
} else {
encryptedBigFile = await cipher.encrypt(b);
}
var after = DateTime.now();
var benchmark =
after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
benchmarkStatus.append('[$size MB] Encryption took $benchmark ms\n');
csv += "$benchmark;";
cryptoTime += benchmark;
// Decryption
before = DateTime.now();
if (usePc) {
pc.decrypt(encryptedBigFile as Uint8List, state.secretKey.bytes);
} else {
await cipher.decrypt(encryptedBigFile as CipherText);
}
after = DateTime.now();
benchmark = after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
benchmarkStatus.append('[$size MB] Decryption took $benchmark ms\n');
csv += "$benchmark;";
cryptoTime += benchmark;
csv += "$cryptoTime\n";
}
var afterBench = DateTime.now();
var benchmark =
afterBench.millisecondsSinceEpoch - beforeBench.millisecondsSinceEpoch;
var sum = testedSizes.reduce((a, b) => a + b);
benchmarkStatus.append(
'Benchmark finished.\nGenerated, encrypted and decrypted $sum MB in $benchmark ms');
debugPrint("[Benchmark cvs]\n$csv");
}
void _clear() {
benchmarkStatus.clear();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
Session state = ref.read(sessionProvider.state).state;
if (state.secretKey.bytes.isEmpty) {
keyContent
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
],
),
),
);
}
keyContent.print(state.secretKey.bytes.toString());
AES cipher = AES(state.secretKey, AESMode.gcm);
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
() => _benchmark(ref, cipher),
"NativeCrypto",
),
Button(
() => _benchmark(ref, cipher, usePc: true),
"PointyCastle",
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
() => _benchmarkEncryptionOnly(ref, cipher),
"NC Persistence",
),
Button(
_clear,
"Clear",
),
],
),
],
),
benchmarkStatus,
],
),
),
);
}
}

View File

@ -1,158 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_page.dart
// Created Date: 28/12/2021 13:33:15
// Last Modified: 25/05/2022 10:49:30
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/widgets/button.dart';
import '../session.dart';
import '../utils.dart';
import '../widgets/output.dart';
// ignore: must_be_immutable
class CipherPage extends ConsumerWidget {
CipherPage({Key? key}) : super(key: key);
final Output keyContent = Output();
final Output encryptionStatus = Output();
final Output decryptionStatus = Output();
final TextEditingController _plainTextController = TextEditingController();
CipherText? cipherText;
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
Session state = ref.read(sessionProvider.state).state;
final plainText = _plainTextController.text.trim();
if (state.secretKey.bytes.isEmpty) {
encryptionStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
} else if (plainText.isEmpty) {
encryptionStatus.print('Entry is empty');
} else {
var stringToBytes = plainText.toBytes();
cipherText = await cipher.encrypt(stringToBytes);
encryptionStatus.print('String successfully encrypted.\n');
encryptionStatus.append("Nonce: " +
cipherText!.iv.toString() +
"\nData: " +
cipherText!.data.toString() +
"\nTag: " +
cipherText!.tag.toString());
}
}
Future<void> _alter() async {
if (cipherText == null) {
decryptionStatus.print('Encrypt before altering CipherText!');
} else {
// Add 1 to the first byte
Uint8List _altered = cipherText!.data;
_altered[0] += 1;
// Recreate cipher text with altered data
cipherText = CipherText(cipherText!.iv, _altered, cipherText!.tag);
encryptionStatus.print('String successfully encrypted.\n');
encryptionStatus.append("Nonce: " +
cipherText!.iv.toString() +
"\nData: " +
cipherText!.data.toString() +
"\nTag: " +
cipherText!.tag.toString());
decryptionStatus.print('CipherText altered!\nDecryption will fail.');
}
}
void _decrypt(WidgetRef ref, Cipher cipher) async {
Session state = ref.read(sessionProvider.state).state;
if (state.secretKey.bytes.isEmpty) {
decryptionStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
} else if (cipherText == null) {
decryptionStatus.print('Encrypt before decrypting!');
} else {
try {
Uint8List plainText = await cipher.decrypt(cipherText!);
var bytesToString = plainText.toStr();
decryptionStatus
.print('String successfully decrypted:\n\n$bytesToString');
} on NativeCryptoException catch (e) {
decryptionStatus.print(e.message ?? 'Decryption failed!');
}
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
Session state = ref.read(sessionProvider.state).state;
if (state.secretKey.bytes.isEmpty) {
keyContent
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
],
),
),
);
}
keyContent.print(state.secretKey.bytes.toString());
AES cipher = AES(state.secretKey, AESMode.gcm);
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
TextField(
controller: _plainTextController,
decoration: const InputDecoration(
hintText: 'Plain text',
),
),
Button(
() => _encrypt(ref, cipher),
"Encrypt",
),
encryptionStatus,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
_alter,
"Alter cipher",
),
Button(
() => _decrypt(ref, cipher),
"Decrypt",
),
],
),
decryptionStatus,
],
),
),
);
}
}

View File

@ -1,130 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: kdf_page.dart
// Created Date: 28/12/2021 13:40:34
// Last Modified: 23/05/2022 22:49:06
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/widgets/button.dart';
import '../session.dart';
import '../utils.dart';
import '../widgets/output.dart';
class KdfPage extends ConsumerWidget {
KdfPage({Key? key}) : super(key: key);
final Output keyContent = Output();
final Output keyStatus = Output();
final Output pbkdf2Status = Output();
final Output hashStatus = Output(large: true);
final TextEditingController _pwdTextController = TextEditingController();
final TextEditingController _messageTextController = TextEditingController();
Future<void> _generate(WidgetRef ref) async {
Session state = ref.read(sessionProvider.state).state;
try {
SecretKey sk = await SecretKey.fromSecureRandom(256);
state.setKey(sk);
keyStatus.print(
"SecretKey successfully generated.\nLength: ${state.secretKey.bytes.length} bytes");
keyContent.print(state.secretKey.bytes.toString());
debugPrint("As hex :\n${sk.base16}");
} catch (e) {
keyStatus.print(e.toString());
}
}
Future<void> _pbkdf2(WidgetRef ref) async {
Session state = ref.read(sessionProvider.state).state;
final password = _pwdTextController.text.trim();
if (password.isEmpty) {
pbkdf2Status.print('Password is empty');
} else {
Pbkdf2 _pbkdf2 = Pbkdf2(32, 1000, algorithm: HashAlgorithm.sha512);
SecretKey sk = await _pbkdf2.derive(password: password, salt: 'salt');
state.setKey(sk);
pbkdf2Status.print('Key successfully derived.');
keyContent.print(state.secretKey.bytes.toString());
debugPrint("As hex :\n${sk.base16}");
}
}
Future<void> _hash(HashAlgorithm hasher) async {
final message = _messageTextController.text.trim();
if (message.isEmpty) {
hashStatus.print('Message is empty');
} else {
Uint8List hash = await hasher.digest(message.toBytes());
hashStatus.print(
'Message successfully hashed with $hasher :${hash.toStr(to: Encoding.hex)}');
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("SecretKey"),
alignment: Alignment.centerLeft,
),
keyContent,
Button(
() => _generate(ref),
"Generate SecretKey",
),
keyStatus,
TextField(
controller: _pwdTextController,
decoration: const InputDecoration(
hintText: 'Password',
),
),
Button(
() => _pbkdf2(ref),
"Apply PBKDF2",
),
pbkdf2Status,
TextField(
controller: _messageTextController,
decoration: const InputDecoration(
hintText: 'Message',
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
() => _hash(HashAlgorithm.sha256),
"SHA256",
),
Button(
() => _hash(HashAlgorithm.sha384),
"SHA384",
),
Button(
() => _hash(HashAlgorithm.sha512),
"SHA512",
),
],
),
hashStatus,
],
),
),
);
}
}

View File

@ -1,11 +1,10 @@
// 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_gcm.dart // license that can be found in the LICENSE file or at
// Created Date: 24/05/2022 16:34:54 // https://opensource.org/licenses/MIT.
// Last Modified: 24/05/2022 17:15:22
// ----- // ignore_for_file: implementation_imports
// Copyright (c) 2022
import 'dart:typed_data'; import 'dart:typed_data';
@ -67,7 +66,8 @@ class AesGcm {
_secureRandom = FortunaRandom(); _secureRandom = FortunaRandom();
_secureRandom!.seed( _secureRandom!.seed(
KeyParameter(Platform.instance.platformEntropySource().getBytes(32))); KeyParameter(Platform.instance.platformEntropySource().getBytes(32)),
);
} }
// Use it to generate the random bytes // Use it to generate the random bytes

View File

@ -0,0 +1,65 @@
// 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: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/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';
import 'package:native_crypto_example/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart';
import 'package:native_crypto_example/presentation/home/state_management/home_state_management.dart';
import 'package:native_crypto_example/presentation/output/blocs/output/output_cubit.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class App extends StatelessWidget {
App({super.key});
final LoggerRepository _loggerRepository =
LoggerRepositoryImpl(loggerDataSource: getIt());
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: [
RepositoryProvider<LoggerRepository>.value(value: _loggerRepository),
RepositoryProvider<SessionRepository>.value(
value: _sessionRepository,
),
RepositoryProvider<CryptoRepository>.value(value: _cryptoRepository),
],
blocProviders: [
BlocProvider<OutputCubit>(
create: (_) => OutputCubit(_loggerRepository),
),
BlocProvider<ModeSwitcherCubit>(
create: (_) => ModeSwitcherCubit(
_sessionRepository,
_cryptoRepository,
),
)
],
child: MaterialApp(
title: 'NativeCrypto',
debugShowCheckedModeBanner: false,
home: HomeStateManagement(),
),
);
}

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

@ -0,0 +1,417 @@
// 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 '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';
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 'aes_state.dart';
class AESCubit extends Cubit<AESState> {
AESCubit({
required this.sessionRepository,
required this.loggerRepository,
required this.cryptoRepository,
}) : super(const AESState.initial());
final SessionRepository sessionRepository;
final LoggerRepository loggerRepository;
final CryptoRepository cryptoRepository;
FutureOr<void> encrypt(String message) 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,
plainText: message.toBytes(),
error: sk.err?.message,
),
);
return;
}
final result = await cryptoRepository.encrypt(message.toBytes(), sk.ok!);
emit(
await result.foldAsync(
(cipherText) async {
await loggerRepository.addLog(
LogInfo('String successfully encrypted.\n'
'Length: ${cipherText.length} bytes.\n'
'Hex:\n${cipherText.toStr(to: Encoding.base16)}'),
);
return state.copyWith(
state: State.success,
plainText: message.toBytes(),
cipherText: cipherText,
plainTextFile: '',
cipherTextFile: '',
);
},
(error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during encryption.'),
);
return state.copyWith(
state: State.failure,
plainText: message.toBytes(),
error: error.message,
);
},
),
);
return;
}
FutureOr<void> alterMemory() async {
emit(state.copyWith(state: State.loading));
if (state.cipherText?.isEmpty ?? true) {
const error = 'Encrypt before decrypting!';
await loggerRepository.addLog(
const LogError(error),
);
emit(
state.copyWith(
state: State.failure,
error: error,
),
);
return;
}
final altered = state.cipherText!;
altered[0] += 1;
await loggerRepository.addLog(
const LogWarning('In memory cipher text altered.'),
);
emit(state.copyWith(cipherText: altered));
return;
}
FutureOr<void> decryptFromMemory() 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;
}
if (state.cipherText?.isEmpty ?? true) {
const error = 'Encrypt before decrypting!';
await loggerRepository.addLog(
const LogError(error),
);
emit(
state.copyWith(
state: State.failure,
error: error,
),
);
return;
}
final result = await cryptoRepository.decrypt(state.cipherText!, sk.ok!);
emit(
await result.foldAsync(
(plainText) async {
await loggerRepository.addLog(
LogInfo('String successfully decrypted.\n'
'Text:\n${plainText.toStr()}'),
);
return state.copyWith(
state: State.success,
plainText: plainText,
plainTextFile: '',
cipherTextFile: '',
);
},
(error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during decryption.'),
);
return state.copyWith(
state: State.failure,
error: error.message,
);
},
),
);
return;
}
FutureOr<void> decryptFromBase16(String message) async {
emit(state.copyWith(state: State.loading));
final sk = await sessionRepository.getSessionKey();
final cipherText = message.toBytes(from: Encoding.base16);
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,
cipherText: cipherText,
error: sk.err?.message,
),
);
return;
}
final result = await cryptoRepository.decrypt(cipherText, sk.ok!);
emit(
await result.foldAsync(
(plainText) async {
await loggerRepository.addLog(
LogInfo('String successfully decrypted.\n'
'Text:\n${plainText.toStr()}'),
);
return state.copyWith(
state: State.success,
plainText: plainText,
cipherText: cipherText,
plainTextFile: '',
cipherTextFile: '',
);
},
(error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during decryption.'),
);
return state.copyWith(
state: State.failure,
cipherText: cipherText,
error: error.message,
);
},
),
);
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

@ -0,0 +1,51 @@
// 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 'aes_cubit.dart';
@immutable
class AESState {
const AESState(
this.state,
this.plainText,
this.cipherText,
this.plainTextFile,
this.cipherTextFile,
this.error,
);
const AESState.initial()
: state = State.initial,
plainText = null,
plainTextFile = null,
cipherText = null,
cipherTextFile = null,
error = null;
final State state;
final Uint8List? plainText;
final Uint8List? cipherText;
final String? plainTextFile;
final String? cipherTextFile;
final String? error;
AESState copyWith({
State? state,
Uint8List? plainText,
Uint8List? cipherText,
String? plainTextFile,
String? cipherTextFile,
String? error,
}) =>
AESState(
state ?? this.state,
plainText ?? this.plainText,
cipherText ?? this.cipherText,
plainTextFile ?? this.plainTextFile,
cipherTextFile ?? this.cipherTextFile,
error ?? this.error,
);
}

View File

@ -0,0 +1,129 @@
// 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/cipher/blocs/aes/aes_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 AESStateManagement extends CubitScreen<AESCubit, AESState> {
AESStateManagement({super.key});
final TextEditingController _plainTextTextController = TextEditingController()
..text = 'abc';
final TextEditingController _cipherTextTextController =
TextEditingController();
@override
AESCubit create(BuildContext context) => AESCubit(
sessionRepository: repo<SessionRepository>(context),
loggerRepository: repo<LoggerRepository>(context),
cryptoRepository: repo<CryptoRepository>(context),
);
@override
Widget onBuild(BuildContext context, AESState state) => ListView(
children: [
const Logs(),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'AES256-GCM',
style: AppTypography.title,
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'''AES is a symmetric-key encryption algorithm that is widely used to encrypt sensitive data. It works by encrypting plaintext (the original data) using a secret key and a set of predefined mathematical operations (known as a cipher). The result is ciphertext (the encrypted data) that can only be decrypted and read by someone who has access to the secret key.\nGCM is a mode of operation for AES that provides both confidentiality and authenticity. It works by combining a block cipher (such as AES) with a Galois Field (a mathematical structure used to define a specific type of encryption) and a Counter (a value that is incremented for each block of data that is processed).''',
style: AppTypography.body,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: TextField(
controller: _plainTextTextController,
decoration: const InputDecoration(
hintText: 'PlainText',
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Encrypt',
onPressed: () =>
bloc(context).encrypt(_plainTextTextController.text),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Alter',
onPressed: () => bloc(context).alterMemory(),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Decrypt',
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(
'External CipherText',
style: AppTypography.title,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: TextField(
controller: _cipherTextTextController,
decoration: const InputDecoration(
hintText: 'Base16 CipherText',
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Decrypt',
onPressed: () => bloc(context)
.decryptFromBase16(_cipherTextTextController.text),
),
),
],
);
}

View File

@ -0,0 +1,53 @@
// 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/native_crypto.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';
part 'hash_state.dart';
class HashCubit extends Cubit<HashState> {
HashCubit({
required this.loggerRepository,
required this.cryptoRepository,
}) : super(const HashState.initial());
final LoggerRepository loggerRepository;
final CryptoRepository cryptoRepository;
FutureOr<void> hash(Hash hasher, String message) async {
emit(const HashState.loading());
final result = await cryptoRepository.hash(hasher, message.toBytes());
emit(
await result.foldAsync(
(digest) async {
await loggerRepository.addLog(
LogInfo('Hash $message using ${hasher.algorithm.name}.\n'
'Length: ${digest.length} bytes.\n'
'Hex:\n${digest.toStr(to: Encoding.base16)}'),
);
return HashState.success(digest);
},
(error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during digest.'),
);
return HashState.failure(error.message);
},
),
);
return;
}
}

View 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.
part of 'hash_cubit.dart';
@immutable
class HashState {
const HashState.initial()
: state = State.initial,
result = null,
error = null;
const HashState.loading()
: state = State.loading,
result = null,
error = null;
const HashState.failure(this.error)
: state = State.failure,
result = null;
const HashState.success(this.result)
: state = State.success,
error = null;
final State state;
final Uint8List? result;
final String? error;
}

View File

@ -0,0 +1,88 @@
// 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/native_crypto.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/presentation/hash/blocs/hash/hash_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 HashStateManagement extends CubitScreen<HashCubit, HashState> {
HashStateManagement({super.key});
final TextEditingController _hashTextController = TextEditingController()
..text = 'abc';
@override
HashCubit create(BuildContext context) => HashCubit(
loggerRepository: repo<LoggerRepository>(context),
cryptoRepository: repo<CryptoRepository>(context),
);
@override
Widget onBuild(BuildContext context, HashState state) => ListView(
children: [
const Logs(),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'Hash',
style: AppTypography.title,
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'''SHA-256, SHA-384, and SHA-512 are cryptographic hash functions. They are used to create a fixed-size output (known as a hash or digest) from an input of any size. The output of a hash function is deterministic, which means that the same input will always produce the same output.''',
style: AppTypography.body,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: TextField(
controller: _hashTextController,
decoration: const InputDecoration(
hintText: 'Message',
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'SHA256',
onPressed: () => bloc(context).hash(
Sha256(),
_hashTextController.text,
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'SHA384',
onPressed: () => bloc(context).hash(
Sha384(),
_hashTextController.text,
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'SHA512',
onPressed: () => bloc(context).hash(
Sha512(),
_hashTextController.text,
),
),
),
],
);
}

View File

@ -0,0 +1,53 @@
// 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/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';
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();
Mode? newMode;
if (currentMode.isOk) {
if (currentMode.ok == const NativeCryptoMode()) {
newMode = const PointyCastleMode();
} else {
newMode = const NativeCryptoMode();
}
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

@ -0,0 +1,26 @@
// 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 'mode_switcher_cubit.dart';
@immutable
class ModeSwitcherState {
const ModeSwitcherState(this.currentMode);
final Mode currentMode;
@override
bool operator ==(covariant ModeSwitcherState other) {
if (identical(this, other)) {
return true;
}
return other.currentMode == currentMode;
}
@override
int get hashCode => currentMode.hashCode;
}

View File

@ -0,0 +1,20 @@
// 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';
part 'navigation_bar_state.dart';
class NavigationBarCubit extends Cubit<NavigationBarState> {
NavigationBarCubit() : super(const NavigationBarState(0));
FutureOr<void> changePage(int page) {
emit(NavigationBarState(page));
}
}

View 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.
part of 'navigation_bar_cubit.dart';
@immutable
class NavigationBarState {
const NavigationBarState(this.index);
final int index;
}

View 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 '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/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';
class HomeStateManagement
extends CubitScreen<NavigationBarCubit, NavigationBarState> {
HomeStateManagement({super.key});
final List<Widget> _children = [
KeyDerivationStateManagement(),
HashStateManagement(),
AESStateManagement(),
TestVectorsStateManagement(),
const BenchmarkStateManagement(),
];
@override
NavigationBarCubit create(BuildContext context) => NavigationBarCubit();
@override
Widget onBuild(BuildContext context, NavigationBarState state) => Scaffold(
appBar: const PreferredSize(
preferredSize: Size(double.infinity, 60),
child: AppBarStateManagement(),
),
body: _children[state.index],
bottomNavigationBar: BottomNavigationBarStateManagement(
onTap: (page) => bloc(context).changePage(page), // new
currentIndex: state.index, // new
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.vpn_key),
label: 'Key',
),
BottomNavigationBarItem(
icon: Icon(Icons.tag),
label: 'Hash',
),
BottomNavigationBarItem(
icon: Icon(Icons.lock),
label: 'Encryption',
),
BottomNavigationBarItem(
icon: Icon(Icons.rule_rounded),
label: 'Tests',
),
BottomNavigationBarItem(
icon: Icon(Icons.timer),
label: 'Benchmark',
),
],
),
);
}

View File

@ -0,0 +1,33 @@
// 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/domain/entities/mode.dart';
import 'package:native_crypto_example/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class AppBarStateManagement
extends CubitConsumerScreen<ModeSwitcherCubit, ModeSwitcherState> {
const AppBarStateManagement({super.key});
@override
Widget onBuild(BuildContext context, ModeSwitcherState state) => AppBar(
centerTitle: true,
title: Text(
state.currentMode == const NativeCryptoMode()
? 'NativeCrypto'
: 'PointyCastle',
),
backgroundColor: state.currentMode.primaryColor,
actions: [
Switch(
activeColor: Colors.white,
value: state.currentMode == const NativeCryptoMode(),
onChanged: (_) => bloc(context).switchMode(),
)
],
);
}

View File

@ -0,0 +1,35 @@
// 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/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class BottomNavigationBarStateManagement
extends CubitConsumerScreen<ModeSwitcherCubit, ModeSwitcherState> {
const BottomNavigationBarStateManagement({
required this.items,
this.currentIndex = 0,
this.onTap,
super.key,
});
final void Function(int)? onTap;
final int currentIndex;
final List<BottomNavigationBarItem> items;
@override
Widget onBuild(BuildContext context, ModeSwitcherState state) =>
BottomNavigationBar(
selectedItemColor: state.currentMode.primaryColor,
unselectedItemColor: Colors.black,
showUnselectedLabels: true,
onTap: onTap,
currentIndex: currentIndex,
items: items,
type: BottomNavigationBarType.shifting,
);
}

View File

@ -0,0 +1,38 @@
// 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:flutter_bloc/flutter_bloc.dart';
import 'package:native_crypto_example/core/typography.dart';
import 'package:native_crypto_example/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class ButtonStateManagement
extends CubitConsumerScreen<ModeSwitcherCubit, ModeSwitcherState> {
const ButtonStateManagement({
required this.label,
this.onPressed,
super.key,
});
final void Function()? onPressed;
final String label;
@override
Widget onBuild(BuildContext context, ModeSwitcherState state) {
context.watch<ModeSwitcherCubit>();
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: state.currentMode.primaryColor,
),
child: Text(
label,
style: AppTypography.body,
),
);
}
}

View File

@ -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.
import 'package:flutter/material.dart';
class Blank extends StatelessWidget {
const Blank({super.key});
@override
Widget build(BuildContext context) =>
const Center(child: Text('Nothing to show'));
}

View File

@ -0,0 +1,112 @@
// 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 'key_derivation_state.dart';
class KeyDerivationCubit extends Cubit<KeyDerivationState> {
KeyDerivationCubit({
required this.sessionRepository,
required this.loggerRepository,
required this.cryptoRepository,
}) : super(const KeyDerivationState.initial());
final SessionRepository sessionRepository;
final LoggerRepository loggerRepository;
final CryptoRepository cryptoRepository;
FutureOr<void> generate() async {
emit(const KeyDerivationState.loading());
final result = await cryptoRepository.generateSecureRandom(32);
emit(
await result.foldAsync(
(key) async {
await sessionRepository.setSessionKey(key);
await loggerRepository.addLog(
LogInfo('SecretKey successfully generated.\n'
'Length: ${key.bytes.length} bytes.\n'
'Hex:\n${key.base16}'),
);
return KeyDerivationState.success(key.bytes);
},
(error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during key generation.'),
);
return KeyDerivationState.failure(error.message);
},
),
);
return;
}
FutureOr<void> pbkdf2(String password) async {
emit(const KeyDerivationState.loading());
if (password.isEmpty) {
const error = 'Password is empty';
await loggerRepository.addLog(
const LogError(error),
);
emit(const KeyDerivationState.failure(error));
}
final result =
await cryptoRepository.deriveKeyFromPassword(password, salt: 'salt');
emit(
await result.foldAsync(
(key) async {
await sessionRepository.setSessionKey(key);
await loggerRepository.addLog(
LogInfo('SecretKey successfully derivated from $password.\n'
'Length: ${key.bytes.length} bytes.\n'
'Hex:\n${key.base16}'),
);
return KeyDerivationState.success(key.bytes);
},
(error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during key derivation.'),
);
return KeyDerivationState.failure(error.message);
},
),
);
return;
}
FutureOr<void> getSessionKey() async {
emit(const KeyDerivationState.loading());
final sk = await sessionRepository.getSessionKey();
emit(
await sk.foldAsync((key) async {
await loggerRepository.addLog(
LogInfo('Session key successfully retreived.\n'
'Length: ${key.bytes.length} bytes.\n'
'Hex:\n${key.base16}'),
);
return KeyDerivationState.success(key.bytes);
}, (error) async {
await loggerRepository.addLog(
LogError(error.message ?? 'Error during key retrieving.'),
);
return KeyDerivationState.failure(error.message);
}),
);
return;
}
}

View 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.
part of 'key_derivation_cubit.dart';
@immutable
class KeyDerivationState {
const KeyDerivationState.initial()
: state = State.initial,
result = null,
error = null;
const KeyDerivationState.loading()
: state = State.loading,
result = null,
error = null;
const KeyDerivationState.failure(this.error)
: state = State.failure,
result = null;
const KeyDerivationState.success(this.result)
: state = State.success,
error = null;
final State state;
final Uint8List? result;
final String? error;
}

View File

@ -0,0 +1,102 @@
// 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/home/state_management/widgets/button_state_management.dart';
import 'package:native_crypto_example/presentation/kdf/blocs/key_derivation/key_derivation_cubit.dart';
import 'package:native_crypto_example/presentation/output/widgets/logs.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class KeyDerivationStateManagement
extends CubitScreen<KeyDerivationCubit, KeyDerivationState> {
KeyDerivationStateManagement({super.key});
final TextEditingController _pwdTextController = TextEditingController()
..text = 'password';
@override
KeyDerivationCubit create(BuildContext context) => KeyDerivationCubit(
sessionRepository: repo<SessionRepository>(context),
loggerRepository: repo<LoggerRepository>(context),
cryptoRepository: repo<CryptoRepository>(context),
);
@override
Widget onBuild(BuildContext context, KeyDerivationState state) => ListView(
children: [
const Logs(),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'Random secret key',
style: AppTypography.title,
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'''A secret key is a piece of information that is used to encrypt and decrypt messages. In symmetric-key encryption, the same secret key is used for both encrypting the original data and for decrypting the encrypted data. The key is kept secret so that only authorized parties can read the encrypted messages.''',
style: AppTypography.body,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Generate secret key',
onPressed: () => bloc(context).generate(),
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'PBKDF2',
style: AppTypography.title,
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'''PBKDF2 (Password-Based Key Derivation Function 2) is a key derivation function that is designed to be computationally expensive and memory-intensive, in order to slow down the process of guessing a password. This makes it more difficult for an attacker to guess a password by using a brute-force attack, which involves trying every possible combination of characters until the correct password is found.''',
style: AppTypography.body,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: TextField(
controller: _pwdTextController,
decoration: const InputDecoration(
hintText: 'Password',
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Compute PBKDF2',
onPressed: () => bloc(context).pbkdf2(_pwdTextController.text),
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'Session',
style: AppTypography.title,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Retrieve session key',
onPressed: () => bloc(context).getSessionKey(),
),
),
],
);
}

View File

@ -0,0 +1,49 @@
// 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/repositories/logger_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'output_state.dart';
class OutputCubit extends Cubit<OutputState> {
OutputCubit(this.loggerRepository) : super(const OutputState.empty()) {
logSubscription = loggerRepository.streamLogs().listen((message) {
if (message.isOk) {
onMessage(message.ok!);
}
});
}
final LoggerRepository loggerRepository;
late StreamSubscription<Result<Map<DateTime, LogMessage>, AppException>>
logSubscription;
FutureOr<void> add(LogMessage message) {
loggerRepository.addLog(message);
}
FutureOr<void> clear() {
loggerRepository.clearLog();
emit(const OutputState.empty());
}
FutureOr<void> onMessage(Map<DateTime, LogMessage> entries) {
emit(OutputState(entries: entries));
}
@override
Future<void> close() {
logSubscription.cancel();
return super.close();
}
}

View 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.
part of 'output_cubit.dart';
@immutable
class OutputState {
const OutputState({
required this.entries,
});
const OutputState.empty() : entries = const {};
final Map<DateTime, LogMessage> entries;
@override
String toString() {
final StringBuffer buffer = StringBuffer();
entries.forEach((key, value) {
buffer
..write(value.prefix)
..write(' at ')
..write(key.toIso8601String())
..write(':\t')
..writeln(value.message);
});
return buffer.toString();
}
}

View File

@ -0,0 +1,76 @@
// 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/presentation/output/blocs/output/output_cubit.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class OutputStateManagement
extends CubitConsumerScreen<OutputCubit, OutputState> {
OutputStateManagement({super.key});
final ScrollController _scrollController = ScrollController();
@override
Widget onBuild(BuildContext context, OutputState state) {
// Auto scroll
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 100),
curve: Curves.easeOut,
);
}
});
return SizedBox(
height: 250,
child: ListView.separated(
controller: _scrollController,
itemCount: state.entries.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.all(8),
child: SizedBox(
height: 1,
child: ColoredBox(color: Colors.black),
),
),
itemBuilder: (context, index) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
state.entries.values.elementAt(index).prefix,
style: TextStyle(
color: state.entries.values.elementAt(index).color,
),
),
Text(
state.entries.keys.elementAt(index).toIso8601String(),
style: TextStyle(
color: state.entries.values.elementAt(index).color,
),
)
],
),
Align(
alignment: Alignment.centerLeft,
child: SelectableText(
state.entries.values.elementAt(index).message,
style: TextStyle(
color: state.entries.values.elementAt(index).color,
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,22 @@
// 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/presentation/home/state_management/widgets/button_state_management.dart';
import 'package:native_crypto_example/presentation/output/blocs/output/output_cubit.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class ClearButtonStateManagement
extends CubitConsumerScreen<OutputCubit, OutputState> {
const ClearButtonStateManagement({super.key});
@override
Widget onBuild(BuildContext context, OutputState state) =>
ButtonStateManagement(
label: 'Clear',
onPressed: () => bloc(context).clear(),
);
}

View File

@ -0,0 +1,44 @@
// 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/presentation/output/state_management/output_state_management.dart';
import 'package:native_crypto_example/presentation/output/state_management/widgets/clear_button_state_management.dart';
class Logs extends StatelessWidget {
const Logs({super.key});
@override
Widget build(BuildContext context) => Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
'Logs',
style: AppTypography.title,
),
ClearButtonStateManagement(),
],
),
),
const SizedBox(
height: 2,
width: double.infinity,
child: ColoredBox(color: Colors.black),
),
OutputStateManagement(),
const SizedBox(
height: 2,
width: double.infinity,
child: ColoredBox(color: Colors.black),
),
],
);
}

View File

@ -0,0 +1,204 @@
// 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/native_crypto.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/entities/test_vector.dart';
import 'package:native_crypto_example/domain/repositories/crypto_repository.dart';
import 'package:native_crypto_example/domain/repositories/logger_repository.dart';
part 'test_vectors_state.dart';
class TestVectorsCubit extends Cubit<TestVectorsState> {
TestVectorsCubit({
required this.loggerRepository,
required this.cryptoRepository,
}) : super(const TestVectorsState.initial());
final LoggerRepository loggerRepository;
final CryptoRepository cryptoRepository;
final tests = [
TestVector(
key: 'b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4',
nonce: '516c33929df5a3284ff463d7',
plainText: '',
cipherText: '',
tag: 'bdc1ac884d332457a1d2664f168c76f0',
),
TestVector(
key: '5fe0861cdc2690ce69b3658c7f26f8458eec1c9243c5ba0845305d897e96ca0f',
nonce: '770ac1a5a3d476d5d96944a1',
plainText: '',
cipherText: '',
tag: '196d691e1047093ca4b3d2ef4baba216',
),
TestVector(
key: '7620b79b17b21b06d97019aa70e1ca105e1c03d2a0cf8b20b5a0ce5c3903e548',
nonce: '60f56eb7a4b38d4f03395511',
plainText: '',
cipherText: '',
tag: 'f570c38202d94564bab39f75617bc87a',
),
TestVector(
key: '31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22',
nonce: '0d18e06c7c725ac9e362e1ce',
plainText: '2db5168e932556f8089a0622981d017d',
cipherText: 'fa4362189661d163fcd6a56d8bf0405a',
tag: 'd636ac1bbedd5cc3ee727dc2ab4a9489',
),
TestVector(
key: '460fc864972261c2560e1eb88761ff1c992b982497bd2ac36c04071cbb8e5d99',
nonce: '8a4a16b9e210eb68bcb6f58d',
plainText: '99e4e926ffe927f691893fb79a96b067',
cipherText: '133fc15751621b5f325c7ff71ce08324',
tag: 'ec4e87e0cf74a13618d0b68636ba9fa7',
),
TestVector(
key: 'f78a2ba3c5bd164de134a030ca09e99463ea7e967b92c4b0a0870796480297e5',
nonce: '2bb92fcb726c278a2fa35a88',
plainText: 'f562509ed139a6bbe7ab545ac616250c',
cipherText: 'e2f787996e37d3b47294bf7ebba5ee25',
tag: '00f613eee9bdad6c9ee7765db1cb45c0',
),
];
FutureOr<void> launchAllTests() async {
emit(const TestVectorsState.loading());
int i = 0;
for (final test in tests) {
final sk = SecretKey(test.keyBytes);
final iv = test.nonceBytes;
// Encryption
final encryption =
await cryptoRepository.encryptWithIV(test.plainTextBytes, sk, iv);
final testCipherText = await encryption
.foldAsync((cipherText) async => cipherText, (error) async {
await loggerRepository.addLog(
LogError(
'TEST $i :: ${error.message ?? 'Error during encryption.'}',
),
);
});
final encryptionSuccess = test.validateCipherText(testCipherText);
if (encryptionSuccess) {
await loggerRepository.addLog(
LogInfo('TEST $i :: Encryption test passed ✅'),
);
} else {
await loggerRepository.addLog(
LogError('TEST $i :: Encryption test failed ❌'),
);
}
// Decryption
final decryption =
await cryptoRepository.decrypt(test.cipherTextBytes, sk);
final testPlainText = await decryption
.foldAsync((plainText) async => plainText, (error) async {
await loggerRepository.addLog(
LogError(
'TEST $i :: ${error.message ?? 'Error during decryption. ❌'}',
),
);
});
final decryptionSuccess = test.validatePlainText(testPlainText);
if (decryptionSuccess) {
await loggerRepository.addLog(
LogInfo('TEST $i :: Decryption test passed ✅'),
);
} else {
await loggerRepository.addLog(
LogError('TEST $i :: Decryption test failed ❌'),
);
}
i++;
}
if (i == tests.length) {
emit(const TestVectorsState.success());
} else {
emit(const TestVectorsState.failure('Error during the test suite'));
}
return;
}
FutureOr<void> launchTest(String number) async {
try {
final id = int.parse(number);
final test = tests.elementAt(id);
final sk = SecretKey(test.keyBytes);
final iv = test.nonceBytes;
// Encryption
final encryption =
await cryptoRepository.encryptWithIV(test.plainTextBytes, sk, iv);
final testCipherText = await encryption
.foldAsync((cipherText) async => cipherText, (error) async {
await loggerRepository.addLog(
LogError(
'TEST $id :: ${error.message ?? 'Error during encryption.'}',
),
);
});
final encryptionSuccess = test.validateCipherText(testCipherText);
if (encryptionSuccess) {
await loggerRepository.addLog(
LogInfo('TEST $id :: Encryption test passed ✅'),
);
} else {
await loggerRepository.addLog(
LogError('TEST $id :: Encryption test failed ❌'),
);
}
// Decryption
final decryption =
await cryptoRepository.decrypt(test.cipherTextBytes, sk);
final testPlainText = await decryption
.foldAsync((plainText) async => plainText, (error) async {
await loggerRepository.addLog(
LogError(
'TEST $id :: ${error.message ?? 'Error during decryption. ❌'}',
),
);
});
final decryptionSuccess = test.validatePlainText(testPlainText);
if (decryptionSuccess) {
await loggerRepository.addLog(
LogInfo('TEST $id :: Decryption test passed ✅'),
);
} else {
await loggerRepository.addLog(
LogError('TEST $id :: Decryption test failed ❌'),
);
}
emit(const TestVectorsState.success());
} catch (e) {
final error = 'Invalid vector number: $number';
await loggerRepository.addLog(LogError(error));
emit(TestVectorsState.failure(error));
}
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 'test_vectors_cubit.dart';
@immutable
class TestVectorsState {
const TestVectorsState.initial()
: state = State.initial,
error = null;
const TestVectorsState.loading()
: state = State.loading,
error = null;
const TestVectorsState.failure(this.error) : state = State.failure;
const TestVectorsState.success()
: state = State.success,
error = null;
final State state;
final String? error;
}

View File

@ -0,0 +1,74 @@
// 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/presentation/home/state_management/widgets/button_state_management.dart';
import 'package:native_crypto_example/presentation/output/widgets/logs.dart';
import 'package:native_crypto_example/presentation/test_vectors/blocs/test_vectors/test_vectors_cubit.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
class TestVectorsStateManagement
extends CubitScreen<TestVectorsCubit, TestVectorsState> {
TestVectorsStateManagement({super.key});
final TextEditingController _vectorNumberTextController =
TextEditingController();
@override
TestVectorsCubit create(BuildContext context) => TestVectorsCubit(
loggerRepository: repo<LoggerRepository>(context),
cryptoRepository: repo<CryptoRepository>(context),
);
@override
Widget onBuild(BuildContext context, TestVectorsState state) => ListView(
children: [
const Logs(),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'Test vectors',
style: AppTypography.title,
),
),
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'''In cryptography, a test vector is a set of input values that is used to test the correct implementation and operation of a cryptographic algorithm or system. Test vectors are usually provided by the creators of the algorithm, or by national standardization organizations, and can be used to verify that an implementation of the algorithm is correct and produces the expected output for a given set of inputs.''',
style: AppTypography.body,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'All Tests',
onPressed: () => bloc(context).launchAllTests(),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: TextField(
controller: _vectorNumberTextController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: 'Vector number',
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ButtonStateManagement(
label: 'Specific Test',
onPressed: () =>
bloc(context).launchTest(_vectorNumberTextController.text),
),
),
],
);
}

View File

@ -1,26 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: session.dart
// Created Date: 28/12/2021 13:54:29
// Last Modified: 28/12/2021 13:58:49
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.dart';
class Session {
SecretKey secretKey;
Session() : secretKey = SecretKey(Uint8List(0));
void setKey(SecretKey sk) {
secretKey = sk;
}
}
// Providers
final sessionProvider = StateProvider<Session>((ref) => Session());

View File

@ -1,52 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: utils.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 28/12/2021 14:40:21
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'dart:convert';
enum Encoding { utf16, base64, hex }
extension StringX on String {
Uint8List toBytes({final from = Encoding.utf16}) {
Uint8List bytes = Uint8List(0);
switch (from) {
case Encoding.utf16:
bytes = Uint8List.fromList(runes.toList());
break;
case Encoding.base64:
bytes = base64.decode(this);
break;
case Encoding.hex:
bytes = Uint8List.fromList(
List.generate(
length ~/ 2,
(i) => int.parse(substring(i * 2, (i * 2) + 2), radix: 16),
).toList(),
);
}
return bytes;
}
}
extension Uint8ListX on Uint8List {
String toStr({final to = Encoding.utf16}) {
String str = "";
switch (to) {
case Encoding.utf16:
str = String.fromCharCodes(this);
break;
case Encoding.base64:
str = base64.encode(this);
break;
case Encoding.hex:
str = map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
}
return str;
}
}

View File

@ -1,31 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: button.dart
// Created Date: 28/12/2021 13:31:17
// Last Modified: 28/12/2021 13:31:34
// -----
// Copyright (c) 2021
import 'package:flutter/material.dart';
class Button extends StatelessWidget {
final void Function() onPressed;
final String label;
const Button(this.onPressed, this.label, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: TextButton.styleFrom(
primary: Colors.blue,
),
child: Text(
label,
style: const TextStyle(color: Colors.white),
),
);
}
}

View File

@ -1,61 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: output.dart
// Created Date: 28/12/2021 13:31:39
// Last Modified: 28/12/2021 14:12:11
// -----
// Copyright (c) 2021
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class Output extends StatelessWidget {
late TextEditingController controller;
final bool large;
final bool editable;
Output({
Key? key,
TextEditingController? controller,
this.large = false,
this.editable = false,
}) : super(key: key) {
this.controller = controller ?? TextEditingController();
}
void print(String message) {
debugPrint(message);
controller.text = message;
}
void append(String message) {
debugPrint(message);
controller.text += message;
}
void appendln(String message) {
debugPrint(message);
controller.text += message + "\n";
}
void clear() {
controller.clear();
}
String read() {
return controller.text;
}
@override
Widget build(BuildContext context) {
return TextField(
enableInteractiveSelection: true,
readOnly: editable ? false : true,
minLines: large ? 3 : 1,
maxLines: large ? 500 : 5,
decoration: const InputDecoration(border: OutlineInputBorder()),
controller: controller,
);
}
}

View File

@ -3,10 +3,10 @@ description: Demonstrates how to use the native_crypto plugin.
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: "none" # Remove this line if you wish to publish to pub.dev
environment: environment:
sdk: ">=2.15.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@ -15,40 +15,37 @@ environment:
# the latest version available on pub.dev. To see which dependencies have newer # the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`. # versions available, run `flutter pub outdated`.
dependencies: dependencies:
flutter: flutter: { sdk: flutter }
sdk: flutter native_crypto: { path: ../ }
cupertino_icons: ^1.0.5
flutter_riverpod: ^2.1.3
pointycastle: ^3.6.2
flutter_bloc: ^8.1.1
native_crypto: wyatt_architecture:
# When depending on this package from a real application you should use: hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
# native_crypto: ^x.y.z version: 0.1.0+1
# See https://dart.dev/tools/pub/dependencies#version-constraints wyatt_type_utils:
# The example app is bundled with the plugin so we use a path dependency on hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
# the parent directory to use the current plugin's version. version: 0.0.4
path: ../ wyatt_bloc_helper:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
# The following adds the Cupertino Icons font to your application. version: 2.0.0
# Use with the CupertinoIcons class for iOS style icons. get_it: ^7.2.0
cupertino_icons: ^1.0.2 file_picker: ^5.2.7
flutter_riverpod: ^1.0.3
pointycastle: ^3.6.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: { sdk: flutter }
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to wyatt_analysis:
# encourage good coding practices. The lint set provided by the package is hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
# activated in the `analysis_options.yaml` file located at the root of your version: 2.4.1
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.4
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter. # The following section is specific to Flutter.
flutter: flutter:
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in # included with your application, so that you can use the icons in
# the material Icons class. # the material Icons class.

View File

@ -0,0 +1,12 @@
# melos_managed_dependency_overrides: native_crypto,native_crypto_android,native_crypto_ios,native_crypto_platform_interface,native_crypto_web
dependency_overrides:
native_crypto:
path: ..
native_crypto_android:
path: ../../native_crypto_android
native_crypto_ios:
path: ../../native_crypto_ios
native_crypto_platform_interface:
path: ../../native_crypto_platform_interface
native_crypto_web:
path: ../../native_crypto_web

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>example</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
}
}).then(function(engineInitializer) {
return engineInitializer.initializeEngine();
}).then(function(appRunner) {
return appRunner.runApp();
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,35 @@
{
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

View File

@ -0,0 +1 @@
../../../native-crypto-rust/pkg/

View File

@ -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: 25/05/2022 10:48:20
// -----
// 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,19 +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/convert.dart';
export 'src/utils/hash_algorithm.dart';
export 'src/utils/kdf_algorithm.dart';
// ignore: constant_identifier_names
const String AUTHOR = 'Hugo Pointcheval';

View File

@ -1,49 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes_builder.dart
// Created Date: 28/12/2021 12:03:11
// Last Modified: 25/05/2022 10:47:11
// -----
// Copyright (c) 2021
import 'package:native_crypto/src/ciphers/aes/aes.dart';
import 'package:native_crypto/src/interfaces/builder.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
class AESBuilder implements Builder<AES> {
SecretKey? _sk;
Future<SecretKey>? _fsk;
AESMode _mode = AESMode.gcm;
AESBuilder withGeneratedKey(int bitsCount) {
_fsk = SecretKey.fromSecureRandom(bitsCount);
return this;
}
AESBuilder withKey(SecretKey secretKey) {
_sk = secretKey;
return this;
}
AESBuilder using(AESMode mode) {
_mode = mode;
return this;
}
@override
Future<AES> build() async {
if (_sk == null) {
if (_fsk == null) {
throw const CipherInitException(
message: 'You must specify or generate a secret key.',
code: 'missing_key',
);
} else {
_sk = await _fsk;
}
}
return AES(_sk!, _mode);
}
}

View File

@ -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: 23/05/2022 22:56:12
// -----
// Copyright (c) 2022
export 'aes_builder.dart'; export 'decryption_builder.dart';
export 'encryption_builder.dart';

View File

@ -0,0 +1,60 @@
// 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 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({
required this.cipher,
required this.cipherText,
required this.onLoading,
required this.onError,
required this.onSuccess,
super.key,
});
/// The [Cipher] that will be used to decrypt the [CipherText].
final Cipher<T> cipher;
/// The [CipherText] that will be decrypted.
final CipherText<T> cipherText;
/// The [Widget] that will be displayed while the [CipherText] is being
/// decrypted.
final Widget Function(BuildContext context) onLoading;
/// The [Widget] that will be displayed if an error occurs while decrypting
/// the [CipherText].
final Widget Function(BuildContext context, Object error) onError;
/// The [Widget] that will be displayed once the [CipherText] has been
/// decrypted.
final Widget Function(BuildContext context, Uint8List plainText) onSuccess;
@override
Widget build(BuildContext context) => FutureBuilder<Uint8List>(
future: cipher.decrypt(cipherText),
builder: (context, snapshot) {
if (snapshot.hasData) {
return onSuccess(context, snapshot.data!);
} else if (snapshot.hasError) {
return onError(context, snapshot.error!);
}
return onLoading(context);
},
);
}

View File

@ -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);
},
);
}

View File

@ -1,111 +1,293 @@
// 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: 25/05/2022 15:40:07
// -----
// 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_list.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_platform_interface/native_crypto_platform_interface.dart'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
export 'package:native_crypto/src/ciphers/aes/aes_key_size.dart'; export 'aes_cipher_chunk.dart';
export 'package:native_crypto/src/ciphers/aes/aes_mode.dart'; export 'aes_key_size.dart';
export 'package:native_crypto/src/ciphers/aes/aes_padding.dart'; export 'aes_mode.dart';
export 'aes_padding.dart';
class AES implements Cipher { /// {@template aes}
/// AES cipher.
///
/// [AES] is a symmetric cipher which means that the same key is used to encrypt
/// and decrypt the data.
/// {@endtemplate}
class AES implements Cipher<AESCipherChunk> {
const AES({
required this.key,
required this.mode,
required this.padding,
this.chunkSize = Constants.defaultChunkSize,
});
static const String _algorithm = 'aes';
/// The key used to encrypt and decrypt the data.
final SecretKey key; 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;
/// The size of the cipher text chunks.
final int chunkSize;
@override @override
CipherAlgorithm get algorithm => CipherAlgorithm.aes; Future<Uint8List> decrypt(CipherText<AESCipherChunk> cipherText) async {
final BytesBuilder plainText = BytesBuilder(copy: false);
final chunks = cipherText.chunks;
AES(this.key, this.mode, {this.padding = AESPadding.none}) { int i = 0;
if (!AESKeySize.supportedSizes.contains(key.bytes.length * 8)) { for (final chunk in chunks) {
throw const CipherInitException( plainText.add(await _decryptChunk(chunk.bytes, count: i++));
message: 'Invalid key length!', }
code: 'invalid_key_length',
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!',
); );
} }
final Map<AESMode, List<AESPadding>> _supported = { if (plainTextFile.existsSync()) {
AESMode.gcm: [AESPadding.none], throw ArgumentError.value(
}; plainTextFile.path,
'plainTextFile.path',
if (!_supported[mode]!.contains(padding)) { 'File already exists!',
throw const CipherInitException(
message: 'Invalid padding!',
code: 'invalid_padding',
); );
} }
}
Future<Uint8List> _decrypt(CipherText cipherText) async { return platform.decryptFile(
return await platform.decryptAsList( cipherTextPath: cipherTextFile.path,
[cipherText.iv, cipherText.payload], plainTextPath: plainTextFile.path,
key.bytes, key: key.bytes,
algorithm.name, algorithm: _algorithm,
) ??
Uint8List(0);
}
Future<CipherText> _encrypt(Uint8List data) async {
final List<Uint8List> cipherText =
await platform.encryptAsList(data, key.bytes, algorithm.name) ??
List.empty();
return CipherText.fromPairIvAndBytes(
cipherText,
dataLength: cipherText.last.length,
); );
} }
@override @override
Future<Uint8List> decrypt(CipherText cipherText) async { Future<CipherText<AESCipherChunk>> encrypt(Uint8List plainText) async {
final BytesBuilder decryptedData = BytesBuilder(copy: false); final chunks = <AESCipherChunk>[];
final chunkedPlainText = plainText.chunked(chunkSize);
if (cipherText is CipherTextList) { int i = 0;
for (final CipherText ct in cipherText.list) { for (final plainTextChunk in chunkedPlainText) {
decryptedData.add(await _decrypt(ct)); final bytes = await _encryptChunk(plainTextChunk, count: i++);
} chunks.add(
} else { AESCipherChunk(
decryptedData.add(await _decrypt(cipherText)); bytes,
ivLength: mode.ivLength,
tagLength: mode.tagLength,
),
);
} }
return decryptedData.toBytes(); return CipherText.fromChunks(
chunks,
chunkFactory: (bytes) => AESCipherChunk(
bytes,
ivLength: mode.ivLength,
tagLength: mode.tagLength,
),
chunkSize: chunkSize,
);
} }
@override @override
Future<CipherText> encrypt(Uint8List data) async { Future<void> encryptFile(File plainTextFile, File cipherTextFile) {
Uint8List dataToEncrypt; if (!plainTextFile.existsSync()) {
throw ArgumentError.value(
final CipherTextList cipherTextList = CipherTextList(); plainTextFile.path,
'plainTextFile.path',
if (data.length > Cipher.bytesCountPerChunk) { 'File does not exist!',
final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil(); );
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);
cipherTextList.add(await _encrypt(dataToEncrypt));
} }
} else {
return _encrypt(data); if (cipherTextFile.existsSync()) {
throw ArgumentError.value(
cipherTextFile.path,
'cipherTextFile.path',
'File already exists!',
);
} }
return cipherTextList;
return platform.encryptFile(
plainTextPath: plainTextFile.path,
cipherTextPath: cipherTextFile.path,
key: key.bytes,
algorithm: _algorithm,
);
}
/// Encrypts the [plainText] with the [iv] chosen by the Flutter side.
///
/// Prefer using [encrypt] instead which will generate a
/// random [iv] on the native side.
///
/// Note: this method doesn't chunk the data. It can lead to memory issues
/// if the [plainText] is too big. Use [encrypt] instead.
Future<AESCipherChunk> encryptWithIV(
Uint8List plainText,
Uint8List iv,
) async {
// Check if the cipher is correctly initialized
_isCorrectlyInitialized();
if (iv.length != mode.ivLength) {
throw ArgumentError.value(
iv.length,
'iv.length',
'Invalid iv length! '
'Expected: ${mode.ivLength}',
);
}
final bytes = await platform.encryptWithIV(
plainText: plainText,
iv: iv,
key: key.bytes,
algorithm: _algorithm,
);
// TODO(hpcl): move these checks to the platform interface
if (bytes == null) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes',
);
}
if (bytes.isEmpty) {
throw const NativeCryptoException(
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned no data',
);
}
return AESCipherChunk(
bytes,
ivLength: mode.ivLength,
tagLength: mode.tagLength,
);
}
/// Ensures that the cipher is correctly initialized.
bool _isCorrectlyInitialized() {
final keySize = key.length * 8;
if (!AESKeySize.supportedSizes.contains(keySize)) {
throw ArgumentError.value(
keySize,
'keySize',
'Invalid key size! '
'Expected: ${AESKeySize.supportedSizes.join(', ')}',
);
}
if (!mode.supportedPaddings.contains(padding)) {
throw ArgumentError.value(
padding,
'padding',
'Invalid padding! '
'Expected: ${mode.supportedPaddings.join(', ')}',
);
}
return true;
}
/// Encrypts the plain text chunk.
Future<Uint8List> _encryptChunk(Uint8List plainChunk, {int count = 0}) async {
// Check if the cipher is correctly initialized
_isCorrectlyInitialized();
Uint8List? bytes;
try {
bytes = await platform.encrypt(
plainChunk,
key: key.bytes,
algorithm: _algorithm,
);
} on NativeCryptoException catch (e) {
throw e.copyWith(
message: 'Failed to encrypt chunk #$count: ${e.message}',
);
}
// TODO(hpcl): move these checks to the platform interface
if (bytes == null) {
throw NativeCryptoException(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes on chunk #$count',
);
}
if (bytes.isEmpty) {
throw NativeCryptoException(
code: NativeCryptoExceptionCode.invalidData,
message: 'Platform returned no data on chunk #$count',
);
}
return bytes;
}
/// Decrypts the cipher text chunk.
Future<Uint8List> _decryptChunk(
Uint8List cipherChunk, {
int count = 0,
}) async {
// Check if the cipher is correctly initialized
_isCorrectlyInitialized();
Uint8List? bytes;
try {
bytes = await platform.decrypt(
cipherChunk,
key: key.bytes,
algorithm: _algorithm,
);
} 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(
code: NativeCryptoExceptionCode.nullError,
message: 'Platform returned null bytes on chunk #$count',
);
}
return bytes;
} }
} }

Some files were not shown because too many files have changed in this diff Show More