From ee549b64756b5921b36d174daa4aef1699d0e96a Mon Sep 17 00:00:00 2001 From: Firstero Date: Fri, 26 Apr 2024 13:30:23 +0800 Subject: [PATCH] v1-11-chacha20poly1305 --- .pre-commit-config.yaml | 2 + Cargo.lock | 83 ++++++++++++++++++++++++++++ Cargo.toml | 1 + fixtures/blake3.key | 2 +- fixtures/blake3.txt | 2 +- fixtures/chacha20poly1305.key | 1 + fixtures/chacha20poly1305.nonce | 1 + src/cli/text.rs | 24 ++++++++ src/lib.rs | 4 +- src/main.rs | 27 +++++++-- src/process/mod.rs | 2 +- src/process/text.rs | 97 +++++++++++++++++++++++++++++++-- 12 files changed, 229 insertions(+), 17 deletions(-) create mode 100644 fixtures/chacha20poly1305.key create mode 100644 fixtures/chacha20poly1305.nonce diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dee6400..9b4d223 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,5 @@ fail_fast: false +exclude: '^/fixtures/*' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 @@ -9,6 +10,7 @@ repos: - id: check-symlinks - id: check-yaml - id: end-of-file-fixer + exclude: ^fixtures/ - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/psf/black diff --git a/Cargo.lock b/Cargo.lock index d6d43fa..fffbeda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -144,6 +154,41 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "4.5.4" @@ -218,6 +263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -473,6 +519,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "itertools" version = "0.10.5" @@ -533,6 +588,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "pkcs8" version = "0.10.2" @@ -549,6 +610,17 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -622,6 +694,7 @@ dependencies = [ "anyhow", "base64", "blake3", + "chacha20poly1305", "clap", "csv", "ed25519", @@ -828,6 +901,16 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index ea32976..af757f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT" anyhow = "1.0.82" base64 = "0.22.0" blake3 = "1.5.1" +chacha20poly1305 = "0.10.1" clap = { version = "4.5.4", features = ["derive"] } csv = "1.3.0" ed25519 = "2.2.3" diff --git a/fixtures/blake3.key b/fixtures/blake3.key index a82dde9..878c9c7 100644 --- a/fixtures/blake3.key +++ b/fixtures/blake3.key @@ -1 +1 @@ -xUSZqzRNnuCE%~FWTh?axY8LuE*Mb4D4 +xUSZqzRNnuCE%~FWTh?axY8LuE*Mb4D4 \ No newline at end of file diff --git a/fixtures/blake3.txt b/fixtures/blake3.txt index 6494781..b5be0fd 100644 --- a/fixtures/blake3.txt +++ b/fixtures/blake3.txt @@ -1 +1 @@ -2PVnPNxWEbfdPuLMMmjbwBL5e6B1LFBD +2PVnPNxWEbfdPuLMMmjbwBL5e6B1LFBD \ No newline at end of file diff --git a/fixtures/chacha20poly1305.key b/fixtures/chacha20poly1305.key new file mode 100644 index 0000000..fce5a32 --- /dev/null +++ b/fixtures/chacha20poly1305.key @@ -0,0 +1 @@ +%XUw4H7JwLXQZ7RdCB5nhyGWp7eZCYvJ \ No newline at end of file diff --git a/fixtures/chacha20poly1305.nonce b/fixtures/chacha20poly1305.nonce new file mode 100644 index 0000000..8467662 --- /dev/null +++ b/fixtures/chacha20poly1305.nonce @@ -0,0 +1 @@ +~4Agy2Tvb8Ja \ No newline at end of file diff --git a/src/cli/text.rs b/src/cli/text.rs index 4db0909..76da761 100644 --- a/src/cli/text.rs +++ b/src/cli/text.rs @@ -16,6 +16,10 @@ pub enum TextSubCommand { Verify(TextVerifyOpts), #[command(name = "generate", about = "Generate random key.")] Generate(TextKeyGenerateOpts), + #[command(name = "encrypt", about = "Encrypt text with public key.")] + Encrypt(TextEncryptOpts), + #[command(name = "decrypt", about = "Decrypt text with private key.")] + Decrypt(TextDecryptOpts), } #[derive(Debug, Parser)] @@ -48,6 +52,26 @@ pub struct TextVerifyOpts { pub format: TextSignFormat, } +#[derive(Debug, Parser)] +pub struct TextEncryptOpts { + #[arg(short, long, value_parser=parse_input_file, default_value="-", help = "input file path, or '-' for stdin")] + pub input: String, + #[arg(short, long, value_parser=parse_input_file, help = "key file path, or '-' for stdin")] + pub key: String, + #[arg(short, long, value_parser=parse_input_file, help = "key file path, or '-' for stdin")] + pub nonce: String, +} + +#[derive(Debug, Parser)] +pub struct TextDecryptOpts { + #[arg(short, long, value_parser=parse_input_file, default_value="-", help = "input file path, or '-' for stdin")] + pub input: String, + #[arg(short, long, value_parser=parse_input_file, help = "key file path, or '-' for stdin")] + pub key: String, + #[arg(short, long, value_parser=parse_input_file, help = "key file path, or '-' for stdin")] + pub nonce: String, +} + #[derive(Debug, Clone, Copy)] pub enum TextSignFormat { Blake3, diff --git a/src/lib.rs b/src/lib.rs index f13c8fe..f7e16c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ pub use cli::{ TextSubCommand, }; pub use process::{ - process_b64decode, process_b64encode, process_csv, process_generate, process_genpass, - process_sign, process_verify, + process_b64decode, process_b64encode, process_csv, process_decrypt, process_encrypt, + process_generate, process_genpass, process_sign, process_verify, }; pub use utils::{get_content, get_reader}; diff --git a/src/main.rs b/src/main.rs index 7883368..beca514 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,13 @@ -use std::fs; - use anyhow::Result; -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, read::DecoderReader, Engine}; use clap::Parser; +use std::fs; use zxcvbn::zxcvbn; use rcli::{ - get_content, get_reader, process_b64decode, process_b64encode, process_csv, process_generate, - process_genpass, process_sign, process_verify, Base64SubCommand, Opts, SubCommand, - TextSignFormat, TextSubCommand, + get_content, get_reader, process_b64decode, process_b64encode, process_csv, process_decrypt, + process_encrypt, process_generate, process_genpass, process_sign, process_verify, + Base64SubCommand, Opts, SubCommand, TextSignFormat, TextSubCommand, }; // usage: @@ -83,6 +82,22 @@ fn main() -> Result<()> { } } } + TextSubCommand::Encrypt(opts) => { + let mut reader = get_reader(&opts.input)?; + let key = get_content(&opts.key)?; + let nonce = get_content(&opts.nonce)?; + let encrypted = process_encrypt(&mut reader, &key, &nonce)?; + println!("{}", URL_SAFE_NO_PAD.encode(encrypted)); + } + TextSubCommand::Decrypt(opts) => { + let reader = get_reader(&opts.input)?; + let mut reader = DecoderReader::new(reader, &URL_SAFE_NO_PAD); + // 创建一个新的 reader,应用 URL_SAFE_NO_PAD 解码 + let key = get_content(&opts.key)?; + let nonce = get_content(&opts.nonce)?; + let decrypted = process_decrypt(&mut reader, &key, &nonce)?; + println!("{}", String::from_utf8(decrypted)?); + } }, } Ok(()) diff --git a/src/process/mod.rs b/src/process/mod.rs index a5dbce6..5f6a460 100644 --- a/src/process/mod.rs +++ b/src/process/mod.rs @@ -7,4 +7,4 @@ pub use base64_processor::process_decode as process_b64decode; pub use base64_processor::process_encode as process_b64encode; pub use csv_processor::process as process_csv; pub use genpass_processor::process as process_genpass; -pub use text::{process_generate, process_sign, process_verify}; +pub use text::{process_decrypt, process_encrypt, process_generate, process_sign, process_verify}; diff --git a/src/process/text.rs b/src/process/text.rs index 8186b7f..d56f430 100644 --- a/src/process/text.rs +++ b/src/process/text.rs @@ -1,13 +1,15 @@ use crate::process_genpass; use crate::TextSignFormat; -use anyhow::anyhow; -use anyhow::Result; +use anyhow::{anyhow, Result}; +use chacha20poly1305::aead::generic_array::GenericArray; +use chacha20poly1305::{ + aead::{Aead, AeadCore, KeyInit, OsRng}, + ChaCha20Poly1305, Nonce, +}; use ed25519::signature::{Signer, Verifier}; use ed25519_dalek::{Signature, SigningKey, VerifyingKey}; -use rand::rngs::OsRng; use std::io::Read; use std::vec; - trait TextSign { fn sign(&self, reader: &mut dyn Read) -> Result>; } @@ -20,6 +22,14 @@ trait KeyGenerator { fn generate() -> Result>>; } +trait Encrypt { + fn encrypt(&self, reader: &mut dyn Read) -> Result>; +} + +trait Decrypt { + fn decrypt(&self, reader: &mut dyn Read) -> Result>; +} + struct Blake3 { key: [u8; 32], } @@ -52,7 +62,6 @@ impl TextSign for Blake3 { impl KeyGenerator for Blake3 { fn generate() -> Result>> { let ret = process_genpass(false, false, false, false, 32); - println!("{:?}", ret); Ok(vec![ret]) } } @@ -93,7 +102,7 @@ impl TextSign for Ed25519Signer { impl KeyGenerator for Ed25519Signer { fn generate() -> Result>> { - let mut rng = OsRng; + let mut rng = rand::rngs::OsRng; let signing_key = SigningKey::generate(&mut rng); let verifying_key = VerifyingKey::from(&signing_key); Ok(vec![ @@ -128,6 +137,59 @@ impl TextVerify for Ed25519Verifier { Ok(ret) } } + +struct ChaCha20Poly1305cryptor { + nonce: Nonce, + cipher: ChaCha20Poly1305, +} + +impl ChaCha20Poly1305cryptor { + pub fn new(key: &[u8], nonce: Nonce) -> Self { + Self { + cipher: ChaCha20Poly1305::new(key.into()), + nonce, + } + } + + pub fn try_new(key: impl AsRef<[u8]>, nonce: impl AsRef<[u8]>) -> Result { + let key = key.as_ref(); + let nonce = Nonce::from_slice(nonce.as_ref()); + Ok(Self::new(key, *nonce)) + } +} + +impl KeyGenerator for ChaCha20Poly1305cryptor { + fn generate() -> Result>> { + let key = ChaCha20Poly1305::generate_key(&mut OsRng); + let nonce: GenericArray = ChaCha20Poly1305::generate_nonce(&mut OsRng); + Ok(vec![key.to_vec(), nonce.to_vec()]) + } +} + +impl Encrypt for ChaCha20Poly1305cryptor { + fn encrypt(&self, reader: &mut dyn Read) -> Result> { + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + let ciphertext = self + .cipher + .encrypt(&self.nonce, data.as_ref()) + .map_err(|v| anyhow!(format!("encrypt error, {}", v)))?; + Ok(ciphertext) + } +} + +impl Decrypt for ChaCha20Poly1305cryptor { + fn decrypt(&self, reader: &mut dyn Read) -> Result> { + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + let plaintext = self + .cipher + .decrypt(&self.nonce, data.as_ref()) + .map_err(|v| anyhow!(format!("decrypt error, {}", v)))?; + Ok(plaintext) + } +} + /// 根据format 调用不同的signer, 依据种子key对输入文本进行签名 pub fn process_sign(reader: &mut dyn Read, key: &[u8], format: TextSignFormat) -> Result> { let signer: Box = match format { @@ -159,6 +221,18 @@ pub fn process_generate(format: TextSignFormat) -> Result>> { } } +// 加密 +pub fn process_encrypt(reader: &mut dyn Read, key: &[u8], nonce: &[u8]) -> Result> { + let encrpytor = ChaCha20Poly1305cryptor::try_new(key, nonce)?; + encrpytor.encrypt(reader) +} + +// 解密 +pub fn process_decrypt(reader: &mut dyn Read, key: &[u8], nonce: &[u8]) -> Result> { + let decrypter = ChaCha20Poly1305cryptor::try_new(key, nonce)?; + decrypter.decrypt(reader) +} + // 生成测试用例 #[cfg(test)] mod tests { @@ -188,4 +262,15 @@ mod tests { assert!(ret.is_ok()); Ok(()) } + + // 使用 fiturex/chacha20poly1305.key 和 fiturex/chacha20poly1305.nonce 测试 chacha20poly1305Encryptor + #[test] + fn test_process_encrypt() -> Result<()> { + let key: &[u8] = include_bytes!("../../fixtures/chacha20poly1305.key"); + let nonce: &[u8] = include_bytes!("../../fixtures/chacha20poly1305.nonce"); + let encrypted = process_encrypt(&mut "hello,world!".as_bytes(), key, nonce)?; + let decrypted = process_decrypt(&mut encrypted.as_slice(), key, nonce)?; + assert_eq!("hello,world!".as_bytes(), decrypted); + Ok(()) + } }