Skip to content

Commit

Permalink
feat: suppot to encrypt and decrypt text from stdin of file with chac…
Browse files Browse the repository at this point in the history
…ha20poly1305.

The nonce would be randomly generated and add to the head of output.
If not specify key, the key wound be randomly generated and add before the nonce.
  • Loading branch information
hedon954 committed May 10, 2024
1 parent 7fa7261 commit 6d0cd09
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 59 deletions.
34 changes: 16 additions & 18 deletions src/cli/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ pub enum TextSubCommand {
Verify(TextVerifyOpts),
#[command(name = "genkey", about = "Generate a pair of key")]
GenKey(TextGenKeyOpts),
#[command(about = "encrypt text with chacha20poly1305 and output in base64")]
#[command(about = "Encrypt text with chacha20poly1305 and output in base64")]
Encrypt(TextEncryptOpts),
#[command(about = "decrypt the base64 text with chacha20poly1305")]
#[command(about = "Decrypt the base64 text with chacha20poly1305")]
Decrypt(TextDecryptOpts),
}

Expand Down Expand Up @@ -74,26 +74,24 @@ pub struct TextGenKeyOpts {

#[derive(Debug, Parser)]
pub struct TextEncryptOpts {
/// The key for chacha30poly1305.
#[arg(long, required = true)]
/// The key for chacha30poly1305, if not specifies,
/// it would be randomly generated than set at the head of the response.
#[arg(long, default_value = "-")]
pub key: String,
/// The unique nonce.
#[arg(long, default_value = "unique nonce")]
pub nonce: String,
/// The text to encrypt.
/// The text to encrypt,
/// support both stdin[-] and file.
#[arg(value_parser = verify_file, default_value = "-")]
pub text: String,
}

#[derive(Debug, Parser)]
pub struct TextDecryptOpts {
/// The key for chacha30poly1305.
#[arg(long, required = true)]
/// The key for chacha30poly1305,
/// if not specifies means it's in the head of the text.
#[arg(long, default_value = "-")]
pub key: String,
/// The unique nonce.
#[arg(long, default_value = "unique nonce")]
pub nonce: String,
/// The base64 text to decrypt.
/// The base64 text to decrypt,
/// support both stdin[-] and file.
#[arg(value_parser = verify_file, default_value = "-")]
pub text: String,
}
Expand Down Expand Up @@ -136,8 +134,8 @@ impl CmdExector for TextEncryptOpts {
async fn execute(self) -> anyhow::Result<()> {
let text = get_input(&self.text)?;
println!(
"{}",
process_encrypt_text(self.key, self.nonce, text.as_slice())?
"\n{}",
process_encrypt_text(self.key.as_bytes(), text.as_slice())?
);
Ok(())
}
Expand All @@ -147,8 +145,8 @@ impl CmdExector for TextDecryptOpts {
async fn execute(self) -> anyhow::Result<()> {
let text = get_input(&self.text)?;
println!(
"{}",
process_decrypt_text(self.key, self.nonce, text.as_slice())?
"\n{}",
process_decrypt_text(self.key.as_bytes(), text.as_slice())?
);
Ok(())
}
Expand Down
8 changes: 7 additions & 1 deletion src/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod http_serve;
pub mod text;
pub mod time;

use std::io::Read;

pub use b64::{process_decode, process_encode};
pub use csv_convert::process_csv;
pub use gen_pass::process_genpass;
Expand All @@ -15,7 +17,11 @@ pub use time::process_unix_to_string;
use crate::utils;

pub fn get_input(input: &str) -> anyhow::Result<Vec<u8>> {
let mut reader = utils::get_reader(input)?;
let reader = utils::get_reader(input)?;
get_content(reader)
}

pub fn get_content(mut reader: impl Read) -> anyhow::Result<Vec<u8>> {
let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;
Ok(buf)
Expand Down
2 changes: 1 addition & 1 deletion src/process/text/blake3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Blake3 {
pub fn try_new(key: &[u8]) -> anyhow::Result<Self> {
if key.len() < KEY_LENGTH {
return Err(anyhow!(
"[invalid key] the length of `key` cannot be less than 32"
"[invalid key] the length of `key` cannot be less than {KEY_LENGTH}"
));
}
let key = &key[..KEY_LENGTH];
Expand Down
104 changes: 104 additions & 0 deletions src/process/text/chacha20_poly1305.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use std::io::Read;

use crate::process::get_content;
use anyhow::anyhow;
use chacha20poly1305::{
aead::{Aead, AeadCore, KeyInit, OsRng},
ChaCha20Poly1305, Key, Nonce,
};

use super::decrypt::Decrypt;
use super::encrypt::Encrypt;

pub const KEY_LENGTH: usize = 32;
pub const NONCE_LENGTH: usize = 12;
pub const RAND_FLAG: &[u8] = b"-";

pub struct Chacha20Poly1305Encryptor {
key: Key,
cipher: ChaCha20Poly1305,
}

pub struct Chacha20Poly1305Decryptor {
cipher: ChaCha20Poly1305,
}

impl Encrypt for Chacha20Poly1305Encryptor {
fn encrypt(&self, reader: &mut dyn Read) -> anyhow::Result<Vec<u8>> {
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng); // 96-bits; unique per message
let buf = get_content(reader)?;
let res = self
.cipher
.encrypt(&nonce, buf.as_slice())
.map_err(|e| anyhow!("encrypt failed, {}", e))?;

// nonce + encrypted_text
let mut nonce = nonce.as_slice().to_vec();
assert_eq!(nonce.len(), NONCE_LENGTH);
nonce.extend(res);
Ok(nonce)
}
}

impl Chacha20Poly1305Decryptor {
pub fn new(key: Key) -> Self {
Self {
cipher: ChaCha20Poly1305::new(&key),
}
}

pub fn try_new(key: &[u8]) -> anyhow::Result<Self> {
Ok(Self::new(get_key(key)?))
}
}

impl Chacha20Poly1305Encryptor {
pub fn new(key: Key) -> Self {
Self {
key,
cipher: ChaCha20Poly1305::new(&key),
}
}

pub fn try_new(key: &[u8]) -> anyhow::Result<Self> {
Ok(Self::new(get_key(key)?))
}

pub fn get_key(&self) -> Vec<u8> {
self.key.to_vec()
}
}

impl Decrypt for Chacha20Poly1305Decryptor {
fn decrypt(&self, reader: &mut dyn Read) -> anyhow::Result<Vec<u8>> {
let content = get_content(reader)?;
if content.len() <= NONCE_LENGTH {
return Err(anyhow!("invalid input"));
}

// nonce + encrypted_text
let nonce = &content[..NONCE_LENGTH];
let content = &content[NONCE_LENGTH..];

let nonce = Nonce::from_slice(nonce);
let res = self
.cipher
.decrypt(nonce, content)
.map_err(|e| anyhow!("decrypt failed, {}", e))?;
Ok(res)
}
}

fn get_key(key: &[u8]) -> anyhow::Result<Key> {
if key == RAND_FLAG {
let key = ChaCha20Poly1305::generate_key(&mut OsRng);
return Ok(key);
}
if key.len() < KEY_LENGTH {
return Err(anyhow!(
"[invalid key] the length of `key` cannot be less than {KEY_LENGTH}"
));
}
let key = &key[..KEY_LENGTH];
Ok(*Key::from_slice(key))
}
7 changes: 1 addition & 6 deletions src/process/text/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,5 @@ use std::io::Read;

pub trait Decrypt {
/// Decrypt the base64 data from the reader and return the origin text.
fn descypt(
&self,
key: &[u8; 32],
nonce: &[u8; 12],
reader: &mut dyn Read,
) -> anyhow::Result<Vec<u8>>;
fn decrypt(&self, reader: &mut dyn Read) -> anyhow::Result<Vec<u8>>;
}
10 changes: 5 additions & 5 deletions src/process/text/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ impl KeyLoader for Ed25519Signer {
impl TextVerify for Ed25519Verifier {
fn verify(&self, mut reader: impl Read, sig: &[u8]) -> anyhow::Result<bool> {
if sig.len() < SIG_LENGTH {
return Err(anyhow!(format!(
"[invalid signature] the length of `sig` cannot be less than 64"
)));
return Err(anyhow!(
"[invalid signature] the length of `sig` cannot be less than {SIG_LENGTH}"
));
}
let sig = &sig[..SIG_LENGTH];
let signature = Signature::from_bytes(sig.try_into()?);
Expand Down Expand Up @@ -101,9 +101,9 @@ impl Ed25519Verifier {

pub fn try_new(key: &[u8]) -> anyhow::Result<Self> {
if key.len() < KEY_LENGTH {
return Err(anyhow!(format!(
return Err(anyhow!(
"[invalid key] the length of `key` cannot be less than {KEY_LENGTH}"
)));
));
}
let key = &key[..KEY_LENGTH];
let key = key.try_into()?;
Expand Down
7 changes: 1 addition & 6 deletions src/process/text/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,5 @@ use std::io::Read;

pub trait Encrypt {
/// Encrypt the text from the reader, then base64 the encrypted text and return it.
fn enscypt(
&self,
key: &[u8; 32],
nonce: &[u8; 12],
reader: &mut dyn Read,
) -> anyhow::Result<Vec<u8>>;
fn encrypt(&self, reader: &mut dyn Read) -> anyhow::Result<Vec<u8>>;
}
66 changes: 44 additions & 22 deletions src/process/text/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod blake3;
pub mod chacha20_poly1305;
pub mod decrypt;
pub mod ed25519;
pub mod encrypt;
Expand All @@ -9,17 +10,18 @@ pub mod verify;

use anyhow::{anyhow, Ok};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use chacha20poly1305::{
aead::{Aead, KeyInit},
ChaCha20Poly1305, Key, Nonce,
};
use colored::*;

use crate::{cli::TextSignFormat, utils::get_reader};

use self::{
blake3::Blake3,
chacha20_poly1305::{
Chacha20Poly1305Decryptor, Chacha20Poly1305Encryptor, NONCE_LENGTH, RAND_FLAG,
},
decrypt::Decrypt,
ed25519::{Ed25519Signer, Ed25519Verifier},
encrypt::Encrypt,
keygenerator::KeyGenerator,
keyloader::KeyLoader,
sign::TextSign,
Expand Down Expand Up @@ -70,28 +72,39 @@ pub fn process_text_gen_key(format: TextSignFormat) -> anyhow::Result<Vec<Vec<u8
}
}

pub fn process_encrypt_text(key: String, nonce: String, text: &[u8]) -> anyhow::Result<String> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(key.as_bytes()));
let nonce = Nonce::from_slice(nonce.as_bytes()); // 96-bits; unique per message
let ciphertext = cipher
.encrypt(nonce, text)
.map_err(|e| anyhow!("encrypt error: {}", e))?;
Ok(URL_SAFE_NO_PAD.encode(ciphertext))
pub fn process_encrypt_text(key: &[u8], mut text: &[u8]) -> anyhow::Result<String> {
let en = Chacha20Poly1305Encryptor::try_new(key)?;
let mut res = en.encrypt(&mut text)?;
if key == RAND_FLAG {
let mut tmp = en.get_key();
tmp.extend(res);
res = tmp;
}
Ok(URL_SAFE_NO_PAD.encode(res))
}

pub fn process_decrypt_text(key: String, nonce: String, text: &[u8]) -> anyhow::Result<String> {
pub fn process_decrypt_text(key: &[u8], text: &[u8]) -> anyhow::Result<String> {
let binary = URL_SAFE_NO_PAD.decode(text)?;
let cipher = ChaCha20Poly1305::new(Key::from_slice(key.as_bytes()));
let nonce = Nonce::from_slice(nonce.as_bytes()); // 96-bits; unique per message
let res = cipher
.decrypt(nonce, binary.as_ref())
.map_err(|e| anyhow!("decrypt error: {}", e))?;
let de;
let res;
if key == RAND_FLAG {
if binary.len() <= KEY_LENGTH + NONCE_LENGTH {
return Err(anyhow!("[invalid text] not contains key or nonce"));
}
de = Chacha20Poly1305Decryptor::try_new(&binary[..KEY_LENGTH])?;
let mut tmp = &binary[KEY_LENGTH..];
res = de.decrypt(&mut tmp)?;
} else {
de = Chacha20Poly1305Decryptor::try_new(key)?;
res = de.decrypt(&mut binary.as_slice())?;
}

Ok(String::from_utf8(res)?)
}

#[cfg(test)]
mod tests {
use crate::process::text::{process_encrypt_text, TextVerify};
use crate::process::text::{chacha20_poly1305::RAND_FLAG, process_encrypt_text, TextVerify};

use super::{process_decrypt_text, Blake3, Ed25519Signer, Ed25519Verifier, TextSign};

Expand Down Expand Up @@ -120,11 +133,20 @@ mod tests {

#[test]
fn test_encrypt_decrypt() -> anyhow::Result<()> {
let key = "noncenoncenoncenoncenoncenonce11".to_string();
let nonce = "noncenonce11".to_string();
let res = process_encrypt_text(key.clone(), nonce.clone(), b"1")?;
let key = b"noncenoncenoncenoncenoncenonce11";
let res = process_encrypt_text(key, b"1")?;
println!("{}", res);
let res = process_decrypt_text(key, res.as_bytes())?;
println!("{}", res);
Ok(())
}

#[test]
fn test_encrypt_decrypt_without_key() -> anyhow::Result<()> {
let key = RAND_FLAG;
let res = process_encrypt_text(key, b"1")?;
println!("{}", res);
let res = process_decrypt_text(key.clone(), nonce.clone(), res.as_bytes())?;
let res = process_decrypt_text(key, res.as_bytes())?;
println!("{}", res);
Ok(())
}
Expand Down

0 comments on commit 6d0cd09

Please sign in to comment.