mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2026-02-04 06:42:11 +00:00
refactor: rename cli to crypt
This commit is contained in:
17
crypt/Cargo.toml
Normal file
17
crypt/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "crypt"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
aes.workspace = true
|
||||
cipher-core.workspace = true
|
||||
cipher-factory = { workspace = true, features = ["clap"] }
|
||||
clap.workspace = true
|
||||
color-eyre.workspace = true
|
||||
des.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
54
crypt/src/args.rs
Normal file
54
crypt/src/args.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use cipher_factory::{Algorithm, CipherContext, OperationMode, OutputFormat};
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// Operation to perform
|
||||
#[arg(value_name = "OPERATION")]
|
||||
pub operation: OperationMode,
|
||||
|
||||
/// Encryption algorithm
|
||||
#[arg(short, long)]
|
||||
pub algorithm: Algorithm,
|
||||
|
||||
/// Key used for encryption/decryption (hex string, e.g., 0x2b7e...)
|
||||
#[arg(short, long, required = true)]
|
||||
pub key: String,
|
||||
|
||||
/// Initialization vector for CBC mode (hex string, e.g., 0x0001...)
|
||||
#[arg(long)]
|
||||
pub iv: Option<String>,
|
||||
|
||||
/// The text to encrypt/decrypt (use --input-file for file input)
|
||||
#[arg(value_name = "TEXT", required_unless_present = "input_file")]
|
||||
pub text: Option<String>,
|
||||
|
||||
/// Input file to encrypt/decrypt
|
||||
#[arg(short, long, value_name = "FILE")]
|
||||
pub input_file: Option<PathBuf>,
|
||||
|
||||
/// Output file (defaults to stdout)
|
||||
#[arg(short, long, value_name = "FILE")]
|
||||
pub output_file: Option<PathBuf>,
|
||||
|
||||
/// Output format for decrypted data
|
||||
#[arg(short = 'f', long)]
|
||||
pub output_format: Option<OutputFormat>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
/// Creates a [`CipherContext`] for text-based operations.
|
||||
#[must_use]
|
||||
pub fn into_context(self, input_text: String) -> CipherContext {
|
||||
CipherContext {
|
||||
algorithm: self.algorithm,
|
||||
operation: self.operation,
|
||||
key: self.key,
|
||||
iv: self.iv,
|
||||
input_text,
|
||||
output_format: self.output_format.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
101
crypt/src/main.rs
Normal file
101
crypt/src/main.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
mod args;
|
||||
|
||||
use crate::args::Args;
|
||||
use aes::{AesCbc, Block128, Iv};
|
||||
use cipher_factory::{Algorithm, OperationMode};
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::{Result, eyre};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Write, stdout};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
let args = Args::parse();
|
||||
|
||||
// Check if we're doing file-based CBC operation
|
||||
if args.input_file.is_some() && args.algorithm == Algorithm::AesCbc {
|
||||
process_cbc_file(&args)?;
|
||||
} else {
|
||||
process_text(&args)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_text(args: &Args) -> Result<()> {
|
||||
let input_text = match (&args.text, &args.input_file) {
|
||||
(Some(text), None) => text.clone(),
|
||||
(None, Some(path)) => fs::read_to_string(path)?,
|
||||
(Some(_), Some(_)) => return Err(eyre!("Cannot specify both TEXT and --input-file")),
|
||||
(None, None) => return Err(eyre!("Must specify TEXT or --input-file")),
|
||||
};
|
||||
|
||||
let context = args.clone().into_context(input_text);
|
||||
let output = context.process()?;
|
||||
|
||||
write_output(args, output.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_cbc_file(args: &Args) -> Result<()> {
|
||||
let input_path = args
|
||||
.input_file
|
||||
.as_ref()
|
||||
.ok_or_else(|| eyre!("No input file"))?;
|
||||
let iv_str = args
|
||||
.iv
|
||||
.as_ref()
|
||||
.ok_or_else(|| eyre!("CBC mode requires --iv"))?;
|
||||
|
||||
let key = Block128::from_str(&args.key).map_err(|e| eyre!("Invalid key: {e}"))?;
|
||||
let iv = Iv::from_str(iv_str).map_err(|e| eyre!("Invalid IV: {e}"))?;
|
||||
|
||||
let cipher = AesCbc::new(key, iv);
|
||||
|
||||
match args.operation {
|
||||
OperationMode::Encrypt => {
|
||||
let plaintext = fs::read(input_path)?;
|
||||
let ciphertext = cipher
|
||||
.encrypt(&plaintext)
|
||||
.map_err(|e| eyre!("Encryption failed: {e}"))?;
|
||||
|
||||
if args.output_file.is_some() {
|
||||
// Write raw binary to file
|
||||
write_output(args, &ciphertext)?;
|
||||
} else {
|
||||
// Write hex to stdout
|
||||
let hex = ciphertext.iter().fold(String::new(), |mut acc, b| {
|
||||
use std::fmt::Write;
|
||||
let _ = write!(acc, "{b:02X}");
|
||||
acc
|
||||
});
|
||||
println!("{hex}");
|
||||
}
|
||||
}
|
||||
OperationMode::Decrypt => {
|
||||
let ciphertext = fs::read(input_path)?;
|
||||
let plaintext = cipher
|
||||
.decrypt(&ciphertext)
|
||||
.map_err(|e| eyre!("Decryption failed: {e}"))?;
|
||||
|
||||
write_output(args, &plaintext)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_output(args: &Args, data: &[u8]) -> Result<()> {
|
||||
if let Some(path) = &args.output_file {
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(data)?;
|
||||
} else {
|
||||
stdout().write_all(data)?;
|
||||
// Add newline if output doesn't end with one
|
||||
if !data.ends_with(b"\n") {
|
||||
println!();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user