├── .github └── workflows │ ├── dart.yml │ └── publish.yml ├── .gitignore ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── benchmark ├── README.md ├── all_benchmark.dart ├── digests │ ├── blake2b_benchmark.dart │ ├── sha256_benchmark.dart │ └── sha512_benchmark.dart ├── helpers │ ├── authenticated_encryption_benchmark.dart │ ├── digest_benchmark.dart │ ├── rate_benchmark.dart │ ├── signature_benchmark.dart │ └── tweetnacl_benchmark.dart └── tweetnacl │ ├── authenticated_encryption_benchmark.dart │ ├── ed25519_benchmark.dart │ └── tweetnacl_benchmark.dart ├── doc ├── ARCHITECTURE.md ├── all_diagrams.wsd ├── api_class_diagram.wsd ├── api_diagrams.wsd ├── boxes.wsd ├── class_diagrams.wsd ├── encryption_message.wsd └── sigantures.wsd ├── example ├── box.dart ├── hashing.dart ├── main.dart ├── sealedbox.dart ├── secretbox.dart ├── secure.dart └── signature.dart ├── lib ├── api.dart ├── api │ ├── api.dart │ ├── authenticated_encryption.dart │ ├── encoding.dart │ └── signatures.dart ├── digests.dart ├── ed25519.dart ├── encoding.dart ├── key_derivation.dart ├── message_authentication.dart ├── src │ ├── authenticated_encryption │ │ ├── public.dart │ │ └── secret.dart │ ├── digests │ │ ├── blake2b.dart │ │ └── digests.dart │ ├── encoding │ │ ├── base16_encoder.dart │ │ ├── base32_encoder.dart │ │ └── bech32_encoder.dart │ ├── key_derivation │ │ └── pbkdf2.dart │ ├── message_authentication │ │ └── hmac.dart │ ├── signatures │ │ └── ed25519.dart │ ├── tweetnacl │ │ ├── poly1305.dart │ │ ├── tweetnacl.dart │ │ └── tweetnacl_ext.dart │ └── utils │ │ └── utils.dart ├── tweetnacl.dart └── x25519.dart ├── pubspec.yaml ├── scripts └── doit.sh ├── test ├── all_tests.dart ├── base_test.dart ├── box_test.dart ├── data │ ├── blake2b_vectors.json │ ├── eddsa_ed25519_vectors.json │ ├── pbkdf2_hmac_sha2_test.json │ ├── sha256_vectors.json │ └── sha512_vectors.json ├── diffie_hellman_test.dart ├── hashing_test.dart ├── hmac_test.dart ├── pbkdf_test.dart ├── secretbox_test.dart ├── signing_test.dart ├── tweetnacl_validation_test.dart └── wycheproof │ └── X25519.json └── tool ├── build_run_bench.sh └── dart_crypto.js /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart 2 | 3 | on: 4 | [push, pull_request] 5 | 6 | jobs: 7 | 8 | # Default test configurations. 9 | test: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | sdk: [stable, beta, dev] 15 | architecture: [x64, arm, arm64] 16 | fail-fast: false 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: dart-lang/setup-dart@v1 20 | with: 21 | sdk: ${{ matrix.sdk }} 22 | - name: Print DART_HOME 23 | run: echo "Dart SDK installed in $DART_HOME" 24 | - run: dart pub get 25 | - run: dart format --output=none --set-exit-if-changed . 26 | - run: dart run example/main.dart 27 | - run: dart run example/box.dart 28 | - run: dart run example/sealedbox.dart 29 | - run: dart run example/secretbox.dart 30 | - run: dart run example/secure.dart 31 | - run: dart run example/signature.dart 32 | - run: dart analyze 33 | - run: dart test 34 | 35 | 36 | # Default test configurations. 37 | test_32bit: 38 | runs-on: ${{ matrix.os }} 39 | strategy: 40 | matrix: 41 | os: [ubuntu-latest] 42 | sdk: [stable] 43 | architecture: [ia32, arm] 44 | fail-fast: false 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: dart-lang/setup-dart@v1 48 | with: 49 | sdk: ${{ matrix.sdk }} 50 | - name: Print DART_HOME 51 | run: echo "Dart SDK installed in $DART_HOME" 52 | - run: dart pub get 53 | - run: dart format --output=none --set-exit-if-changed . 54 | - run: dart run example/main.dart 55 | - run: dart run example/box.dart 56 | - run: dart run example/sealedbox.dart 57 | - run: dart run example/secretbox.dart 58 | - run: dart run example/secure.dart 59 | - run: dart run example/signature.dart 60 | - run: dart analyze 61 | - run: dart test 62 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/publish.yml 2 | name: Publish to pub.dev 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v[0-9]+.[0-9]+.[0-9]+*' # tag pattern on pub.dev: 'v{{version}' 8 | 9 | # Publish using custom workflow 10 | jobs: 11 | publish: 12 | permissions: 13 | id-token: write # Required for authentication using OIDC 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: dart-lang/setup-dart@v1 18 | - name: Install dependencies 19 | run: dart pub get 20 | - name: Publish - dry run 21 | run: dart pub publish --dry-run 22 | - name: Publish to pub.dev 23 | run: dart pub publish -f -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | 5 | # Remove the following pattern if you wish to check in your lock file 6 | pubspec.lock 7 | 8 | # Conventional directory for build outputs 9 | build/ 10 | 11 | # Directory created by dartdoc 12 | doc/api/ 13 | 14 | # IntelliJ IDEA 15 | *.iml 16 | /.idea 17 | 18 | # compiled bin files 19 | bin/ 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "pinenacl-dart", 9 | "request": "launch", 10 | "type": "dart", 11 | "program": "${file}" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## v0.6.0 3 | - Updated for the 3.x SDK version. 4 | - Updated CI 5 | 6 | ## v0.5.1 7 | Increased ByteList's max length to 1MB 8 | 9 | ## v0.5.0 10 | API Break changes see details below: 11 | - Renamed HexCoder to Base16Encoder to align with Base32, Bech32 implementations 12 | - Rename Bech32Coder to Bech32Encoder for similar reasons above. 13 | - Added decodeNoHrpCheck static fucntion to Bech32Encoder for being able to decode and alreay encoded bech32 string. 14 | - Made internal Bech32 classes private. 15 | - Removed unnecessary fromList constructors, as ByteList based default constructors require Iterable 16 | - Added 17 | - ByteList.withConstraint: Create a ByteList wit min, max length set to constraint specified 18 | - ByteList.decodeWithConstraint: Same as above but from (base16, bech32 etc) encoded strings. 19 | - ByteList.withConstraintRange: Create a ByteList within the min, max length range i.e., 20 | the created ByteList mist be in the range. 21 | - ByteList.decodeWithConstraintRange: Same as above but from (base16, bech32 etc) encoded strings. 22 | - Refactored the code based on these above changes. 23 | ## v0.4.2 24 | Added bytes length for decoding ByteList 25 | 26 | ## v0.4.1 27 | Updated CHANGELOG 28 | Updated README.md 29 | Switched to lint 30 | 31 | ## v0.4.0 32 | Bumped to v0.4.0 due to the v0.3.5's API breaks 33 | 34 | ## v0.3.5 35 | Fixing LateInitializationError: Field 'prefixLength' has not been initialized - take 2 (#18). 36 | 37 | ## v0.3.4 38 | Fixing SigningKey decode issue (#17). 39 | 40 | ## v0.3.3 41 | Remove unnecessry 0xff masks. 42 | 43 | ## v0.3.2 44 | Fixed #15 that caused the ciphertext differ from messages bigger than 16KB 45 | 46 | ## v0.3.0 47 | 48 | Breaking Changes 49 | 50 | ## [v0.2.1](https://github.com/ilap/pinenacl-dart/tree/v0.2.1) (2021-05-23) 51 | 52 | ## [v0.2.0](https://github.com/ilap/pinenacl-dart/tree/v0.2.0) (2021-03-06) 53 | 54 | ## [v0.2.0-nullsafety.8](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.8) (2021-01-20) 55 | 56 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.7...v0.2.0-nullsafety.8) 57 | 58 | ## [v0.2.0-nullsafety.7](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.7) (2021-01-19) 59 | 60 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.6...v0.2.0-nullsafety.7) 61 | 62 | ## [v0.2.0-nullsafety.6](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.6) (2021-01-17) 63 | 64 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.5...v0.2.0-nullsafety.6) 65 | 66 | # Changelog 67 | 68 | ## [v0.2.0-nullsafety.5](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.5) (2021-01-15) 69 | 70 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.4...v0.2.0-nullsafety.5) 71 | 72 | # Changelog 73 | 74 | ## [v0.2.0-nullsafety.4](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.4) (2021-01-13) 75 | 76 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.3...v0.2.0-nullsafety.4) 77 | 78 | # Changelog 79 | 80 | ## [v0.2.0-nullsafety.3](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.3) (2021-01-11) 81 | 82 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.2...v0.2.0-nullsafety.3) 83 | 84 | # Changelog 85 | 86 | ## [v0.2.0-nullsafety.2](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.2) (2021-01-10) 87 | 88 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.1...v0.2.0-nullsafety.2) 89 | 90 | # Changelog 91 | 92 | ## [v0.2.0-nullsafety.1](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.1) (2020-12-21) 93 | 94 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.0...v0.2.0-nullsafety.1) 95 | 96 | **Merged pull requests:** 97 | 98 | - Pre-release for the null safety migration of this package. [\#7](https://github.com/ilap/pinenacl-dart/pull/7) ([ilap](https://github.com/ilap)) 99 | 100 | # Changelog 101 | 102 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.2.0-nullsafety.0...HEAD) 103 | 104 | **Merged pull requests:** 105 | 106 | - Pre-release for the null safety migration of this package. [\#7](https://github.com/ilap/pinenacl-dart/pull/7) ([ilap](https://github.com/ilap)) 107 | 108 | ## [v0.2.0-nullsafety.0](https://github.com/ilap/pinenacl-dart/tree/v0.2.0-nullsafety.0) (2020-11-20) 109 | 110 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.1.5...v0.2.0-nullsafety.0) 111 | 112 | - Pre-release for the null safety migration of this package. 113 | - Reformatted CHANGELOG.md 114 | - Added `in-house` HexCoder class 115 | - Refactored, cleaned the code for preparing `null-safety` 116 | - Removed `hex`, `bech32` and `convert` package dependencies 117 | - Added analyzer strong-mode's `implicit-casts: false` and `implicit-dynamic: false` 118 | 119 | 120 | ## [v0.1.5](https://github.com/ilap/pinenacl-dart/tree/v0.1.5) (2020-11-20) 121 | 122 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.1.4...v0.1.5) 123 | 124 | - Reverted SHA-256 changes back as it behaved differently on JIT and AOT 125 | i.e. failed test for `pub run test` but not for `pub run tests/all*dart` 126 | - Fixed imports 127 | 128 | ## [v0.1.4](https://github.com/ilap/pinenacl-dart/tree/v0.1.4) (2020-11-20) 129 | 130 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.1.3...v0.1.4) 131 | 132 | - Removed fixnum dependency from poly1305 133 | - Code cleanup and removing fixnum dependencies from some modules. 134 | - Bumped version to 0.1.4 135 | 136 | **Closed issues:** 137 | 138 | - Will this project be supported and maintained going forward? [\#5](https://github.com/ilap/pinenacl-dart/issues/5) 139 | - Support for HKDF \(RFC 5869\) [\#4](https://github.com/ilap/pinenacl-dart/issues/4) 140 | - Second constructor for EncryptedMessage [\#3](https://github.com/ilap/pinenacl-dart/issues/3) 141 | 142 | ## [v0.1.3](https://github.com/ilap/pinenacl-dart/tree/v0.1.3) (2019-10-03) 143 | 144 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.1.2...v0.1.3) 145 | 146 | - Added constructor for `EncryptedMessage` class, see ilap/pinenacl-dart#3 147 | 148 | 149 | ## [v0.1.2](https://github.com/ilap/pinenacl-dart/tree/v0.1.2) (2019-10-01) 150 | 151 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.1.1...v0.1.2) 152 | 153 | - Complete refactor of the API and the base classes. 154 | - Added API class diagrams. 155 | - Swapped `Bech32` back to the latest and working pub package. 156 | - Refactored the `decode` factories. 157 | 158 | ### v0.1.2-dev.4 159 | 160 | - Refactored `SuffixByteList` class to `Suffix` mixin. 161 | - Updated README.md (added HMAC, SHA-256) 162 | - Refactored the `Encoding` classes. 163 | - Swapped `Bech32` to the github version, as pub package does not have custom length for messages. 164 | 165 | ### v0.1.2-dev.3 166 | 167 | - Added SHA-256. 168 | - Added SHA-256 unit tests with the official testvectors. 169 | - Fixed some typos. 170 | - Added crypto_scalar_base for Ed25519Bip32 compatibility 171 | - Added Encoding classes. 172 | - Renamed `ed25519_vectors.json` (RFC8032's EdDSA) to `eddsa_ed25519_vectors.json`. 173 | 174 | ### v0.1.2-dev.2 175 | 176 | - Added TweetNaclExt (Extension) class, that implements the HMAC-SHA-512's based `crypto_auth` 177 | and `crypto_auth_verify` functions of the `NaCl` library (does not exists in TweetNaCl). 178 | - Added HMAC-SHA-512. 179 | - Added HMAC-SHA-512 unit tests. 180 | - Added some `TweetNaCl`'s tests. 181 | - Cleaned some code. 182 | - Fixed exports. 183 | - Renamed _EncryptionMessage class to SuffixByteList. 184 | - Fixed `ByteList`'s constructor 185 | 186 | ### v0.1.2-dev.1 187 | 188 | - Added Class diagrams. 189 | - Added ByteList's immutability tests. 190 | 191 | ## [v0.1.1](https://github.com/ilap/pinenacl-dart/tree/v0.1.1) (2019-09-08) 192 | 193 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/v0.1.0...v0.1.1) 194 | 195 | - Refactored the library for using a simplified API. 196 | - Refactored `AsymmetricKey` and `AsymmetricPrivateKey` classes. 197 | - Refactored `ByteList` to be `unmodofiable` 198 | - Refactored `EncrytpionMessage` based classes e.g. `EncryptedMessage`, `SealedMessage` and `SignedMessage`. 199 | - Refactored `SigningKey` and `VerifyKey` by adding `Sign` and `Verify` interfaces. 200 | - Bumped version to 0.1.1 201 | 202 | ### v0.1.1-dev.2 203 | 204 | - Refactored the `EncryptionMessage` classes 205 | 206 | ### v0.1.1-dev.1 207 | 208 | - Added the `Curve25519`'s official `DH (Diffie-Hellman)` test vector. 209 | - Added the `Wycheproof`'s X25519 test vectors. 210 | 211 | 212 | ## [v0.1.0](https://github.com/ilap/pinenacl-dart/tree/v0.1.0) (2019-09-07) 213 | 214 | [Full Changelog](https://github.com/ilap/pinenacl-dart/compare/dec86ad613679b046dac1044db4744024efba6b9...v0.1.0) 215 | 216 | - Added the `byte-length` official SHA-512 test vectors. 217 | - Added `hashing` example 218 | - Allowed `personalisation` paramater to be less then 16 bytes long by zero-padding to 16 bytes. 219 | 220 | ### v0.1.0-dev.1 221 | 222 | - The initial `draft` version. 223 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pal Dorogi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PineNaCl 2 | 3 | [![Pub Version (stable)](https://img.shields.io/pub/v/pinenacl?color=important&label=pub%20stable&logo=dart)](https://pub.dartlang.org/packages/pinenacl) [![Pub Version (including pre-releases)](https://img.shields.io/pub/v/pinenacl?color=blueviolet&label=pub%20prerelease&include_prereleases&logo=dart)](https://pub.dartlang.org/packages/pinenacl) [![Dart](https://github.com/ilap/pinenacl-dart/workflows/Dart/badge.svg)](https://github.com/ilap/pinenacl-dart/actions?query=workflow%3A%22Dart%22) [![Publish to pub.dev](https://github.com/ilap/pinenacl-dart/actions/workflows/publish.yml/badge.svg)](https://github.com/ilap/pinenacl-dart/actions/workflows/publish.yml) 4 | 5 | 6 | PineNaCl is a Dart implementation of the [`TweetNaCl`](https://tweetnacl.cr.yp.to/) the world's first auditable [high-security cryptographic library](https://tweetnacl.cr.yp.to/tweetnacl-20140917.pdf). 7 | 8 | This dart implementation based on the [`tweetnacl-dart`](https://github.com/jspschool/tweetnacl-dart) library, but it's slightly rewritten and extended by some higher level API implementations, similar to the [PyNaCl library's APIs and concepts](https://github.com/pyca/pynacl), for `real-life` applications. 9 | 10 | Thes library has the aim of 11 | - improving the 12 | - usability, by implementing high-level and simple-to-use APIs, 13 | - security and speed, by using the dart implementation of the high-security `TweetNaCl` library. 14 | - and providing a simple API for creating real-life applications easier. 15 | 16 | # Installing 17 | 18 | 1. Add the following into the `pubspec.yaml` of your dart package: 19 | ``` yaml 20 | dependencies: 21 | pinenacl: ^0.6.0 22 | ``` 23 | 24 | 2. You can install now from the command line with pub: 25 | ``` 26 | $ pub get 27 | ``` 28 | 29 | 3. In your `Dart` code, you can use the similar: 30 | 31 | ``` dart 32 | import 'package:pinenacl/api.dart'; 33 | import 'package:pinenacl/public.dart'; 34 | ``` 35 | 36 | # Features 37 | 38 | `PineNaCl` reuses a lot of terminologies, concepts, sections of documents and implements examples and some features from, the before mentioned [PyNaCl](https://github.com/pyca/pynacl)'s publicly available [readthedocs.io](https://pynacl.readthedocs.io). 39 | 40 | Implemented features: 41 | - __ECDH__ (with Curve25519) for key exchange (authenticated encryptions) 42 | - **_Public-key Encryption_** 43 | - Box (public-key authenticated encryption) and 44 | - SealedBox 45 | - **_Private-key encryption_** 46 | - SecretBox (private-key authenticated encryption) 47 | - __EdDSA__ for **_Digital signatures_** (signing). It is complete (they are valid for all points on the curve) and deterministic i.e. no unique random nonce is required. 48 | - __Ed25519__ Signatures i.e. Curve25519 with SHA-512. 49 | - **_Hashing_** and **_message authentication_** 50 | - SHA-256, 51 | - SHA-512, the default hashing algorithm of the original `TweetNaCl` 52 | - BLAKE2b for KDF and MAC (not implemented in `TweetNaCl`). 53 | - HMAC-SHA512. 54 | - **_Password based key derivation_** and **_password hashing_**. 55 | - **PBKDF2** with `HMAC-SHA512`, iterating the `HMAC-SHA512` many times on a combination of the password and a **_random salt_**. 56 | 57 | 58 | ## Low-level Functions supported by `PineNaCl` 59 | 60 | This library supports all 25 of the [C NaCl functions](#functions_supported_by_tweetnacl), that can be used to build `NaCl` applications. 61 | 1. crypto_box = crypto_box_curve25519xsalsa20poly1305 62 | 2. crypto_box_open 63 | 3. crypto_box_keypair 64 | 4. crypto_box_beforenm 65 | 5. crypto_box_afternm 66 | 6. crypto_box_open_afternm 67 | 7. crypto_core_salsa20 68 | 8. crypto_core_hsalsa20 69 | 9. crypto_hashblocks = crypto_hashblocks_sha512 70 | 10. crypto_hash = crypto_hash_sha512 71 | 11. crypto_onetimeauth = crypto_onetimeauth_poly1305 72 | 12. crypto_onetimeauth_verify 73 | 13. crypto_scalarmult = crypto_scalarmult_curve25519 74 | 14. crypto_scalarmult_base 75 | 15. crypto_secretbox = crypto_secretbox_xsalsa20poly1305 76 | 16. crypto_secretbox_open 77 | 17. crypto_sign = crypto_sign_ed25519 78 | 18. crypto_sign_keypair 79 | 19. crypto_sign_open 80 | 20. crypto_stream = crypto_stream_xsalsa20 81 | 21. crypto_stream_salsa20 82 | 22. crypto_stream_salsa20_xor 83 | 23. crypto_stream_xor 84 | 24. crypto_verify_16 85 | 25. crypto_verify_32 86 | 87 | However a simple `NaCl` application would only need the following six high-level NaCl API functions. 88 | - crypto_box for public-key authenticated encryption; 89 | - crypto_box_open for verification and decryption; 90 | - crypto_box_keypair to create a public key (scalarmult _k_ with basepoint _B_=9) for key exchange. 91 | 92 | Similarly for signatures 93 | - crypto_sign, 94 | - crypto_sign_open, and 95 | - crypto_sign_keypair, to create signing keypair for signing (scalarmult _k_ with basepoint _B_=(x, 4/5)) 96 | 97 | ### Extension to the `TweetNaCl` 98 | 99 | The following `NaCl` library's high-level functions are implemented as the extension to the `TweetNaCl` library. 100 | 101 | - HMAC-SHA512 and HMAC-SHA256 102 | - crypto_auth = crypto_auth_hmacsha512, HMAC-SHA-512 103 | - crypto_auth_hmacsha256, HMAC-SHA-256 104 | - Hashing algorithm 105 | - crypto_hash_sha256, SHA-256 106 | - Utils 107 | - crypto_verify_64, verifying function for SHA-512 as an example 108 | - X25519 conversion utilities 109 | - crypto_sign_ed25519_sk_to_x25519_sk 110 | - crypto_sign_ed25519_pk_to_x25519_pk 111 | - Curve25519 low-level functions 112 | - crypto_scalar_base, for retrieving different type of public-keys e.g. `A = k * B`. 113 | - crypto_point_add, for adding two public keys' point together `A = y1 : y2`. 114 | 115 | 116 | ## Key Types 117 | 118 | | Key id* | Alt key id | Key length | Function | Comment | 119 | |-------------|---------------|------------|---------------------------------|--------------------------------------------------------------------------------------------------| 120 | | ed25519_sk | ed25519_skpk | 64 | Digital Signatures (EdDSA) | `Ed25519` signing key. It can be converted to `X25519` secret key for authenticated encryption. | 121 | | ed25519_pk | | 32 | Digital Signatures (EdDSA) | `Ed25519` verifying key. It can be converted to `X25519` public key for authenticated encryption | 122 | | x25519_sk | curve25519_sk | 32 | Authenticated encryption (ECDH) | `X25519` private key. | 123 | | x25519_pk | curve25519_pk | 32 | Authenticated encryption (ECDH) | `X25519` public key. | 124 | | ed25519e_sk | ed25519_esk | 64 | EdDSA and ECDH | The first 32 byte is a valid `X25519` secret key. | 125 | | ed25519_pk | ed25519e_pk | 32 | EdDSA | It's an `Ed25519` verifying key, so it can be converted to `X25519` public key. | 126 | 127 | 128 | __*__: Key id is the Human-Readable Part (HRP) of the Bech32 (binary-to-text encoding standard/scheme) encoded keys used in `pinenacl-dart`. 129 | 130 | ## Examples 131 | 132 | `PineNaCl` comes /w the following examples: 133 | - `Public Key Encryption` examples 134 | - [Box](#box) example and its [source code](example/box.dart). 135 | - [SealedBox](#sealedbox) example and its [source code](example/sealedbox.dart). 136 | - `Private Key Encryption` example 137 | - [SecretBox](#secretbox) example and its [source code](example/secretbox.dart). 138 | - `Digital Signatures` example 139 | - [Signatures](#signing) example and its [source code](example/signature.dart). 140 | - `Hashing` example 141 | - [Hashing](#hashing) example and its [source code](example/hashing.dart). 142 | 143 | 144 | ### `Public Key Encryption` example 145 | 146 | Implemented from [PyNaCl's example](https://pynacl.readthedocs.io/en/stable/public/#examples) 147 | 148 | #### Box 149 | > Imagine Alice wants something valuable shipped to her. Because it’s valuable, she wants to make sure it arrives securely (i.e. hasn’t been opened or tampered with) and that it’s not a forgery (i.e. it’s actually from the sender she’s expecting it to be from and nobody’s pulling the old switcheroo). 150 | > 151 | > One way she can do this is by providing the sender (let’s call him Bob) with a high-security box of her choosing. She provides Bob with this box, and something else: a padlock, but a padlock without a key. Alice is keeping that key all to herself. Bob can put items in the box then put the padlock onto it. But once the padlock snaps shut, the box cannot be opened by anyone who doesn’t have Alice’s private key. 152 | > 153 | > Here’s the twist though: Bob also puts a padlock onto the box. This padlock uses a key Bob has published to the world, such that if you have one of Bob’s keys, you know a box came from him because Bob’s keys will open Bob’s padlocks (let’s imagine a world where padlocks cannot be forged even if you know the key). Bob then sends the box to Alice. 154 | > 155 | > In order for Alice to open the box, she needs two keys: her private key that opens her own padlock, and Bob’s well-known key. If Bob’s key doesn’t open the second padlock, then Alice knows that this is not the box she was expecting from Bob, it’s a forgery. 156 | > 157 | >This bidirectional guarantee around identity is known as mutual authentication. 158 | > 159 | > -- [PyNaCl](https://pynacl.readthedocs.io/en/stable/public/#examples) 160 | 161 | ``` dart 162 | import 'package:pinenacl/api.dart'; 163 | import 'package:pinenacl/public.dart' show PrivateKey; 164 | 165 | void main() { 166 | // Generate Bob's private key, which must be kept secret 167 | final skbob = PrivateKey.generate(); 168 | 169 | // Bob's public key can be given to anyone wishing to send 170 | // Bob an encrypted message 171 | final pkbob = skbob.publicKey; 172 | 173 | // Alice does the same and then Alice and Bob exchange public keys 174 | final skalice = PrivateKey.generate(); 175 | 176 | final pkalice = skalice.publicKey; 177 | 178 | // Bob wishes to send Alice an encrypted message so Bob must make a Box with 179 | // his private key and Alice's public key 180 | final bobBox = Box(myPrivateKey: skbob, theirPublicKey: pkalice); 181 | 182 | // This is our message to send, it must be a bytestring as Box will treat it 183 | // as just a binary blob of data. 184 | final message = 'There is no conspiracy out there, but lack of the incentives to drive the people towards the answers.'; 185 | 186 | // TweetNaCl can automatically generate a random nonce for us, making the encryption very simple: 187 | // Encrypt our message, it will be exactly 40 bytes longer than the 188 | // original message as it stores authentication information and the 189 | // nonce alongside it. 190 | final encrypted = bobBox.encrypt(message.codeUnits); 191 | 192 | // Finally, the message is decrypted (regardless of how the nonce was generated): 193 | // Alice creates a second box with her private key to decrypt the message 194 | final aliceBox = Box(myPrivateKey: skalice, theirPublicKey: pkbob); 195 | 196 | // Decrypt our message, an exception will be raised if the encryption was 197 | // tampered with or there was otherwise an error. 198 | final decrypted = aliceBox.decrypt(encrypted); 199 | print(String.fromCharCodes(decrypted.plaintext)); 200 | } 201 | ``` 202 | 203 | #### SealedBox 204 | 205 | > The SealedBox class encrypts messages addressed to a specified key-pair by using ephemeral sender’s keypairs, which will be discarded just after encrypting a single plaintext message. 206 | > 207 | > This kind of construction allows sending messages, which only the recipient can decrypt without providing any kind of cryptographic proof of sender’s authorship. 208 | > 209 | > __Warning__ 210 | > 211 | > By design, the recipient will have no means to trace the ciphertext to a known author, since the sending keypair itself is not bound to any sender’s identity, and the sender herself will not be able to decrypt the ciphertext she just created, since the private part of the key cannot be recovered after use. 212 | > 213 | > -- [PyNaCl](https://pynacl.readthedocs.io/en/stable/public/#examples) 214 | 215 | ``` dart 216 | import 'package:pinenacl/public.dart' show SealedBox, PrivateKey; 217 | 218 | void main() { 219 | 220 | // Generate Bob's private key, which must be kept secret 221 | final skbob = PrivateKey.generate(); 222 | final pkbob = skbob.publicKey; 223 | 224 | // Alice wishes to send a encrypted message to Bob, 225 | // but prefers the message to be untraceable 226 | // she puts it into a secretbox and seals it. 227 | final sealedBox = SealedBox(pkbob); 228 | 229 | final message = 'The world is changing around us and we can either get ' 230 | 'with the change or we can try to resist it'; 231 | 232 | final encrypted = sealedBox.encrypt(message.codeUnits); 233 | 234 | // Bob unseals the box with his privatekey, and decrypts it. 235 | final unsealedBox = SealedBox(skbob); 236 | 237 | final plainText = unsealedBox.decrypt(encrypted); 238 | print(String.fromCharCodes(plainText)); 239 | assert(message == String.fromCharCodes(plainText)); 240 | } 241 | ``` 242 | ### A `Secret Key Encryption` example 243 | 244 | Implemented from [PyNaCl's example](https://pynacl.readthedocs.io/en/stable/private/#examples) 245 | 246 | #### SecretBox 247 | > Secret key encryption (also called symmetric key encryption) is analogous to a safe. You can store something secret through it and anyone who has the key can open it and view the contents. SecretBox functions as just such a safe, and like any good safe any attempts to tamper with the contents are easily detected. 248 | > 249 | > Secret key encryption allows you to store or transmit data over insecure channels without leaking the contents of that message, nor anything about it other than the length. 250 | > 251 | > -- [PyNaCl](https://pynacl.readthedocs.io/en/stable/secret/#secret-key-encryption) 252 | 253 | ``` dart 254 | import 'package:pinenacl/api.dart'; 255 | import 'package:pinenacl/x25519.dart' show SecretBox; 256 | 257 | void main() { 258 | print('\n### Secret Key Encryption - SecretBox Example ###\n'); 259 | final key = PineNaClUtils.randombytes(SecretBox.keyLength); 260 | final box = SecretBox(key); 261 | 262 | final message = 263 | 'Change is a tricky thing, it threatens what we find familiar with...'; 264 | 265 | final encrypted = box.encrypt(Uint8List.fromList(message.codeUnits)); 266 | 267 | final decrypted = box.decrypt(encrypted); 268 | 269 | final ctext = encrypted.cipherText; 270 | 271 | assert(ctext.length == message.length + SecretBox.macBytes); 272 | 273 | final plaintext = String.fromCharCodes(decrypted); 274 | print(plaintext); 275 | assert(message == plaintext); 276 | } 277 | ``` 278 | 279 | ### `Digital Signatures` example 280 | 281 | Implemented from [PyNaCl's example](https://pynacl.readthedocs.io/en/stable/public/#examples) 282 | 283 | #### Signing 284 | > You can use a digital signature for many of the same reasons that you might sign a paper document. A valid digital signature gives a recipient reason to believe that the message was created by a known sender such that they cannot deny sending it (authentication and non-repudiation) and that the message was not altered in transit (integrity). 285 | > 286 | > Digital signatures allow you to publish a public key, and then you can use your private signing key to sign messages. Others who have your public key can then use it to validate that your messages are actually authentic. 287 | > 288 | > -- [PyNaCl](https://pynacl.readthedocs.io/en/stable/signing) 289 | 290 | ``` dart 291 | import 'package:convert/convert.dart'; 292 | import 'package:pinenacl/signing.dart'; 293 | 294 | void main() { 295 | /// 296 | /// Signer’s perspective (SigningKey) 297 | /// 298 | 299 | // Generate a new random signing key 300 | final signingKey = SigningKey.generate(); 301 | 302 | final message = 'People see the things they want to see...'; 303 | // Sign a message with the signing key 304 | final signed = signingKey.sign(message.codeUnits); 305 | 306 | // Obtain the verify key for a given signing key 307 | final verifyKey = signingKey.verifyKey; 308 | 309 | // Serialize the verify key to send it to a third party 310 | final verifyKeyHex = hex.encode(verifyKey); 311 | 312 | /// 313 | /// Verifier’s perspective (VerifyKey) 314 | /// 315 | final verifyKey2 = VerifyKey.fromHexString(verifyKeyHex); 316 | assert(verifyKey == verifyKey2); 317 | print('The "$message" is successfully verified'); 318 | 319 | // Check the validity of a message's signature 320 | // The message and the signature can either be passed separately or 321 | // concatenated together. These are equivalent: 322 | verifyKey.verify(signed); 323 | verifyKey.verify(signed.message, signed.signature); 324 | 325 | // Alter the signed message text 326 | signed[0] ^= signed[0] + 1; 327 | 328 | try { 329 | // Forged message. 330 | verifyKey.verify(signed); 331 | } on Exception catch(e) { 332 | print('Successfully cought: $e'); 333 | } 334 | } 335 | ``` 336 | 337 | ### `Hashing` example 338 | Implemented from [PyNaCl's example](https://pynacl.readthedocs.io/en/stable/hashing/#examples) 339 | 340 | > Cryptographic secure hash functions are irreversible transforms of input data to a fixed length digest. 341 | > 342 | > The standard properties of a cryptographic hash make these functions useful both for standalone usage as data integrity checkers, as well as `black-box` building blocks of other kind of algorithms and data structures. 343 | > 344 | > All of the hash functions exposed in `hashing` can be used as data integrity checkers. 345 | > 346 | > 347 | > As already hinted above, traditional cryptographic hash functions can be used as building blocks for other uses, typically combining a secret-key with the message via some construct like the HMAC one. 348 | > 349 | > -- [PyNaCl](https://pynacl.readthedocs.io/en/stable/signing) 350 | 351 | ### Blake2b 352 | 353 | > The `blake2b` hash function can be used directly both for `message authentication` and `key derivation`, replacing the HMAC construct and the HKDF one by setting the additional parameters `key`, `salt` and `person`. 354 | > 355 | > __`Warning`__ 356 | > 357 | > Please note that key stretching procedures like `HKDF` or the one outlined in `Key derivation` are not suited to derive a `cryptographically-strong key` from a `low-entropy input` like a `plain-text password` or to compute a strong long-term stored hash used as password verifier. 358 | > 359 | > 360 | > -- [PyNaCl](https://pynacl.readthedocs.io/en/stable/hashing) 361 | 362 | #### Hashing 363 | ``` dart 364 | ... 365 | void main() { 366 | 367 | final hasher = Hash.blake2b; 368 | 369 | 370 | print('Hash example\nH(\'\'): ${hex.encode(hasher(''))}'); 371 | ``` 372 | 373 | #### Message authentication 374 | 375 | > To authenticate a message, using a secret key, the blake2b function must be called as in the following example. 376 | 377 | ``` dart 378 | /// It can ganarate a MAC to be sure that the message is not forged. 379 | 380 | final msg = '256 BytesMessage' * 16; 381 | 382 | // the simplest way to get a cryptographic quality authKey 383 | // is to generate it with a cryptographic quality 384 | // random number generator 385 | final authKey = Utils.randombytes(64); 386 | final mac = hasher(msg, key: authKey); 387 | 388 | print('MAC(msg, authKey): ${hex.encode(mac)}.\n'); 389 | ``` 390 | 391 | #### Key derivation 392 | 393 | > The blake2b algorithm can replace a key derivation function by following the lines of: 394 | ``` dart 395 | print('Key derivation example'); 396 | final masterKey = Utils.randombytes(64); 397 | final derivationSalt = Utils.randombytes(16); 398 | 399 | final personalisation = Uint8List.fromList(''.codeUnits); 400 | 401 | final subKey = hasher('', key: masterKey, salt: derivationSalt, personalisation: personalisation); 402 | print('KDF(\'\', masterKey, salt, personalisation): ${hex.encode(subKey)}'); 403 | ``` 404 | 405 | > By repeating the key derivation procedure before encrypting our messages, and sending the derivationSalt along with the encrypted message, we can expect to never reuse a key, drastically reducing the risks which ensue from such a reuse. 406 | # TODOS 407 | 408 | - [ ] **Important**: refactor for working in 32-bit systems and browser (js). 409 | - [ ] Implement proper Error/Exception handling. 410 | - [x] Implement encoding/decoding) classes. 411 | - [x] Add more unit tests. 412 | - [x] Refactor to much simpler code. 413 | - [x] Simplify or refactor the APIs and modules' dependencies. 414 | - [x] Remove [bech32], [hex] and [convert] pakages dependency. 415 | - [x] Remove [fixnum] pakage dependency. 416 | - [x] Add Ed25519 to X25519 function, to allow Ed25519 to be used in 417 | authenticated encryption too, see note below. 418 | - [ ] Refactor hashers to have some similar methods such: init(), update(), finalize() etc. 419 | - [x] Refactor key derivation function to be able to use diff `hmac`s. 420 | - [x] Optimise `SHA-256` for faster execution. Done 5 time faster now 0.41 sec instead of 3.1 sec for 256K iterations. 421 | - [ ] Optimise `SHA-512` for faster execution. 422 | - [x] Add some benchmark files. 423 | - [ ] **IMPORTANT**. refactor, rewrite source code for security auditing. 424 | 425 | Note: `Ed25519` keys that are used only for digital signatures (EdDSA), can be 426 | converted to `Curve25519/X25519` key (that is used only for authenticated encryption i.e. 427 | ECDH), therefore the same key pairs can be used for 428 | - digital signatures (EdDSA), as it's already used, using `crypto_sign` and 429 | - authenticated encryption (ECDH) using `crypto_box`. 430 | 431 | # Thanks and Credits 432 | 433 | - [PyNaCl library](https://github.com/pyca/pynacl) 434 | - [TweetNaCl dart implementation](https://github.com/jspschool/tweetnacl-dart) 435 | - [TweetNaCl: a crypto library in 100 tweets](https://tweetnacl.cr.yp.to/index.html) 436 | - [blake2b](https://github.com/emilbayes/blake2b) 437 | - [bech32](https://github.com/haarts/bech32) 438 | - And many others... 439 | 440 | # References 441 | - [How do Ed5519 keys work?](https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/) 442 | - [Using Ed25519 signing keys for authenticated encryption](https://blog.filippo.io/using-ed25519-keys-for-encryption/) 443 | - [The Provable Security of Ed25519: Theory and Practice](https://eprint.iacr.org/2020/823.pdf) 444 | - [Implementing Curve25519/X25519: A Tutorial on Elliptic Curve Cryptography](https://martin.kleppmann.com/papers/curve25519.pdf) 445 | 446 | # License 447 | 448 | - [MIT License](LICENSE) 449 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Defines a default set of lint rules enforced for 2 | # projects at Google. For details and rationale, 3 | # see https://github.com/dart-lang/pedantic#enabled-lints. 4 | # include: package:pedantic/analysis_options.yaml 5 | 6 | include: package:lints/recommended.yaml 7 | 8 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 9 | linter: 10 | rules: 11 | - always_declare_return_types 12 | - camel_case_types 13 | - empty_constructor_bodies 14 | - annotate_overrides 15 | - avoid_init_to_null 16 | - constant_identifier_names 17 | - one_member_abstracts 18 | - slash_for_doc_comments 19 | - sort_constructors_first 20 | - unnecessary_brace_in_string_interps 21 | 22 | # analyzer: 23 | # exclude: 24 | # - path/to/excluded/files/** 25 | 26 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark 2 | 3 | There are 3 type of executables in dart: 4 | - JiT: runs the program in the Dart VM by `dart` or `pub run` 5 | - AoT: compiled native binary for the target OS (iOS, Android, Linux macOS Windows), 6 | - JavaScript: runs with `node` or in browser. 7 | 8 | > Note: Keep in mind that there are `32` and `64` bit architectures, and it seems that the `armv7` compiled `dart-sdk` does not run under `QEMU` emulation. 9 | 10 | **All benchmarks used a 1MB large message for hashing, en/decrypt and sign/verify.** 11 | 12 | ## JiT (Dart VM) Benhcmark 13 | 14 | Just simply run the benchmarks in Dart VM. 15 | 16 | ```dart 17 | $ pub get 18 | $ pub run benchmark/all_benchmark.dart 19 | | Digest | BLAKE2B | 22.67 MB/s | 114 iterations | 5029 ms | 114.00 MB | 20 | | Digest | SHA-256 | 14.21 MB/s | 72 iterations | 5066 ms | 72.00 MB | 21 | ... 22 | ``` 23 | 24 | ### Results 25 | 26 | Converted to table 27 | MacBook Pro (2016), macOS Big Sur, with 2.7GHz i7 /w 16GB 28 | 29 | 20/01/2021 30 | 31 | | Alg type | Alg | rate | iterations | time | data throughput | 32 | |----------|---------|:----------:|---------------|:-------:|:---------------:| 33 | | Digest | BLAKE2B | 22.67 MB/s | 114 iterations | 5029 ms | 114.00 MB | 34 | | Digest | SHA-256 | 14.21 MB/s | 72 iterations | 5066 ms | 72.00 MB | 35 | | Digest | SHA-512 | 7.27 MB/s | 37 iterations | 5089 ms | 37.00 MB | 36 | | Signatures | Ed25519 - sign | 3.58 MB/s | 18 iterations | 5023 ms | 18.00 MB | 37 | | Signatures | Ed25519 - verify | 5.76 MB/s | 29 iterations | 5033 ms | 29.00 MB | 38 | | Authenticated Encryption | SecretBox - encrypt | 9.56 MB/s | 48 iterations | 5022 ms | 48.00 MB | 39 | | Authenticated Encryption | SecretBox - decrypt | 10.78 MB/s | 54 iterations | 5007 ms | 54.00 MB | 40 | | Authenticated Encryption | Box - encrypt | 8.77 MB/s | 44 iterations | 5014 ms | 44.00 MB | 41 | | Authenticated Encryption | Box - decrypt | 9.88 MB/s | 50 iterations | 5058 ms | 50.00 MB | 42 | | TweetNaCl | crypto_hash_sha256 | 13.35 MB/s | 67 iterations | 5018 ms | 67.00 MB | 43 | | TweetNaCl | crypto_hash_sha512 | 7.60 MB/s | 38 iterations | 5002 ms | 38.00 MB | 44 | | TweetNaCl | crypto_auth_hmacsha256 | 11.47 MB/s | 58 iterations | 5057 ms | 58.00 MB | 45 | | TweetNaCl | crypto_auth_hmacsha512 | 6.50 MB/s | 33 iterations | 5079 ms | 33.00 MB | 46 | | TweetNaCl | crypto_box_keypair | 623.14 MB/s | 3116 iterations | 5000 ms | 3.04 GB | 47 | | TweetNaCl | crypto_box_curve25519xsalsa20poly1305 | 18.21 MB/s | 92 iterations | 5052 ms | 92.00 MB | 48 | | TweetNaCl | crypto_box_beforenm | 954.37 MB/s | 4772 iterations | 5000 ms | 4.66 GB | 49 | | TweetNaCl | crypto_box_afternm | 16.44 MB/s | 83 iterations | 5048 ms | 83.00 MB | 50 | | TweetNaCl | crypto_box_open_afternm | 17.77 MB/s | 89 iterations | 5007 ms | 89.00 MB | 51 | | TweetNaCl | crypto_stream | 340.49 GB/s | 1743291 iterations | 5000 ms | 1702.43 GB | 52 | | TweetNaCl | crypto_stream_xor | 20.88 MB/s | 105 iterations | 5029 ms | 105.00 MB | 53 | | TweetNaCl | crypto_core_salsa20 | 740.98 GB/s | 3793818 iterations | 5000 ms | 3704.90 GB | 54 | | TweetNaCl | crypto_core_hsalsa20 | 754.57 GB/s | 3863415 iterations | 5000 ms | 3772.87 GB | 55 | | TweetNaCl | crypto_point_add | 2.01 GB/s | 10294 iterations | 5000 ms | 10.05 GB | 56 | | TweetNaCl | crypto_scalar_base | 527.46 MB/s | 2638 iterations | 5001 ms | 2.58 GB | 57 | | TweetNaCl | crypto_scalarmult_base | 940.88 MB/s | 4705 iterations | 5000 ms | 4.59 GB | 58 | | TweetNaCl | crypto_scalarmult | 911.16 MB/s | 4556 iterations | 5000 ms | 4.45 GB | 59 | | TweetNaCl | crypto_sign_ed25519_pk_to_x25519_pk | 3.04 GB/s | 15552 iterations | 5000 ms | 15.19 GB | 60 | | TweetNaCl | crypto_sign_ed25519_sk_to_x25519_sk | 99.41 GB/s | 508972 iterations | 5000 ms | 497.04 GB | 61 | | TweetNaCl | crypto_sign_keypair | 520.30 MB/s | 2602 iterations | 5001 ms | 2.54 GB | 62 | | TweetNaCl | crypto_sign | 3.78 MB/s | 19 iterations | 5029 ms | 19.00 MB | 63 | | TweetNaCl | crypto_sign_open | 7.64 MB/s | 39 iterations | 5107 ms | 39.00 MB | 64 | | TweetNaCl | crypto_verify_16 | 4515.12 GB/s | 23117428 iterations | 5000 ms | 22575.61 GB | 65 | | TweetNaCl | crypto_verify_32 | 3963.50 GB/s | 20293152 iterations | 5000 ms | 19817.53 GB | 66 | | TweetNaCl | crypto_verify_64 | 4048.47 GB/s | 20728189 iterations | 5000 ms | 20242.37 GB | 67 | 68 | ## AoT (native binary) 69 | 70 | ```dart 71 | $ pub get 72 | $ dart2native benchmark/all_benchmark.dart -o all_benchmark 73 | $ ./all_benchmark 74 | | Digest | BLAKE2B | 21.99 MB/s | 110 iterations | 5001 ms | 110.00 MB | 75 | | Digest | SHA-256 | 7.44 MB/s | 38 iterations | 5108 ms | 38.00 MB | 76 | ... 77 | ``` 78 | 79 | ### Results 80 | 81 | | Alg type | Alg | rate | iterations | time | data throughput | 82 | |----------|---------|:----------:|---------------|:-------:|:---------------:| 83 | | Digest | BLAKE2B | 21.99 MB/s | 110 iterations | 5001 ms | 110.00 MB | 84 | | Digest | SHA-256 | 7.44 MB/s | 38 iterations | 5108 ms | 38.00 MB | 85 | | Digest | SHA-512 | 4.89 MB/s | 25 iterations | 5108 ms | 25.00 MB | 86 | | Signatures | Ed25519 - sign | 2.46 MB/s | 13 iterations | 5290 ms | 13.00 MB | 87 | | Signatures | Ed25519 - verify | 3.60 MB/s | 18 iterations | 5004 ms | 18.00 MB | 88 | | Authenticated Encryption | SecretBox - encrypt | 7.14 MB/s | 36 iterations | 5040 ms | 36.00 MB | 89 | | Authenticated Encryption | SecretBox - decrypt | 6.60 MB/s | 34 iterations | 5154 ms | 34.00 MB | 90 | | Authenticated Encryption | Box - encrypt | 7.01 MB/s | 36 iterations | 5133 ms | 36.00 MB | 91 | | Authenticated Encryption | Box - decrypt | 6.58 MB/s | 33 iterations | 5013 ms | 33.00 MB | 92 | | TweetNaCl | crypto_hash_sha256 | 8.48 MB/s | 43 iterations | 5070 ms | 43.00 MB | 93 | | TweetNaCl | crypto_hash_sha512 | 5.25 MB/s | 27 iterations | 5140 ms | 27.00 MB | 94 | | TweetNaCl | crypto_auth_hmacsha256 | 7.07 MB/s | 36 iterations | 5093 ms | 36.00 MB | 95 | | TweetNaCl | crypto_auth_hmacsha512 | 4.68 MB/s | 24 iterations | 5126 ms | 24.00 MB | 96 | | TweetNaCl | crypto_box_keypair | 183.84 MB/s | 920 iterations | 5004 ms | 920.00 MB | 97 | | TweetNaCl | crypto_box_curve25519xsalsa20poly1305 | 12.66 MB/s | 64 iterations | 5055 ms | 64.00 MB | 98 | | TweetNaCl | crypto_box_beforenm | 219.73 MB/s | 1099 iterations | 5001 ms | 1.07 GB | 99 | | TweetNaCl | crypto_box_afternm | 13.51 MB/s | 68 iterations | 5031 ms | 68.00 MB | 100 | | TweetNaCl | crypto_box_open_afternm | 13.53 MB/s | 68 iterations | 5026 ms | 68.00 MB | 101 | | TweetNaCl | crypto_stream | 253.90 GB/s | 1299990 iterations | 5000 ms | 1269.52 GB | 102 | | TweetNaCl | crypto_stream_xor | 15.38 MB/s | 77 iterations | 5007 ms | 77.00 MB | 103 | | TweetNaCl | crypto_core_salsa20 | 501.70 GB/s | 2568713 iterations | 5000 ms | 2508.51 GB | 104 | | TweetNaCl | crypto_core_hsalsa20 | 542.12 GB/s | 2775633 iterations | 5000 ms | 2710.58 GB | 105 | | TweetNaCl | crypto_point_add | 1.40 GB/s | 7151 iterations | 5000 ms | 6.98 GB | 106 | | TweetNaCl | crypto_scalar_base | 119.08 MB/s | 596 iterations | 5005 ms | 596.00 MB | 107 | | TweetNaCl | crypto_scalarmult_base | 222.95 MB/s | 1115 iterations | 5001 ms | 1.09 GB | 108 | | TweetNaCl | crypto_scalarmult | 213.73 MB/s | 1069 iterations | 5001 ms | 1.04 GB | 109 | | TweetNaCl | crypto_sign_ed25519_pk_to_x25519_pk | 1.26 GB/s | 6467 iterations | 5000 ms | 6.32 GB | 110 | | TweetNaCl | crypto_sign_ed25519_sk_to_x25519_sk | 54.29 GB/s | 277969 iterations | 5000 ms | 271.45 GB | 111 | | TweetNaCl | crypto_sign_keypair | 137.13 MB/s | 686 iterations | 5002 ms | 686.00 MB | 112 | | TweetNaCl | crypto_sign | 2.67 MB/s | 14 iterations | 5235 ms | 14.00 MB | 113 | | TweetNaCl | crypto_sign_open | 4.60 MB/s | 23 iterations | 5004 ms | 23.00 MB | 114 | | TweetNaCl | crypto_verify_16 | 3872.89 GB/s | 19829184 iterations | 5000 ms | 19364.44 GB | 115 | | TweetNaCl | crypto_verify_32 | 3581.01 GB/s | 18334757 iterations | 5000 ms | 17905.04 GB | 116 | | TweetNaCl | crypto_verify_64 | 3682.11 GB/s | 18852390 iterations | 5000 ms | 18410.54 GB | 117 | 118 | 119 | ## Javascript 120 | 121 | In javascript converted code, the bitwise operations are handled as `32-bit` operations. 122 | 123 | ```dart 124 | $ pub get 125 | $ dart2js benchmark/all_benchmark.dart -o all_benchmark.js 126 | $ node all_benchmark.js 127 | | Digest | BLAKE2B | 14.67 MB/s | 74 iterations | 5044 ms | 74.00 MB | 128 | | Digest | SHA-256 | 14.75 MB/s | 74 iterations | 5018 ms | 74.00 MB | 129 | ... 130 | ``` 131 | 132 | ### Resultss 133 | 134 | | Alg type | Alg | rate | iterations | time | data throughput | 135 | |----------|---------|:----------:|---------------|:-------:|:---------------:| 136 | | Digest | BLAKE2B | 14.67 MB/s | 74 iterations | 5044 ms | 74.00 MB | 137 | | Digest | SHA-256 | 14.75 MB/s | 74 iterations | 5018 ms | 74.00 MB | 138 | | Digest | SHA-512 | 1.69 MB/s | 9 iterations | 5324 ms | 9.00 MB | 139 | | Signatures | Ed25519 - sign | 877.01 KB/s | 5 iterations | 5838 ms | 5.00 MB | 140 | | Signatures | Ed25519 - verify | 1.52 MB/s | 8 iterations | 5267 ms | 8.00 MB | 141 | | Authenticated Encryption | SecretBox - encrypt | 2.65 MB/s | 14 iterations | 5276 ms | 14.00 MB | 142 | | Authenticated Encryption | SecretBox - decrypt | 3.67 MB/s | 19 iterations | 5175 ms | 19.00 MB | 143 | | Authenticated Encryption | Box - encrypt | 2.75 MB/s | 14 iterations | 5099 ms | 14.00 MB | 144 | | Authenticated Encryption | Box - decrypt | 3.69 MB/s | 19 iterations | 5144 ms | 19.00 MB | 145 | | TweetNaCl | crypto_hash_sha256 | 11.24 MB/s | 57 iterations | 5072 ms | 57.00 MB | 146 | | TweetNaCl | crypto_hash_sha512 | 1.91 MB/s | 10 iterations | 5237 ms | 10.00 MB | 147 | | TweetNaCl | crypto_auth_hmacsha256 | 4.53 MB/s | 23 iterations | 5074 ms | 23.00 MB | 148 | | TweetNaCl | crypto_auth_hmacsha512 | 1.53 MB/s | 8 iterations | 5235 ms | 8.00 MB | 149 | | TweetNaCl | crypto_box_keypair | 499.60 MB/s | 2498 iterations | 5000 ms | 2.44 GB | 150 | | TweetNaCl | crypto_box_curve25519xsalsa20poly1305 | 6.75 MB/s | 34 iterations | 5039 ms | 34.00 MB | 151 | | TweetNaCl | crypto_box_beforenm | 544.29 MB/s | 2722 iterations | 5001 ms | 2.66 GB | 152 | | TweetNaCl | crypto_box_afternm | 6.38 MB/s | 32 iterations | 5013 ms | 32.00 MB | 153 | | TweetNaCl | crypto_box_open_afternm | 6.63 MB/s | 34 iterations | 5129 ms | 34.00 MB | 154 | | TweetNaCl | crypto_stream | 92.58 GB/s | 473999 iterations | 5000 ms | 462.89 GB | 155 | | TweetNaCl | crypto_stream_xor | 7.57 MB/s | 38 iterations | 5017 ms | 38.00 MB | 156 | | TweetNaCl | crypto_core_salsa20 | 195.29 GB/s | 999893 iterations | 5000 ms | 976.46 GB | 157 | | TweetNaCl | crypto_core_hsalsa20 | 196.11 GB/s | 1004089 iterations | 5000 ms | 980.56 GB | 158 | | TweetNaCl | crypto_point_add | 3.55 GB/s | 18177 iterations | 5000 ms | 17.75 GB | 159 | | TweetNaCl | crypto_scalar_base | 295.16 MB/s | 1477 iterations | 5004 ms | 1.44 GB | 160 | | TweetNaCl | crypto_scalarmult_base | 529.09 MB/s | 2646 iterations | 5001 ms | 2.58 GB | 161 | | TweetNaCl | crypto_scalarmult | 556.69 MB/s | 2784 iterations | 5001 ms | 2.72 GB | 162 | | TweetNaCl | crypto_sign_ed25519_pk_to_x25519_pk | 3.62 GB/s | 18515 iterations | 5000 ms | 18.08 GB | 163 | | TweetNaCl | crypto_sign_ed25519_sk_to_x25519_sk | 13.41 GB/s | 68635 iterations | 5000 ms | 67.03 GB | 164 | | TweetNaCl | crypto_sign_keypair | 287.09 MB/s | 1436 iterations | 5002 ms | 1.40 GB | 165 | | TweetNaCl | crypto_sign | 932.10 KB/s | 5 iterations | 5493 ms | 5.00 MB | 166 | | TweetNaCl | crypto_sign_open | 1.78 MB/s | 9 iterations | 5066 ms | 9.00 MB | 167 | | TweetNaCl | crypto_verify_16 | 3523.37 GB/s | 18039674 iterations | 5000 ms | 17616.87 GB | 168 | | TweetNaCl | crypto_verify_32 | 2585.52 GB/s | 13237885 iterations | 5000 ms | 12927.62 GB | 169 | | TweetNaCl | crypto_verify_64 | 2594.30 GB/s | 13282803 iterations | 5000 ms | 12971.49 GB | 170 | 171 | # Conclusion 172 | 173 | TBD 174 | -------------------------------------------------------------------------------- /benchmark/all_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'digests/sha256_benchmark.dart' as sha256_benchmark; 2 | import 'digests/sha512_benchmark.dart' as sha512_benchmark; 3 | import 'digests/blake2b_benchmark.dart' as blake2b_benchmark; 4 | import 'tweetnacl/ed25519_benchmark.dart' as ed25519_benchmark; 5 | import 'tweetnacl/authenticated_encryption_benchmark.dart' 6 | as authenticated_encryption_benchmark; 7 | import 'tweetnacl/tweetnacl_benchmark.dart' as tweetnacl_benchmark; 8 | 9 | void main() { 10 | // Digest algorythms 11 | blake2b_benchmark.main(); 12 | sha256_benchmark.main(); 13 | sha512_benchmark.main(); 14 | 15 | // Ed25519 16 | ed25519_benchmark.main(); 17 | 18 | // HMAC 19 | authenticated_encryption_benchmark.main(); 20 | 21 | // TweetNaCl and TweeetNaClExt 22 | tweetnacl_benchmark.main(); 23 | } 24 | -------------------------------------------------------------------------------- /benchmark/digests/blake2b_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/digests.dart'; 2 | 3 | import '../helpers/digest_benchmark.dart'; 4 | 5 | void main() { 6 | DigestBenchmark('BLAKE2B', Hash.blake2b).report(); 7 | } 8 | -------------------------------------------------------------------------------- /benchmark/digests/sha256_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/digests.dart'; 2 | 3 | import '../helpers/digest_benchmark.dart'; 4 | 5 | void main() { 6 | DigestBenchmark('SHA-256', Hash.sha256).report(); 7 | } 8 | -------------------------------------------------------------------------------- /benchmark/digests/sha512_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/digests.dart'; 2 | 3 | import '../helpers/digest_benchmark.dart'; 4 | 5 | void main() { 6 | DigestBenchmark('SHA-512', Hash.sha512).report(); 7 | } 8 | -------------------------------------------------------------------------------- /benchmark/helpers/authenticated_encryption_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/x25519.dart'; 2 | 3 | import 'rate_benchmark.dart'; 4 | 5 | class EncryptionBenchmark extends RateBenchmark { 6 | EncryptionBenchmark(this._cryptor, String cryptorName, bool forEncryption, 7 | [int dataLength = 1024 * 1024]) 8 | : _forEncryption = forEncryption, 9 | _data = Uint8List(dataLength), 10 | super( 11 | 'Authenticated Encryption | $cryptorName - ${forEncryption ? 'encrypt' : 'decrypt'}'); 12 | 13 | final Uint8List _data; 14 | final bool _forEncryption; 15 | final BoxBase _cryptor; 16 | 17 | EncryptedMessage? encrypted; 18 | 19 | @override 20 | void setup() { 21 | encrypted = _forEncryption ? null : _cryptor.encrypt(_data); 22 | } 23 | 24 | @override 25 | void run() { 26 | if (_forEncryption) { 27 | _cryptor.encrypt(_data); 28 | } else { 29 | _cryptor.decrypt(encrypted!); 30 | } 31 | addSample(_data.length); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /benchmark/helpers/digest_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'rate_benchmark.dart'; 4 | 5 | typedef DigestAlg = Uint8List Function(dynamic message); 6 | 7 | class DigestBenchmark extends RateBenchmark { 8 | DigestBenchmark(String digestName, DigestAlg digest, 9 | [int dataLength = 1024 * 1024]) 10 | : _digest = digest, 11 | _data = Uint8List(dataLength), 12 | super('Digest | $digestName'); 13 | 14 | final Uint8List _data; 15 | final DigestAlg _digest; 16 | 17 | @override 18 | void setup() {} 19 | 20 | @override 21 | void run() { 22 | _digest(_data); 23 | addSample(_data.length); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /benchmark/helpers/rate_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:benchmark_harness/benchmark_harness.dart'; 2 | 3 | abstract class RateBenchmark extends BenchmarkBase { 4 | RateBenchmark(super.name, {this.runLength = 5000}) 5 | : super(emitter: RateEmitter()) { 6 | (emitter as RateEmitter).benchmark = this; 7 | } 8 | 9 | final int runLength; 10 | int _totalData = 0; 11 | int _iterations = 0; 12 | 13 | void addSample(int processedData) { 14 | _totalData += processedData; 15 | } 16 | 17 | @override 18 | void exercise() { 19 | _totalData = 0; 20 | _iterations = 0; 21 | 22 | var watch = Stopwatch()..start(); 23 | while (watch.elapsedMilliseconds < runLength) { 24 | run(); 25 | _iterations++; 26 | } 27 | } 28 | } 29 | 30 | class RateEmitter implements ScoreEmitter { 31 | late RateBenchmark benchmark; 32 | 33 | int get totalData => benchmark._totalData; 34 | int get iterations => benchmark._iterations; 35 | 36 | @override 37 | void emit(String testName, double value) { 38 | final ms = value / 1000; 39 | final s = ms / 1000; 40 | final date = DateTime.now().toString().split('.')[0]; 41 | 42 | print('| $date | ' 43 | '$testName | ' 44 | '${_formatDataLength(totalData / s)}/s | ' 45 | '$iterations iterations | ' 46 | '${ms.toInt()} ms | ' 47 | '${_formatDataLength(totalData)} |'); 48 | } 49 | 50 | String _formatDataLength(num dataLen) { 51 | if (dataLen < 1024) { 52 | return '${dataLen.toStringAsFixed(2)} B'; 53 | } else if (dataLen < (1024 * 1024)) { 54 | return '${(dataLen / 1024).toStringAsFixed(2)} KB'; 55 | } else if (dataLen < (1024 * 1024 * 1024)) { 56 | return '${(dataLen / (1024 * 1024)).toStringAsFixed(2)} MB'; 57 | } else { 58 | return '${(dataLen / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /benchmark/helpers/signature_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/ed25519.dart'; 2 | 3 | import 'rate_benchmark.dart'; 4 | 5 | class SignatureBenchmark extends RateBenchmark { 6 | SignatureBenchmark(String signerName, bool forSigning, 7 | [int dataLength = 1024 * 1024]) 8 | : _forSigning = forSigning, 9 | _data = PineNaClUtils.randombytes(dataLength), 10 | super( 11 | 'Signatures (${dataLength / 1024 / 1024}MB) | $signerName - ${forSigning ? 'sign' : 'verify'}'); 12 | 13 | final Uint8List _data; 14 | final bool _forSigning; 15 | late final SigningKey _signer; 16 | SignedMessage? _signature; 17 | 18 | @override 19 | void setup() { 20 | _signer = SigningKey.generate(); 21 | _signature = _signer.sign(_data); 22 | } 23 | 24 | @override 25 | void run() { 26 | if (_forSigning) { 27 | _signer.sign(_data); 28 | } else if (_signature != null) { 29 | _signer.verifyKey.verifySignedMessage(signedMessage: _signature!); 30 | } 31 | addSample(_data.length); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /benchmark/helpers/tweetnacl_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'rate_benchmark.dart'; 2 | 3 | class TweetNaClBenchmark extends RateBenchmark { 4 | TweetNaClBenchmark(this._tweetNaCl, String funcName, this._dataLength) 5 | : super('TweetNaCl | $funcName'); 6 | 7 | final Function _tweetNaCl; 8 | final int _dataLength; 9 | 10 | @override 11 | void setup() {} 12 | 13 | @override 14 | void run() { 15 | _tweetNaCl(); 16 | addSample(_dataLength); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /benchmark/tweetnacl/authenticated_encryption_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/x25519.dart'; 2 | 3 | import '../helpers/authenticated_encryption_benchmark.dart'; 4 | 5 | void main() { 6 | EncryptionBenchmark( 7 | SecretBox(PineNaClUtils.randombytes(32)), 'SecretBox', true) 8 | .report(); 9 | EncryptionBenchmark( 10 | SecretBox(PineNaClUtils.randombytes(32)), 'SecretBox', false) 11 | .report(); 12 | 13 | // FIXME: decode should be used pk string only such as x25519_pk1..... 14 | EncryptionBenchmark(Box.decode(PineNaClUtils.randombytes(32)), 'Box', true) 15 | .report(); 16 | EncryptionBenchmark(Box.decode(PineNaClUtils.randombytes(32)), 'Box', false) 17 | .report(); 18 | } 19 | -------------------------------------------------------------------------------- /benchmark/tweetnacl/ed25519_benchmark.dart: -------------------------------------------------------------------------------- 1 | import '../helpers/signature_benchmark.dart'; 2 | 3 | /* 4 | Original JIT - order is important for stopwatch. 5 | 2000 iterations of ED25519 Signing took 3.955 sec(s) 6 | 2000 iterations of ED25519 Verifying took 11.645 sec(s) 7 | 500 iterations of ED25519 Signing took 1.074 sec(s) 8 | 500 iterations of ED25519 Verifying took 3.165 sec(s) 9 | 10 | Original AOT 11 | 2000 iterations of ED25519 Signing took 40.471 sec(s) 12 | 2000 iterations of ED25519 Verifying took 120.152 sec(s) 13 | 500 iterations of ED25519 Signing took 10.407 sec(s) 14 | 500 iterations of ED25519 Verifying took 31.304 sec(s) 15 | 16 | Javascript 17 | 500 iterations of ED25519 Signing took 6.009 sec(s) // 19/01/2021 18 | 500 iterations of ED25519 Verifying took 17.237 sec(s) // 19/01/2021 19 | 20 | 21 | macOS - Big Sur 22 | | Signatures | Ed25519 - sign | 3.44 MB/s | 18 iterations | 5231 ms | 18.00 MB | 23 | | Signatures | Ed25519 - verify | 5.03 MB/s | 26 iterations | 5163 ms | 26.00 MB | 24 | 25 | */ 26 | 27 | void main() { 28 | SignatureBenchmark('Ed25519', true).report(); 29 | SignatureBenchmark('Ed25519', false).report(); 30 | } 31 | -------------------------------------------------------------------------------- /benchmark/tweetnacl/tweetnacl_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/api.dart'; 2 | import 'package:pinenacl/tweetnacl.dart'; 3 | 4 | import '../helpers/tweetnacl_benchmark.dart'; 5 | 6 | // TODO: Implement the following too. 7 | // crypto_onetimeauth(Uint8List out, Uint8List m, final int n, Uint8List k) 8 | // crypto_onetimeauth_verify(Uint8List h, Uint8List m, Uint8List k) 9 | // crypto_stream_salsa20(Uint8List c, int cpos, int b, Uint8List n, Uint8List k) 10 | // crypto_stream_salsa20_xor(Uint8List c, int cpos, Uint8List m, int mpos, int b, Uint8List n, Uint8List k, [int ic = 0]) 11 | // crypto_secretbox(outData, data, dataLength, nonce, sk), 12 | // crypto_secretbox_open(data, outData, outDataLength, nonce, sk), 13 | 14 | void main() { 15 | const dataLength = 1024 * 1024; 16 | const outDataLength = dataLength; 17 | 18 | final data = Uint8List(dataLength); 19 | 20 | final sk = PineNaClUtils.randombytes(32); 21 | final pk = Uint8List(32); 22 | 23 | final sharedKey = Uint8List(32); 24 | final nonce = PineNaClUtils.randombytes(24); 25 | 26 | final randOut16 = PineNaClUtils.randombytes(16); 27 | final out32 = Uint8List(32); 28 | final out64 = Uint8List(64); 29 | final outData = Uint8List(outDataLength); 30 | const outSignatureLength = outDataLength + TweetNaCl.signatureLength; 31 | 32 | final outSignature = Uint8List(outSignatureLength); 33 | 34 | final funcMap = { 35 | // SHA-256 SHA-512 36 | 'crypto_hash_sha256': () => TweetNaClExt.crypto_hash_sha256(out32, data), 37 | 'crypto_hash_sha512': () => TweetNaCl.crypto_hash(out64, data), 38 | 39 | // HMAC 40 | 'crypto_auth_hmacsha256': () => 41 | TweetNaClExt.crypto_auth_hmacsha256(out32, data, sk), 42 | 'crypto_auth_hmacsha512': () => 43 | TweetNaClExt.crypto_auth_hmacsha512(out64, data, sk), 44 | 45 | // Public - Authenticated Encryption 46 | 'crypto_box_keypair': () => TweetNaCl.crypto_box_keypair(pk, sk), 47 | 'crypto_box_curve25519xsalsa20poly1305': () => 48 | TweetNaCl.crypto_box(outData, data, dataLength, nonce, pk, sk), 49 | 'crypto_box_beforenm': () => 50 | TweetNaCl.crypto_box_beforenm(sharedKey, pk, sk), 51 | 'crypto_box_afternm': () => TweetNaCl.crypto_box_afternm( 52 | outData, data, dataLength, nonce, sharedKey), 53 | 'crypto_box_open_afternm': () => TweetNaCl.crypto_box_open_afternm( 54 | data, outData, outDataLength, nonce, sharedKey), 55 | 56 | // Secret-key cryptography - Stream Encryption 57 | 'crypto_stream': () => TweetNaCl.crypto_stream(out32, 0, 32, nonce, sk), 58 | 'crypto_stream_xor': () => 59 | TweetNaCl.crypto_stream_xor(outData, 0, data, 0, dataLength, nonce, sk), 60 | 61 | 'crypto_core_salsa20': () => 62 | TweetNaCl.crypto_core_salsa20(outData, data, sharedKey, nonce), 63 | 'crypto_core_hsalsa20': () => 64 | TweetNaCl.crypto_core_hsalsa20(outData, data, sharedKey, nonce), 65 | 66 | // TweetNaCl Extension. 67 | 'crypto_point_add': () => TweetNaClExt.crypto_point_add(out32, pk, pk), 68 | 'crypto_scalar_base': () => TweetNaClExt.crypto_scalar_base(out32, sk), 69 | 70 | 'crypto_scalarmult_base': () => TweetNaCl.crypto_scalarmult_base(out32, sk), 71 | 'crypto_scalarmult': () => 72 | TweetNaCl.crypto_scalarmult(out32, sk, Uint8List(32)), 73 | 74 | 'crypto_sign_ed25519_pk_to_x25519_pk': () => 75 | TweetNaClExt.crypto_sign_ed25519_pk_to_x25519_pk(out32, pk), 76 | 'crypto_sign_ed25519_sk_to_x25519_sk': () => 77 | TweetNaClExt.crypto_sign_ed25519_sk_to_x25519_sk(out64, sk), 78 | 'crypto_sign_keypair': () => 79 | TweetNaCl.crypto_sign_keypair(pk, out64, out32), 80 | 'crypto_sign': () => 81 | TweetNaCl.crypto_sign(outSignature, 0, data, 0, dataLength, out64), 82 | 'crypto_sign_open': () => TweetNaCl.crypto_sign_open( 83 | data, 0, outSignature.sublist(64), 0, dataLength, pk), 84 | 85 | 'crypto_verify_16': () => TweetNaCl.crypto_verify_16(randOut16, randOut16), 86 | 'crypto_verify_32': () => TweetNaCl.crypto_verify_32(pk, pk), 87 | 'crypto_verify_64': () => TweetNaClExt.crypto_verify_64(out64, out64), 88 | }; 89 | 90 | funcMap.forEach((funcName, func) { 91 | TweetNaClBenchmark(func, funcName, data.length).report(); 92 | }); 93 | } 94 | -------------------------------------------------------------------------------- /doc/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Asymmetric Key Encryption 2 | 3 | ## Class diagrams 4 | 5 | ### Base API classes. 6 | 7 | ![PlantUML model](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/ilap/pinenacl-dart/master/doc/api_diagrams.wsd) 8 | 9 | ### Boxes 10 | 11 | ![PlantUML model](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/ilap/pinenacl-dart/master/doc/boxes.wsd) 12 | 13 | ### Signatures 14 | 15 | ![PlantUML model](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/ilap/pinenacl-dart/master/doc/sigantures.wsd) 16 | 17 | ### EncryptionMessages 18 | 19 | ![PlantUML model](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/ilap/pinenacl-dart/master/doc/encryption_message.wsd) 20 | 21 | ## Tools 22 | 23 | - Use [Plantext.com](https://www.planttext.com/) or [PlantUML](http://www.plantuml.com/plantuml)'s services 24 | - Use Plantuml as Proxy see details in [Example](#Example) 25 | 26 | # Example 27 | 28 | 29 | ![Example](http://www.plantuml.com/plantuml/proxy?src=https://raw.github.com/plantuml/plantuml-server/master/src/main/webapp/resource/test2diagrams.txt) 30 | 31 | As proxy or rendered images (PNG or SVG) 32 | ``` 33 | http://www.plantuml.com/plantuml/proxy?src=https://raw.github.com/plantuml/plantuml-server/master/src/main/webapp/resource/test2diagrams.txt 34 | ![PlantUML model](http://plantuml.com:80/plantuml/png/png/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000) 35 | ![PlantUML model](http://plantuml.com:80/plantuml/svg/png/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000) 36 | ``` 37 | -------------------------------------------------------------------------------- /doc/all_diagrams.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | abstract class AsymmetricKey 4 | 5 | abstract class AsymmetricPrivateKey implements AsymmetricKey { 6 | + final AsymmetricKey publicKey; 7 | } 8 | 9 | class Signature extends ByteList { 10 | + Signature(List bytes) : super(bytes, bytesLength); 11 | + static const bytesLength = TweetNaCl.signatureLength; 12 | } 13 | 14 | class PublicKey extends ByteList implements AsymmetricKey { 15 | + static const int keyLength = TweetNaCl.publicKeyLength; 16 | } 17 | 18 | class PrivateKey extends ByteList implements AsymmetricPrivateKey { 19 | 20 | + factory PrivateKey.generate(); 21 | 22 | + static const seedSize = TweetNaCl.seedSize; 23 | + static const keyLength = TweetNaCl.secretKeyLength; 24 | + final PublicKey publicKey; 25 | } 26 | 27 | abstract class Sign { 28 | + Verify get verifyKey; 29 | + SignedMessage sign(List message); 30 | } 31 | 32 | abstract class Verify { 33 | + bool verify({Signature signature, ByteList message}); 34 | + bool verifySignedMessage({SignedMessage signedMessage}); 35 | } 36 | 37 | class _EncryptionMessage extends ByteList { 38 | + ByteList get prefix 39 | + ByteList get suffix 40 | } 41 | 42 | class EncryptedMessage extends _EncryptionMessage { 43 | + static const nonceLength = 24; 44 | + Uint8List get nonce => prefix; 45 | + Uint8List get cipherText => suffix; 46 | } 47 | 48 | class SealedMessage extends _EncryptionMessage { 49 | + static const publicLength = 32; 50 | + Uint8List get public => prefix; 51 | + Uint8List get cipherText => suffix; 52 | } 53 | 54 | class SignedMessage extends _EncryptionMessage { 55 | + static const signatureLength = 64; 56 | + Uint8List get message => suffix; 57 | + Signature get signature => Signature(prefix); 58 | } 59 | 60 | class VerifyKey extends ByteList implements AsymmetricKey, Verify { 61 | 62 | } 63 | 64 | class SigningKey extends ByteList implements AsymmetricPrivateKey, Sign { 65 | + static const seedSize = TweetNaCl.seedSize; 66 | + AsymmetricKey get publicKey => verifyKey; 67 | + final VerifyKey verifyKey; 68 | } 69 | 70 | abstract class BoxBase extends ByteList implements AsymmetricKey { 71 | + Crypting doEncrypt; 72 | + Crypting doDecrypt; 73 | + ByteList get key; 74 | 75 | + Uint8List decrypt(Uint8List encryptedMessage, {Uint8List nonce}) 76 | + EncryptedMessage encrypt(List plainText, {List nonce}) 77 | } 78 | 79 | class SecretBox extends BoxBase { 80 | + static const keyLength = TweetNaCl.keyLength; 81 | + static const macBytes = TweetNaCl.macBytes; 82 | + ByteList get key => this; 83 | 84 | + Crypting doEncrypt = TweetNaCl.crypto_box_afternm; 85 | + Crypting doDecrypt = TweetNaCl.crypto_box_open_afternm; 86 | } 87 | 88 | class Box extends BoxBase { 89 | + ByteList get sharedKey => this; 90 | + ByteList get key => sharedKey; 91 | 92 | + Crypting doEncrypt = TweetNaCl.crypto_box_afternm; 93 | + Crypting doDecrypt = TweetNaCl.crypto_box_open_afternm; 94 | 95 | + static ByteList _beforeNm(...); 96 | } 97 | 98 | class SealedBox extends ByteList implements AsymmetricKey { 99 | + final PrivateKey _privateKey; 100 | 101 | - static const _zerobytesLength = TweetNaCl.zerobytesLength; 102 | - static const _nonceLength = 24; 103 | - static const _pubLength = TweetNaCl.publicKeyLength; 104 | - static const _secretLength = TweetNaCl.secretKeyLength; 105 | - static const _macBytes = TweetNaCl.macBytes; 106 | - static const _sealBytes = _pubLength + _macBytes; 107 | 108 | + Uint8List decrypt(Uint8List ciphertext) 109 | + Uint8List encrypt(List plaintext) 110 | 111 | - static void _generateNonce(Uint8List out, Uint8List in1, Uint8List in2) 112 | - Uint8List _cryptoBoxSeal(Uint8List message, ByteList pk) 113 | - void _cryptoBoxDetached(...) 114 | - Uint8List _cryptoBoxSealOpen(Uint8List ciphertext); 115 | } 116 | 117 | SignedMessage --> Signature 118 | Verify --o SignedMessage 119 | Sign --o SignedMessage 120 | 121 | @enduml -------------------------------------------------------------------------------- /doc/api_class_diagram.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | skinparam packageStyle rectangle 3 | 4 | ' Reference: https://visual-paradigm.com/guide/uml-unified-modeling-language/uml-aggregation-vs-composition/ 5 | 6 | 'Base Classes and Interfaces 7 | '################################################################ 8 | namespace pinenacl.api { 9 | 10 | class ByteList implements Encodable { 11 | } 12 | 13 | abstract class Suffix { 14 | _prefixlength 15 | get prefix 16 | get suffix 17 | } 18 | 19 | abstract class Encodable { 20 | get encoder 21 | encode([encoder]) 22 | } 23 | 24 | abstract class AsymmetricKey extends ByteList { 25 | } 26 | 27 | abstract class AsymmetricPrivateKey implements AsymmetricKey { 28 | 29 | publicKey 30 | generate() 31 | } 32 | 33 | abstract class AsymmetricPublicKey implements AsymmetricKey { 34 | } 35 | 36 | 37 | abstract class Encoder { 38 | encode(ByteList data); 39 | decode(String data); 40 | } 41 | 42 | 43 | namespace pinenacl.api.signatures { 44 | 45 | 46 | abstract class Sign { 47 | 48 | sign(msg) 49 | } 50 | 51 | abstract class Verify { 52 | 53 | verify(msg) 54 | } 55 | abstract class SignatureBase extends pinenacl.api.ByteList { 56 | } 57 | 58 | abstract class EncryptionMessage { 59 | get signature; 60 | get message; 61 | } 62 | } 63 | 64 | namespace pinenacl.api.key_encryption { 65 | 66 | abstract class BoxBase extends pinenacl.api.AsymmetricKey { 67 | doEncrypt; 68 | doDecrypt; 69 | encrypt() 70 | decrypt() 71 | } 72 | 73 | class PublicKey extends pinenacl.api.AsymmetricPublicKey { 74 | } 75 | 76 | class PrivateKey extends pinenacl.api.AsymmetricPrivateKey { 77 | } 78 | 79 | class EncryptedMessage extends pinenacl.api.ByteList implements pinenacl.api.Suffix { 80 | nonce => prefix; 81 | cipherText => suffix; 82 | } 83 | 84 | class SealedMessage extends pinenacl.api.ByteList implements pinenacl.api.Suffix { 85 | public => prefix 86 | cipherText => suffix 87 | } 88 | } 89 | } 90 | @enduml -------------------------------------------------------------------------------- /doc/api_diagrams.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | title Asymmetric classes' Diagram 4 | 5 | abstract class AsymmetricKey 6 | 7 | abstract class AsymmetricPrivateKey implements AsymmetricKey { 8 | + final AsymmetricKey publicKey; 9 | } 10 | 11 | class Signature extends ByteList { 12 | + Signature(List bytes) : super(bytes, bytesLength); 13 | + static const bytesLength = TweetNaCl.signatureLength; 14 | } 15 | 16 | class PublicKey extends ByteList implements AsymmetricKey { 17 | + static const int keyLength = TweetNaCl.publicKeyLength; 18 | } 19 | 20 | class PrivateKey extends ByteList implements AsymmetricPrivateKey { 21 | 22 | + factory PrivateKey.generate(); 23 | 24 | + static const seedSize = TweetNaCl.seedSize; 25 | + static const keyLength = TweetNaCl.secretKeyLength; 26 | + final PublicKey publicKey; 27 | } 28 | 29 | @enduml -------------------------------------------------------------------------------- /doc/boxes.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | title Authenticated Encryptions 4 | 5 | abstract class BoxBase extends ByteList implements AsymmetricKey { 6 | + Crypting doEncrypt; 7 | + Crypting doDecrypt; 8 | + ByteList get key; 9 | 10 | + Uint8List decrypt(Uint8List encryptedMessage, {Uint8List nonce}) 11 | + EncryptedMessage encrypt(List plainText, {List nonce}) 12 | } 13 | 14 | class SecretBox extends BoxBase { 15 | + static const keyLength = TweetNaCl.keyLength; 16 | + static const macBytes = TweetNaCl.macBytes; 17 | + ByteList get key => this; 18 | 19 | + Crypting doEncrypt = TweetNaCl.crypto_box_afternm; 20 | + Crypting doDecrypt = TweetNaCl.crypto_box_open_afternm; 21 | } 22 | 23 | class Box extends BoxBase { 24 | + ByteList get sharedKey => this; 25 | + ByteList get key => sharedKey; 26 | 27 | + Crypting doEncrypt = TweetNaCl.crypto_box_afternm; 28 | + Crypting doDecrypt = TweetNaCl.crypto_box_open_afternm; 29 | 30 | + static ByteList _beforeNm(...); 31 | } 32 | 33 | class SealedBox extends ByteList implements AsymmetricKey { 34 | - final PrivateKey _privateKey; 35 | 36 | - static const _zerobytesLength = TweetNaCl.zerobytesLength; 37 | - static const _nonceLength = 24; 38 | - static const _pubLength = TweetNaCl.publicKeyLength; 39 | - static const _secretLength = TweetNaCl.secretKeyLength; 40 | - static const _macBytes = TweetNaCl.macBytes; 41 | - static const _sealBytes = _pubLength + _macBytes; 42 | 43 | + Uint8List decrypt(Uint8List ciphertext) 44 | + Uint8List encrypt(List plaintext) 45 | 46 | - static void _generateNonce(Uint8List out, Uint8List in1, Uint8List in2) 47 | - Uint8List _cryptoBoxSeal(Uint8List message, ByteList pk) 48 | - void _cryptoBoxDetached(...) 49 | - Uint8List _cryptoBoxSealOpen(Uint8List ciphertext); 50 | } 51 | 52 | @enduml 53 | -------------------------------------------------------------------------------- /doc/class_diagrams.wsd: -------------------------------------------------------------------------------- 1 | @startuml Class Diagrams 2 | 3 | page 1x3 4 | skinparam pageMargin 10 5 | skinparam pageExternalColor gray 6 | skinparam pageBorderColor black 7 | namespace pinenacl.api { 8 | 9 | class ByteList 10 | 11 | abstract class AsymmetricKey { 12 | int keyLength 13 | } 14 | 15 | abstract class AsymmetricPrivateKey { 16 | AsymmetricKey publicKey; 17 | generate() 18 | } 19 | 20 | abstract class EncryptionMessage 21 | abstract class BlockBase 22 | 23 | ByteList <|- AsymmetricKey 24 | AsymmetricKey <|-down- AsymmetricPrivateKey 25 | ByteList <|-down- EncryptionMessage 26 | } 27 | 28 | namespace pinenacl.public { 29 | class Box 30 | class SealedBox 31 | 32 | class SealedMessage { 33 | + public 34 | } 35 | 36 | pinenacl.api.BlockBase <|-up- Box 37 | 38 | pinenacl.api.BlocKbase <|-up- SealedBox 39 | pinenacl.api.BlocKbase <|-up- SealedMessage 40 | 41 | pinenacl.hash.Hash <|-down- SealedBox 42 | } 43 | 44 | namespace pinenacl.secret { 45 | class SecretBox 46 | pinenacl.api.ByteList <|- SecretBox 47 | } 48 | 49 | namespace pinenacl.signatures { 50 | class SigningKey 51 | class VerifyKey 52 | pinenacl.api.ByteList <|- SigningKey 53 | pinenacl.api.ByteList <|- VerifiKey 54 | 55 | } 56 | 57 | namespace pinenacl.hash { 58 | class Hash { 59 | + blake2b(message) 60 | + sha512(message) 61 | } 62 | } 63 | 64 | 65 | 66 | 67 | @enduml -------------------------------------------------------------------------------- /doc/encryption_message.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | title Encryption Messages 4 | 5 | abstract class Sign { 6 | + Verify get verifyKey; 7 | + SignedMessage sign(List message); 8 | } 9 | 10 | abstract class Verify { 11 | + bool verify({Signature signature, ByteList message}); 12 | + bool verifySignedMessage({SignedMessage signedMessage}); 13 | } 14 | 15 | class _EncryptionMessage extends ByteList { 16 | + ByteList get prefix 17 | + ByteList get suffix 18 | } 19 | 20 | class EncryptedMessage extends _EncryptionMessage { 21 | + static const nonceLength = 24; 22 | + Uint8List get nonce => prefix; 23 | + Uint8List get cipherText => suffix; 24 | } 25 | 26 | class SealedMessage extends _EncryptionMessage { 27 | + static const publicLength = 32; 28 | + Uint8List get public => prefix; 29 | + Uint8List get cipherText => suffix; 30 | } 31 | 32 | class SignedMessage extends _EncryptionMessage { 33 | + static const signatureLength = 64; 34 | + Uint8List get message => suffix; 35 | + Signature get signature => Signature(prefix); 36 | } 37 | 38 | SignedMessage --> Signature 39 | Verify --o SignedMessage 40 | Sign --o SignedMessage 41 | 42 | @enduml 43 | -------------------------------------------------------------------------------- /doc/sigantures.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | title Digital Signature's class diagrams 4 | 5 | class VerifyKey extends PublicKey implements Verify { 6 | 7 | } 8 | 9 | class SigningKey extends PrivateKey implements Sign { 10 | + static const seedSize = TweetNaCl.seedSize; 11 | + AsymmetricKey get publicKey => verifyKey; 12 | + final VerifyKey verifyKey; 13 | } 14 | 15 | @enduml 16 | -------------------------------------------------------------------------------- /example/box.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/x25519.dart' show Box, PrivateKey, EncryptedMessage; 2 | import 'package:pinenacl/api.dart'; 3 | 4 | void main() { 5 | print('\n### Public Key Encryption - Box Example ###\n'); 6 | // Generate Bob's private key, which must be kept secret 7 | final skbob = PrivateKey.generate(); 8 | 9 | // Bob's public key can be given to anyone wishing to send 10 | // Bob an encrypted message 11 | final pkbob = skbob.publicKey; 12 | 13 | // Alice does the same and then Alice and Bob exchange public keys 14 | final skalice = PrivateKey.generate(); 15 | 16 | final pkalice = skalice.publicKey; 17 | 18 | // Bob wishes to send Alice an encrypted message so Bob must make a Box with 19 | // his private key and Alice's public key 20 | final bobBox = Box(myPrivateKey: skbob, theirPublicKey: pkalice); 21 | 22 | // This is our message to send, it must be a bytestring as Box will treat it 23 | // as just a binary blob of data. 24 | final message = 25 | 'There is no conspiracy out there, but lack of the incentives to drive the people towards the answers.'; 26 | 27 | // TweetNaCl can automatically generate a random nonce for us, making the encryption very simple: 28 | // Encrypt our message, it will be exactly 40 bytes longer than the 29 | // original message as it stores authentication information and the 30 | // nonce alongside it. 31 | final encryptedAsList = 32 | bobBox.encrypt(Uint8List.fromList(message.codeUnits)).sublist(0); 33 | 34 | // Finally, the message is decrypted (regardless of how the nonce was generated): 35 | // Alice creates a second box with her private key to decrypt the message 36 | final aliceBox = Box(myPrivateKey: skalice, theirPublicKey: pkbob); 37 | 38 | // Decrypt our message, an exception will be raised if the encryption was 39 | // tampered with or there was otherwise an error. 40 | final decrypted = 41 | aliceBox.decrypt(EncryptedMessage.fromList(encryptedAsList.asTypedList)); 42 | print(String.fromCharCodes(decrypted)); 43 | } 44 | -------------------------------------------------------------------------------- /example/hashing.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/api.dart'; 2 | import 'package:pinenacl/src/digests/digests.dart'; 3 | import 'package:pinenacl/tweetnacl.dart'; 4 | 5 | void main() { 6 | const hex = Base16Encoder.instance; 7 | 8 | print('\n### Hashing - Blake2b Example ###\n'); 9 | 10 | final hasher = Hash.blake2b; 11 | 12 | /// # Hashing 13 | print('Hash example\nH(\'\'): ${hex.encode(hasher(''))}'); 14 | 15 | /// # Message authentication 16 | /// To authenticate a message, using a secret key, the blake2b function must be called as in the following example. 17 | print('\nMessage authentication'); 18 | 19 | /// Message authentication example 20 | /// It can ganarate a MAC to be sure that the message is not forged. 21 | 22 | final msg = '256 BytesMessage' * 16; 23 | 24 | // the simplest way to get a cryptographic quality authKey 25 | // is to generate it with a cryptographic quality 26 | // random number generator 27 | final authKey = PineNaClUtils.randombytes(64); 28 | final mac = hasher(msg, key: authKey); 29 | 30 | print('MAC(msg, authKey): ${hex.encode(mac)}.\n'); 31 | 32 | /// # Key derivation example 33 | /// The blake2b algorithm can replace a key derivation function by following the lines of: 34 | print('Key derivation example'); 35 | final masterKey = PineNaClUtils.randombytes(64); 36 | final derivationSalt = PineNaClUtils.randombytes(16); 37 | 38 | final personalisation = Uint8List.fromList(''.codeUnits); 39 | 40 | final subKey = hasher('', 41 | key: masterKey, salt: derivationSalt, personalisation: personalisation); 42 | print('KDF(\'\', masterKey, salt, personalisation): ${hex.encode(subKey)}'); 43 | 44 | /// By repeating the key derivation procedure before encrypting our messages, 45 | /// and sending the derivationSalt along with the encrypted message, we can expect to never reuse a key, 46 | /// drastically reducing the risks which ensue from such a reuse. 47 | 48 | /// SHA-256 Example. 49 | print('\nSHA-256 Example.\n'); 50 | var message = 51 | '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'; 52 | final out = Hash.sha256(Uint8List.fromList(message.codeUnits)); 53 | print('Resulted: ${hex.encode(out)}'); 54 | print( 55 | 'Expected: 3935959adc03ef044edba6e0c69dc7322e34668c2ca74470e4d39f20362b977a'); 56 | 57 | final macOut = Uint8List(64); 58 | final k = List.generate(128, (i) => i).toUint8List(); 59 | final text = 60 | Uint8List.fromList('Sample message for keylen=blocklen'.codeUnits); 61 | 62 | TweetNaClExt.crypto_auth_hmacsha256(macOut, text, k); 63 | print('MAC 256: ${hex.encode(macOut)}'); 64 | 65 | TweetNaClExt.crypto_auth_hmacsha512(macOut, text, k); 66 | print('MAC 512: ${hex.encode(macOut)}'); 67 | } 68 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | import 'box.dart' as box_example; 2 | import 'secretbox.dart' as secretbox_example; 3 | import 'sealedbox.dart' as seladedbox_example; 4 | import 'hashing.dart' as hashing_example; 5 | import 'signature.dart' as signing_example; 6 | 7 | void main() { 8 | box_example.main(); 9 | secretbox_example.main(); 10 | seladedbox_example.main(); 11 | hashing_example.main(); 12 | signing_example.main(); 13 | } 14 | -------------------------------------------------------------------------------- /example/sealedbox.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/x25519.dart' show SealedBox, PrivateKey; 2 | import 'package:pinenacl/api.dart'; 3 | 4 | void main() { 5 | print('\n### Public Key Encryption - SealedBox Example ###\n'); 6 | 7 | // Generate Bob's private key, which must be kept secret 8 | final skbob = PrivateKey.generate(); 9 | final pkbob = skbob.publicKey; 10 | 11 | // Alice wishes to send a encrypted message to Bob, 12 | // but prefers the message to be untraceable 13 | // she puts it into a secretbox and seals it. 14 | final sealedBox = SealedBox(pkbob); 15 | 16 | final message = 'The world is changing around us and we can either get ' 17 | 'with the change or we can try to resist it'; 18 | 19 | final encrypted = sealedBox.encrypt(message.codeUnits.toUint8List()); 20 | try { 21 | sealedBox.decrypt(encrypted); 22 | } on Exception catch (e) { 23 | print('Exception\'s successfully cought:\n$e'); 24 | } 25 | 26 | // Bob unseals the box with his privatekey, and decrypts it. 27 | final unsealedBox = SealedBox(skbob); 28 | 29 | final plainText = unsealedBox.decrypt(encrypted); 30 | print(String.fromCharCodes(plainText)); 31 | assert(message == String.fromCharCodes(plainText)); 32 | } 33 | -------------------------------------------------------------------------------- /example/secretbox.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/api.dart'; 2 | import 'package:pinenacl/x25519.dart' show SecretBox; 3 | 4 | void main() { 5 | print('\n### Secret Key Encryption - SecretBox Example ###\n'); 6 | final key = PineNaClUtils.randombytes(SecretBox.keyLength); 7 | final box = SecretBox(key); 8 | 9 | final message = 10 | 'Change is a tricky thing, it threatens what we find familiar with...'; 11 | 12 | final encrypted = box.encrypt(Uint8List.fromList(message.codeUnits)); 13 | 14 | final decrypted = box.decrypt(encrypted); 15 | 16 | final ctext = encrypted.cipherText; 17 | 18 | assert(ctext.length == message.length + SecretBox.macBytes); 19 | 20 | final plaintext = String.fromCharCodes(decrypted); 21 | print(plaintext); 22 | assert(message == plaintext); 23 | } 24 | -------------------------------------------------------------------------------- /example/secure.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | 3 | import 'dart:math'; 4 | import 'dart:typed_data'; 5 | import 'package:pinenacl/api.dart'; 6 | 7 | /// Add to js 8 | /// var self = global; 9 | /// var crypto = require('crypto'); 10 | /// self.crypto = crypto; 11 | /// self.crypto.getRandomBytes = crypto.randomBytes; 12 | /// self.crypto.getRandomValues = crypto.randomBytes; 13 | /// 14 | void main() { 15 | print(1 << 32); 16 | print(0x100000000); 17 | print('${randombytes(10)}'); 18 | } 19 | 20 | final _krandom = Random.secure(); 21 | 22 | Uint8List _randombytes_array(Uint8List x) { 23 | var rnd = 0; 24 | 25 | for (var i = 0; i < x.length; i++) { 26 | var iter = i % 4; 27 | 28 | if (iter == 0) { 29 | // rnd is always a 32-bit positive integer. 30 | rnd = _krandom.nextInt(0x100000000); 31 | } 32 | 33 | x[i] = (rnd >> (8 * iter)) & 0xFF; 34 | } 35 | 36 | return x; 37 | } 38 | 39 | Uint8List randombytes(int len) { 40 | return _randombytes_array(Uint8List(len)); 41 | } 42 | -------------------------------------------------------------------------------- /example/signature.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/ed25519.dart'; 2 | 3 | void main() { 4 | const hex = Base16Encoder.instance; 5 | print('\n### Digital Signatures - Signing Example ###\n'); 6 | 7 | /// Signer’s perspective (SigningKey) 8 | //final signingKey = SigningKey.generate(); 9 | const seed = 10 | '19a91fe23a4e9e33ecc474878f57c64cf154b394203487a7035e1ad9cd697b0d'; 11 | const publ = 12 | '2bf32ba142ba4622d8f3e29ecd85eea07b9c47be9d64412c9b510b27dd218b23'; 13 | 14 | const mesg = '82cb53c4d5a013bae5070759ec06c3c6955ab7a4050958ec328c'; 15 | const sigd = 16 | '881f5b8c5a030df0f75b6634b070dd27bd1ee3c08738ae349338b3ee6469bbf9760b13578a237d5182535ede121283027a90b5f865d63a6537dca07b44049a0f82cb53c4d5a013bae5070759ec06c3c6955ab7a4050958ec328c'; 17 | 18 | final signingKey = SigningKey(seed: hex.decode(seed)); 19 | final verifyKey = signingKey.verifyKey; 20 | final publicKey = VerifyKey(hex.decode(publ)); 21 | assert(publicKey == verifyKey); 22 | print('Verify Key: ${hex.encode(verifyKey)}'); 23 | 24 | final signed = signingKey.sign(hex.decode(mesg)); 25 | final encoded = hex.encode(signed); 26 | 27 | print(encoded); 28 | assert(sigd == encoded); 29 | // Obtain the verify key for a given signing key 30 | 31 | // Serialize the verify key to send it to a third party 32 | final verifyKeyHex = verifyKey.encode(hex); 33 | 34 | /// 35 | /// Verifier’s perspective (VerifyKey) 36 | /// 37 | final verifyKey2 = VerifyKey.decode(verifyKeyHex, coder: hex); 38 | assert(verifyKey == verifyKey2); 39 | 40 | // Check the validity of a message's signature 41 | // The message and the signature can either be passed separately or 42 | // concatenated together. These are equivalent: 43 | var isVerified = verifyKey.verifySignedMessage(signedMessage: signed); 44 | isVerified &= verifyKey.verify( 45 | signature: signed.signature, message: signed.message.asTypedList); 46 | 47 | final resString = isVerified ? '' : 'UN'; 48 | print('Verification of the signature was: ${resString}SUCCESSFULL '); 49 | } 50 | -------------------------------------------------------------------------------- /lib/api.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.api; 2 | 3 | import 'dart:collection'; 4 | 5 | import 'package:pinenacl/encoding.dart'; 6 | 7 | export 'dart:typed_data'; 8 | export 'encoding.dart'; 9 | 10 | export 'package:pinenacl/src/utils/utils.dart'; 11 | export 'package:pinenacl/api/authenticated_encryption.dart'; 12 | 13 | part 'api/api.dart'; 14 | part 'api/encoding.dart'; 15 | -------------------------------------------------------------------------------- /lib/api/api.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: hash_and_equals 2 | 3 | part of '../api.dart'; 4 | 5 | abstract class AsymmetricKey extends ByteList with Encodable { 6 | AsymmetricKey(Uint8List super.bytes, {required int keyLength}) 7 | : super.withConstraint(constraintLength: keyLength); 8 | AsymmetricPublicKey get publicKey; 9 | } 10 | 11 | abstract class AsymmetricPublicKey extends AsymmetricKey { 12 | AsymmetricPublicKey(super.bytes, {required super.keyLength}); 13 | } 14 | 15 | abstract class AsymmetricPrivateKey extends AsymmetricKey { 16 | AsymmetricPrivateKey(super.bytes, {required super.keyLength}); 17 | } 18 | 19 | /// 20 | /// `ByteList` is the base of the PineNaCl cryptographic library, 21 | /// which is based on the unmodifiable Uin8List class 22 | /// The bytelist can be created either from 23 | /// - hex string (with or without '0x' prefix) or 24 | /// - List of int's 25 | /// 26 | /// ByteList can have a `min` and `max` length specified. 27 | /// - `minLength` means the length of the constructable ByteList must be equal 28 | /// of bigger. 29 | /// - `maxLength` means the ByteList length must be less (till `minLength`) or 30 | /// equal. 31 | /// 32 | /// Theses two options can be used for creating a class with fixed-length ByteList or 33 | /// a class which has some constraints e.g., a class that can only create a ByteList 34 | /// that is longer or equal than 16 and shorter or equal than 32. 35 | /// 36 | class ByteList with ListMixin, Encodable { 37 | /// It creates an data's length ByteList 38 | ByteList(Iterable data) 39 | : _u8l = _constructList(data, data.length, data.length); 40 | 41 | /// It creates a ByteList and checks wheter the data's length is equal with 42 | /// the specified constraint (min and max length equal). 43 | 44 | ByteList.withConstraint(Iterable data, {required int constraintLength}) 45 | : _u8l = _constructList(data, constraintLength, constraintLength); 46 | 47 | /// It creates a ByteList and checks wheter the data's length is equal with 48 | /// the specified constraints (allowed range i.e., min and max length) 49 | /// 50 | /// e.g. data.length >= min and data.length <= max. 51 | ByteList.withConstraintRange(Iterable data, 52 | {int min = _minLength, int max = _maxLength}) 53 | : _u8l = _constructList(data, min, max); 54 | 55 | /// Decoding encoded String to a ByteList. There is no size constraints for the 56 | /// decoded bytes. 57 | /// TODO: create unit tests for decoding constructors. 58 | ByteList.decode(String encodedString, {Encoder coder = decoder}) 59 | : this(coder.decode(encodedString)); 60 | 61 | /// Decoding encoded string to a ByteList with the expected length of the 62 | /// encoded bytes. 63 | ByteList.decodeWithConstraint(String encodedString, 64 | {Encoder coder = decoder, required int constraintLength}) 65 | : this.withConstraint(coder.decode(encodedString), 66 | constraintLength: constraintLength); 67 | 68 | /// Decoding encoded string to a ByteList with the expected min and max lengths of the 69 | /// encoded bytes. 70 | ByteList.decodeWithConstraintRange(String encodedString, 71 | {Encoder coder = decoder, int min = _minLength, int max = _maxLength}) 72 | : this.withConstraintRange(coder.decode(encodedString), 73 | min: min, max: max); 74 | 75 | static const _minLength = 0; 76 | 77 | // Maximum message/bytes' length is 1MB currently 78 | static const _maxLength = 1048576; 79 | 80 | final Uint8List _u8l; 81 | 82 | static Uint8List _constructList( 83 | Iterable data, int minLength, int maxLength) { 84 | if (data.length < minLength || data.length > maxLength) { 85 | throw Exception( 86 | 'The list length (${data.length}) is invalid (min: $minLength, max: $maxLength)'); 87 | } 88 | 89 | return Uint8List.fromList(data.toList()).asUnmodifiableView(); 90 | } 91 | 92 | // Default encoder/decoder is the HexCoder() 93 | static const decoder = Base16Encoder.instance; 94 | 95 | @override 96 | Encoder get encoder => decoder; 97 | 98 | // Original getters/setters 99 | @override 100 | set length(int newLength) => _u8l.length = newLength; 101 | 102 | @override 103 | int get length => _u8l.length; 104 | 105 | @override 106 | int operator [](int index) => _u8l[index]; 107 | 108 | @override 109 | operator []=(int index, value) { 110 | _u8l[index] = value; 111 | } 112 | 113 | @override 114 | bool operator ==(Object other) { 115 | var isEqual = identical(this, other) || 116 | other is ByteList && 117 | runtimeType == other.runtimeType && 118 | length == other.length; 119 | 120 | if (!isEqual) return false; 121 | 122 | for (var i = 0; i < length; i++) { 123 | if (this[i] != (other as List)[i]) return false; 124 | } 125 | return true; 126 | } 127 | 128 | @override 129 | ByteList sublist(int start, [int? end]) { 130 | final sublist = _u8l.sublist(start, end ?? _u8l.length); 131 | return ByteList.withConstraint(sublist, constraintLength: sublist.length); 132 | } 133 | } 134 | 135 | mixin Suffix on ByteList { 136 | int get prefixLength; 137 | ByteList get prefix => ByteList.withConstraint(take(prefixLength), 138 | constraintLength: prefixLength); 139 | ByteList get suffix => ByteList.withConstraint(skip(prefixLength), 140 | constraintLength: length - prefixLength); 141 | } 142 | 143 | extension ByteListExtension on ByteList { 144 | Uint8List get asTypedList => _u8l; 145 | } 146 | 147 | /// Add a global extension for converting List to Uint8List. 148 | extension IntListExtension on List { 149 | Uint8List toUint8List() { 150 | return Uint8List.fromList(this); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /lib/api/authenticated_encryption.dart: -------------------------------------------------------------------------------- 1 | //part of pinenacl.api; 2 | import '../api.dart'; 3 | import '../src/tweetnacl/tweetnacl.dart'; 4 | 5 | typedef Crypting = Uint8List Function( 6 | Uint8List out, Uint8List text, int textLen, Uint8List nonce, Uint8List k); 7 | 8 | abstract class BoxBase extends ByteList { 9 | BoxBase.fromList(Uint8List super.list); 10 | 11 | late Crypting doEncrypt; 12 | late Crypting doDecrypt; 13 | ByteList get key; 14 | 15 | Uint8List decrypt(ByteList encryptedMessage, {Uint8List? nonce}) { 16 | ByteList ciphertext; 17 | if (encryptedMessage is EncryptedMessage) { 18 | nonce = encryptedMessage.nonce.asTypedList; 19 | ciphertext = encryptedMessage.cipherText; 20 | } else if (nonce != null) { 21 | ciphertext = encryptedMessage; 22 | } else { 23 | throw Exception('Nonce is required for a message'); 24 | } 25 | 26 | final c = 27 | Uint8List(TweetNaCl.boxzerobytesLength).toList() + ciphertext.toList(); 28 | final m = Uint8List(c.length); 29 | final plaintext = 30 | doDecrypt(m, Uint8List.fromList(c), c.length, nonce, key.asTypedList); 31 | return Uint8List.fromList(plaintext); 32 | } 33 | 34 | EncryptedMessage encrypt(Uint8List plainText, {Uint8List? nonce}) { 35 | final nonce1 = nonce ?? TweetNaCl.randombytes(TweetNaCl.nonceLength); 36 | 37 | final m = Uint8List(TweetNaCl.zerobytesLength).toList() + plainText; 38 | final c = Uint8List(m.length); 39 | 40 | final cipherText = doEncrypt(c, Uint8List.fromList(m), m.length, 41 | Uint8List.fromList(nonce1), key.asTypedList); 42 | 43 | return EncryptedMessage(nonce: nonce1, cipherText: cipherText); 44 | } 45 | } 46 | 47 | class EncryptedMessage extends ByteList with Suffix { 48 | EncryptedMessage({required Uint8List nonce, required Uint8List cipherText}) 49 | : super.withConstraintRange((nonce + cipherText).toUint8List(), 50 | min: nonceLength, max: nonce.length + cipherText.length); 51 | 52 | EncryptedMessage.fromList(Uint8List super.bytes) 53 | : super.withConstraintRange(min: nonceLength); 54 | 55 | static const nonceLength = 24; 56 | 57 | @override 58 | int get prefixLength => nonceLength; 59 | 60 | ByteList get nonce => prefix; 61 | ByteList get cipherText => suffix; 62 | } 63 | 64 | class PublicKey extends AsymmetricPublicKey { 65 | PublicKey(super.bytes) : super(keyLength: keyLength); 66 | 67 | PublicKey.decode(String keyString, [Encoder coder = decoder]) 68 | : this(coder.decode(keyString)); 69 | 70 | static const decoder = Bech32Encoder(hrp: 'x25519_pk'); 71 | 72 | @override 73 | PublicKey get publicKey => this; 74 | 75 | @override 76 | Encoder get encoder => decoder; 77 | 78 | static const keyLength = TweetNaCl.publicKeyLength; 79 | } 80 | 81 | /// 82 | /// The PrivateKey implements the X25519 key agreement scheme (ECDH) using 83 | /// Curve25519 that provides a fast, simple, constant time, and fast 84 | /// `variable-base` scalar multiplication algorithm, which is is optimal for 85 | /// ECDH 86 | /// 87 | class PrivateKey extends AsymmetricPrivateKey { 88 | PrivateKey(super.secret) : super(keyLength: keyLength); 89 | 90 | PrivateKey.fromSeed(Uint8List seed) : this(_seedToHash(seed)); 91 | 92 | PrivateKey.generate() : this(TweetNaCl.randombytes(seedSize)); 93 | 94 | PrivateKey.decode(String keyString, [Encoder coder = decoder]) 95 | : this(coder.decode(keyString)); 96 | 97 | static const decoder = Bech32Encoder(hrp: 'x25519_sk'); 98 | static const seedSize = TweetNaCl.seedSize; 99 | static const keyLength = TweetNaCl.secretKeyLength; 100 | 101 | @override 102 | Encoder get encoder => decoder; 103 | 104 | PublicKey? _publicKey; 105 | 106 | @override 107 | PublicKey get publicKey => 108 | _publicKey ??= PublicKey(_secretToPublic(asTypedList)); 109 | 110 | static Uint8List _secretToPublic(Uint8List secret) { 111 | if (secret.length != keyLength) { 112 | throw Exception( 113 | 'PrivateKey\'s seed must be a $seedSize bytes long binary sequence'); 114 | } 115 | 116 | final public = Uint8List(TweetNaCl.publicKeyLength); 117 | return TweetNaCl.crypto_scalarmult_base(public, Uint8List.fromList(secret)); 118 | } 119 | 120 | static Uint8List _seedToHash(Uint8List seed) { 121 | if (seed.length != seedSize) { 122 | throw Exception( 123 | 'PrivateKey\'s seed must be a $seedSize bytes long binary sequence'); 124 | } 125 | 126 | final out = Uint8List(64); 127 | TweetNaCl.crypto_hash(out, Uint8List.fromList(seed)); 128 | return out.sublist(0, keyLength); 129 | } 130 | } 131 | 132 | class SealedMessage extends ByteList with Suffix { 133 | SealedMessage({required Uint8List public, required Uint8List cipherText}) 134 | : super(public + cipherText); 135 | 136 | @override 137 | int get prefixLength => publicLength; 138 | 139 | static const publicLength = 32; 140 | ByteList get public => prefix; 141 | ByteList get cipherText => suffix; 142 | } 143 | -------------------------------------------------------------------------------- /lib/api/encoding.dart: -------------------------------------------------------------------------------- 1 | part of '../api.dart'; 2 | 3 | /// The Encoder interface for classes that are capable for encoding data, 4 | /// therefore they need decoding function too. 5 | abstract class Encoder { 6 | String encode(ByteList data); 7 | Uint8List decode(String data); 8 | } 9 | 10 | mixin Encodable { 11 | Encoder get encoder; 12 | String encode([Encoder? encoder]) { 13 | encoder = encoder ?? this.encoder; 14 | return encoder.encode(this as ByteList); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/api/signatures.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/api.dart'; 2 | 3 | abstract class Sign { 4 | Verify get verifyKey; 5 | EncryptionMessage sign(Uint8List message); 6 | } 7 | 8 | abstract class Verify implements ByteList { 9 | bool verify({required SignatureBase signature, required Uint8List message}); 10 | bool verifySignedMessage({required EncryptionMessage signedMessage}); 11 | } 12 | 13 | abstract class SignatureBase implements ByteList {} 14 | 15 | abstract class EncryptionMessage { 16 | SignatureBase get signature; 17 | ByteList get message; 18 | } 19 | -------------------------------------------------------------------------------- /lib/digests.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.hashing; 2 | 3 | export 'package:pinenacl/src/digests/digests.dart'; 4 | -------------------------------------------------------------------------------- /lib/ed25519.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.ed25519; 2 | 3 | export 'dart:typed_data'; 4 | export 'package:pinenacl/src/utils/utils.dart'; 5 | 6 | export 'package:pinenacl/api.dart'; 7 | export 'package:pinenacl/api/signatures.dart'; 8 | export 'package:pinenacl/src/signatures/ed25519.dart'; 9 | 10 | //import 'package:pinenacl/api/signatures.dart'; 11 | 12 | //part 'api/signatures.dart'; 13 | -------------------------------------------------------------------------------- /lib/encoding.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.encoding; 2 | 3 | export 'dart:typed_data'; 4 | 5 | import 'dart:convert'; 6 | 7 | import 'package:pinenacl/api.dart'; 8 | 9 | part 'src/encoding/base32_encoder.dart'; 10 | part 'src/encoding/bech32_encoder.dart'; 11 | part 'src/encoding/base16_encoder.dart'; 12 | -------------------------------------------------------------------------------- /lib/key_derivation.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.key_derivation; 2 | 3 | export 'package:pinenacl/src/key_derivation/pbkdf2.dart'; 4 | -------------------------------------------------------------------------------- /lib/message_authentication.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.message_authentication; 2 | 3 | export 'package:pinenacl/src/message_authentication/hmac.dart'; 4 | -------------------------------------------------------------------------------- /lib/src/authenticated_encryption/public.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: overridden_fields 2 | 3 | import 'package:pinenacl/api.dart'; 4 | import 'package:pinenacl/src/digests/blake2b.dart'; 5 | import 'package:pinenacl/src/tweetnacl/tweetnacl.dart'; 6 | 7 | /// Public Key Encryption 8 | /// 9 | /// Package box authenticates and encrypts small messages using public-key cryptography. 10 | /// Box uses Curve25519, XSalsa20 and Poly1305 to encrypt and authenticate messages. 11 | /// The length of messages is not hidden. 12 | /// 13 | /// It is the caller's responsibility to ensure the uniqueness of nonces—for example, 14 | /// by using nonce 1 for the first message, nonce 2 for the second message, etc. 15 | /// Nonces are long enough that randomly generated nonces have negligible risk of collision. 16 | /// 17 | /// Messages should be small because: 18 | /// 1. The whole message needs to be held in memory to be processed. 19 | /// 2. Using large messages pressures implementations on small machines to decrypt and process 20 | /// plaintext before authenticating it. This is very dangerous, and this API does not allow it, 21 | /// but a protocol that uses excessive message sizes might present some implementations with no other choice. 22 | /// 3. Fixed overheads will be sufficiently amortised by messages as small as 8KB. 23 | /// 4. Performance may be improved by working with messages that fit into data caches. 24 | /// 25 | /// Thus large amounts of data should be chunked so that each message is small. 26 | /// (Each message still needs a unique nonce.) If in doubt, 16KB is a reasonable chunk size. 27 | /// 28 | /// Doc Comment from: [PyNaCl's readthedocs](https://pynacl.readthedocs.io) 29 | /// 30 | /// The __Box__ class boxes and unboxes messages between a pair of keys 31 | /// 32 | /// The ciphertexts generated by Box include a 16 byte authenticator which 33 | /// is checked as part of the decryption. 34 | /// An invalid authenticator will cause the decrypt function to raise an exception. 35 | /// The authenticator is not a signature. Once you’ve decrypted the message you’ve 36 | /// demonstrated the ability to create arbitrary valid message, 37 | /// so messages you send are repudiable. For non-repudiable messages, 38 | /// sign them after encryption. 39 | /// 40 | /// Doc comment from: [PyNaCl's readthedocs](https://pynacl.readthedocs.io) 41 | class Box extends BoxBase { 42 | Box( 43 | {required AsymmetricPrivateKey myPrivateKey, 44 | required AsymmetricPublicKey theirPublicKey}) 45 | : super.fromList( 46 | _beforeNm(theirPublicKey, myPrivateKey, null) as Uint8List); 47 | 48 | Box._fromSharedKey(Uint8List sharedKey) 49 | : super.fromList(_beforeNm(null, null, sharedKey) as Uint8List); 50 | 51 | factory Box.decode(Uint8List encoded) { 52 | return Box._fromSharedKey(encoded); 53 | } 54 | 55 | ByteList get sharedKey => this; 56 | 57 | static const decoder = Base16Encoder.instance; 58 | 59 | @override 60 | Encoder get encoder => decoder; 61 | 62 | // NOTE: properties and function must be public i.e. not underscore names e.g. _key 63 | @override 64 | ByteList get key => sharedKey; 65 | 66 | @override 67 | Crypting doEncrypt = TweetNaCl.crypto_box_afternm; 68 | 69 | @override 70 | Crypting doDecrypt = TweetNaCl.crypto_box_open_afternm; 71 | 72 | // Initialize the sharedKey 73 | static Uint8List? _beforeNm(AsymmetricPublicKey? publicKey, 74 | AsymmetricPrivateKey? secretKey, Uint8List? sharedKey) { 75 | if (publicKey == null && secretKey == null) { 76 | /// Using the predefined sharedKey we must have the 77 | /// publicKey and privateKey unset. 78 | /// It returns a null or a list, which checked in the parent classes. 79 | return sharedKey; 80 | } else if (publicKey == null || secretKey == null) { 81 | /// Invalid combination 82 | return null; 83 | } else if (sharedKey != null) { 84 | throw Exception( 85 | 'The sharedKey must be null when the private and public keys are provided.'); 86 | } 87 | 88 | final priv = Uint8List.fromList(secretKey); 89 | final pub = Uint8List.fromList(publicKey); 90 | final k = Uint8List(TweetNaCl.keyLength); 91 | 92 | TweetNaCl.crypto_box_beforenm(k, pub, priv); 93 | // Returns the constructed sharedKey. 94 | return Uint8List.fromList(k); 95 | } 96 | } 97 | 98 | /// The SealedBox class encrypts messages addressed to a specified key-pair 99 | /// by using ephemeral sender’s keypairs, which will be discarded just after 100 | /// encrypting a single plaintext message. 101 | /// 102 | /// This kind of construction allows sending messages, which only the recipient 103 | /// can decrypt without providing any kind of cryptographic proof of sender’s authorship. 104 | /// 105 | /// ## `Warning` 106 | /// By design, the recipient will have no means to trace the ciphertext to a known author, 107 | /// since the sending keypair itself is not bound to any sender’s identity, and the sender 108 | /// herself will not be able to decrypt the ciphertext she just created, since the private 109 | /// part of the key cannot be recovered after use. 110 | /// 111 | /// Doc comment from: [PyNaCl's readthedocs](https://pynacl.readthedocs.io) 112 | class SealedBox extends ByteList { 113 | SealedBox._fromKeyPair( 114 | AsymmetricPrivateKey? privateKey, AsymmetricPublicKey super.publicKey) 115 | : _privateKey = privateKey; 116 | 117 | factory SealedBox(AsymmetricKey key) { 118 | if (key is AsymmetricPrivateKey) { 119 | final pub = key.publicKey; 120 | return SealedBox._fromKeyPair(key, pub); 121 | } else if (key is AsymmetricPublicKey) { 122 | return SealedBox._fromKeyPair(null, key); 123 | } else { 124 | throw Exception( 125 | 'SealedBox must be created from a PublicKey or a PrivateKey'); 126 | } 127 | } 128 | 129 | final AsymmetricPrivateKey? _privateKey; 130 | 131 | static const _zerobytesLength = TweetNaCl.zerobytesLength; 132 | static const _nonceLength = 24; 133 | static const _pubLength = TweetNaCl.publicKeyLength; 134 | static const _secretLength = TweetNaCl.secretKeyLength; 135 | static const _macBytes = TweetNaCl.macBytes; 136 | static const _sealBytes = _pubLength + _macBytes; 137 | 138 | static const decoder = Base16Encoder.instance; 139 | 140 | @override 141 | Encoder get encoder => decoder; 142 | 143 | /// Decrypts the ciphertext using the ephemeral public key enclosed 144 | /// in the ciphertext and the SealedBox private key, returning 145 | /// the plaintext message. 146 | Uint8List decrypt(Uint8List ciphertext) { 147 | if (_privateKey == null) { 148 | throw Exception( 149 | 'SealedBox does not have the privateKey to decrypt the ciphertext'); 150 | } 151 | return _cryptoBoxSealOpen(ciphertext); 152 | } 153 | 154 | /// Encrypts the plaintext message using a random-generated ephemeral 155 | /// keypair and returns a "composed ciphertext", containing both 156 | /// the public part of the keypair and the ciphertext proper, 157 | /// encoded with the encoder. 158 | /// 159 | /// The private part of the ephemeral key-pair will be scrubbed before 160 | /// returning the ciphertext, therefore, the sender will not be able to 161 | /// decrypt the generated ciphertext. 162 | Uint8List encrypt(Uint8List plaintext) { 163 | return _cryptoBoxSeal(plaintext, this); 164 | } 165 | 166 | static void _generateNonce(Uint8List out, Uint8List in1, Uint8List in2) { 167 | final state = Blake2b.init(_nonceLength, null, null, null); 168 | Blake2b.update(state, in1); 169 | Blake2b.update(state, in2); 170 | 171 | final digest = Blake2b.finalise(state); 172 | PineNaClUtils.listCopy(digest, digest.length, out, 0); 173 | } 174 | 175 | /// The `crypto_box_seal` is not in the `TweetNaCl`, that's why 176 | /// is implemented here and not in `TweetNaClFast` 177 | /// 178 | /// Encrypts and returns a message `message` using an ephemeral secret key 179 | /// and the public key `pk`. 180 | /// The ephemeral public key, which is embedded in the sealed box, is also 181 | /// used, in combination with `pk`, to derive the nonce needed for the 182 | /// underlying box construct. 183 | Uint8List _cryptoBoxSeal(Uint8List message, ByteList pk) { 184 | final mLen = message.length; 185 | final cLen = _sealBytes + mLen; 186 | final ciphertext = Uint8List(cLen); 187 | 188 | var epk = Uint8List(_pubLength); 189 | var esk = Uint8List(_secretLength); 190 | TweetNaCl.crypto_box_keypair(epk, esk); 191 | 192 | final nonce = Uint8List(_nonceLength); 193 | _generateNonce(nonce, epk, pk.asTypedList); 194 | 195 | final k = Uint8List(_secretLength); 196 | TweetNaCl.crypto_box_beforenm(k, pk.asTypedList, esk); 197 | 198 | var mac = ciphertext.sublist(_pubLength, _pubLength + _macBytes); 199 | 200 | _cryptoBoxDetached(ciphertext, mac, message, mLen, nonce, k); 201 | PineNaClUtils.listCopy(epk, epk.length, ciphertext, 0); 202 | PineNaClUtils.listCopy(mac, mac.length, ciphertext, _pubLength); 203 | 204 | // Clean the sensitive data which are not required anymore. 205 | PineNaClUtils.listZero(esk); 206 | PineNaClUtils.listZero(nonce); 207 | PineNaClUtils.listZero(k); 208 | 209 | return ciphertext; 210 | } 211 | 212 | void _cryptoBoxDetached(Uint8List c, Uint8List mac, Uint8List m, int d, 213 | Uint8List n, Uint8List k) { 214 | var ciphertext = Uint8List(d + _zerobytesLength); 215 | 216 | TweetNaCl.crypto_stream_xor( 217 | ciphertext, 218 | 0, 219 | Uint8List.fromList(List.filled(_zerobytesLength, 0) + m), 220 | 0, 221 | d + _zerobytesLength, 222 | n, 223 | k); 224 | 225 | final block0 = ciphertext.sublist(0, _zerobytesLength); 226 | ciphertext = ciphertext.sublist(_zerobytesLength, d + _zerobytesLength); 227 | PineNaClUtils.listCopy( 228 | ciphertext, ciphertext.length, c, _zerobytesLength + _macBytes); 229 | 230 | TweetNaCl.crypto_onetimeauth(mac, ciphertext, d, block0); 231 | } 232 | 233 | /// Decrypts and returns an encrypted message `ciphertext`, using the 234 | /// recipent's secret key `sk` and the sender's ephemeral public key 235 | /// embedded in the sealed box. The box contruct nonce is derived from 236 | /// the recipient's public key `pk` and the sender's public key. 237 | Uint8List _cryptoBoxSealOpen(Uint8List ciphertext) { 238 | final cLen = ciphertext.length; 239 | 240 | if (cLen < _sealBytes) { 241 | throw Exception('Input cyphertext must be at least $_sealBytes long'); 242 | } else if (cLen == _sealBytes) { 243 | // means empty message, no point of calculate anything. 244 | return Uint8List(0); 245 | } 246 | 247 | final mLen = cLen - _sealBytes; 248 | final plaintext = Uint8List(mLen + _zerobytesLength); 249 | 250 | final epk = ciphertext.sublist(0, _pubLength); 251 | 252 | final k = Uint8List(_secretLength); 253 | TweetNaCl.crypto_box_beforenm(k, epk, _privateKey!.asTypedList); 254 | final nonce = Uint8List(_nonceLength); 255 | _generateNonce(nonce, epk, asTypedList); 256 | 257 | var x = Uint8List(_secretLength); 258 | TweetNaCl.crypto_stream(x, 0, _zerobytesLength, nonce, k); 259 | 260 | final mac = ciphertext.sublist(_zerobytesLength, _sealBytes); 261 | final mm = ciphertext.sublist(_sealBytes); 262 | 263 | if (TweetNaCl.crypto_onetimeauth_verify(mac, mm, x) != 0) { 264 | throw 'The message is forged, malformed or the shared secret is invalid'; 265 | } 266 | 267 | final a = x.toList() + mm; 268 | final cc = Uint8List.fromList(a); 269 | 270 | TweetNaCl.crypto_stream_xor(plaintext, 0, cc, 0, cc.length, nonce, k); 271 | 272 | return plaintext.sublist(_zerobytesLength); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /lib/src/authenticated_encryption/secret.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: overridden_fields 2 | 3 | import 'package:pinenacl/api.dart'; 4 | import 'package:pinenacl/src/tweetnacl/tweetnacl.dart'; 5 | 6 | /// From: [PyNaCl's readthedocs](https://pynacl.readthedocs.io) 7 | /// 8 | /// Secret Key Encryption 9 | /// 10 | /// Secret key encryption (also called symmetric key encryption) is analogous to a safe. 11 | /// You can store something secret through it and anyone who has the key can open it and view the contents. 12 | /// SecretBox functions as just such a safe, and like any good safe any attempts to tamper 13 | /// with the contents are easily detected. 14 | /// 15 | /// Secret key encryption allows you to store or transmit data over insecure channels without leaking the contents of that message, 16 | /// nor anything about it other than the length. 17 | /// 18 | /// Secretbox uses XSalsa20 and Poly1305 to encrypt and authenticate messages with secret-key cryptography. 19 | /// The length of messages is not hidden. 20 | /// 21 | /// It is the caller's responsibility to ensure the uniqueness of nonces—for example, 22 | /// by using nonce 1 for the first message, nonce 2 for the second message, etc. 23 | /// Nonces are long enough that randomly generated nonces have negligible risk of collision. 24 | /// 25 | /// Messages should be small because: 26 | /// 1. The whole message needs to be held in memory to be processed. 27 | /// 2. Using large messages pressures implementations on small machines to decrypt and 28 | /// process plaintext before authenticating it. This is very dangerous, and this API 29 | /// does not allow it, but a protocol that uses excessive message sizes might present 30 | /// some implementations with no other choice. 31 | /// 3. Fixed overheads will be sufficiently amortised by messages as small as 8KB. 32 | /// 4. Performance may be improved by working with messages that fit into data caches. 33 | /// Thus large amounts of data should be chunked so that each message is small. 34 | /// (Each message still needs a unique nonce.) If in doubt, 16KB is a reasonable chunk size. 35 | class SecretBox extends BoxBase { 36 | SecretBox(super.secret) : super.fromList(); 37 | 38 | factory SecretBox.decode(String data, [Encoder defaultDecoder = decoder]) { 39 | final decoded = defaultDecoder.decode(data); 40 | return SecretBox(decoded); 41 | } 42 | 43 | static const keyLength = TweetNaCl.keyLength; 44 | static const macBytes = TweetNaCl.macBytes; 45 | 46 | static const decoder = Base16Encoder.instance; 47 | 48 | @override 49 | Encoder get encoder => decoder; 50 | 51 | @override 52 | ByteList get key => this; 53 | 54 | @override 55 | Crypting doEncrypt = TweetNaCl.crypto_box_afternm; 56 | 57 | @override 58 | Crypting doDecrypt = TweetNaCl.crypto_box_open_afternm; 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/digests/blake2b.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: library_private_types_in_public_api 2 | 3 | import 'package:pinenacl/api.dart'; 4 | 5 | /* 6 | TODO: implement this type of API 7 | abstract Digest { 8 | Digest init(length, key, salt, personalisation) 9 | Digest update(List message); 10 | Uint8List finalise(); 11 | } 12 | 13 | var blake = Blake2b(24); 14 | /// Context is unmodifiable, therefore new opject is created 15 | /// similar to the copyWith(); 16 | blake = blake.init(); 17 | blake = blake.update(message); 18 | blake = blake.update(message); 19 | var result = blake.finalise(); 20 | */ 21 | class _Context { 22 | _Context(this.b, this.h, this.t, this.c, this.outLen); 23 | final Uint8List b; 24 | final Uint32List h; 25 | int t; 26 | int c; 27 | final int outLen; 28 | } 29 | 30 | class Blake2b { 31 | static const bytes = 32; 32 | static const minBytes = 16; 33 | static const maxBytes = 64; 34 | // FIXME: we should only support 16 for min key length, 35 | // but testvectors use shorter. 36 | static const minKeyBytes = 1; 37 | static const maxKeyBytes = maxBytes; 38 | static const keyBytes = bytes; 39 | static const saltBytes = minBytes; 40 | static const personalBytes = minBytes; 41 | 42 | static const _blake2bIv32 = [ 43 | 0xF3BCC908, 0x6A09E667, 0x84CAA73B, 0xBB67AE85, // 0-3 44 | 0xFE94F82B, 0x3C6EF372, 0x5F1D36F1, 0xA54FF53A, // 4-7 45 | 0xADE682D1, 0x510E527F, 0x2B3E6C1F, 0x9B05688C, 46 | 0xFB41BD6B, 0x1F83D9AB, 0x137E2179, 0x5BE0CD19 47 | ]; 48 | 49 | static const List _sigma8 = [ 50 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // 0-15 51 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, 52 | 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, 53 | 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 54 | 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 55 | 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, 56 | 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 57 | 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 58 | 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, 59 | 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 60 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 61 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 62 | ]; 63 | 64 | static final _sigma82 = 65 | Uint8List.fromList(_sigma8.map((x) => x * 2).toList()); 66 | 67 | /// 64-bit unsigned addition 68 | /// Sets v[a,a+1] += v[b,b+1] 69 | /// v should be a Uint32List 70 | static void _add64aa(Uint32List v, int a, int b) { 71 | final o0 = v[a] + v[b]; 72 | var o1 = v[a + 1] + v[b + 1]; 73 | if (o0 >= 0x100000000) { 74 | o1++; 75 | } 76 | v[a] = o0; 77 | v[a + 1] = o1; 78 | } 79 | 80 | /// 64-bit unsigned addition 81 | /// Sets v[a,a+1] += b 82 | /// b0 is the low 32 bits of b, b1 represents the high 32 bits 83 | static void _add64ac(Uint32List v, int a, int b0, int b1) { 84 | var o0 = v[a] + b0; 85 | if (b0 < 0) { 86 | o0 += 0x100000000; 87 | } 88 | var o1 = v[a + 1] + b1; 89 | if (o0 >= 0x100000000) { 90 | o1++; 91 | } 92 | v[a] = o0; 93 | v[a + 1] = o1; 94 | } 95 | 96 | /// Little-endian byte access 97 | static int _b2bGET32(Uint8List arr, int i) { 98 | return (arr[i] ^ 99 | (arr[i + 1] << 8) ^ 100 | (arr[i + 2] << 16) ^ 101 | (arr[i + 3] << 24)); 102 | } 103 | 104 | /// Compression function. [last] flag indicates last block. 105 | static void _blake2bCompress(_Context context, bool last) { 106 | final v = Uint32List(32); 107 | final m = Uint32List(32); 108 | 109 | /// G Mixing function 110 | /// The ROTRs are inlined for speed 111 | void b2bG(int a, int b, int c, int d, int ix, int iy) { 112 | var x0 = m[ix]; 113 | var x1 = m[ix + 1]; 114 | var y0 = m[iy]; 115 | var y1 = m[iy + 1]; 116 | 117 | // v[a,a+1] += v[b,b+1] 118 | _add64aa(v, a, b); 119 | // v[a, a+1] += x 120 | _add64ac(v, a, x0, x1); 121 | 122 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits 123 | var xor0 = v[d] ^ v[a]; 124 | var xor1 = v[d + 1] ^ v[a + 1]; 125 | v[d] = xor1; 126 | v[d + 1] = xor0; 127 | 128 | _add64aa(v, c, d); 129 | 130 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits 131 | xor0 = v[b] ^ v[c]; 132 | xor1 = v[b + 1] ^ v[c + 1]; 133 | v[b] = (xor0 >> 24) ^ (xor1 << 8); 134 | v[b + 1] = (xor1 >> 24) ^ (xor0 << 8); 135 | 136 | _add64aa(v, a, b); 137 | _add64ac(v, a, y0, y1); 138 | 139 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits 140 | xor0 = v[d] ^ v[a]; 141 | xor1 = v[d + 1] ^ v[a + 1]; 142 | v[d] = (xor0 >> 16) ^ (xor1 << 16); 143 | v[d + 1] = (xor1 >> 16) ^ (xor0 << 16); 144 | 145 | _add64aa(v, c, d); 146 | 147 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits 148 | xor0 = v[b] ^ v[c]; 149 | xor1 = v[b + 1] ^ v[c + 1]; 150 | v[b] = (xor1 >> 31) ^ (xor0 << 1); 151 | v[b + 1] = (xor0 >> 31) ^ (xor1 << 1); 152 | } 153 | 154 | // init work variables 155 | for (var i = 0; i < 16; i++) { 156 | v[i] = context.h[i]; 157 | v[i + 16] = _blake2bIv32[i]; 158 | } 159 | 160 | // low 64 bits of offset 161 | v[24] = v[24] ^ context.t; 162 | v[25] = v[25] ^ (context.t ~/ 0x100000000); 163 | // high 64 bits not supported, offset may not be higher than 2**53-1 164 | 165 | // last block flag set ? 166 | if (last) { 167 | v[28] = ~v[28]; 168 | v[29] = ~v[29]; 169 | } 170 | 171 | // get little-endian words 172 | for (var i = 0; i < 32; i++) { 173 | m[i] = _b2bGET32(context.b, 4 * i); 174 | } 175 | 176 | for (var i = 0; i < 12; i++) { 177 | b2bG(0, 8, 16, 24, _sigma82[i * 16 + 0], _sigma82[i * 16 + 1]); 178 | b2bG(2, 10, 18, 26, _sigma82[i * 16 + 2], _sigma82[i * 16 + 3]); 179 | b2bG(4, 12, 20, 28, _sigma82[i * 16 + 4], _sigma82[i * 16 + 5]); 180 | b2bG(6, 14, 22, 30, _sigma82[i * 16 + 6], _sigma82[i * 16 + 7]); 181 | b2bG(0, 10, 20, 30, _sigma82[i * 16 + 8], _sigma82[i * 16 + 9]); 182 | b2bG(2, 12, 22, 24, _sigma82[i * 16 + 10], _sigma82[i * 16 + 11]); 183 | b2bG(4, 14, 16, 26, _sigma82[i * 16 + 12], _sigma82[i * 16 + 13]); 184 | b2bG(6, 8, 18, 28, _sigma82[i * 16 + 14], _sigma82[i * 16 + 15]); 185 | } 186 | 187 | for (var i = 0; i < 16; i++) { 188 | context.h[i] = context.h[i] ^ v[i] ^ v[i + 16]; 189 | } 190 | } 191 | 192 | /// Creates a BLAKE2b hashing context. 193 | /// 194 | /// Requires an output length between 1 and 64 bytes 195 | /// Takes an optional Uint8List key, salte and personalisation parameters 196 | /// for KDF or MAC. 197 | static _Context init(int outlen, 198 | [Uint8List? key, Uint8List? salt, Uint8List? personal]) { 199 | if (outlen <= 0 || outlen > maxBytes) { 200 | throw Exception('Illegal output length, expected 0 < length <= 64'); 201 | } 202 | if (key != null && (key.length < minKeyBytes || key.length > maxKeyBytes)) { 203 | throw Exception( 204 | 'Illegal key, expected Uint8List with $minKeyBytes <= length <= $maxKeyBytes'); 205 | } 206 | if (salt != null && salt.length != saltBytes) { 207 | throw Exception( 208 | 'Illegal salt parameter, expected Uint8List of $saltBytes length'); 209 | } 210 | if (personal != null && 211 | (personal.isEmpty || personal.length > personalBytes)) { 212 | throw Exception( 213 | 'Illegal personalization parameter, expected Uint8List of 0 < $personalBytes <= length'); 214 | } 215 | // Initialise the context. 216 | final context = _Context(Uint8List(128), Uint32List(16), 0, 0, outlen); 217 | 218 | // Initialise the parameter block 219 | // 0- 3: outlen, keylen, fanout, depth 220 | // 4- 7: leaf length, sequential mode 221 | // 8-15: node offset 222 | // 16 : node depth, inner length, rfu 223 | // 20-31: rfu 224 | // 32-47: salt 225 | // 48-63: personal 226 | final params = Uint8List(maxBytes); 227 | // In default, t's filled /w zero but, better safe than sorry 228 | PineNaClUtils.listZero(params); 229 | 230 | params[0] = outlen; 231 | if (key != null) { 232 | params[1] = key.length; 233 | } 234 | // Fanout 235 | params[2] = 1; 236 | // Depth 237 | params[3] = 1; 238 | 239 | if (salt != null) { 240 | PineNaClUtils.listCopy(salt, salt.length, params, 32); 241 | } 242 | 243 | if (personal != null) { 244 | // padding if length < $personalBytes 245 | final offset = 48 + personalBytes - personal.length; 246 | PineNaClUtils.listCopy(personal, personal.length, params, offset); 247 | } 248 | 249 | for (var i = 0; i < 16; i++) { 250 | context.h[i] = _blake2bIv32[i] ^ _b2bGET32(params, i * 4); 251 | } 252 | 253 | // key the hash, if applicable 254 | if (key != null) { 255 | update(context, key); 256 | // at the end 257 | context.c = 128; 258 | } 259 | 260 | return context; 261 | } 262 | 263 | /// Updates a BLAKE2b streaming hash. 264 | /// 265 | /// Requires hash [context] and Uint8List [input] (byte array) 266 | static void update(_Context context, Uint8List input) { 267 | for (var i = 0; i < input.length; i++) { 268 | if (context.c == 128) { 269 | context.t += context.c; // add counters 270 | _blake2bCompress(context, false); 271 | context.c = 0; // reset the counter 272 | } 273 | context.b[context.c++] = input[i]; 274 | } 275 | } 276 | 277 | /// Completes a BLAKE2b streaming hash. 278 | /// 279 | /// Returns a Uint8List containing the message digest 280 | static Uint8List finalise(_Context context) { 281 | context.t += context.c; // mark last block offset 282 | 283 | while (context.c < 128) { 284 | // fill up with zeros 285 | context.b[context.c++] = 0; 286 | } 287 | _blake2bCompress(context, true); // final block flag = 1 288 | 289 | // little endian convert and store 290 | final out = Uint8List(context.outLen); 291 | for (var i = 0; i < context.outLen; i++) { 292 | out[i] = context.h[i >> 2] >> (8 * (i & 3)); 293 | } 294 | return out; 295 | } 296 | 297 | /// Computes the BLAKE2B hash of a string or byte array, and returns a Uint8List 298 | /// 299 | /// Returns a n-byte Uint8List 300 | /// 301 | /// Parameters: 302 | /// - input - the input bytes, as a string, Buffer or Uint8List 303 | /// - key - optional key Uint8List, up to 64 bytes 304 | /// - outlen - optional output length in bytes, default 64 305 | static Uint8List digest(Uint8List data, 306 | {int? digestSize, Uint8List? key, Uint8List? salt, Uint8List? personal}) { 307 | digestSize = digestSize ?? 64; 308 | 309 | final context = init(digestSize, key, salt, personal); 310 | update(context, data); 311 | return finalise(context); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /lib/src/digests/digests.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.api.hashing; 2 | 3 | import 'dart:convert'; 4 | import 'dart:typed_data'; 5 | 6 | import 'package:pinenacl/src/digests/blake2b.dart'; 7 | import 'package:pinenacl/src/tweetnacl/tweetnacl.dart'; 8 | 9 | /// Hash algorithms, Implements SHA-512, SHA-256 and Blake2b. 10 | /// Cryptographic secure hash functions are irreversible transforms of input data to a fixed length digest. 11 | /// The standard properties of a cryptographic hash make these functions useful both for standalone usage as data integrity checkers, as well as black-box building blocks of other kind of algorithms and data structures. 12 | /// 13 | /// All of the hash functions exposed in `pinenacl.hashing` can be used as data integrity checkers. 14 | class Hash { 15 | //Length of hash in bytes. 16 | static const int defaultHashLength = 64; 17 | 18 | /// Returns SHA-256 hash of the message. 19 | static Uint8List sha256(dynamic message) { 20 | if (message is String) { 21 | message = Uint8List.fromList(utf8.encode(message)); 22 | } else if (message is! Uint8List) { 23 | throw Exception('The message must be either of string or Uint8List'); 24 | } 25 | var out = Uint8List(32); 26 | TweetNaClExt.crypto_hash_sha256(out, message as Uint8List); 27 | return out; 28 | } 29 | 30 | /// Returns SHA-512 hash of the message. 31 | static Uint8List sha512(dynamic message, {int? hashLength}) { 32 | if (message is String) { 33 | message = Uint8List.fromList(utf8.encode(message)); 34 | } else if (message is! Uint8List) { 35 | throw Exception('The message must be either of string or Uint8List'); 36 | } 37 | final out = Uint8List(defaultHashLength); 38 | TweetNaCl.crypto_hash(out, message as Uint8List); 39 | return out.sublist(0, hashLength ?? 64); 40 | } 41 | 42 | /// Returns a Blake2b hash of the message. 43 | static Uint8List blake2b(dynamic message, 44 | {int? digestSize, 45 | Uint8List? key, 46 | Uint8List? salt, 47 | Uint8List? personalisation}) { 48 | if (message is String) { 49 | message = Uint8List.fromList(utf8.encode(message)); 50 | } else if (message is! Uint8List) { 51 | throw Exception('The message must be either of string or Uint8List'); 52 | } 53 | 54 | return Blake2b.digest(message as Uint8List, 55 | digestSize: digestSize ?? defaultHashLength, 56 | key: key, 57 | salt: salt, 58 | personal: personalisation); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/encoding/base16_encoder.dart: -------------------------------------------------------------------------------- 1 | part of '../../encoding.dart'; 2 | 3 | class Base16Encoder implements Encoder { 4 | const Base16Encoder._singleton(); 5 | static const Base16Encoder instance = Base16Encoder._singleton(); 6 | 7 | static const _alphabet = '0123456789abcdef'; 8 | static const _hexMap = { 9 | '0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, // 4-5 10 | '4': 0x4, '5': 0x5, '6': 0x6, '7': 0x7, 11 | '8': 0x8, '9': 0x9, 'a': 0xa, 'b': 0xb, 12 | 'c': 0xc, 'd': 0xd, 'e': 0xe, 'f': 0xf, 13 | 'A': 0xa, 'B': 0xb, 'C': 0xc, 'D': 0xd, 14 | 'E': 0xe, 'F': 0xf, 15 | }; 16 | 17 | static List _decode(String hexString) { 18 | if (hexString.length % 2 != 0) { 19 | throw Exception( 20 | 'Invalid `length`. Expected even number got `${hexString.length}`'); 21 | } 22 | 23 | var startsWithHexStart = hexString.startsWith('0x'); 24 | if (startsWithHexStart && hexString.length == 2) { 25 | throw Exception('There is no any character in the hexadecimal string'); 26 | } 27 | 28 | final startIndex = startsWithHexStart ? 2 : 0; 29 | var result = List.filled((hexString.length - startIndex) ~/ 2, 0); 30 | 31 | for (var x = 0, i = startIndex; i < hexString.length; i += 2, x++) { 32 | final left = _hexMap[hexString[i]] ?? -1; 33 | final right = _hexMap[hexString[i + 1]] ?? -1; 34 | 35 | if (left < 0 || right < 0) { 36 | final invalidChar = left < 0 ? hexString[i] : hexString[i + 1]; 37 | throw Exception('The `$invalidChar` character is undefined in hex'); 38 | } 39 | 40 | result[x] = (left << 4) | right; 41 | } 42 | 43 | return result; 44 | } 45 | 46 | static String _encode(List hexArray, {bool withHexString = false}) { 47 | if (hexArray.isEmpty) { 48 | return ''; 49 | } 50 | 51 | var result = withHexString ? '0x' : ''; 52 | 53 | for (var element in hexArray) { 54 | result += _alphabet[(element & 0xff) >> 4] + _alphabet[element & 0x0f]; 55 | } 56 | 57 | return result; 58 | } 59 | 60 | @override 61 | String encode(List data) { 62 | return _encode(data); 63 | } 64 | 65 | @override 66 | Uint8List decode(String data) { 67 | return Uint8List.fromList(_decode(data)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/encoding/base32_encoder.dart: -------------------------------------------------------------------------------- 1 | part of '../../encoding.dart'; 2 | 3 | const _alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; 4 | final _alphabetMap = 5 | _alphabet.codeUnits.asMap().map((idx, value) => MapEntry(value, idx)); 6 | 7 | class Base32Encoder implements Encoder { 8 | const Base32Encoder._singleton(); 9 | static const Base32Encoder instance = Base32Encoder._singleton(); 10 | 11 | @override 12 | String encode(List data) { 13 | final result = 14 | _convertBits(data, 8, 5, true).fold('', (prev, item) { 15 | prev += _alphabet[item]; 16 | return prev; 17 | }); 18 | return result; 19 | } 20 | 21 | @override 22 | Uint8List decode(String data) { 23 | final result = _convertBits( 24 | data.codeUnits.fold([], (prev, item) { 25 | return prev..add(_alphabetMap[item]!); 26 | }), 27 | 5, 28 | 8, 29 | false); 30 | return Uint8List.fromList(result); 31 | } 32 | 33 | static List _convertBits(List data, int from, int to, bool pad) { 34 | var acc = 0; 35 | var bits = 0; 36 | var result = []; 37 | var maxv = (1 << to) - 1; 38 | 39 | for (var v in data) { 40 | if (v < 0 || (v >> from) != 0) { 41 | throw Exception('Bit conversion error - Invalid input value'); 42 | } 43 | acc = (acc << from) | v; 44 | bits += from; 45 | while (bits >= to) { 46 | bits -= to; 47 | result.add((acc >> bits) & maxv); 48 | } 49 | } 50 | 51 | if (pad) { 52 | if (bits > 0) { 53 | result.add((acc << (to - bits)) & maxv); 54 | } 55 | } else if (bits >= from) { 56 | throw Exception('Bit conversion error - Illegal zero padding'); 57 | } else if (((acc << (to - bits)) & maxv) != 0) { 58 | throw Exception('Bit conversion error - non zero'); 59 | } 60 | 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/encoding/bech32_encoder.dart: -------------------------------------------------------------------------------- 1 | part of '../../encoding.dart'; 2 | 3 | class Bech32Encoder implements Encoder { 4 | const Bech32Encoder({required this.hrp}); 5 | final String hrp; 6 | 7 | static const maxHrpLength = 83; 8 | static const checksumLength = 6; 9 | // 100 character long i.e. 100 * 8 / 5 10 | static const maxLength = maxHrpLength + 160 + checksumLength; 11 | 12 | @override 13 | String encode(List data) { 14 | var be = Base32Encoder._convertBits(data, 8, 5, true); 15 | return _Bech32Codec().encode(_Bech32(hrp, be), maxLength); 16 | } 17 | 18 | @override 19 | Uint8List decode(String data) { 20 | final be32 = _Bech32Codec().decode(data, maxLength); 21 | if (be32.hrp != hrp) { 22 | throw Exception('Invalid `hrp`. Expected $hrp got ${be32.hrp}'); 23 | } 24 | return Uint8List.fromList( 25 | Base32Encoder._convertBits(be32.data, 5, 8, false)); 26 | } 27 | 28 | static Uint8List decodeNoHrpCheck(String data, int maxLenght) { 29 | final be32 = _Bech32Codec().decode(data, maxLength); 30 | return Uint8List.fromList( 31 | Base32Encoder._convertBits(be32.data, 5, 8, false)); 32 | } 33 | } 34 | 35 | // ignore: slash_for_doc_comments 36 | /** 37 | * The below part is copied from `bech32` dart package under the MIT license for 38 | * `pre-release` null safety migration of this package. 39 | * 40 | * Copyright 2020 Harm Aarts 41 | * 42 | * Permission is hereby granted, free of charge, to any person obtaining a copy 43 | * of this software and associated documentation files (the "Software"), to 44 | * deal in the Software without restriction, including without limitation the 45 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 46 | * sell copies of the Software, and to permit persons to whom the Software is 47 | * furnished to do so, subject to the following conditions: 48 | * 49 | * The above copyright notice and this permission notice shall be included in 50 | * all copies or substantial portions of the Software. 51 | * 52 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 53 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 54 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 55 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 56 | * LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 57 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 58 | * IN THE SOFTWARE. 59 | */ 60 | class _Bech32 { 61 | _Bech32(this.hrp, this.data); 62 | 63 | final String hrp; 64 | final List data; 65 | } 66 | 67 | //const _Bech32Codec bech32 = _Bech32Codec(); 68 | 69 | class _Bech32Codec extends Codec<_Bech32, String> { 70 | const _Bech32Codec(); 71 | 72 | @override 73 | _Bech32Decoder get decoder => _Bech32Decoder(); 74 | @override 75 | _Bech32Encoder get encoder => _Bech32Encoder(); 76 | 77 | @override 78 | String encode(_Bech32 input, 79 | [int maxLength = Bech32Validations.maxInputLength]) { 80 | return _Bech32Encoder().convert(input, maxLength); 81 | } 82 | 83 | @override 84 | _Bech32 decode(String encoded, 85 | [int maxLength = Bech32Validations.maxInputLength]) { 86 | return _Bech32Decoder().convert(encoded, maxLength); 87 | } 88 | } 89 | 90 | // This class converts a Bech32 class instance to a String. 91 | class _Bech32Encoder extends Converter<_Bech32, String> with Bech32Validations { 92 | @override 93 | String convert(_Bech32 input, 94 | [int maxLength = Bech32Validations.maxInputLength]) { 95 | var hrp = input.hrp; 96 | var data = input.data; 97 | 98 | if (hrp.length + 99 | data.length + 100 | separator.length + 101 | Bech32Validations.checksumLength > 102 | maxLength) { 103 | throw TooLong( 104 | hrp.length + data.length + 1 + Bech32Validations.checksumLength); 105 | } 106 | 107 | if (hrp.isEmpty) { 108 | throw TooShortHrp(); 109 | } 110 | 111 | if (hasOutOfRangeHrpCharacters(hrp)) { 112 | throw OutOfRangeHrpCharacters(hrp); 113 | } 114 | 115 | if (isMixedCase(hrp)) { 116 | throw MixedCase(hrp); 117 | } 118 | 119 | hrp = hrp.toLowerCase(); 120 | 121 | var checksummed = data + _createChecksum(hrp, data); 122 | 123 | if (hasOutOfBoundsChars(checksummed)) { 124 | // TODO this could be more informative 125 | throw OutOfBoundChars(''); 126 | } 127 | 128 | return hrp + separator + checksummed.map((i) => charset[i]).join(); 129 | } 130 | } 131 | 132 | // This class converts a String to a Bech32 class instance. 133 | class _Bech32Decoder extends Converter with Bech32Validations { 134 | @override 135 | _Bech32 convert(String input, 136 | [int maxLength = Bech32Validations.maxInputLength]) { 137 | if (input.length > maxLength) { 138 | throw TooLong(input.length); 139 | } 140 | 141 | if (isMixedCase(input)) { 142 | throw MixedCase(input); 143 | } 144 | 145 | if (hasInvalidSeparator(input)) { 146 | throw InvalidSeparator(input.lastIndexOf(separator)); 147 | } 148 | 149 | var separatorPosition = input.lastIndexOf(separator); 150 | 151 | if (isChecksumTooShort(separatorPosition, input)) { 152 | throw TooShortChecksum(); 153 | } 154 | 155 | if (isHrpTooShort(separatorPosition)) { 156 | throw TooShortHrp(); 157 | } 158 | 159 | input = input.toLowerCase(); 160 | 161 | var hrp = input.substring(0, separatorPosition); 162 | var data = input.substring( 163 | separatorPosition + 1, input.length - Bech32Validations.checksumLength); 164 | var checksum = 165 | input.substring(input.length - Bech32Validations.checksumLength); 166 | 167 | if (hasOutOfRangeHrpCharacters(hrp)) { 168 | throw OutOfRangeHrpCharacters(hrp); 169 | } 170 | 171 | var dataBytes = data.split('').map((c) { 172 | return charset.indexOf(c); 173 | }).toList(); 174 | 175 | if (hasOutOfBoundsChars(dataBytes)) { 176 | throw OutOfBoundChars(data[dataBytes.indexOf(-1)]); 177 | } 178 | 179 | var checksumBytes = checksum.split('').map((c) { 180 | return charset.indexOf(c); 181 | }).toList(); 182 | 183 | if (hasOutOfBoundsChars(checksumBytes)) { 184 | throw OutOfBoundChars(checksum[checksumBytes.indexOf(-1)]); 185 | } 186 | 187 | if (isInvalidChecksum(hrp, dataBytes, checksumBytes)) { 188 | throw InvalidChecksum(); 189 | } 190 | 191 | return _Bech32(hrp, dataBytes); 192 | } 193 | } 194 | 195 | /// Generic validations for Bech32 standard. 196 | mixin Bech32Validations { 197 | static const int maxInputLength = 90; 198 | static const checksumLength = 6; 199 | 200 | // From the entire input subtract the hrp length, the separator and the required checksum length 201 | bool isChecksumTooShort(int separatorPosition, String input) { 202 | return (input.length - separatorPosition - 1 - checksumLength) < 0; 203 | } 204 | 205 | bool hasOutOfBoundsChars(List data) { 206 | return data.any((c) => c == -1); 207 | } 208 | 209 | bool isHrpTooShort(int separatorPosition) { 210 | return separatorPosition == 0; 211 | } 212 | 213 | bool isInvalidChecksum(String hrp, List data, List checksum) { 214 | return !_verifyChecksum(hrp, data + checksum); 215 | } 216 | 217 | bool isMixedCase(String input) { 218 | return input.toLowerCase() != input && input.toUpperCase() != input; 219 | } 220 | 221 | bool hasInvalidSeparator(String bech32) { 222 | return bech32.lastIndexOf(separator) == -1; 223 | } 224 | 225 | bool hasOutOfRangeHrpCharacters(String hrp) { 226 | return hrp.codeUnits.any((c) => c < 33 || c > 126); 227 | } 228 | } 229 | 230 | const String separator = '1'; 231 | 232 | const List charset = [ 233 | 'q', 'p', 'z', 'r', // Bech32 charset 234 | 'y', '9', 'x', '8', 235 | 'g', 'f', '2', 't', 236 | 'v', 'd', 'w', '0', 237 | 's', '3', 'j', 'n', 238 | '5', '4', 'k', 'h', 239 | 'c', 'e', '6', 'm', 240 | 'u', 'a', '7', 'l', 241 | ]; 242 | 243 | const List generator = [ 244 | 0x3b6a57b2, 245 | 0x26508e6d, 246 | 0x1ea119fa, 247 | 0x3d4233dd, 248 | 0x2a1462b3, 249 | ]; 250 | 251 | int _polymod(List values) { 252 | var chk = 1; 253 | for (var v in values) { 254 | var top = chk >> 25; 255 | chk = (chk & 0x1ffffff) << 5 ^ v; 256 | for (var i = 0; i < generator.length; i++) { 257 | if ((top >> i) & 1 == 1) { 258 | chk ^= generator[i]; 259 | } 260 | } 261 | } 262 | 263 | return chk; 264 | } 265 | 266 | List _hrpExpand(String hrp) { 267 | var result = hrp.codeUnits.map((c) => c >> 5).toList(); 268 | result = result + [0]; 269 | 270 | result = result + hrp.codeUnits.map((c) => c & 31).toList(); 271 | 272 | return result; 273 | } 274 | 275 | bool _verifyChecksum(String hrp, List dataIncludingChecksum) { 276 | return _polymod(_hrpExpand(hrp) + dataIncludingChecksum) == 1; 277 | } 278 | 279 | List _createChecksum(String hrp, List data) { 280 | var values = _hrpExpand(hrp) + data + [0, 0, 0, 0, 0, 0]; 281 | var polymod = _polymod(values) ^ 1; 282 | 283 | var result = [0, 0, 0, 0, 0, 0]; 284 | 285 | for (var i = 0; i < result.length; i++) { 286 | result[i] = (polymod >> (5 * (5 - i))) & 31; 287 | } 288 | return result; 289 | } 290 | 291 | class TooShortHrp implements Exception { 292 | @override 293 | String toString() => 'The human readable part should have non zero length.'; 294 | } 295 | 296 | class TooLong implements Exception { 297 | TooLong(this.length); 298 | 299 | final int length; 300 | 301 | @override 302 | String toString() => 'The bech32 string is too long: $length (>90)'; 303 | } 304 | 305 | class OutOfRangeHrpCharacters implements Exception { 306 | OutOfRangeHrpCharacters(this.hpr); 307 | 308 | final String hpr; 309 | 310 | @override 311 | String toString() => 312 | 'The human readable part contains invalid characters: $hpr'; 313 | } 314 | 315 | class MixedCase implements Exception { 316 | MixedCase(this.hpr); 317 | 318 | final String hpr; 319 | 320 | @override 321 | String toString() => 322 | 'The human readable part is mixed case, should either be all lower or all upper case: $hpr'; 323 | } 324 | 325 | class OutOfBoundChars implements Exception { 326 | OutOfBoundChars(this.char); 327 | 328 | final String char; 329 | 330 | @override 331 | String toString() => 'A character is undefined in bech32: $char'; 332 | } 333 | 334 | class InvalidSeparator implements Exception { 335 | InvalidSeparator(this.pos); 336 | 337 | final int pos; 338 | 339 | @override 340 | String toString() => "separator '1' at invalid position: $pos"; 341 | } 342 | 343 | class InvalidAddress implements Exception { 344 | @override 345 | String toString() => ''; 346 | } 347 | 348 | class InvalidChecksum implements Exception { 349 | @override 350 | String toString() => 'Checksum verification failed'; 351 | } 352 | 353 | class TooShortChecksum implements Exception { 354 | @override 355 | String toString() => 'Checksum is shorter than 6 characters'; 356 | } 357 | 358 | class InvalidHrp implements Exception { 359 | @override 360 | String toString() => "Human readable part should be 'bc' or 'tb'."; 361 | } 362 | 363 | class InvalidProgramLength implements Exception { 364 | InvalidProgramLength(this.reason); 365 | 366 | final String reason; 367 | 368 | @override 369 | String toString() => 'Program length is invalid: $reason'; 370 | } 371 | 372 | class InvalidWitnessVersion implements Exception { 373 | InvalidWitnessVersion(this.version); 374 | 375 | final int version; 376 | 377 | @override 378 | String toString() => 'Witness version $version > 16'; 379 | } 380 | 381 | class InvalidPadding implements Exception { 382 | InvalidPadding(this.reason); 383 | 384 | final String reason; 385 | 386 | @override 387 | String toString() => 'Invalid padding: $reason'; 388 | } 389 | -------------------------------------------------------------------------------- /lib/src/key_derivation/pbkdf2.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | 3 | import 'dart:typed_data'; 4 | 5 | import 'package:pinenacl/tweetnacl.dart'; 6 | import 'package:pinenacl/src/utils/utils.dart'; 7 | 8 | /// 9 | /// PBKDF2 (RFC 2898) is a cryptographic key derivation function, which is 10 | /// resistant to rainbow table- and dictionary attacks. 11 | /// 12 | /// This is very simple implementation of the PBKDF2 that iteratively deriving 13 | /// hmac (currently only HMAC-SHA512) with a cryptographically secure `salt`. 14 | /// 15 | class PBKDF2 { 16 | // TODO: currently only HMAC-SHA-512 and HMAC-SHA-256 are implemented. 17 | static Uint8List hmac_sha512( 18 | Uint8List password, Uint8List salt, int count, int key_length) { 19 | var hasher = TweetNaClExt.crypto_auth_hmacsha512; 20 | return _deriveKey(hasher, 64, password, salt, count, key_length); 21 | } 22 | 23 | static Uint8List hmac_sha256( 24 | Uint8List password, Uint8List salt, int count, int key_length) { 25 | var hasher = TweetNaClExt.crypto_auth_hmacsha256; 26 | return _deriveKey(hasher, 32, password, salt, count, key_length); 27 | } 28 | 29 | static Uint8List _deriveKey(MacHasher hasher, int hash_length, 30 | Uint8List password, Uint8List salt, int count, int key_length) { 31 | if (count <= 0 || key_length < 1 || key_length > 0xffffffff) { 32 | throw Exception(); 33 | } 34 | 35 | final block_count = (key_length / hash_length).ceil(); 36 | 37 | final derived_key = Uint8List(key_length + hash_length); 38 | final U = Uint8List(hash_length); 39 | 40 | final idx = [0, 0, 0, 0]; 41 | 42 | for (var i = 1; i <= block_count; i++) { 43 | // `block` is encoded as 4 bytes big endian 44 | idx[0] = (i >> 24) & 0xff; 45 | idx[1] = (i >> 16) & 0xff; 46 | idx[2] = (i >> 8) & 0xff; 47 | idx[3] = i & 0xff; 48 | 49 | final message = Uint8List.fromList([...salt, ...idx]); 50 | 51 | hasher(U, message, password); 52 | 53 | final offset = (i - 1) * hash_length; 54 | 55 | PineNaClUtils.listCopy(U, hash_length, derived_key, offset); 56 | 57 | for (var j = 1; j < count; j++) { 58 | hasher(U, U, password); 59 | 60 | for (var k = 0; k < hash_length; k++) { 61 | derived_key[k + offset] ^= U[k]; 62 | } 63 | } 64 | } 65 | 66 | var result = derived_key.sublist(0, key_length); 67 | 68 | PineNaClUtils.listZero(U); 69 | PineNaClUtils.listZero(derived_key); 70 | 71 | return result; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/message_authentication/hmac.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/api.dart'; 2 | import 'package:pinenacl/tweetnacl.dart'; 3 | 4 | void main() { 5 | final key = List.generate(20, (index) => 0xb).toUint8List(); 6 | final data = Uint8List.fromList('Hi There'.codeUnits); 7 | final hex = Base16Encoder.instance; 8 | // Test case 1 fro https://www.rfc-editor.org/rfc/rfc4231.txt 9 | 10 | /*final hmac_sha_224 = 11 | hex.decode('896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22'); 12 | final hmac_sha_256 = hex.decode( 13 | 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'); 14 | final hmac_sha_384 = hex.decode( 15 | 'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6'); 16 | final hmac_sha_512 = hex.decode( 17 | '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854'); 18 | */ 19 | 20 | //final out224 = Uint8List(28); 21 | final out256 = ByteList(Uint8List(32)); 22 | //final out384 = Uint8List(48); 23 | final out512 = ByteList(Uint8List(64)); 24 | 25 | TweetNaClExt.crypto_auth_hmacsha512(out512.asTypedList, data, key); 26 | TweetNaClExt.crypto_auth_hmacsha256(out256.asTypedList, data, key); 27 | 28 | print(hex.encode(out512)); 29 | print(hex.encode(out256)); 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/signatures/ed25519.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/api.dart'; 2 | import 'package:pinenacl/api/signatures.dart'; 3 | import 'package:pinenacl/src/tweetnacl/tweetnacl.dart'; 4 | 5 | class Signature extends ByteList implements SignatureBase { 6 | Signature(Uint8List super.bytes) 7 | : super.withConstraint(constraintLength: signatureLength); 8 | static const signatureLength = TweetNaCl.signatureLength; 9 | } 10 | 11 | class VerifyKey extends AsymmetricPublicKey implements Verify { 12 | VerifyKey(super.bytes, {super.keyLength = keyLength}); 13 | VerifyKey.decode(String keyString, 14 | {Encoder coder = decoder, int keyLength = keyLength}) 15 | : this(coder.decode(keyString), keyLength: keyLength); 16 | 17 | static const keyLength = TweetNaCl.publicKeyLength; 18 | 19 | static const decoder = Bech32Encoder(hrp: 'ed25519_pk'); 20 | 21 | @override 22 | VerifyKey get publicKey => this; 23 | 24 | @override 25 | Encoder get encoder => decoder; 26 | 27 | @override 28 | bool verifySignedMessage({required EncryptionMessage signedMessage}) => 29 | verify( 30 | signature: signedMessage.signature, 31 | message: signedMessage.message.asTypedList); 32 | @override 33 | bool verify({required SignatureBase signature, required Uint8List message}) { 34 | if (signature.length != TweetNaCl.signatureLength) { 35 | throw Exception( 36 | 'Signature length (${signature.length}) is invalid, expected "${TweetNaCl.signatureLength}"'); 37 | } 38 | final newmessage = signature.asTypedList + message; 39 | 40 | if (newmessage.length < TweetNaCl.signatureLength) { 41 | throw Exception( 42 | 'Signature length (${newmessage.length}) is invalid, expected "${TweetNaCl.signatureLength}"'); 43 | } 44 | 45 | var m = Uint8List(newmessage.length); 46 | 47 | final result = TweetNaCl.crypto_sign_open(m, -1, 48 | Uint8List.fromList(newmessage), 0, newmessage.length, asTypedList); 49 | if (result != 0) { 50 | throw Exception( 51 | 'The message is forged or malformed or the signature is invalid'); 52 | } 53 | return true; 54 | } 55 | } 56 | 57 | /// 58 | /// SigningKey implements the Ed25519 deterministic signature scheme (EdDSA) 59 | /// using Curve25519, that provides a very fast `fixed-base` and `double-base` 60 | /// scalar multiplications, which faster on most platform than the 61 | /// `variable-base` algorithm of X25519, due to the fast and complete twisted 62 | /// Edwards addition law. 63 | /// 64 | /// Cannot extends `AsymmetricPrivateKey` as it would have to implement 65 | /// the final `publicKey`. 66 | /// 67 | class SigningKey extends AsymmetricPrivateKey with Suffix implements Sign { 68 | /// An Ed25519 signingKey is the private key for producing digital signatures 69 | /// using the Ed25519 algorithm. 70 | /// simply the concatenation of the seed and 71 | /// the generated public key from the `SHA512`-ed and `prone-to-buffer`-ed 72 | /// seed as a private key. 73 | /// 74 | /// seed (i.e. private key) is a random 32-byte value. 75 | SigningKey({required Uint8List seed}) : this.fromSeed(seed); 76 | 77 | SigningKey.fromValidBytes(super.secret, 78 | {super.keyLength = TweetNaCl.signingKeyLength}); 79 | 80 | SigningKey.fromSeed(Uint8List seed) 81 | : this.fromValidBytes(_seedToSecret(seed)); 82 | 83 | SigningKey.generate() 84 | : this.fromSeed(TweetNaCl.randombytes(TweetNaCl.seedSize)); 85 | 86 | SigningKey.decode(String keyString, [Encoder coder = decoder]) 87 | : this.fromValidBytes(coder.decode(keyString)); 88 | 89 | static const decoder = Bech32Encoder(hrp: 'ed25519_sk'); 90 | 91 | @override 92 | Encoder get encoder => decoder; 93 | 94 | static const seedSize = TweetNaCl.seedSize; 95 | 96 | @override 97 | int get prefixLength => seedSize; 98 | 99 | ByteList get seed => prefix; 100 | 101 | VerifyKey? _verifyKey; 102 | 103 | @override 104 | VerifyKey get verifyKey => _verifyKey ??= VerifyKey(suffix.asTypedList); 105 | 106 | @override 107 | AsymmetricPublicKey get publicKey => verifyKey; 108 | 109 | static Uint8List _seedToSecret(Uint8List seed) { 110 | if (seed.length != seedSize) { 111 | throw Exception('SigningKey must be created from a $seedSize byte seed'); 112 | } 113 | 114 | //if (seed is Uint8List) { 115 | // seed = seed.toList(); 116 | //} 117 | 118 | final priv = 119 | Uint8List.fromList(seed + Uint8List(TweetNaCl.publicKeyLength)); 120 | final pub = Uint8List(TweetNaCl.publicKeyLength); 121 | TweetNaCl.crypto_sign_keypair(pub, priv, Uint8List.fromList(seed)); 122 | 123 | return SigningKey.fromValidBytes(priv).asTypedList; 124 | } 125 | 126 | @override 127 | SignedMessage sign(Uint8List message) { 128 | // signed message 129 | var sm = Uint8List(message.length + TweetNaCl.signatureLength); 130 | final result = TweetNaCl.crypto_sign( 131 | sm, -1, Uint8List.fromList(message), 0, message.length, asTypedList); 132 | if (result != 0) { 133 | throw Exception('Signing the massage is failed'); 134 | } 135 | 136 | return SignedMessage.fromList(signedMessage: sm); 137 | } 138 | } 139 | 140 | class SignedMessage extends ByteList with Suffix implements EncryptionMessage { 141 | SignedMessage({required SignatureBase signature, required Uint8List message}) 142 | : super.withConstraint(signature + message, 143 | constraintLength: signatureLength); 144 | SignedMessage.fromList({required Uint8List signedMessage}) 145 | : super(signedMessage); 146 | 147 | @override 148 | int get prefixLength => signatureLength; 149 | 150 | static const signatureLength = TweetNaCl.signatureLength; 151 | 152 | @override 153 | SignatureBase get signature => Signature(prefix.asTypedList); 154 | 155 | @override 156 | ByteList get message => suffix; 157 | } 158 | -------------------------------------------------------------------------------- /lib/src/tweetnacl/poly1305.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | /// Port of Andrew Moon's Poly1305-donna-16. Public domain. 4 | /// https://github.com/floodyberry/poly1305-donna 5 | class Poly1305 { 6 | Poly1305(Uint8List key) { 7 | final t0 = key[0] | key[1] << 8, 8 | t1 = key[2] | key[3] << 8, 9 | t2 = key[4] | key[5] << 8, 10 | t3 = key[6] | key[7] << 8, 11 | t4 = key[8] | key[9] << 8, 12 | t5 = key[10] | key[11] << 8, 13 | t6 = key[12] | key[13] << 8, 14 | t7 = key[14] | key[15] << 8; 15 | 16 | _r[0] = t0 & 0x1fff; 17 | _r[1] = ((t0 >> 13) | (t1 << 3)) & 0x1fff; 18 | _r[2] = ((t1 >> 10) | (t2 << 6)) & 0x1f03; 19 | _r[3] = ((t2 >> 7) | (t3 << 9)) & 0x1fff; 20 | _r[4] = ((t3 >> 4) | (t4 << 12)) & 0x00ff; 21 | _r[5] = (t4 >> 1) & 0x1ffe; 22 | _r[6] = ((t4 >> 14) | (t5 << 2)) & 0x1fff; 23 | _r[7] = ((t5 >> 11) | (t6 << 5)) & 0x1f81; 24 | _r[8] = ((t6 >> 8) | (t7 << 8)) & 0x1fff; 25 | _r[9] = (t7 >> 5) & 0x007f; 26 | 27 | _pad[0] = key[16] | key[17] << 8; 28 | _pad[1] = key[18] | key[19] << 8; 29 | _pad[2] = key[20] | key[21] << 8; 30 | _pad[3] = key[22] | key[23] << 8; 31 | _pad[4] = key[24] | key[25] << 8; 32 | _pad[5] = key[26] | key[27] << 8; 33 | _pad[6] = key[28] | key[29] << 8; 34 | _pad[7] = key[30] | key[31] << 8; 35 | } 36 | 37 | final _buffer = Uint8List(16); 38 | final _r = Int32List(10); 39 | final _h = Int32List(10); 40 | final _pad = Int32List(8); 41 | int _leftover = 0; 42 | int _fin = 0; 43 | 44 | Poly1305 _blocks(Uint8List m, int mpos, int bytes) { 45 | final hibit = _fin != 0 ? 0 : (1 << 11); 46 | int t0, t1, t2, t3, t4, t5, t6, t7, c; 47 | int d0, d1, d2, d3, d4, d5, d6, d7, d8, d9; 48 | 49 | var h0 = _h[0], 50 | h1 = _h[1], 51 | h2 = _h[2], 52 | h3 = _h[3], 53 | h4 = _h[4], 54 | h5 = _h[5], 55 | h6 = _h[6], 56 | h7 = _h[7], 57 | h8 = _h[8], 58 | h9 = _h[9]; 59 | 60 | final r0 = _r[0], 61 | r1 = _r[1], 62 | r2 = _r[2], 63 | r3 = _r[3], 64 | r4 = _r[4], 65 | r5 = _r[5], 66 | r6 = _r[6], 67 | r7 = _r[7], 68 | r8 = _r[8], 69 | r9 = _r[9]; 70 | 71 | while (bytes >= 16) { 72 | t0 = m[mpos + 0] & 0xff | (m[mpos + 1] & 0xff) << 8; 73 | h0 += t0 & 0x1fff; 74 | t1 = m[mpos + 2] & 0xff | (m[mpos + 3] & 0xff) << 8; 75 | h1 += ((t0 >> 13) | (t1 << 3)) & 0x1fff; 76 | t2 = m[mpos + 4] & 0xff | (m[mpos + 5] & 0xff) << 8; 77 | h2 += ((t1 >> 10) | (t2 << 6)) & 0x1fff; 78 | t3 = m[mpos + 6] & 0xff | (m[mpos + 7] & 0xff) << 8; 79 | h3 += ((t2 >> 7) | (t3 << 9)) & 0x1fff; 80 | t4 = m[mpos + 8] & 0xff | (m[mpos + 9] & 0xff) << 8; 81 | h4 += ((t3 >> 4) | (t4 << 12)) & 0x1fff; 82 | h5 += (t4 >> 1) & 0x1fff; 83 | t5 = m[mpos + 10] & 0xff | (m[mpos + 11] & 0xff) << 8; 84 | h6 += ((t4 >> 14) | (t5 << 2)) & 0x1fff; 85 | t6 = m[mpos + 12] & 0xff | (m[mpos + 13] & 0xff) << 8; 86 | h7 += ((t5 >> 11) | (t6 << 5)) & 0x1fff; 87 | t7 = m[mpos + 14] & 0xff | (m[mpos + 15] & 0xff) << 8; 88 | h8 += ((t6 >> 8) | (t7 << 8)) & 0x1fff; 89 | h9 += (t7 >> 5) | hibit; 90 | 91 | c = 0; 92 | 93 | d0 = c; 94 | d0 += h0 * r0; 95 | d0 += h1 * 5 * r9; 96 | d0 += h2 * 5 * r8; 97 | d0 += h3 * 5 * r7; 98 | d0 += h4 * 5 * r6; 99 | c = d0 >> 13; 100 | d0 &= 0x1fff; 101 | d0 += h5 * 5 * r5; 102 | d0 += h6 * 5 * r4; 103 | d0 += h7 * 5 * r3; 104 | d0 += h8 * 5 * r2; 105 | d0 += h9 * 5 * r1; 106 | c += d0 >> 13; 107 | d0 &= 0x1fff; 108 | 109 | d1 = c; 110 | d1 += h0 * r1; 111 | d1 += h1 * r0; 112 | d1 += h2 * 5 * r9; 113 | d1 += h3 * 5 * r8; 114 | d1 += h4 * 5 * r7; 115 | c = d1 >> 13; 116 | d1 &= 0x1fff; 117 | d1 += h5 * 5 * r6; 118 | d1 += h6 * 5 * r5; 119 | d1 += h7 * 5 * r4; 120 | d1 += h8 * 5 * r3; 121 | d1 += h9 * 5 * r2; 122 | c += d1 >> 13; 123 | d1 &= 0x1fff; 124 | 125 | d2 = c; 126 | d2 += h0 * r2; 127 | d2 += h1 * r1; 128 | d2 += h2 * r0; 129 | d2 += h3 * 5 * r9; 130 | d2 += h4 * 5 * r8; 131 | c = d2 >> 13; 132 | d2 &= 0x1fff; 133 | d2 += h5 * 5 * r7; 134 | d2 += h6 * 5 * r6; 135 | d2 += h7 * 5 * r5; 136 | d2 += h8 * 5 * r4; 137 | d2 += h9 * 5 * r3; 138 | c += d2 >> 13; 139 | d2 &= 0x1fff; 140 | 141 | d3 = c; 142 | d3 += h0 * r3; 143 | d3 += h1 * r2; 144 | d3 += h2 * r1; 145 | d3 += h3 * r0; 146 | d3 += h4 * 5 * r9; 147 | c = d3 >> 13; 148 | d3 &= 0x1fff; 149 | d3 += h5 * 5 * r8; 150 | d3 += h6 * 5 * r7; 151 | d3 += h7 * 5 * r6; 152 | d3 += h8 * 5 * r5; 153 | d3 += h9 * 5 * r4; 154 | c += d3 >> 13; 155 | d3 &= 0x1fff; 156 | 157 | d4 = c; 158 | d4 += h0 * r4; 159 | d4 += h1 * r3; 160 | d4 += h2 * r2; 161 | d4 += h3 * r1; 162 | d4 += h4 * r0; 163 | c = d4 >> 13; 164 | d4 &= 0x1fff; 165 | d4 += h5 * 5 * r9; 166 | d4 += h6 * 5 * r8; 167 | d4 += h7 * 5 * r7; 168 | d4 += h8 * 5 * r6; 169 | d4 += h9 * 5 * r5; 170 | c += d4 >> 13; 171 | d4 &= 0x1fff; 172 | 173 | d5 = c; 174 | d5 += h0 * r5; 175 | d5 += h1 * r4; 176 | d5 += h2 * r3; 177 | d5 += h3 * r2; 178 | d5 += h4 * r1; 179 | c = d5 >> 13; 180 | d5 &= 0x1fff; 181 | d5 += h5 * r0; 182 | d5 += h6 * 5 * r9; 183 | d5 += h7 * 5 * r8; 184 | d5 += h8 * 5 * r7; 185 | d5 += h9 * 5 * r6; 186 | c += d5 >> 13; 187 | d5 &= 0x1fff; 188 | 189 | d6 = c; 190 | d6 += h0 * r6; 191 | d6 += h1 * r5; 192 | d6 += h2 * r4; 193 | d6 += h3 * r3; 194 | d6 += h4 * r2; 195 | c = d6 >> 13; 196 | d6 &= 0x1fff; 197 | d6 += h5 * r1; 198 | d6 += h6 * r0; 199 | d6 += h7 * 5 * r9; 200 | d6 += h8 * 5 * r8; 201 | d6 += h9 * 5 * r7; 202 | c += d6 >> 13; 203 | d6 &= 0x1fff; 204 | 205 | d7 = c; 206 | d7 += h0 * r7; 207 | d7 += h1 * r6; 208 | d7 += h2 * r5; 209 | d7 += h3 * r4; 210 | d7 += h4 * r3; 211 | c = d7 >> 13; 212 | d7 &= 0x1fff; 213 | d7 += h5 * r2; 214 | d7 += h6 * r1; 215 | d7 += h7 * r0; 216 | d7 += h8 * 5 * r9; 217 | d7 += h9 * 5 * r8; 218 | c += d7 >> 13; 219 | d7 &= 0x1fff; 220 | 221 | d8 = c; 222 | d8 += h0 * r8; 223 | d8 += h1 * r7; 224 | d8 += h2 * r6; 225 | d8 += h3 * r5; 226 | d8 += h4 * r4; 227 | c = d8 >> 13; 228 | d8 &= 0x1fff; 229 | d8 += h5 * r3; 230 | d8 += h6 * r2; 231 | d8 += h7 * r1; 232 | d8 += h8 * r0; 233 | d8 += h9 * 5 * r9; 234 | c += d8 >> 13; 235 | d8 &= 0x1fff; 236 | 237 | d9 = c; 238 | d9 += h0 * r9; 239 | d9 += h1 * r8; 240 | d9 += h2 * r7; 241 | d9 += h3 * r6; 242 | d9 += h4 * r5; 243 | c = d9 >> 13; 244 | d9 &= 0x1fff; 245 | d9 += h5 * r4; 246 | d9 += h6 * r3; 247 | d9 += h7 * r2; 248 | d9 += h8 * r1; 249 | d9 += h9 * r0; 250 | c += d9 >> 13; 251 | d9 &= 0x1fff; 252 | 253 | c = ((c << 2) + c) | 0; 254 | c = (c + d0) | 0; 255 | d0 = c & 0x1fff; 256 | c = c >> 13; 257 | d1 += c; 258 | 259 | h0 = d0; 260 | h1 = d1; 261 | h2 = d2; 262 | h3 = d3; 263 | h4 = d4; 264 | h5 = d5; 265 | h6 = d6; 266 | h7 = d7; 267 | h8 = d8; 268 | h9 = d9; 269 | 270 | mpos += 16; 271 | bytes -= 16; 272 | } 273 | _h[0] = h0; 274 | _h[1] = h1; 275 | _h[2] = h2; 276 | _h[3] = h3; 277 | _h[4] = h4; 278 | _h[5] = h5; 279 | _h[6] = h6; 280 | _h[7] = h7; 281 | _h[8] = h8; 282 | _h[9] = h9; 283 | 284 | return this; 285 | } 286 | 287 | Poly1305 finish(Uint8List mac, int macpos) { 288 | final g = Int32List(10); 289 | int i; 290 | int c, mask, f; 291 | 292 | if (_leftover != 0) { 293 | i = _leftover; 294 | _buffer[i++] = 1; 295 | for (; i < 16; i++) { 296 | _buffer[i] = 0; 297 | } 298 | _fin = 1; 299 | _blocks(_buffer, 0, 16); 300 | } 301 | 302 | c = _h[1] >> 13; 303 | _h[1] &= 0x1fff; 304 | for (i = 2; i < 10; i++) { 305 | _h[i] += c; 306 | c = _h[i] >> 13; 307 | _h[i] &= 0x1fff; 308 | } 309 | _h[0] += (c * 5); 310 | c = _h[0] >> 13; 311 | _h[0] &= 0x1fff; 312 | _h[1] += c; 313 | c = _h[1] >> 13; 314 | _h[1] &= 0x1fff; 315 | _h[2] += c; 316 | 317 | g[0] = _h[0] + 5; 318 | c = g[0] >> 13; 319 | g[0] &= 0x1fff; 320 | for (i = 1; i < 10; i++) { 321 | g[i] = _h[i] + c; 322 | c = g[i] >> 13; 323 | g[i] &= 0x1fff; 324 | } 325 | g[9] -= (1 << 13); 326 | g[9] &= 0xffff; 327 | 328 | /// BACKPORT from [tweetnacl-fast.js ](https://github.com/dchest/tweetnacl-js/releases/tag/v0.14.3) 329 | /// 330 | /// "The issue was not properly detecting if st->h was >= 2^130 - 5, 331 | /// coupled with [testing mistake] not catching the failure. 332 | /// The chance of the bug affecting anything in the real world is essentially zero luckily, 333 | /// but it's good to have it fixed." 334 | /// 335 | /// change mask = (g[9] >>> ((2 * 8) - 1)) - 1; to as 336 | mask = (c ^ 1) - 1; 337 | mask &= 0xffff; 338 | 339 | /// END OF BACKPORT 340 | 341 | for (i = 0; i < 10; i++) { 342 | g[i] &= mask; 343 | } 344 | mask = ~mask; 345 | for (i = 0; i < 10; i++) { 346 | _h[i] = (_h[i] & mask) | g[i]; 347 | } 348 | 349 | _h[0] = _h[0] | (_h[1] << 13) & 0xffff; 350 | _h[1] = (_h[1] >> 3) | (_h[2] << 10) & 0xffff; 351 | _h[2] = (_h[2] >> 6) | (_h[3] << 7) & 0xffff; 352 | _h[3] = (_h[3] >> 9) | (_h[4] << 4) & 0xffff; 353 | _h[4] = (_h[4] >> 12) | (_h[5] << 1) | (_h[6] << 14) & 0xffff; 354 | _h[5] = (_h[6] >> 2) | (_h[7] << 11) & 0xffff; 355 | _h[6] = (_h[7] >> 5) | (_h[8] << 8) & 0xffff; 356 | _h[7] = (_h[8] >> 8) | (_h[9] << 5) & 0xffff; 357 | 358 | f = _h[0] + _pad[0]; 359 | _h[0] = f & 0xffff; 360 | for (i = 1; i < 8; i++) { 361 | f = (((_h[i] + _pad[i]) | 0) + (f >> 16)) | 0; 362 | _h[i] = f & 0xffff; 363 | } 364 | 365 | mac[macpos + 0] = (_h[0] >> 0) & 0xff; 366 | mac[macpos + 1] = (_h[0] >> 8) & 0xff; 367 | mac[macpos + 2] = (_h[1] >> 0) & 0xff; 368 | mac[macpos + 3] = (_h[1] >> 8) & 0xff; 369 | mac[macpos + 4] = (_h[2] >> 0) & 0xff; 370 | mac[macpos + 5] = (_h[2] >> 8) & 0xff; 371 | mac[macpos + 6] = (_h[3] >> 0) & 0xff; 372 | mac[macpos + 7] = (_h[3] >> 8) & 0xff; 373 | mac[macpos + 8] = (_h[4] >> 0) & 0xff; 374 | mac[macpos + 9] = (_h[4] >> 8) & 0xff; 375 | mac[macpos + 10] = (_h[5] >> 0) & 0xff; 376 | mac[macpos + 11] = (_h[5] >> 8) & 0xff; 377 | mac[macpos + 12] = (_h[6] >> 0) & 0xff; 378 | mac[macpos + 13] = (_h[6] >> 8) & 0xff; 379 | mac[macpos + 14] = (_h[7] >> 0) & 0xff; 380 | mac[macpos + 15] = (_h[7] >> 8) & 0xff; 381 | 382 | return this; 383 | } 384 | 385 | Poly1305 update(Uint8List m, int mpos, int bytes) { 386 | int i, want; 387 | 388 | if (_leftover != 0) { 389 | want = (16 - _leftover); 390 | if (want > bytes) want = bytes; 391 | for (i = 0; i < want; i++) { 392 | _buffer[_leftover + i] = m[mpos + i]; 393 | } 394 | bytes -= want; 395 | mpos += want; 396 | _leftover += want; 397 | if (_leftover < 16) return this; 398 | _blocks(_buffer, 0, 16); 399 | _leftover = 0; 400 | } 401 | 402 | if (bytes >= 16) { 403 | want = bytes - (bytes % 16); 404 | _blocks(m, mpos, want); 405 | mpos += want; 406 | bytes -= want; 407 | } 408 | 409 | if (bytes != 0) { 410 | for (i = 0; i < bytes; i++) { 411 | _buffer[_leftover + i] = m[mpos + i]; 412 | } 413 | _leftover += bytes; 414 | } 415 | 416 | return this; 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /lib/src/tweetnacl/tweetnacl_ext.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | 3 | part of 'tweetnacl.dart'; 4 | 5 | typedef Hasher = void Function(Uint8List out, Uint8List m); 6 | typedef MacHasher = void Function(Uint8List out, Uint8List m, Uint8List k); 7 | 8 | /// 9 | /// TweetNaCl's extension class. 10 | /// Following the TweetNaCl convention and added some extras. 11 | /// Extension is just an eye-candy here as it uses only static methods. 12 | /// 13 | /// Implemented features: 14 | /// - HMAC-SHA512 and HMAC-SHA256 15 | /// - crypto_auth_hmacsha512, HMAC-SHA-512 16 | /// - crypto_auth_hmacsha256, HMAC-SHA-256 17 | /// - Hashing algorithm 18 | /// - SHA256 19 | /// - Utils 20 | /// - crypto_verify_64, verifying function for SHA-512 as an example 21 | /// - X25519 conversion utulities 22 | /// - crypto_sign_ed25519_sk_to_x25519_sk 23 | /// - crypto_sign_ed25519_pk_to_x25519_pk 24 | /// - Curve25519 low-level functions 25 | /// - crypto_scalar_base, for retrieving different type of public-keys e.g. `A = k * B`. 26 | /// - crypto_point_add, for adding two public keys' point together `A = y1 : y2`. 27 | /// 28 | extension TweetNaClExt on TweetNaCl { 29 | static int crypto_auth_hmacsha512(Uint8List out, Uint8List m, Uint8List k) { 30 | _crypto_auth(TweetNaCl.crypto_hash, 128, out, m, k); 31 | return 0; 32 | } 33 | 34 | static int crypto_auth_hmacsha256(Uint8List out, Uint8List m, Uint8List k) { 35 | _crypto_auth(crypto_hash_sha256, 64, out, m, k); 36 | return 0; 37 | } 38 | 39 | static int _crypto_verify_64( 40 | Uint8List x, final int xoff, Uint8List y, final int yoff) { 41 | return TweetNaCl._vn(x, xoff, y, yoff, 32); 42 | } 43 | 44 | static int crypto_verify_64(Uint8List x, Uint8List y) { 45 | return _crypto_verify_64(x, 0, y, 0); 46 | } 47 | 48 | static int crypto_scalar_base(Uint8List pk, Uint8List sk) { 49 | final p = List.generate(4, (_) => Int32List(16)); 50 | 51 | TweetNaCl._scalarbase(p, sk, 0); 52 | TweetNaCl._pack(pk, p); 53 | 54 | return 0; 55 | } 56 | 57 | /// Converts Ed25519 private/signing key to Curve25519 private key. 58 | /// It's just simply the SHA512 and prone-to-buffered seed. 59 | static int crypto_sign_ed25519_sk_to_x25519_sk( 60 | Uint8List x25519_sk, Uint8List ed25519_sk) { 61 | final h = Uint8List(64); 62 | 63 | TweetNaCl._crypto_hash_off(h, ed25519_sk, 0, 32); 64 | 65 | h[0] &= 248; 66 | h[31] &= 127; 67 | h[31] |= 64; 68 | 69 | for (var i = 0; i < 32; i++) { 70 | x25519_sk[i] = h[i]; 71 | } 72 | 73 | for (var i = 0; i < 64; i++) { 74 | h[i] = 0; 75 | } 76 | return 0; 77 | } 78 | 79 | /// Converts Ed25519 public/verifying key to Curve25519 public key. 80 | /// Xmont = (1 + Yed)/(1 - Yed) mod p 81 | static int crypto_sign_ed25519_pk_to_x25519_pk( 82 | Uint8List x25519_pk, Uint8List ed25519_pk) { 83 | final z = Uint8List(32); 84 | final q = List.generate(4, (_) => Int32List(16)); 85 | final a = Int32List(16); 86 | final b = Int32List(16); 87 | 88 | if (TweetNaCl._unpackneg(q, ed25519_pk) != 0) return -1; 89 | 90 | final y = q[1]; 91 | 92 | // b = 1 + Yed 93 | TweetNaCl._A(a, TweetNaCl._gf1, y); 94 | // b = 1 - Yed 95 | TweetNaCl._Z(b, TweetNaCl._gf1, y); 96 | // b = inv(b) 97 | TweetNaCl._inv25519(b, 0, b, 0); 98 | // a = a * inv(b) i.e. a / b 99 | TweetNaCl._M(a, a, b); 100 | TweetNaCl._pack25519(z, a, 0); 101 | 102 | for (var i = 0; i < 32; i++) { 103 | x25519_pk[i] = z[i]; 104 | } 105 | 106 | return 0; 107 | } 108 | 109 | static int crypto_point_add(Uint8List out, Uint8List p1, Uint8List p2) { 110 | final p = 111 | List.generate(4, (_) => Int32List(16), growable: false); 112 | 113 | final q = 114 | List.generate(4, (_) => Int32List(16), growable: false); 115 | 116 | if (TweetNaCl._unpackneg(p, p1) != 0) return -1; 117 | if (TweetNaCl._unpackneg(q, p2) != 0) return -1; 118 | 119 | TweetNaCl._add(p, q); 120 | TweetNaCl._pack(out, p); 121 | 122 | out[31] ^= 0x80; 123 | 124 | return 0; 125 | } 126 | 127 | /// crypto_auth_ 128 | /// https://csrc.nist.gov/csrc/media/publications/fips/198/1/final/documents/fips-198-1_final.pdf 129 | /// HMAC-SHA-256 and HMAC-SHA-512 implementation 130 | /// 131 | /// https://tools.ietf.org/html/rfc2104 132 | /// 133 | /// HMACs uses shared key which may lead to non-repudiation. If either sender or receiver’s 134 | /// key is compromised then it will be easy for attackers to create unauthorized messages. 135 | /// 136 | /// `ipad` Inner pad; the byte x‘36’ repeated B times. 137 | static const _ipad = [ 138 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, // 16*8 = 128 139 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 140 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 141 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 142 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 143 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 144 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 145 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 146 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 147 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 148 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 149 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 150 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 151 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 152 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 153 | 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36 154 | ]; 155 | 156 | /// `opad `Outer pad; the byte x‘5c’ repeated B times. 157 | static const _opad = [ 158 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, // 16*8 = 128 159 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 160 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 161 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 162 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 163 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 164 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 165 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 166 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 167 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 168 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 169 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 170 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 171 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 172 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 173 | 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c 174 | ]; 175 | 176 | static void _crypto_auth(Hasher hasher, int blockSize, Uint8List out, 177 | Uint8List message, Uint8List key) { 178 | final k0 = Uint8List(blockSize); 179 | final k0i = Uint8List.fromList([...Uint8List(blockSize), ...message]); 180 | final k0o = Uint8List(blockSize); 181 | 182 | if (key.length <= blockSize) { 183 | PineNaClUtils.listCopy(key, key.length, k0); 184 | } else { 185 | hasher(k0, key); 186 | } 187 | 188 | _xor(k0i, k0, blockSize, _ipad); 189 | _xor(k0o, k0, blockSize, _opad); 190 | 191 | hasher(out, k0i); 192 | hasher(out, Uint8List.fromList([...k0o, ...out])); 193 | 194 | // For safety clear the key's data 195 | // Check Dart's GC what does it do /w local variables. 196 | PineNaClUtils.listZero(k0); 197 | PineNaClUtils.listZero(k0o, blockSize); 198 | PineNaClUtils.listZero(k0i, blockSize); 199 | } 200 | 201 | static void _xor(List out, List a, int l, List b) { 202 | for (var i = 0; i < l; i++) { 203 | out[i] = a[i] ^ b[i]; 204 | } 205 | } 206 | 207 | /// 208 | /// SHA-256 Implementation 209 | /// 210 | // ignore: constant_identifier_names 211 | static const _K = [ 212 | // 4x16 213 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 214 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 215 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 216 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 217 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 218 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 219 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 220 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 221 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 222 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 223 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 224 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 225 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 226 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 227 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 228 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, 229 | ]; 230 | 231 | static int _rotr32(int x, int n) => _shr32(x, n) | x << (32 - n); 232 | static int _shr32(int x, int n) => (x & 0xffffffff) >> n; 233 | static int _ch32(int x, int y, int z) => (x & y) ^ (~x & z); 234 | static int _maj32(int x, int y, int z) => (x & y) ^ (x & z) ^ (y & z); 235 | static int _sigma0_32(int x) => 236 | _rotr32(x, 2) ^ _rotr32(x, 13) ^ _rotr32(x, 22); 237 | static int _sigma1_32(int x) => 238 | _rotr32(x, 6) ^ _rotr32(x, 11) ^ _rotr32(x, 25); 239 | static int _gamma0_32(int x) => _rotr32(x, 7) ^ _rotr32(x, 18) ^ _shr32(x, 3); 240 | static int _gamma1_32(int x) => 241 | _rotr32(x, 17) ^ _rotr32(x, 19) ^ _shr32(x, 10); 242 | 243 | static Uint8List crypto_hash_sha256(Uint8List out, Uint8List m) { 244 | return _crypto_hash_sha256(out, m, m.length); 245 | } 246 | 247 | static Uint8List _crypto_hash_sha256(Uint8List out, Uint8List m, int l) { 248 | /// It assumes at least 32-byte long sequence. 249 | if (out.length < 32 && out.length % 32 != 0) { 250 | throw Exception('Invalid block for the message to digest.'); 251 | } 252 | 253 | final w = Uint32List(64); 254 | int a, b, c, d, e, f, g, h, T1, T2; 255 | 256 | final hh = Uint32List.fromList([ 257 | 0x6a09e667, 258 | 0xbb67ae85, 259 | 0x3c6ef372, 260 | 0xa54ff53a, 261 | 0x510e527f, 262 | 0x9b05688c, 263 | 0x1f83d9ab, 264 | 0x5be0cd19 265 | ]); 266 | 267 | final paddedLen = ((l + 8 >> 6) << 4) + 16; 268 | final padded = Uint32List(paddedLen); 269 | 270 | final bitLength = l << 3; 271 | final dataLength = bitLength >> 5; 272 | 273 | for (var i = 0; i < bitLength; i += 8) { 274 | padded[i >> 5] |= (m[i ~/ 8]) << (24 - i % 32); 275 | } 276 | 277 | padded[dataLength] |= 0x80 << (24 - bitLength % 32); 278 | padded[paddedLen - 1] = bitLength; 279 | 280 | // for Each 512-bit chunk 281 | for (var i = 0; i < padded.length; i += 16) { 282 | a = hh[0]; 283 | b = hh[1]; 284 | c = hh[2]; 285 | d = hh[3]; 286 | e = hh[4]; 287 | f = hh[5]; 288 | g = hh[6]; 289 | h = hh[7]; 290 | 291 | for (var j = 0; j < 64; j++) { 292 | if (j < 16) { 293 | w[j] = padded[j + i]; 294 | } else { 295 | w[j] = _gamma1_32(w[j - 2]) + 296 | w[j - 7] + 297 | _gamma0_32(w[j - 15]) + 298 | w[j - 16]; 299 | } 300 | 301 | T1 = (_sigma1_32(e) + _ch32(e, f, g) + h + _K[j] + w[j]); 302 | // Leave this `& 0xfffffffff` is it's extremely decrease performance 303 | // if it's removed. 304 | T2 = (_sigma0_32(a) + _maj32(a, b, c)) & 0xffffffff; 305 | 306 | h = g; 307 | g = f; 308 | f = e; 309 | e = d + T1; 310 | d = c; 311 | c = b; 312 | b = a; 313 | a = T1 + T2; 314 | } 315 | 316 | hh[0] = a + hh[0]; 317 | hh[1] = b + hh[1]; 318 | hh[2] = c + hh[2]; 319 | hh[3] = d + hh[3]; 320 | hh[4] = e + hh[4]; 321 | hh[5] = f + hh[5]; 322 | hh[6] = g + hh[6]; 323 | hh[7] = h + hh[7]; 324 | } 325 | 326 | for (var i = 0; i < hh.length; i++) { 327 | out[4 * i + 0] = hh[i] >> 24; 328 | out[4 * i + 1] = hh[i] >> 16; 329 | out[4 * i + 2] = hh[i] >> 8; 330 | out[4 * i + 3] = hh[i]; 331 | } 332 | 333 | return out; 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /lib/src/utils/utils.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.utils; 2 | 3 | import 'dart:typed_data'; 4 | 5 | import 'package:pinenacl/src/tweetnacl/tweetnacl.dart'; 6 | 7 | /// Utils class, provides basic list functions. 8 | class PineNaClUtils { 9 | static Uint8List randombytes(int len) { 10 | return TweetNaCl.randombytes(len); 11 | } 12 | 13 | static void listCopy(List from, int fromLength, List to, [int toOffset = 0]) { 14 | for (var i = 0; i < fromLength; i++) { 15 | to[i + toOffset] = from[i]; 16 | } 17 | } 18 | 19 | static void listZero(List list, [int l = -1]) { 20 | final length = l >= 0 ? l : list.length; 21 | for (var i = 0; i < length; i++) { 22 | list[i] = 0; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/tweetnacl.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.tweetnacl; 2 | 3 | export 'package:pinenacl/src/tweetnacl/tweetnacl.dart'; 4 | -------------------------------------------------------------------------------- /lib/x25519.dart: -------------------------------------------------------------------------------- 1 | library pinenacl.ecdh; 2 | 3 | export 'dart:typed_data'; 4 | //export 'package:pinenacl/src/utils/utils.dart'; 5 | export 'package:pinenacl/api.dart'; 6 | export 'package:pinenacl/src/authenticated_encryption/public.dart'; 7 | export 'package:pinenacl/src/authenticated_encryption/secret.dart'; 8 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pinenacl 2 | description: The Dart implementation of the PyNaCl APIs with the TweetNaCl cryptographic library 3 | version: 0.6.0 4 | homepage: https://github.com/ilap/pinenacl-dart 5 | 6 | environment: 7 | sdk: '>=3.4.0 <4.0.0' 8 | 9 | dev_dependencies: 10 | test: ^1.25.5 11 | benchmark_harness: ^2.2.0 12 | lints: ^4.0.0 13 | -------------------------------------------------------------------------------- /scripts/doit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for i in $(ls -1 ../example/); 4 | do 5 | B=$(basename $i .dart) 6 | O="$B.js" 7 | dart2js ../example/$i -o "$B.js" 8 | sed -i '/^(function dartProgram() {/r ../tool/dart_crypto.js' "$B.js" 9 | done 10 | for i in $(ls -1 ../benchmark/); 11 | do 12 | B=$(basename $i .dart) 13 | O="$B.js" 14 | dart2js ../benchmark/$i -o "$B.js" 15 | sed -i '/^(function dartProgram() {/r ../tool/dart_crypto.js' "$B.js" 16 | done 17 | 18 | -------------------------------------------------------------------------------- /test/all_tests.dart: -------------------------------------------------------------------------------- 1 | import 'base_test.dart' as base_test; 2 | import 'tweetnacl_validation_test.dart' as tweetnacl_validation_test; 3 | import 'box_test.dart' as box_test; 4 | import 'secretbox_test.dart' as secretbox_test; 5 | import 'diffie_hellman_test.dart' as dh_test; 6 | import 'hashing_test.dart' as hashing_test; 7 | import 'signing_test.dart' as signing_test; 8 | import 'pbkdf_test.dart' as pbkdf_test; 9 | import 'hmac_test.dart' as hmac_test; 10 | 11 | void main() { 12 | base_test.main(); 13 | tweetnacl_validation_test.main(); 14 | box_test.main(); 15 | secretbox_test.main(); 16 | hashing_test.main(); 17 | signing_test.main(); 18 | dh_test.main(); 19 | pbkdf_test.main(); 20 | hmac_test.main(); 21 | } 22 | -------------------------------------------------------------------------------- /test/base_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:pinenacl/api.dart'; 4 | 5 | void main() { 6 | group('Base Classes Test', () { 7 | group('ByteList', () { 8 | test('Immutability', () { 9 | final l32 = List.generate(32, (i) => 0xb).toUint8List(); 10 | final byteList = ByteList(l32); 11 | final sk = PrivateKey(l32); 12 | 13 | expect(() { 14 | sk[0] += 1; 15 | }, throwsUnsupportedError); 16 | expect(() { 17 | sk.sublist(0, 5)[0] += 1; 18 | }, throwsUnsupportedError); 19 | 20 | expect(() { 21 | byteList[0] += 1; 22 | }, throwsUnsupportedError); 23 | expect(() { 24 | byteList.sublist(0, 5)[0] += 1; 25 | }, throwsUnsupportedError); 26 | }); 27 | 28 | test('Growing', () { 29 | final l32 = List.generate(32, (i) => 0xb).toUint8List(); 30 | final byteList = ByteList(l32); 31 | final sk = PrivateKey(l32); 32 | 33 | expect(() { 34 | sk.length += 1; 35 | }, throwsUnsupportedError); 36 | expect(() { 37 | byteList.length += 1; 38 | }, throwsUnsupportedError); 39 | }); 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /test/box_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:pinenacl/api.dart'; 4 | import 'package:pinenacl/tweetnacl.dart' show TweetNaClExt; 5 | import 'package:pinenacl/src/authenticated_encryption/public.dart'; 6 | import 'package:pinenacl/src/signatures/ed25519.dart'; 7 | 8 | const _vectors = { 9 | 'privalice': 10 | '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a', 11 | 'pubalice': 12 | '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a', 13 | 'privbob': '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb', 14 | 'pubbob': 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f', 15 | 'nonce': '69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37', 16 | 'plaintext': 17 | 'be075fc53c81f2d5cf141316ebeb0c7b5228c52a4c62cbd44b66849b64244ffce5e' 18 | 'cbaaf33bd751a1ac728d45e6c61296cdc3c01233561f41db66cce314adb310e3be8' 19 | '250c46f06dceea3a7fa1348057e2f6556ad6b1318a024a838f21af1fde048977eb4' 20 | '8f59ffd4924ca1c60902e52f0a089bc76897040e082f937763848645e0705', 21 | 'ciphertext': 22 | 'f3ffc7703f9400e52a7dfb4b3d3305d98e993b9f48681273c29650ba32fc76ce483' 23 | '32ea7164d96a4476fb8c531a1186ac0dfc17c98dce87b4da7f011ec48c97271d2c2' 24 | '0f9b928fe2270d6fb863d51738b48eeee314a7cc8ab932164548e526ae902243685' 25 | '17acfeabd6bb3732bc0e9da99832b61ca01b6de56244a9e88d5f9b37973f622a43d' 26 | '14a6599b1f654cb45a74e355a5' 27 | }; 28 | 29 | void main() { 30 | const hex = Base16Encoder.instance; 31 | group('Public Key Encryption', () { 32 | final pub = PublicKey.decode( 33 | 'ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798', 34 | hex); 35 | final priv = PrivateKey.decode( 36 | '5c2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798', 37 | hex); 38 | 39 | test('Test X25519 PrivateKey generation from seed', () { 40 | const seed = 41 | '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a'; 42 | // Secret/Private key is simply the first 32 bytes of the SHA512 hash of the seed 43 | const secret = 44 | 'accd44eb8e93319c0570bc11005c0e0189d34ff02f6c17773411ad191293c98f'; 45 | const public = 46 | 'ed7749b4d989f6957f3bfde6c56767e988e21c9f8784d91d610011cd553f9b06'; 47 | 48 | final prv = PrivateKey.fromSeed(hex.decode(seed)); 49 | final pub = PublicKey(hex.decode(public)); 50 | final prv1 = PrivateKey(hex.decode(secret)); 51 | assert(pub == prv1.publicKey); 52 | assert(pub == prv.publicKey); 53 | assert(prv == prv1); 54 | }); 55 | 56 | test('Test the conversion of an Ed25519 keypair to X25519 keypair', () { 57 | // Libsodium test vector 58 | const seed = 59 | '421151a459faeade3d247115f94aedae42318124095afabe4d1451a559faedee'; 60 | // ignore_for_file: constant_identifier_names 61 | const ed25519_sk = 62 | '421151a459faeade3d247115f94aedae42318124095afabe4d1451a559faedeeb5076a8474a832daee4dd5b4040983b6623b5f344aca57d4d6ee4baf3f259e6e'; 63 | const ed25519_pk = 64 | 'b5076a8474a832daee4dd5b4040983b6623b5f344aca57d4d6ee4baf3f259e6e'; 65 | const x25519_sk = 66 | '8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166'; 67 | const x25519_pk = 68 | 'f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50'; 69 | 70 | final ed25519Priv = SigningKey.fromSeed(hex.decode(seed)); 71 | final ed25519Pub = VerifyKey(hex.decode(ed25519_pk)); 72 | final x25519Pub = Uint8List(32); 73 | 74 | TweetNaClExt.crypto_sign_ed25519_pk_to_x25519_pk( 75 | x25519Pub, hex.decode(ed25519_pk)); 76 | 77 | final x25519PubHex = hex.encode(x25519Pub); 78 | assert(x25519PubHex == x25519_pk); 79 | 80 | final x25519Pub1 = Uint8List(32); 81 | TweetNaClExt.crypto_sign_ed25519_pk_to_x25519_pk( 82 | x25519Pub1, ed25519Pub.asTypedList); 83 | 84 | final x25519Prv = Uint8List(32); 85 | TweetNaClExt.crypto_sign_ed25519_sk_to_x25519_sk( 86 | x25519Prv, ed25519Priv.asTypedList); 87 | 88 | final ed25519Prv2 = PrivateKey(x25519Prv); 89 | final ed25519Pub2 = PublicKey(x25519Pub); 90 | 91 | final ed25519Prv3 = PrivateKey(hex.decode(x25519_sk)); 92 | final ed25519Pub3 = PublicKey(hex.decode(x25519_pk)); 93 | 94 | // The x25519_sk must be the same as with the converted ed25519_sk 95 | assert(ed25519Prv2 == ed25519Prv3); 96 | // The x25519_pk must be the same as with the converted ed25519_pk 97 | assert(ed25519Pub2 == ed25519Pub3); 98 | assert(ed25519Pub2 == ed25519Prv3.publicKey); 99 | assert(ed25519_sk == ed25519Priv.encode(Base16Encoder.instance)); 100 | }); 101 | 102 | test( 103 | 'Test libsodium the conversion of an Ed25519 keypair to X25519 keypair', 104 | () { 105 | // TODO: gather and test testvectors from diff sources. 106 | //const curve25519Pk = 107 | // 'f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50'; 108 | //const curve25519Sk = 109 | // '8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166'; 110 | }); 111 | 112 | test('Test Box decode', () { 113 | final b1 = Box(myPrivateKey: priv, theirPublicKey: pub); 114 | final b2 = Box.decode(b1.sharedKey.asTypedList); 115 | assert(b1.sharedKey == b2.sharedKey); 116 | }); 117 | 118 | test('Test Box class', () { 119 | final pub = PublicKey.decode( 120 | 'ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798', 121 | hex); 122 | final priv = PrivateKey.decode( 123 | '5c2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798', 124 | hex); 125 | final b = Box(myPrivateKey: priv, theirPublicKey: pub); 126 | 127 | assert(b == b.sharedKey); 128 | assert(b.length == b.sharedKey.length); 129 | }); 130 | 131 | test('Test Box encryption', () { 132 | final pubalice = PublicKey.decode(_vectors['pubalice']!, hex); 133 | final privbob = PrivateKey.decode(_vectors['privbob']!, hex); 134 | 135 | final box = Box(myPrivateKey: privbob, theirPublicKey: pubalice); 136 | 137 | final nonce = _vectors['nonce']!; 138 | final ciphertext = _vectors['ciphertext']!; 139 | final plaintext = _vectors['plaintext']!; 140 | 141 | final encrypted = 142 | box.encrypt(hex.decode(plaintext), nonce: hex.decode(nonce)); 143 | 144 | final expected = hex.decode(nonce + ciphertext); 145 | 146 | assert(hex.encode(encrypted) == hex.encode(expected)); 147 | assert(hex.encode(encrypted.nonce) == nonce); 148 | assert(hex.encode(encrypted.cipherText) == ciphertext); 149 | }); 150 | 151 | test('Test Box decryption', () { 152 | final privAlice = PrivateKey.decode(_vectors['privalice']!, hex); 153 | final pubBob = PublicKey.decode(_vectors['pubbob']!, hex); 154 | final nonce = _vectors['nonce']!; 155 | final plaintext = _vectors['plaintext']!; 156 | final ciphertext = _vectors['ciphertext']!; 157 | 158 | final box = Box(myPrivateKey: privAlice, theirPublicKey: pubBob); 159 | 160 | final decrypted = box.decrypt(ByteList(hex.decode(ciphertext)), 161 | nonce: hex.decode(nonce)); 162 | 163 | assert(hex.encode(ByteList(decrypted)) == plaintext); 164 | }); 165 | 166 | test('Test Box encryption and decryption combined', () { 167 | final pubalice = PublicKey.decode(_vectors['pubalice']!, hex); 168 | final privbob = PrivateKey.decode(_vectors['privbob']!, hex); 169 | 170 | final box = Box(myPrivateKey: privbob, theirPublicKey: pubalice); 171 | 172 | final nonce = _vectors['nonce']!; 173 | final plaintext = _vectors['plaintext']!; 174 | 175 | final encrypted = 176 | box.encrypt(hex.decode(plaintext), nonce: hex.decode(nonce)); 177 | 178 | // NOTE: nonce is retrieved from the EncryptedMessage class 179 | final decrypted = box.decrypt(encrypted); 180 | 181 | assert(hex.encode(decrypted) == plaintext); 182 | }); 183 | 184 | test('Test Nonce encryption and decryption combined', () { 185 | final privalice = PrivateKey.decode(_vectors['privalice']!, hex); 186 | final pubbob = PublicKey.decode(_vectors['pubbob']!, hex); 187 | 188 | final box = Box(myPrivateKey: privalice, theirPublicKey: pubbob); 189 | 190 | final plaintext = _vectors['plaintext']!; 191 | final nonce_0 = box.encrypt(hex.decode(plaintext)).nonce; 192 | 193 | final nonce_1 = box.encrypt(hex.decode(plaintext)).nonce; 194 | 195 | assert(nonce_0 != nonce_1); 196 | }); 197 | 198 | test('Test Wrong AsymmetricKey types', () { 199 | final priv = PrivateKey.generate(); 200 | final v31 = Uint8List(31); 201 | final v32 = Uint8List(32); 202 | final k32 = PublicKey(v32); 203 | 204 | // TODO: Generalize and implement proper Error handling by implementing proper exception classes. 205 | // expect(() => PrivateKey(priv.publicKey), throwsException); 206 | // expect(() => PrivateKey, returnsNormally); 207 | // expect(() => PrivateKey(), throwsA(TypeMatcher())); 208 | // expect(() => PrivateKey(priv.publicKey), throwsA(predicate((e) => e is Error))) 209 | // expect(() => PrivateKey(), throwsA(predicate((e) => e is ArgumentError && e.message == 'Error'))); 210 | // expect(() => PrivateKey(), throwsA(allOf(isArgumentError, predicate((e) => e.message == 'Error')))); 211 | expect(() => PrivateKey(priv.asTypedList), returnsNormally); 212 | expect(() => PrivateKey.fromSeed(v31), throwsException); 213 | expect(() => PublicKey(v32), returnsNormally); 214 | expect(() => PublicKey(v31), throwsException); 215 | 216 | // Valid combinations 217 | expect(() => Box.decode(k32.asTypedList), returnsNormally); 218 | expect(() => Box(myPrivateKey: priv, theirPublicKey: priv.publicKey), 219 | returnsNormally); 220 | 221 | // Invalid combinations 222 | // eliminated by null-safety 223 | //expect( 224 | // () => Box(myPrivateKey: null, theirPublicKey: null), throwsException); 225 | //expect(() => Box(myPrivateKey: null, theirPublicKey: priv.publicKey), 226 | // throwsException); 227 | //expect( 228 | // () => Box(myPrivateKey: priv, theirPublicKey: null), throwsException); 229 | }); 230 | }); 231 | } 232 | -------------------------------------------------------------------------------- /test/diffie_hellman_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | 4 | import 'package:test/test.dart'; 5 | 6 | import 'package:pinenacl/api.dart'; 7 | import 'package:pinenacl/tweetnacl.dart'; 8 | 9 | const hex = Base16Encoder.instance; 10 | 11 | void _doShared(String sk, String pk, String sharedSecret) { 12 | final bobpriv = PrivateKey.decode(sk, hex); 13 | final alicepub = PublicKey.decode(pk, hex); 14 | 15 | final expected = Uint8List(32); 16 | 17 | /// The expected shared secret, the 18 | /// K = X25519(a, X25519(b, 9)) = X25519(b, X25519(a, 9)) 19 | TweetNaCl.crypto_scalarmult( 20 | expected, bobpriv.asTypedList, alicepub.asTypedList); 21 | assert(sharedSecret == hex.encode(expected)); 22 | } 23 | 24 | void main() { 25 | /// Official sharedkey from the [RFC7748](https://tools.ietf.org/html/rfc7748#page-14) 26 | const officialVector = { 27 | 'ask': '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a', 28 | 'apk': '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a', 29 | 'bsk': '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb', 30 | 'bpk': 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f', 31 | 'shr': '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742' 32 | }; 33 | group('Digital Signatures #2', () { 34 | group('Curve25519 (Diffie-Hellman)', () { 35 | test('official testvector', () { 36 | final sharedSecret = officialVector['shr']; 37 | 38 | final alicePriv = PrivateKey.decode(officialVector['ask']!, hex); 39 | final aliceGenPub = alicePriv.publicKey; 40 | final alicePub = PublicKey.decode(officialVector['apk']!, hex); 41 | 42 | assert(hex.encode(aliceGenPub) == hex.encode(alicePub)); 43 | 44 | final bobPriv = PrivateKey.decode(officialVector['bsk']!, hex); 45 | final bobGenPub = bobPriv.publicKey; 46 | final bobPub = PublicKey.decode(officialVector['bpk']!, hex); 47 | 48 | assert(hex.encode(bobGenPub) == hex.encode(bobPub)); 49 | 50 | final sharedSecret1 = Uint8List(32); 51 | final sharedSecret2 = Uint8List(32); 52 | 53 | /// The expected shared secret, the 54 | /// K = X25519(a, X25519(b, 9)) = X25519(b, X25519(a, 9)) 55 | TweetNaCl.crypto_scalarmult( 56 | sharedSecret1, bobPriv.asTypedList, alicePub.asTypedList); 57 | TweetNaCl.crypto_scalarmult( 58 | sharedSecret2, alicePriv.asTypedList, bobPub.asTypedList); 59 | 60 | assert(hex.encode(sharedSecret1) == hex.encode(sharedSecret2)); 61 | assert(hex.encode(sharedSecret1) == sharedSecret); 62 | }); 63 | }); 64 | 65 | group('Wycheproof', () { 66 | final dir = Directory.current; 67 | final file = File('${dir.path}/test/wycheproof/X25519.json'); 68 | final contents = file.readAsStringSync(); 69 | final dynamic x25519 = JsonDecoder().convert(contents); 70 | 71 | final dynamic testGroups = x25519['testGroups'][0]; 72 | final dynamic tests = testGroups['tests']; 73 | 74 | tests.forEach((dynamic vector) { 75 | final curve = vector['curve']! as String; 76 | final comment = vector['comment']! as String; 77 | final idx = vector['tcId']! as int; 78 | var description = '$curve - $comment ($idx)'; 79 | 80 | test(description, () { 81 | final public = vector['public']! as String; 82 | final private = vector['private']! as String; 83 | final shared = vector['shared']! as String; 84 | final result = vector['result']! as String; 85 | 86 | if (result == 'valid' || result == 'acceptable') { 87 | _doShared(private, public, shared); 88 | } else { 89 | expect(() => _doShared(private, public, shared), throwsException); 90 | } 91 | }); 92 | }); 93 | }); 94 | }); 95 | } 96 | -------------------------------------------------------------------------------- /test/hashing_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | 4 | import 'package:pinenacl/encoding.dart'; 5 | import 'package:pinenacl/digests.dart'; 6 | 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | const hex = Base16Encoder.instance; 11 | group('Hashing', () { 12 | group('SHA-256', () { 13 | final dir = Directory.current; 14 | final file = File('${dir.path}/test/data/sha256_vectors.json'); 15 | final contents = file.readAsStringSync(); 16 | final dynamic tests = JsonDecoder().convert(contents); 17 | 18 | var idx = 0; 19 | tests.forEach((dynamic vector) { 20 | final description = 'SHA Validation System\'s testvectors (${++idx})'; 21 | test(description, () { 22 | final digest = vector['digest']! as String; 23 | final message = 24 | Uint8List.fromList(hex.decode(vector['message']! as String)); 25 | 26 | final hash = Hash.sha256(message); 27 | assert(digest == hex.encode(hash)); 28 | }); 29 | }); 30 | }); 31 | 32 | group('SHA-512', () { 33 | final dir = Directory.current; 34 | final file = File('${dir.path}/test/data/sha512_vectors.json'); 35 | final contents = file.readAsStringSync(); 36 | final dynamic tests = JsonDecoder().convert(contents); 37 | 38 | var idx = 0; 39 | tests.forEach((dynamic vector) { 40 | final description = 'SHA Validation System\'s testvectors (${++idx})'; 41 | test(description, () { 42 | final digest = vector['digest']! as String; 43 | final message = 44 | Uint8List.fromList(hex.decode(vector['message']! as String)); 45 | 46 | final hash = Hash.sha512(message); 47 | 48 | assert(digest == hex.encode(hash)); 49 | }); 50 | }); 51 | }); 52 | 53 | group('Blake2B', () { 54 | final dir = Directory.current; 55 | final file = File('${dir.path}/test/data/blake2b_vectors.json'); 56 | final contents = file.readAsStringSync(); 57 | final dynamic tests = JsonDecoder().convert(contents); 58 | 59 | var idx = 0; 60 | tests.forEach((dynamic vector) { 61 | ++idx; 62 | final origin = idx < 513 ? 'RFC7693' : 'Libsodium'; 63 | final offset = idx < 513 ? idx : idx - 512; 64 | final description = '$origin testvectors ($offset)'; 65 | test(description, () { 66 | final out = vector['out']! as String; 67 | final outlen = vector['outlen']! as int; 68 | final input = 69 | Uint8List.fromList(hex.decode(vector['input']! as String)); 70 | final key = Uint8List.fromList(hex.decode(vector['key']! as String)); 71 | final salt = 72 | Uint8List.fromList(hex.decode(vector['salt']! as String)); 73 | final personal = 74 | Uint8List.fromList(hex.decode(vector['personal']! as String)); 75 | 76 | final hash = Hash.blake2b(input, 77 | digestSize: outlen, 78 | key: key.isEmpty ? null : key, 79 | salt: salt.isEmpty ? null : salt, 80 | personalisation: personal.isEmpty ? null : personal); 81 | 82 | assert(out == hex.encode(hash)); 83 | }); 84 | }); 85 | }); 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /test/hmac_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:pinenacl/encoding.dart'; 4 | import 'package:pinenacl/tweetnacl.dart'; 5 | 6 | /// The official testvectors from the 7 | /// [`Identifiers and Test Vectors for HMAC-SHA-224, HMAC-SHA-256, HMAC-SHA-384, and HMAC-SHA-512`](https://tools.ietf.org/html/rfc4231) 8 | /// RFC (RFC4231) 9 | const vectors = [ 10 | { 11 | 'key': '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 12 | 'key_length': 20, 13 | 'data': '4869205468657265', // 'Hi There', 14 | 'hmac-sha-224': '896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22', 15 | 'hmac-sha-256': 16 | 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7', 17 | 'hmac-sha-384': 18 | 'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6', 19 | 'hmac-sha-512': 20 | '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854' 21 | }, 22 | { 23 | 'key': '4a656665', // ("Jefe") 24 | 'key_length': 4, 25 | 'data': 26 | '7768617420646f2079612077616e7420666f72206e6f7468696e673f', // ("what do ya want ")("for nothing?") 27 | 'hmac-sha-224': 'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44', 28 | 'hmac-sha-256': 29 | '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843', 30 | 'hmac-sha-384': 31 | 'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649', 32 | 'hmac-sha-512': 33 | '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737' 34 | }, 35 | { 36 | 'key': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', //(20 bytes) 37 | 'key_length': 20, 38 | 'data': 39 | 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', // (50 bytes) 40 | 41 | 'hmac-sha-224': '7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea', 42 | 'hmac-sha-256': 43 | '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe', 44 | 'hmac-sha-384': 45 | '88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27', 46 | 'hmac-sha-512': 47 | 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb', 48 | }, 49 | { 50 | 'key': '0102030405060708090a0b0c0d0e0f10111213141516171819', // (25 bytes) 51 | 'key_length': 25, 52 | 'data': 53 | 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd', // (50 bytes) 54 | 'hmac-sha-224': '6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a', 55 | 'hmac-sha-256': 56 | '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b', 57 | 'hmac-sha-384': 58 | '3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb', 59 | 'hmac-sha-512': 60 | 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd' 61 | }, 62 | { 63 | 'key': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 64 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 65 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 66 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 67 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 68 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 69 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 70 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 71 | 'aaaaaa', // (131 bytes) 72 | 'key_length': 131, 73 | 'data': '54657374205573696e67204c61726765' // ("Test Using Large") 74 | '72205468616e20426c6f636b2d53697a' // ("r Than Block-Siz") 75 | '65204b6579202d2048617368204b6579' // ("e Key - Hash Key") 76 | '204669727374', // (" First") 77 | 78 | 'hmac-sha-224': '95e9a0db962095adaebe9b2d6f0dbce2' 79 | 'd499f112f2d2b7273fa6870e', 80 | 'hmac-sha-256': '60e431591ee0b67f0d8a26aacbf5b77f' 81 | '8e0bc6213728c5140546040f0ee37f54', 82 | 'hmac-sha-384': '4ece084485813e9088d2c63a041bc5b4' 83 | '4f9ef1012a2b588f3cd11f05033ac4c6' 84 | '0c2ef6ab4030fe8296248df163f44952', 85 | 'hmac-sha-512': '80b24263c7c1a3ebb71493c1dd7be8b4' 86 | '9b46d1f41b4aeec1121b013783f8f352' 87 | '6b56d037e05f2598bd0fd2215d6a1e52' 88 | '95e64f73f63f0aec8b915a985d786598', 89 | }, 90 | { 91 | 'key': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 92 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 93 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 94 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 95 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 96 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 97 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 98 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 99 | 'aaaaaa', // (131 bytes) 100 | 'key_length': 131, 101 | 'data': '54686973206973206120746573742075' // ("This is a test u") 102 | '73696e672061206c6172676572207468' // ("sing a larger th") 103 | '616e20626c6f636b2d73697a65206b65' // ("an block-size ke") 104 | '7920616e642061206c61726765722074' // ("y and a larger t") 105 | '68616e20626c6f636b2d73697a652064' // ("han block-size d") 106 | '6174612e20546865206b6579206e6565' // ("ata. The key nee") 107 | '647320746f2062652068617368656420' // ("ds to be hashed ") 108 | '6265666f7265206265696e6720757365' // ("before being use") 109 | '642062792074686520484d414320616c' // ("d by the HMAC al") 110 | '676f726974686d2e', // ("gorithm.") 111 | 112 | 'hmac-sha-224': '3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1', 113 | 'hmac-sha-256': 114 | '9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2', 115 | 'hmac-sha-384': 116 | '6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e', 117 | 'hmac-sha-512': 118 | 'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58', 119 | } 120 | ]; 121 | 122 | void main() { 123 | const hex = Base16Encoder.instance; 124 | group('Hash-based message authentication code', () { 125 | group('HMAC-SHA-', () { 126 | var idx = 0; 127 | for (var vector in vectors) { 128 | final description = 'RFC4231\'s testvectors (${++idx})'; 129 | final k = Uint8List.fromList(hex.decode(vector['key'] as String)); 130 | final kLen = vector['key_length']; 131 | final data = Uint8List.fromList(hex.decode(vector['data'] as String)); 132 | 133 | test('512 $description', () { 134 | final mac = vector['hmac-sha-512']; 135 | 136 | assert(k.length == kLen); 137 | final out = Uint8List(64); 138 | TweetNaClExt.crypto_auth_hmacsha512(out, data, k); 139 | 140 | assert(mac == hex.encode(out)); 141 | }); 142 | 143 | test('256 $description', () { 144 | final mac = vector['hmac-sha-256']; 145 | 146 | assert(k.length == kLen); 147 | 148 | final out = Uint8List(32); 149 | 150 | TweetNaClExt.crypto_auth_hmacsha256(out, data, k); 151 | 152 | assert(mac == hex.encode(out)); 153 | }); 154 | } 155 | }); 156 | }); 157 | } 158 | -------------------------------------------------------------------------------- /test/pbkdf_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | 4 | import 'package:test/test.dart'; 5 | 6 | import 'package:pinenacl/api.dart'; 7 | import 'package:pinenacl/encoding.dart'; 8 | 9 | import 'package:pinenacl/key_derivation.dart'; 10 | 11 | void main() { 12 | const hex = Base16Encoder.instance; 13 | 14 | group('Password Based Key Derivation Function #2 (PBKDF2)', () { 15 | final dir = Directory.current; 16 | final file = File('${dir.path}/test/data/pbkdf2_hmac_sha2_test.json'); 17 | final contents = file.readAsStringSync(); 18 | final dynamic pbkdf2 = JsonDecoder().convert(contents); 19 | 20 | final dynamic tests = pbkdf2['hmac-sha-512-vectors']; 21 | 22 | var idx = 0; 23 | tests.forEach((dynamic vector) { 24 | final description = 'HMAC-SHA-512 based PBKDF2 (${++idx})'; 25 | 26 | test(description, () { 27 | //final id = vector['id']! as int; 28 | //final pwd_len = vector['pwd_len']! as int; 29 | //final salt_len = vector['salt_len']! as int; 30 | final password = vector['password']! as String; 31 | final salt = vector['salt']! as String; 32 | final iterations = vector['iterations']! as int; 33 | final outputBytes = vector['output_bytes']! as int; 34 | final hexResult = vector['hex_result']! as String; 35 | 36 | final passwordBytes = Uint8List.fromList(password.codeUnits); 37 | final saltBytes = Uint8List.fromList(salt.codeUnits); 38 | 39 | // Ignoring the 2m's iterations. 40 | // FIXME: 41 | if (iterations < 1) { 42 | final out = PBKDF2.hmac_sha512( 43 | passwordBytes, saltBytes, iterations, outputBytes); 44 | final outHex = hex.encode(out); 45 | 46 | assert(outHex == hexResult); 47 | } 48 | }); 49 | }); 50 | 51 | final dynamic tests2 = pbkdf2['hmac-sha-256-vectors']; 52 | idx = 0; 53 | tests2.forEach((dynamic vector) { 54 | final description = 'HMAC-SHA-256 based PBKDF2 (${++idx})'; 55 | 56 | test(description, () { 57 | //final id = vector['id']! as int; 58 | //final pwd_len = vector['pwd_len']! as int; 59 | //final salt_len = vector['salt_len']! as int; 60 | final password = vector['password']! as String; 61 | final salt = vector['salt']! as String; 62 | final iterations = vector['iterations']! as int; 63 | final outputBytes = vector['output_bytes']! as int; 64 | final hexResult = vector['hex_result']! as String; 65 | 66 | final passwordBytes = Uint8List.fromList(password.codeUnits); 67 | final saltBytes = Uint8List.fromList(salt.codeUnits); 68 | 69 | // Ignoring the 2m's iterations. 70 | // FIXME: 100000 71 | if (iterations < 1) { 72 | final out = PBKDF2.hmac_sha256( 73 | passwordBytes, saltBytes, iterations, outputBytes); 74 | final outHex = hex.encode(out); 75 | 76 | assert(outHex == hexResult); 77 | } 78 | }); 79 | }); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /test/secretbox_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'package:pinenacl/src/authenticated_encryption/secret.dart' 5 | show SecretBox; 6 | 7 | const _vectors = { 8 | 'key': '1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389', 9 | 'nonce': '69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37', 10 | 'plaintext': 11 | 'be075fc53c81f2d5cf141316ebeb0c7b5228c52a4c62cbd44b66849b64244ffce5e' 12 | 'cbaaf33bd751a1ac728d45e6c61296cdc3c01233561f41db66cce314adb310e3be8' 13 | '250c46f06dceea3a7fa1348057e2f6556ad6b1318a024a838f21af1fde048977eb4' 14 | '8f59ffd4924ca1c60902e52f0a089bc76897040e082f937763848645e0705', 15 | 'ciphertext': 16 | 'f3ffc7703f9400e52a7dfb4b3d3305d98e993b9f48681273c29650ba32fc76ce483' 17 | '32ea7164d96a4476fb8c531a1186ac0dfc17c98dce87b4da7f011ec48c97271d2c2' 18 | '0f9b928fe2270d6fb863d51738b48eeee314a7cc8ab932164548e526ae902243685' 19 | '17acfeabd6bb3732bc0e9da99832b61ca01b6de56244a9e88d5f9b37973f622a43d' 20 | '14a6599b1f654cb45a74e355a5' 21 | }; 22 | 23 | void main() { 24 | const hex = Base16Encoder.instance; 25 | group('Secret Key Encryption', () { 26 | test('SecretBox basic', () { 27 | final s = SecretBox.decode( 28 | 'ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798'); 29 | assert(s == s.key); 30 | }); 31 | 32 | test('SecretBox encryption', () { 33 | final box = SecretBox.decode(_vectors['key']!); 34 | 35 | final nonce = _vectors['nonce']!; 36 | final cipherText = _vectors['ciphertext']!; 37 | final plainText = _vectors['plaintext']!; 38 | 39 | final encrypted = 40 | box.encrypt(hex.decode(plainText), nonce: hex.decode(nonce)); 41 | 42 | final expected = hex.decode(nonce + cipherText); 43 | 44 | assert(hex.encode(encrypted) == hex.encode(expected)); 45 | assert(hex.encode(encrypted.nonce) == nonce); 46 | assert(hex.encode(encrypted.cipherText) == cipherText); 47 | }); 48 | 49 | test('SecretBox decryption', () { 50 | final box = SecretBox.decode(_vectors['key']!); 51 | 52 | final nonce = _vectors['nonce']!; 53 | final ciphertext = _vectors['ciphertext']!; 54 | final plaintext = _vectors['plaintext']!; 55 | 56 | final decrypted = box.decrypt(ByteList(hex.decode(ciphertext)), 57 | nonce: hex.decode(nonce)); 58 | 59 | assert(hex.encode(decrypted) == plaintext); 60 | }); 61 | 62 | test('SecretBox decryption (no nonce)', () { 63 | final box = SecretBox.decode(_vectors['key']!); 64 | 65 | final plaintext = _vectors['plaintext']; 66 | 67 | final encrypted = box.encrypt(hex.decode(plaintext!)); 68 | final decrypted = box.decrypt(encrypted); 69 | 70 | assert(hex.encode(decrypted) == plaintext); 71 | }); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /test/signing_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | 4 | import 'package:pinenacl/api.dart'; 5 | 6 | import 'package:test/test.dart'; 7 | 8 | import 'package:pinenacl/src/signatures/ed25519.dart'; 9 | 10 | const _vectors = { 11 | 'seed': '002fdd1f7641793ab064bb7aa848f762e7ec6e332ffc26eeacda141ae33b1783', 12 | 'pk': '77d1d8ebacd13f4e2f8a40e28c4a63bc9ce3bfb69716334bcb28a33eb134086c', 13 | 'public': '77d1d8ebacd13f4e2f8a40e28c4a63bc9ce3bfb69716334bcb28a33eb134086c', 14 | 'message': 15 | '5ac1dfc324f43e6cb79a87ab0470fa857b51fb944982e19074ca44b1e40082c1d07b92efa7ea55ad42b7c027e0b9e33756d95a2c1796a7c2066811dc41858377d4b835c1688d638884cd2ad8970b74c1a54aadd27064163928a77988b24403aa85af82ceab6b728e554761af7175aeb99215b7421e4474c04d213e01ff03e3529b11077cdf28964b8c49c5649e3a46fa0a09dcd59dcad58b9b922a83210acd5e65065531400234f5e40cddcf9804968e3e9ac6f5c44af65001e158067fc3a660502d13fa8874fa93332138d9606bc41b4cee7edc39d753dae12a873941bb357f7e92a4498847d6605456cb8c0b425a47d7d3ca37e54e903a41e6450a35ebe5237c6f0c1bbbc1fd71fb7cd893d189850295c199b7d88af26bc8548975fda1099ffefee42a52f3428ddff35e0173d3339562507ac5d2c45bbd2c19cfe89b', 16 | 'signature': 17 | '0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c', 18 | 'expected': 19 | '0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c5ac1dfc324f43e6cb79a87ab0470fa857b51fb944982e19074ca44b1e40082c1d07b92efa7ea55ad42b7c027e0b9e33756d95a2c1796a7c2066811dc41858377d4b835c1688d638884cd2ad8970b74c1a54aadd27064163928a77988b24403aa85af82ceab6b728e554761af7175aeb99215b7421e4474c04d213e01ff03e3529b11077cdf28964b8c49c5649e3a46fa0a09dcd59dcad58b9b922a83210acd5e65065531400234f5e40cddcf9804968e3e9ac6f5c44af65001e158067fc3a660502d13fa8874fa93332138d9606bc41b4cee7edc39d753dae12a873941bb357f7e92a4498847d6605456cb8c0b425a47d7d3ca37e54e903a41e6450a35ebe5237c6f0c1bbbc1fd71fb7cd893d189850295c199b7d88af26bc8548975fda1099ffefee42a52f3428ddff35e0173d3339562507ac5d2c45bbd2c19cfe89b', 20 | }; 21 | 22 | const _cardanoVectors = [ 23 | { 24 | 'seed': '2d2086832cc2fe3fd18cb51d6c5e99a5759f02211f85e5ff2f904a780f58006f', 25 | 'message': 26 | '898f9c4b2c6ee9e228761ca50897b71ffeca1c352846f5fe13f7d3d57e2c15ac60900ca32c5b5dd953c9a6810acc64394ffd149826d99806292addd13fc3bb7dac701c5b4a2d615d15960128ed9f736b98854f6f0705b0f0dacbdc2c262d27397519149b0e4cbe1677c576c1397aae5ce34916e3513104632ec2190db8d22289c3723c8d01213cad803f4d7574c4dbb53731b01c8ec75d082ef7dc9d7f1b73159f63db56aa12a2ca39eace6b28e4c31d9d256741452e8387e1536d03026ee48410d43b219188ba14a8af', 27 | 'signature': 28 | '912091661eed18a4034bc7db4bd60fe2deebf3ff3b6b998dae2094b609865c2019ec6722bfdc87bda54091922e11e393f5fdceea3e091f2ee6bc62df948e9909' 29 | }, 30 | { 31 | 'seed': '33191782c1704f60d0848d7562a2fa19f9924fea4e7733cd45f6c32f219a7291', 32 | 'message': 33 | '7713435a0e346f6771ae5adea87ae7a452c65d748f4869d31ed36747c328ddc4ec0e486793a51c6766f7064826d074514dd05741f3be273ef21f280e4907ed89be301a4ec8496eb6ab900006e5a3c8e9c993621d6a3b0f6cbad0fddef3b9c82d', 34 | 'signature': 35 | '4b8d9b1eca5400eac6f5cc0c9439630052f734ce453e9426f319dd9603b6aeaeb9d23a5f93f06a460018f069df194448f56051ab9e6bfaeb641016f7a90be20c' 36 | } 37 | ]; 38 | 39 | void main() { 40 | const hex = Base16Encoder.instance; 41 | group('Digital Signatures #1', () { 42 | group('Signing and/or Verifying tests', () { 43 | test('Simple signing and verifying test', () { 44 | final seed = _vectors['seed']!; 45 | final public = _vectors['public']; 46 | final message = _vectors['message']!; 47 | final signature = _vectors['signature']!; 48 | final expected = SignedMessage.fromList( 49 | signedMessage: hex.decode(_vectors['expected']!)); 50 | 51 | final signingKey = SigningKey(seed: hex.decode(seed)); 52 | final signed = signingKey.sign(hex.decode(message)); 53 | 54 | final publicKey = VerifyKey(hex.decode(public!)); 55 | final verifyKey = signingKey.verifyKey; 56 | 57 | assert(publicKey == verifyKey); 58 | assert(verifyKey.verifySignedMessage(signedMessage: signed)); 59 | assert(signed == expected); 60 | assert(signed == expected); 61 | assert(hex.encode(signed.message) == message); 62 | assert(hex.encode(signed.signature) == signature); 63 | }); 64 | }); 65 | 66 | group('Sign Cardano\'s cryptoxide ed25519 testvectors', () { 67 | var idx = 0; 68 | for (var vector in _cardanoVectors) { 69 | final description = ' (${++idx})'; 70 | test(description, () { 71 | final seed = vector['seed']!; 72 | final message = vector['message']!; 73 | final signature = vector['signature']!; 74 | 75 | final signingKey = SigningKey(seed: hex.decode(seed)); 76 | final signed = signingKey.sign(hex.decode(message)); 77 | 78 | assert(hex.encode(signed.message) == message); 79 | assert(hex.encode(signed.signature) == signature); 80 | }); 81 | } 82 | }); 83 | 84 | group('Sign and verify Ed25519 (EdDSA RFC8032) testvectors', () { 85 | final dir = Directory.current; 86 | final file = File('${dir.path}/test/data/eddsa_ed25519_vectors.json'); 87 | final contents = file.readAsStringSync(); 88 | final dynamic tests = JsonDecoder().convert(contents); 89 | 90 | var idx = 0; 91 | tests.forEach((dynamic vector) { 92 | var description = ' (${++idx})'; 93 | test(description, () { 94 | final seed = hex.decode(vector['seed']! as String).sublist(0, 32); 95 | final public = hex.decode(vector['publ']! as String); 96 | final message = hex.decode(vector['mesg']! as String); 97 | final signedMessage = hex.decode(vector['sigd']! as String); 98 | final signature = signedMessage.sublist(0, 64); 99 | final expected = SignedMessage.fromList(signedMessage: signedMessage); 100 | 101 | final signingKey = SigningKey(seed: seed); 102 | final publicKey = signingKey.verifyKey; 103 | final signed = signingKey.sign(message); 104 | 105 | final verifyKey = VerifyKey(public); 106 | 107 | assert(verifyKey == publicKey); 108 | expect(() => verifyKey.verifySignedMessage(signedMessage: signed), 109 | returnsNormally); 110 | expect( 111 | () => verifyKey.verify( 112 | signature: signed.signature, 113 | message: signed.message.asTypedList), 114 | returnsNormally); 115 | 116 | assert(signed == expected); 117 | assert(hex.encode(signed.message) == hex.encode(message)); 118 | assert(hex.encode(signed.signature) == hex.encode(signature)); 119 | }); 120 | }); 121 | }); 122 | group('Wrong types test', () { 123 | test('SigningKey and VerifyKey', () { 124 | final sk = SigningKey.generate(); 125 | final l31 = Uint8List(31); 126 | 127 | expect(() => SigningKey(seed: sk.asTypedList), throwsException); 128 | expect(() => SigningKey.fromSeed(l31), throwsException); 129 | 130 | /// Any validlength bytes (except private key) or 131 | /// publicKey can be a VerifyKey 132 | //expect(() => VerifyKey(sk), throwsException); 133 | }); 134 | }); 135 | }); 136 | } 137 | -------------------------------------------------------------------------------- /test/tweetnacl_validation_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:pinenacl/encoding.dart'; 2 | import 'package:pinenacl/api.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import 'package:pinenacl/tweetnacl.dart' show TweetNaCl; 6 | 7 | /// The [`NaCl`](https://nacl.cr.yp.to/valid.html) official testvectors from the 8 | /// [Cryptography in NaCl](https://cr.yp.to/highspeed/naclcrypto-20090310.pdf) paper 9 | void main() { 10 | const hex = Base16Encoder.instance; 11 | const aliceSk = 12 | '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a'; 13 | const alicePk = 14 | '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a'; 15 | const bobSk = 16 | '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb'; 17 | 18 | const bobPk = 19 | 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f'; 20 | 21 | const sharedSecret = 22 | '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742'; 23 | final zero = Uint8List(32); 24 | const c = '657870616e642033322d62797465206b'; 25 | const firstKey = 26 | '1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389'; 27 | 28 | const noncesuffix = '8219e0036b7a0b37'; 29 | const noncePrefix = '69696ee955b62b73cd62bda875fc73d6'; 30 | const nonce = noncePrefix + noncesuffix; 31 | 32 | /* TODO: implement them. 33 | const secondKey = 34 | 'dc908dda0b9344a953629b733820778880f3ceb421bb61b91cbd4c3e66256ce4'; 35 | const m = '0000000000000000000000000000000000000000000000000' 36 | 'be075fc53c81f2d5cf141316ebeb0c7b5228c52a4c62cbd44' 37 | 'b66849b64244ffce5ecbaaf33bd751a1ac728d45e6c61296c' 38 | 'dc3c01233561f41db66cce314adb310e3be8250c46f06dcee' 39 | 'a3a7fa1348057e2f6556ad6b1318a024a838f21af1fde0489' 40 | '77eb48f59ffd4924ca1c60902e52f0a089bc76897040e082f' 41 | '937763848645e0705'; 42 | 43 | const boxedPacket = 44 | 'f3ffc7703f9400e52a7dfb4b3d3305d98e993b9f48681273c29650ba32fc76ce' 45 | '48332ea7164d96a4476fb8c531a1186ac0dfc17c98dce87b4da7f011ec48c972' 46 | '71d2c20f9b928fe2270d6fb863d51738b48eeee314a7cc8ab932164548e526ae' 47 | '90224368517acfeabd6bb3732bc0e9da99832b61ca01b6de56244a9e88d5f9b3' 48 | '7973f622a43d14a6599b1f654cb45a74e355a5'; 49 | */ 50 | group('TweetNaCl', () { 51 | group('Validation', () { 52 | test('Alice\'s SecretKey to PublicKey to', () { 53 | final pk = Uint8List(TweetNaCl.publicKeyLength); 54 | 55 | TweetNaCl.crypto_scalarmult_base(pk, hex.decode(aliceSk)); 56 | 57 | assert(hex.encode(pk) == alicePk); 58 | }); 59 | test('Bob\'s SecretKey to PublicKey test', () { 60 | final pk = Uint8List(TweetNaCl.publicKeyLength); 61 | 62 | TweetNaCl.crypto_scalarmult_base(pk, hex.decode(bobSk)); 63 | 64 | assert(hex.encode(pk) == bobPk); 65 | }); 66 | test('Shared secret (Alice Secret, Bob pulic) test', () { 67 | final k = Uint8List(TweetNaCl.secretKeyLength); 68 | 69 | TweetNaCl.crypto_scalarmult(k, hex.decode(aliceSk), hex.decode(bobPk)); 70 | 71 | assert(hex.encode(k) == sharedSecret); 72 | }); 73 | test('Shared secret (Bob secret, Alice pulic) test', () { 74 | final k = Uint8List(TweetNaCl.secretKeyLength); 75 | 76 | TweetNaCl.crypto_scalarmult(k, hex.decode(bobSk), hex.decode(alicePk)); 77 | 78 | assert(hex.encode(k) == sharedSecret); 79 | }); 80 | test('Nonce and stream (1st key) test', () { 81 | final l1k = Uint8List(TweetNaCl.secretKeyLength); 82 | 83 | TweetNaCl.crypto_core_hsalsa20( 84 | l1k, zero, hex.decode(sharedSecret), hex.decode(c)); 85 | 86 | assert(hex.encode(l1k) == firstKey); 87 | }); 88 | test('Nonce and stream (4194304 long output) test', () { 89 | final outLen = 4194304; 90 | final out = Uint8List(outLen); 91 | final hashOut = Uint8List(64); 92 | 93 | expect( 94 | () => TweetNaCl.crypto_stream_salsa20( 95 | out, 0, outLen, hex.decode(nonce), hex.decode(firstKey)), 96 | returnsNormally); 97 | final hexOut = TweetNaCl.crypto_hash(hashOut, out); 98 | 99 | assert(hexOut == 0); 100 | }); 101 | }); 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /tool/build_run_bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROJ_DIR="$(dirname $0)/.." 4 | BENCH_DIR="$PROJ_DIR/benchmark" 5 | 6 | # Does not handle white chars in path i.e. space, tab etc. 7 | to_native=$(find $BENCH_DIR -name \*.dart) 8 | 9 | rm -rf bin && mkdir "$PROJ_DIR/bin" 10 | 11 | # Build 12 | echo "Building benchmark files" 13 | for source in $to_native 14 | do 15 | file=$(basename $source .dart) 16 | echo "- $file" 17 | dart2native "$source" --output "$PROJ_DIR/bin/$file" 18 | done 19 | 20 | # Run 21 | echo "Running built benchmark files" 22 | for bin_file in $to_native 23 | do 24 | file=$(basename $bin_file .dart) 25 | echo "Running $file" 26 | "$PROJ_DIR/bin/$file" 27 | done 28 | 29 | echo "Deleting files..." 30 | [[ -n "$PROJ_DIR" ]] && rm -f "$PROJ_DIR/bin/*" 31 | -------------------------------------------------------------------------------- /tool/dart_crypto.js: -------------------------------------------------------------------------------- 1 | // Add this below to the dart2js generated files if it requires 2 | // random numbers. You can use similar to this below: 3 | // ```bash 4 | // for i in $(find ../benchmark/ -name \*.dart); 5 | // do 6 | // B=$(basename $i .dart) 7 | // O="$B.js" 8 | // dart2js "$i" -o "$B.js" 9 | // sed -i '' -e '/^(function dartProgram() {/r ../tool/dart_crypto.js' "$B.js" 10 | // done``` 11 | // One-liner example 12 | // dart2js ../benchmark/all_benchmark.dart -o all_benchmark.js && sed -i '' -e '/^(function dartProgram() {/r dart_crypto.js' all_benchmark.js 13 | 14 | var self = typeof self !== 'undefined' ? self : Object.create(global); 15 | var crypto = typeof self !== 'undefined' ? (self.crypto || self.msCrypto) : null; 16 | 17 | var _randomBytes; 18 | 19 | if (!(crypto && crypto.getRandomValues) && typeof require !== 'undefined') { 20 | // Node.js. 21 | crypto = require('crypto'); 22 | if (crypto && crypto.randomBytes) { 23 | _randomBytes = function (x, n) { 24 | var i, v = crypto.randomBytes(n); 25 | for (i = 0; i < n; i++) x[i] = v[i]; 26 | for (i = 0; i < n; i++) v[i] = 0; 27 | }; 28 | 29 | crypto.getRandomValues = function _randomValues(t) { 30 | _randomBytes(t, t.length); 31 | } 32 | } 33 | } 34 | 35 | self.crypto = typeof self.crypto !== 'undefined' ? self.crypto : crypto; 36 | --------------------------------------------------------------------------------