happy_cracking/crypto/
base85.rs1use anyhow::{Context, Result};
2use clap::Subcommand;
3
4#[derive(Subcommand)]
5pub enum Base85Action {
6 #[command(about = "Encode to Base85 (Ascii85)")]
7 Encode {
8 #[arg(help = "Input text")]
9 input: String,
10 },
11 #[command(about = "Decode from Base85 (Ascii85)")]
12 Decode {
13 #[arg(help = "Base85 encoded string")]
14 input: String,
15 },
16}
17
18pub fn run(action: Base85Action) -> Result<()> {
19 match action {
20 Base85Action::Encode { input } => {
21 println!("{}", encode(&input));
22 }
23 Base85Action::Decode { input } => {
24 println!("{}", decode(&input)?);
25 }
26 }
27 Ok(())
28}
29
30pub fn encode(input: &str) -> String {
31 let bytes = input.as_bytes();
32 if bytes.is_empty() {
33 return String::new();
34 }
35
36 let mut result = String::new();
37 let chunks = bytes.chunks(4);
38
39 for chunk in chunks {
40 if chunk.len() == 4 {
41 let value = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
42 if value == 0 {
43 result.push('z');
44 } else {
45 let mut chars = [0u8; 5];
46 let mut v = value;
47 for i in (0..5).rev() {
48 chars[i] = (v % 85) as u8 + b'!';
49 v /= 85;
50 }
51 for c in &chars {
52 result.push(*c as char);
53 }
54 }
55 } else {
56 let mut padded = [0u8; 4];
58 padded[..chunk.len()].copy_from_slice(chunk);
59 let value = u32::from_be_bytes(padded);
60 let mut chars = [0u8; 5];
61 let mut v = value;
62 for i in (0..5).rev() {
63 chars[i] = (v % 85) as u8 + b'!';
64 v /= 85;
65 }
66 let output_len = chunk.len() + 1;
68 for c in &chars[..output_len] {
69 result.push(*c as char);
70 }
71 }
72 }
73
74 result
75}
76
77pub fn decode(input: &str) -> Result<String> {
78 let input = input.trim();
79 if input.is_empty() {
80 return Ok(String::new());
81 }
82
83 let mut bytes = Vec::new();
84 let chars: Vec<u8> = input.bytes().collect();
85 let mut i = 0;
86
87 while i < chars.len() {
88 if chars[i] == b'z' {
89 bytes.extend_from_slice(&[0, 0, 0, 0]);
90 i += 1;
91 } else {
92 let remaining = chars.len() - i;
93 let block_len = remaining.min(5);
94
95 if block_len < 2 {
96 anyhow::bail!("Invalid Base85 input: incomplete block");
97 }
98
99 let mut block = [b'u'; 5];
101 for j in 0..block_len {
102 let c = chars[i + j];
103 if !(b'!'..=b'u').contains(&c) {
104 anyhow::bail!("Invalid Base85 character: {}", c as char);
105 }
106 block[j] = c;
107 }
108
109 let mut value: u64 = 0;
110 for &b in &block {
111 value = value * 85 + (b - b'!') as u64;
112 }
113
114 if value > u32::MAX as u64 {
115 anyhow::bail!("Invalid Base85 input: block value overflow");
116 }
117
118 let decoded = (value as u32).to_be_bytes();
119
120 if block_len == 5 {
121 bytes.extend_from_slice(&decoded);
122 } else {
123 let output_len = block_len - 1;
125 bytes.extend_from_slice(&decoded[..output_len]);
126 }
127
128 i += block_len;
129 }
130 }
131
132 String::from_utf8(bytes).context("Decoded data is not valid UTF-8")
133}