mirror of
https://github.com/aljazceru/pubky-core.git
synced 2025-12-31 12:54:35 +01:00
feat: encrypt seed and encode as URI
This commit is contained in:
353
Cargo.lock
generated
353
Cargo.lock
generated
@@ -2,6 +2,66 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3a318f1f38d2418400f8209655bfd825785afd25aa30bb7ba6cc792e4596748"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
"cpufeatures",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
@@ -14,6 +74,31 @@ version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bessie"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30407622e75e71bbc4f53708a3016c75eb960ed417ec11abeae034bd78e1a196"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.5.0"
|
||||
@@ -27,6 +112,15 @@ dependencies = [
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.5.0"
|
||||
@@ -48,12 +142,98 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.11"
|
||||
@@ -65,13 +245,22 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "kytes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"argon2",
|
||||
"bessie",
|
||||
"bytes",
|
||||
"clap",
|
||||
"rand",
|
||||
"thiserror",
|
||||
"z32",
|
||||
]
|
||||
|
||||
@@ -81,12 +270,41 @@ version = "0.2.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@@ -117,12 +335,145 @@ dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "z32"
|
||||
version = "1.0.2"
|
||||
|
||||
18
design/seed.md
Normal file
18
design/seed.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Seed
|
||||
|
||||
Kytes seed is an encrypted seed encoded as URI as follows:
|
||||
|
||||
```
|
||||
kytes:seed:<suffix>
|
||||
```
|
||||
|
||||
The `suffix` is a `z-base32` encoded bytes as follows:
|
||||
|
||||
```
|
||||
<VERSION><Encrypted seed ciphertext>
|
||||
```
|
||||
|
||||
## Version 0
|
||||
|
||||
For version 0 the encrypted seed is using [`bessie`](https://github.com/oconnor663/bessie/blob/44f9500ebeb0f28efc9689184ff5b1d79e2308e0/design.md) version 0.0.1
|
||||
|
||||
@@ -5,10 +5,21 @@ edition = "2021"
|
||||
description = "Soaring in the Cloud, but you pull the strings."
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
blake3 = "1.5.0"
|
||||
bytes = "1.5.0"
|
||||
rand = "0.8.5"
|
||||
z32 = "1.0.2"
|
||||
|
||||
# cli
|
||||
clap = { version = "4.4.11", optional = true, features = ["derive"] }
|
||||
argon2 = "0.5.2"
|
||||
thiserror = "1.0.50"
|
||||
bessie = "0.0.1"
|
||||
|
||||
[features]
|
||||
default = ["cli"]
|
||||
cli = ["clap"]
|
||||
|
||||
[[bin]]
|
||||
name = "kytes"
|
||||
required-features = ["cli"]
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use rand::Rng;
|
||||
|
||||
const SEED_FILE_PREFIX: &str = "kytes seed";
|
||||
const VERSION: u8 = 0;
|
||||
|
||||
const PASSPHRASE_HASHING_ITERATIONS: i32 = 100_000;
|
||||
|
||||
/// Takes an encrypted seed and format it into a seed file as follows:
|
||||
/// `kytes encrypted-seed v<version> <zbase32 encoded encrypted_seed>`
|
||||
pub fn format_encrypted_seed_file(encrypted_seed: &[u8; 32]) -> Bytes {
|
||||
let mut seed_file = BytesMut::with_capacity(SEED_FILE_PREFIX.len() + 33);
|
||||
seed_file.extend_from_slice(SEED_FILE_PREFIX.as_bytes());
|
||||
seed_file.extend_from_slice(b" v");
|
||||
seed_file.put_u8(VERSION + 48);
|
||||
seed_file.extend_from_slice(b" ");
|
||||
seed_file.extend_from_slice(z32::encode(encrypted_seed).as_bytes());
|
||||
|
||||
seed_file.freeze()
|
||||
}
|
||||
|
||||
pub fn encrypted_seed_file_version(seed_file: &Bytes) -> Option<u8> {
|
||||
let version_start = SEED_FILE_PREFIX.len() + 2;
|
||||
let version_end = version_start + 2;
|
||||
|
||||
seed_file
|
||||
.get(version_start..version_end)
|
||||
.map(|version| version[0] - 48_u8)
|
||||
}
|
||||
|
||||
pub fn encrypt_seed_file() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn generate_seed() -> [u8; 32] {
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.gen()
|
||||
}
|
||||
|
||||
pub fn seed_encryption_key(passphrase: &str) -> [u8; 32] {
|
||||
let mut hash: [u8; 32] = blake3::hash(passphrase.as_bytes()).into();
|
||||
|
||||
for i in 0..PASSPHRASE_HASHING_ITERATIONS {
|
||||
hash = blake3::hash(&hash).into();
|
||||
}
|
||||
|
||||
hash.to_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::passphrase::generate_4words_passphrase;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_format_encrypted_seed_file() {
|
||||
let seed = generate_seed();
|
||||
let seed_file = format_encrypted_seed_file(&seed);
|
||||
|
||||
dbg!(&seed_file);
|
||||
|
||||
assert_eq!(seed_file.len(), 52 + 4 + SEED_FILE_PREFIX.len());
|
||||
assert!(seed_file.starts_with(SEED_FILE_PREFIX.as_bytes()));
|
||||
assert!(seed_file.starts_with(SEED_FILE_PREFIX.as_bytes()));
|
||||
assert_eq!(encrypted_seed_file_version(&seed_file).unwrap(), 0);
|
||||
assert!(seed_file.ends_with(&z32::encode(&seed).as_bytes()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash() {
|
||||
let passphrase = generate_4words_passphrase();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
println!("start hashing...");
|
||||
|
||||
let hash = seed_encryption_key(&passphrase);
|
||||
|
||||
println!("final hash: {:?}", hash);
|
||||
|
||||
println!("{} ms", start.elapsed().as_millis());
|
||||
}
|
||||
}
|
||||
33
kytes/src/crypto/encryption.rs
Normal file
33
kytes/src/crypto/encryption.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
//! Encryption functions.
|
||||
|
||||
use crate::{Error, Result};
|
||||
use rand::{Rng, RngCore};
|
||||
|
||||
use crate::crypto::{Key, Nonce};
|
||||
|
||||
/// Compute the length of a ciphertext, given the length of a plaintext.
|
||||
///
|
||||
/// This function returns `None` if the resulting ciphertext length would overflow a `u64`.
|
||||
pub fn ciphertext_len(plaintext_len: u64) -> Option<u64> {
|
||||
bessie::ciphertext_len(plaintext_len)
|
||||
}
|
||||
|
||||
/// Encrypt a message and write the ciphertext to an existing slice.
|
||||
///
|
||||
/// This function does not allocate memory. However, `ciphertext.len()` must be exactly equal to
|
||||
/// [`ciphertext_len(plaintext.len())`](ciphertext_len), or else this function will panic.
|
||||
pub fn encrypt_to_slice(key: &Key, plaintext: &[u8], ciphertext: &mut [u8]) {
|
||||
bessie::encrypt_to_slice(key, plaintext, ciphertext)
|
||||
}
|
||||
|
||||
/// Encrypt a message and return the ciphertext as a `Vec<u8>`.
|
||||
pub fn encrypt(key: &Key, plaintext: &[u8]) -> Vec<u8> {
|
||||
bessie::encrypt(key, plaintext)
|
||||
}
|
||||
|
||||
/// Decrypt a message and return the plaintext as `Result` of `Vec<u8>`.
|
||||
///
|
||||
/// If the ciphertext or key has been changed, decryption will return `Err`.
|
||||
pub fn decrypt(key: &Key, ciphertext: &[u8]) -> Result<Vec<u8>> {
|
||||
bessie::decrypt(key, ciphertext).map_err(|err| Error::Generic(err.to_string()))
|
||||
}
|
||||
8
kytes/src/crypto/keys.rs
Normal file
8
kytes/src/crypto/keys.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! Keypairs.
|
||||
|
||||
use crate::crypto::Key;
|
||||
|
||||
/// Generate a random secret seed.
|
||||
pub fn generate_seed() -> Key {
|
||||
rand::random()
|
||||
}
|
||||
9
kytes/src/crypto/mod.rs
Normal file
9
kytes/src/crypto/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod encryption;
|
||||
mod keys;
|
||||
mod passphrase;
|
||||
mod seed;
|
||||
|
||||
/// A 32 bytes key (encryption key or public key or shared_secret key).
|
||||
pub type Key = [u8; bessie::KEY_LEN];
|
||||
/// A 24 bytes Nonce or salt.
|
||||
pub type Nonce = [u8; bessie::NONCE_LEN];
|
||||
@@ -1,3 +1,5 @@
|
||||
//! EFF large wordlist passphrase generator.
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
/// Generate a 4 words passphrase with spaces, from the [EFF large wordlist](https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt).
|
||||
124
kytes/src/crypto/seed.rs
Normal file
124
kytes/src/crypto/seed.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
//! Manage Kytes seed files.
|
||||
//!
|
||||
//! Seed file contains a seed encrypted with a strong passphrase.
|
||||
|
||||
use argon2::PasswordHasher;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
crypto::{
|
||||
encryption::{ciphertext_len, decrypt, encrypt_to_slice},
|
||||
keys::generate_seed,
|
||||
passphrase::generate_4words_passphrase,
|
||||
Key, Nonce,
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
const SEED_SCHEME: &[u8] = b"kytes:seed:";
|
||||
|
||||
const VERSION: u8 = 0;
|
||||
const KNOWN_VERSIONS: [u8; 1] = [0];
|
||||
|
||||
/// Encrypt the seed with a strong passphrase, and return an [encrypted seed
|
||||
/// file](../../../design/seed.md).
|
||||
pub fn encrypt_seed(seed: &Key, passphrase: &str) -> Bytes {
|
||||
let encryption_key = derive_encrypiton_key(passphrase);
|
||||
|
||||
let mut seed_file = BytesMut::with_capacity(SEED_SCHEME.len() + 33);
|
||||
seed_file.extend_from_slice(SEED_SCHEME);
|
||||
|
||||
let suffix_len = 1 + ciphertext_len(seed.len() as u64).unwrap() as usize;
|
||||
let mut suffix = vec![0_u8; suffix_len];
|
||||
|
||||
suffix[0] = VERSION;
|
||||
encrypt_to_slice(&encryption_key, seed, &mut suffix[1..]);
|
||||
|
||||
seed_file.extend_from_slice(z32::encode(&suffix).as_bytes());
|
||||
|
||||
seed_file.freeze()
|
||||
}
|
||||
|
||||
pub fn decrypt_seed(seed_file: Bytes, passphrase: &str) -> Result<Vec<u8>> {
|
||||
if !seed_file.starts_with(SEED_SCHEME) {
|
||||
return Err(Error::Generic("Not a Kytes seed".to_string()));
|
||||
}
|
||||
|
||||
let suffix = z32::decode(&seed_file[SEED_SCHEME.len()..])
|
||||
.map_err(|_| Error::Generic("Invalid seed encoding".to_string()))?;
|
||||
|
||||
let version = suffix[0];
|
||||
|
||||
match version {
|
||||
0 => decrypted_seed_v0(&suffix, passphrase),
|
||||
_ => Err(Error::Generic(
|
||||
"Unknown kytes seed file version".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypted_seed_v0(suffix: &[u8], passphrase: &str) -> Result<Vec<u8>> {
|
||||
let encryption_key = derive_encrypiton_key(passphrase);
|
||||
let encrypted_seed = &suffix[1..];
|
||||
|
||||
decrypt(&encryption_key, encrypted_seed)
|
||||
}
|
||||
|
||||
fn parse_version(byte_string: &[u8]) -> Result<u8> {
|
||||
// Convert byte array to string slice
|
||||
let str_slice = std::str::from_utf8(byte_string)
|
||||
.map_err(|_| Error::Generic("Invalid version number".to_string()))?;
|
||||
|
||||
str_slice
|
||||
.parse::<u8>()
|
||||
.map(Ok)
|
||||
.map_err(|_| Error::Generic("Invalid version number".to_string()))?
|
||||
}
|
||||
|
||||
/// Derive a secret key from a strong passphrase for encrypting/decrypting the seed.
|
||||
fn derive_encrypiton_key(passphrase: &str) -> Key {
|
||||
// Argon2 with default params (Argon2id v19)
|
||||
let hasher = argon2::Argon2::default();
|
||||
|
||||
let mut encryption_key: Key = [0; 32];
|
||||
|
||||
hasher
|
||||
.hash_password_into(
|
||||
passphrase.as_bytes(),
|
||||
// While this is technically a Nonce reuse, it should not be a problem
|
||||
// since the encryption key is never shared or stored anywhere.
|
||||
SEED_SCHEME,
|
||||
&mut encryption_key,
|
||||
)
|
||||
// There shouldn't be any error, as we use the default params.
|
||||
.unwrap();
|
||||
|
||||
encryption_key
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::time::Instant;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_seed() {
|
||||
let seed = generate_seed();
|
||||
let passphrase = generate_4words_passphrase();
|
||||
|
||||
let encrypted_seed_file = encrypt_seed(&seed, &passphrase);
|
||||
|
||||
dbg!(&encrypted_seed_file);
|
||||
|
||||
let start = Instant::now();
|
||||
let decrypted_seed = decrypt_seed(encrypted_seed_file, &passphrase)
|
||||
.expect("Failde to decrypt the seed file");
|
||||
|
||||
assert!(
|
||||
start.elapsed().as_millis() > 300,
|
||||
"decrypting the seed shouldn't be too fast"
|
||||
);
|
||||
assert_eq!(decrypted_seed, seed);
|
||||
}
|
||||
}
|
||||
9
kytes/src/error.rs
Normal file
9
kytes/src/error.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! Main Crate Error
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// Kytes crate error enum.
|
||||
pub enum Error {
|
||||
/// For starter, to remove as code matures.
|
||||
#[error("Generic error: {0}")]
|
||||
Generic(String),
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
#[allow(unused)]
|
||||
#![allow(unused)]
|
||||
mod crypto;
|
||||
mod passphrase;
|
||||
mod error;
|
||||
|
||||
// Exports
|
||||
pub use crate::error::Error;
|
||||
|
||||
// Alias Result to be the crate Result.
|
||||
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
||||
|
||||
@@ -1,8 +1,66 @@
|
||||
mod passphrase;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use passphrase::generate_4words_passphrase;
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
/// Optional name to operate on
|
||||
name: Option<String>,
|
||||
|
||||
/// Sets a custom config file
|
||||
// #[arg(short, long, value_name = "FILE")]
|
||||
// config: Option<PathBuf>,
|
||||
|
||||
/// Turn debugging information on
|
||||
#[arg(short, long, action = clap::ArgAction::Count)]
|
||||
debug: u8,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// does testing things
|
||||
Test {
|
||||
/// lists test values
|
||||
#[arg(short, long)]
|
||||
list: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
println!("{}", generate_4words_passphrase());
|
||||
let cli = Cli::parse();
|
||||
|
||||
// You can check the value provided by positional arguments, or option arguments
|
||||
if let Some(name) = cli.name.as_deref() {
|
||||
println!("Value for name: {name}");
|
||||
}
|
||||
|
||||
// if let Some(config_path) = cli.config.as_deref() {
|
||||
// println!("Value for config: {}", config_path.display());
|
||||
// }
|
||||
|
||||
// You can see how many times a particular flag or argument occurred
|
||||
// Note, only flags can have multiple occurrences
|
||||
match cli.debug {
|
||||
0 => println!("Debug mode is off"),
|
||||
1 => println!("Debug mode is kind of on"),
|
||||
2 => println!("Debug mode is on"),
|
||||
_ => println!("Don't be crazy"),
|
||||
}
|
||||
|
||||
// You can check for the existence of subcommands, and if found use their
|
||||
// matches just as you would the top level cmd
|
||||
match &cli.command {
|
||||
Some(Commands::Test { list }) => {
|
||||
if *list {
|
||||
println!("Printing testing lists...");
|
||||
} else {
|
||||
println!("Not printing testing lists...");
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
// Continued program logic goes here...
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user