From e400b87e9feeb7b90d18145686e99434fd34e2e7 Mon Sep 17 00:00:00 2001 From: sangge <2251250136@qq.com> Date: Fri, 1 Aug 2025 16:00:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E6=8C=91=E6=88=98=E5=B9=B6=E6=94=B9=E8=BF=9B=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重写p1实现纯Rust的hex到base64转换 - 完成p13 ECB剪切粘贴攻击和破解脚本 - 实现p33 Diffie-Hellman密钥交换算法 - 修复p9的PKCS#7测试用例 - 在common库添加gen_random_key和pkcs7_unpadding函数 - 更新workspace依赖管理 --- Cargo.lock | 115 ++++++++++++++------- Cargo.toml | 5 +- common/Cargo.toml | 3 +- common/src/lib.rs | 24 +++++ problems/p1/Cargo.toml | 3 +- problems/p1/src/main.rs | 105 ++++++++++++++++++-- problems/p11/src/main.rs | 9 +- problems/p12/src/main.rs | 10 +- problems/p13/Cargo.toml | 2 + problems/p13/crack.py | 42 ++++++++ problems/p13/src/main.rs | 131 +++++++++++++++++++++++- problems/p2/src/main.rs | 2 +- problems/p3/src/main.rs | 2 +- problems/p33/Cargo.toml | 11 +++ problems/p33/src/main.rs | 209 +++++++++++++++++++++++++++++++++++++++ problems/p9/Cargo.toml | 1 + problems/p9/src/main.rs | 58 +++++++++++ 17 files changed, 664 insertions(+), 68 deletions(-) create mode 100644 problems/p13/crack.py create mode 100644 problems/p33/Cargo.toml create mode 100644 problems/p33/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index e16a0af..c5edd3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,18 +8,18 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" - [[package]] name = "cfg-if" version = "1.0.1" @@ -31,6 +31,7 @@ name = "common" version = "0.1.0" dependencies = [ "anyhow", + "rand", ] [[package]] @@ -39,13 +40,12 @@ version = "0.1.0" [[package]] name = "getrandom" -version = "0.3.3" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "r-efi", "wasi", ] @@ -61,12 +61,46 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "rand", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "p1" version = "0.1.0" dependencies = [ - "base64", - "hex", + "anyhow", ] [[package]] @@ -100,10 +134,20 @@ dependencies = [ [[package]] name = "p13" version = "0.1.0" +dependencies = [ + "common", + "hex", +] [[package]] name = "p14" version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "common", + "rand", +] [[package]] name = "p15" @@ -139,6 +183,17 @@ dependencies = [ "hex", ] +[[package]] +name = "p33" +version = "0.1.0" +dependencies = [ + "anyhow", + "num-bigint", + "num-traits", + "once_cell", + "rand", +] + [[package]] name = "p4" version = "0.1.0" @@ -184,6 +239,9 @@ dependencies = [ [[package]] name = "p9" version = "0.1.0" +dependencies = [ + "anyhow", +] [[package]] name = "ppv-lite86" @@ -212,27 +270,22 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "rand" -version = "0.9.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" -version = "0.9.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", @@ -240,9 +293,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -266,21 +319,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "zerocopy" diff --git a/Cargo.toml b/Cargo.toml index bf4fef7..9c8aa7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,7 @@ members = ["problems/*", "common"] hex = "0.4.3" base64 = "0.22.1" anyhow = "1.0.98" -rand = "0.9.2" +rand = "0.8" +num-bigint = { version = "0.4", features = ["rand"] } +num-traits = "0.2" +once_cell = "1.21" diff --git a/common/Cargo.toml b/common/Cargo.toml index 085dcc3..f5ff82d 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] -anyhow = "1.0.98" +anyhow = { workspace = true } +rand = { workspace = true } diff --git a/common/src/lib.rs b/common/src/lib.rs index 283c671..71d3762 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use anyhow::{Result, anyhow}; +use rand::prelude::*; const SBOX: [u8; 256] = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, @@ -396,6 +397,22 @@ pub fn pkcs7_padding(data: &mut Vec, block_size: usize) { data.extend(vec![padding_length as u8; padding_length]); } +pub fn pkcs7_unpadding(input: &[u8]) -> Result> { + if input.is_empty() { + return Err(anyhow!("Input cannot be empty")); + } + let padding_length = *input.last().unwrap(); + if padding_length == 0 || padding_length > input.len() as u8 { + return Err(anyhow!("Invalid PKCS#7 padding")); + } + for &byte in input.iter().rev().take(padding_length as usize) { + if byte != padding_length { + return Err(anyhow!("Invalid PKCS#7 padding")); + } + } + Ok(input[..input.len() - padding_length as usize].to_vec()) +} + pub fn is_ecb(cipher: &[u8]) -> bool { // Check if the input is a valid ECB encrypted data let mut seen_blocks = HashSet::new(); @@ -458,3 +475,10 @@ pub fn xor_with_key(input: &[u8], key: &[u8]) -> Result> { .map(|(&a, &b)| a ^ b) .collect()) } + +pub fn gen_random_key() -> [u8; 16] { + let mut rng = rand::rng(); + let mut key = [0u8; 16]; + rng.fill(&mut key); + key +} diff --git a/problems/p1/Cargo.toml b/problems/p1/Cargo.toml index d5055a0..1d7aa9e 100644 --- a/problems/p1/Cargo.toml +++ b/problems/p1/Cargo.toml @@ -4,5 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] -hex = { workspace = true } -base64 = { workspace = true } +anyhow = { workspace = true } diff --git a/problems/p1/src/main.rs b/problems/p1/src/main.rs index d7d569b..08fd55c 100644 --- a/problems/p1/src/main.rs +++ b/problems/p1/src/main.rs @@ -1,13 +1,106 @@ -use base64::{Engine as _, engine::general_purpose::STANDARD}; +use anyhow::{Ok, Result, anyhow}; -fn hex_to_base64(hex_str: &str) -> String { - let bytes = hex::decode(hex_str).expect("解码失败"); - STANDARD.encode(&bytes) +const BASE_TABLE: [char; 64] = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '+', '/', +]; + +fn hex_to_base64(hex_str: &str) -> Result { + if hex_str.is_empty() { + return Ok("".to_string()); + } + let mut hex_str = hex_str.to_lowercase(); + if hex_str.len() % 2 == 1 { + hex_str.insert(0, '0'); + } + let mut hex_bytes: Vec = Vec::new(); + + let mut result = String::new(); + + for i in hex_str.as_bytes().chunks(2) { + let high = match i[0] { + b'0'..=b'9' => i[0] - b'0', + b'a'..=b'f' => i[0] - b'a' + 10, + b'A'..=b'F' => i[0] - b'A' + 10, + _ => return Err(anyhow!("Invalid hex character: {}", i[0] as char)), + }; + let low = match i[1] { + b'0'..=b'9' => i[1] - b'0', + b'a'..=b'f' => i[1] - b'a' + 10, + b'A'..=b'F' => i[1] - b'A' + 10, + _ => return Err(anyhow!("Invalid hex character: {}", i[0] as char)), + }; + + hex_bytes.push(high << 4 | low); + } + + for base_bytes in hex_bytes.chunks(3) { + match base_bytes.len() { + 1 => { + let index_1 = base_bytes[0] >> 2 & 0x3F; + let index_2 = (base_bytes[0] & 0x03) << 4; + + result.push(BASE_TABLE[index_1 as usize]); + result.push(BASE_TABLE[index_2 as usize]); + result.push('='); + result.push('='); + } + 2 => { + let index_1 = base_bytes[0] >> 2 & 0x3F; + let index_2 = (base_bytes[0] & 0x03) << 4 | (base_bytes[1] >> 4) & 0x0f; + let index_3 = (base_bytes[1] & 0x0F) << 2; + result.push(BASE_TABLE[index_1 as usize]); + result.push(BASE_TABLE[index_2 as usize]); + result.push(BASE_TABLE[index_3 as usize]); + result.push('='); + } + 3 => { + let index_1 = base_bytes[0] >> 2 & 0x3F; + let index_2 = (base_bytes[0] & 0x03) << 4 | (base_bytes[1] >> 4) & 0x0f; + let index_3 = (base_bytes[1] & 0x0F) << 2 | (base_bytes[2] >> 6) & 0x03; + let index_4 = base_bytes[2] & 0x3F; + result.push(BASE_TABLE[index_1 as usize]); + result.push(BASE_TABLE[index_2 as usize]); + result.push(BASE_TABLE[index_3 as usize]); + result.push(BASE_TABLE[index_4 as usize]); + } + _ => {} + } + } + Ok(result) } -fn main() { +fn main() -> Result<()> { // 从十六进制字符串解码为原始字节 let hex_str = "49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d"; - let base64_str = hex_to_base64(hex_str); + let base64_str = hex_to_base64(hex_str)?; println!("十六进制字符串: {base64_str}"); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; // 导入上层的函数 + #[test] + fn test_hex_to_base64_empty() { + let hex_str = ""; + let expected_base64 = ""; + assert_eq!(hex_to_base64(hex_str).unwrap(), expected_base64); + } + + #[test] + fn test_hex_to_base64_single_byte() { + let hex_str = "4a"; + let expected_base64 = "Sg=="; + assert_eq!(hex_to_base64(hex_str).unwrap(), expected_base64); + } + + #[test] + fn test_hex_to_base64_all_bytes() { + let hex_str = "000102ff"; + let expected_base64 = "AAEC/w=="; + assert_eq!(hex_to_base64(hex_str).unwrap(), expected_base64); + } } diff --git a/problems/p11/src/main.rs b/problems/p11/src/main.rs index 70ba68e..dac3403 100644 --- a/problems/p11/src/main.rs +++ b/problems/p11/src/main.rs @@ -1,13 +1,6 @@ -use common::{aes_cbc_enc, aes_ecb_enc, is_ecb, pkcs7_padding}; +use common::{aes_cbc_enc, aes_ecb_enc, gen_random_key, is_ecb, pkcs7_padding}; use rand::prelude::*; -fn gen_random_key() -> [u8; 16] { - let mut rng = rand::rng(); - let mut key = [0u8; 16]; - rng.fill(&mut key); - key -} - fn encryption_oracle(plaintext: &[u8]) -> Vec { let key = gen_random_key(); let mut rng = rand::rng(); diff --git a/problems/p12/src/main.rs b/problems/p12/src/main.rs index bde7810..583f11b 100644 --- a/problems/p12/src/main.rs +++ b/problems/p12/src/main.rs @@ -2,15 +2,7 @@ use std::process::exit; use anyhow::Result; use base64::{Engine, engine::general_purpose::STANDARD}; -use common::{aes_ecb_enc, pkcs7_padding}; -use rand::{prelude::*, rand_core::block}; - -fn gen_random_key() -> [u8; 16] { - let mut rng = rand::rng(); - let mut key = [0u8; 16]; - rng.fill(&mut key); - key -} +use common::{aes_ecb_enc, gen_random_key, pkcs7_padding}; fn oracle(controlled_input: &[u8], key: &[u8; 16], unknown_string: &[u8]) -> Vec { let mut data = Vec::new(); diff --git a/problems/p13/Cargo.toml b/problems/p13/Cargo.toml index 9e9121c..a303970 100644 --- a/problems/p13/Cargo.toml +++ b/problems/p13/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +common = { path = "../../common" } +hex = { workspace = true } diff --git a/problems/p13/crack.py b/problems/p13/crack.py new file mode 100644 index 0000000..9e07300 --- /dev/null +++ b/problems/p13/crack.py @@ -0,0 +1,42 @@ +from pwn import * + +# context.log_level = "debug" + +p = process("../../target/x86_64-unknown-linux-musl/debug/p13") +p.recvuntil(b"> ") # 等待提示符 + + +p.sendline(b"1") +p.recvuntil(b"email: ") +p.sendline(b"foo@bar.com12") +cipher = p.recvline().strip() +print(f"Cipher: {cipher}") + +role_cipher = cipher[32:64] + +p.sendline(b"1") +p.recvuntil(b"email: ") +p.sendline(b"foo@bar.co" + b"admin" + b"\x0b" * 11) +cipher = p.recvline().strip() +# admin_cipher = cipher[32:64] +print(f"Cipher: {cipher}") + +cracked_cipher = cipher[0:32] + role_cipher + cipher[32:] + +p.recvuntil(b"> ") +p.sendline(b"2") +p.recvuntil(b"cipher: ") +p.sendline(cracked_cipher) + +profile = p.recvline().strip() +print(f"Profile: {profile}") + +json = p.recvline().strip() +print(f"Json: {json}") + +if b"Cracked!" in json: + print("Success!") + + +p.recvuntil(b"> ") +# p.interactive() diff --git a/problems/p13/src/main.rs b/problems/p13/src/main.rs index e7a11a9..25229de 100644 --- a/problems/p13/src/main.rs +++ b/problems/p13/src/main.rs @@ -1,3 +1,130 @@ -fn main() { - println!("Hello, world!"); +use common::{aes_ecb_dec, aes_ecb_enc, gen_random_key, pkcs7_padding, pkcs7_unpadding}; +use std::fmt::Write as FmtWrite; +use std::io::Write as IoWrite; + +use std::io::{stdin, stdout}; + +fn kv_to_json(input: &str) -> String { + let mut json = String::new(); + if input.is_empty() { + return json; + } + + json.push('{'); + + for kv in input.split('&') { + if let Some((key, value)) = kv.split_once('=') { + if value.contains('=') { + todo!("Ignore here"); + } + if json.contains(key) { + todo!("Ignore here") + } + match value.parse::() { + Ok(num) => write!(json, "{key}: {num}, ").unwrap(), + Err(_) => write!(json, "{key}: \'{value}\', ").unwrap(), + } + } + } + json.pop(); + json.pop(); + json.push('}'); + json +} + +/// Generates a user profile string from an email address. +/// +/// # Example +/// ``` +/// let profile = profile_for("foo@bar.com"); +/// assert_eq!(profile, "email=foo@bar.com&uid=10&role=user"); +/// ``` +fn profile_for(email: &str) -> String { + let email = email.replace(['=', '&'], ""); + format!("email={email}&uid=10&role=user") +} + +fn enc_profile(profile: &str, key: &[u8; 16]) -> Vec { + let mut profile = profile.as_bytes().to_vec(); + pkcs7_padding(&mut profile, 16); + + aes_ecb_enc(&profile, key).unwrap() +} + +fn dec_profile(cipher: &[u8], key: &[u8; 16]) -> Vec { + let profile = aes_ecb_dec(cipher, key).unwrap(); + pkcs7_unpadding(&profile).unwrap() +} + +fn main() { + let key = gen_random_key(); + println!("input 1 for enc, input 2 for dec"); + loop { + print!("> "); + stdout().flush().unwrap(); // 强制刷新输出缓冲区 + + let mut option = String::new(); + stdin().read_line(&mut option).unwrap(); + let option = option.trim().parse::(); + match option { + Ok(1) => { + print!("email: "); + stdout().flush().unwrap(); + let mut email = String::new(); + stdin().read_line(&mut email).unwrap(); + let email = email.trim(); + let profile = profile_for(email); + let cipher = enc_profile(&profile, &key); + for i in cipher { + print!("{i:02x}"); + } + println!(); + } + Ok(2) => { + print!("cipher: "); + stdout().flush().unwrap(); + let mut cipher = String::new(); + stdin().read_line(&mut cipher).unwrap(); + let cipher = match hex::decode(cipher.trim()) { + Ok(bytes) => bytes, + Err(_) => { + println!("invalid cipher"); + continue; + } + }; + let profile = String::from_utf8(dec_profile(&cipher, &key)).unwrap(); + println!("{profile}"); + let json = kv_to_json(&profile); + if json.contains("role: 'admin'") { + println!("Cracked!"); + } else { + println!("{json}"); + } + } + _ => { + println!("input 1 for enc, input 2 for dec"); + continue; + } + } + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_kv_to_json() { + let test = kv_to_json("foo=bar&baz=qux&zap=zazzle"); + println!("{test}"); + let test = kv_to_json("foo=bar&baz=qux&zap=123"); + println!("{test}"); + let test = kv_to_json("foo=bar&baz=qux&zap=123a"); + println!("{test}"); + } + + #[test] + fn test_profile_for() { + let test = profile_for("1@2"); + println!("u{test}"); + } } diff --git a/problems/p2/src/main.rs b/problems/p2/src/main.rs index e7ebe59..d641a86 100644 --- a/problems/p2/src/main.rs +++ b/problems/p2/src/main.rs @@ -20,6 +20,6 @@ fn main() -> Result<(), String> { // 将结果转换为十六进制字符串 let hex_result = hex::encode(&xor_string); - println!("{}", hex_result); + println!("{hex_result}"); Ok(()) } diff --git a/problems/p3/src/main.rs b/problems/p3/src/main.rs index 8489716..5ba2e21 100644 --- a/problems/p3/src/main.rs +++ b/problems/p3/src/main.rs @@ -11,7 +11,7 @@ fn main() { if let Ok(text) = std::str::from_utf8(&plaintext) { // 只有当转换成功时才调用is_valid_english if is_valid_english(text, None) { - println!("Found valid sentence with key {}: {}", key, text); + println!("Found valid sentence with key {key}: {text}"); } } } diff --git a/problems/p33/Cargo.toml b/problems/p33/Cargo.toml new file mode 100644 index 0000000..1228f6f --- /dev/null +++ b/problems/p33/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "p33" +version = "0.1.0" +edition = "2024" + +[dependencies] +num-bigint = { workspace = true } +num-traits = { workspace = true } +rand = { workspace = true } +anyhow = { workspace = true } +once_cell = { workspace = true } diff --git a/problems/p33/src/main.rs b/problems/p33/src/main.rs new file mode 100644 index 0000000..4910003 --- /dev/null +++ b/problems/p33/src/main.rs @@ -0,0 +1,209 @@ +use anyhow::{Ok, Result, anyhow}; +use num_bigint::{BigUint, RandBigInt}; +use num_traits::{One, Zero}; +use once_cell::sync::Lazy; +use rand::thread_rng; + +static NIST_P: Lazy = Lazy::new(|| { + BigUint::parse_bytes(b"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff",16).unwrap() +}); + +static NIST_G: Lazy = Lazy::new(|| BigUint::from(2u8)); + +fn mod_exp(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> Result { + if modulus.is_zero() { + return Err(anyhow!("modulus should greater than 0")); + } + let mut base = base % modulus; + if exp.is_zero() { + return Ok(BigUint::one()); + } + if base.is_zero() { + return Ok(base); + } + let mut result = BigUint::one(); + let mut exp = exp.clone(); + while !exp.is_zero() { + if exp.bit(0) { + result = (&result * &base) % modulus; + } + base = (&base * &base) % modulus; + exp >>= 1; + } + + Ok(result) +} + +type PublicKey = BigUint; +type PrivateKey = BigUint; + +fn gen_dh_keypair() -> Result<(PrivateKey, PublicKey)> { + let mut rng = thread_rng(); + let private_key = rng.gen_biguint_range(&BigUint::zero(), &NIST_P); + let public_key = mod_exp(&NIST_G, &private_key, &NIST_P)?; + Ok((private_key, public_key)) +} + +fn gen_secret(public_key: &BigUint, privite_key: &BigUint) -> Result { + mod_exp(public_key, privite_key, &NIST_P) +} + +fn main() -> Result<()> { + let (sk_alice, pk_alice) = gen_dh_keypair()?; + let (sk_bob, pk_bob) = gen_dh_keypair()?; + let secret_alice = gen_secret(&pk_bob, &sk_alice)?; + let secret_bob = gen_secret(&pk_alice, &sk_bob)?; + if secret_alice == secret_bob { + println!("We share the same secret"); + } else { + println!("Secret incorrect"); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use num_bigint::BigUint; + use num_traits::{One, Zero}; + + #[test] + fn test_basic_modexp() { + // 3^4 mod 5 = 81 mod 5 = 1 + let result = mod_exp( + &BigUint::from(3u32), + &BigUint::from(4u32), + &BigUint::from(5u32), + ) + .unwrap(); + assert_eq!(result, BigUint::from(1u32)); + } + + #[test] + fn test_large_numbers() { + // 2^100 mod 17 + let result = mod_exp( + &BigUint::from(2u32), + &BigUint::from(100u32), + &BigUint::from(17u32), + ) + .unwrap(); + // 2^100 mod 17 = 16 (can verify with smaller calculation) + assert_eq!(result, BigUint::from(16u32)); + } + + #[test] + fn test_zero_exponent() { + // Any number^0 mod m = 1 (except 0^0) + let result = mod_exp( + &BigUint::from(123u32), + &BigUint::zero(), + &BigUint::from(456u32), + ) + .unwrap(); + assert_eq!(result, BigUint::one()); + } + + #[test] + fn test_zero_base() { + // 0^n mod m = 0 (for n > 0) + let result = mod_exp(&BigUint::zero(), &BigUint::from(5u32), &BigUint::from(7u32)).unwrap(); + assert_eq!(result, BigUint::zero()); + } + + #[test] + fn test_one_base() { + // 1^n mod m = 1 + let result = mod_exp( + &BigUint::one(), + &BigUint::from(999u32), + &BigUint::from(123u32), + ) + .unwrap(); + assert_eq!(result, BigUint::one()); + } + + #[test] + fn test_modulus_one() { + // Any number mod 1 = 0 + let result = mod_exp( + &BigUint::from(123u32), + &BigUint::from(456u32), + &BigUint::one(), + ) + .unwrap(); + assert_eq!(result, BigUint::zero()); + } + + #[test] + fn test_rsa_example() { + // RSA-like calculation: 42^17 mod 77 + let result = mod_exp( + &BigUint::from(42u32), + &BigUint::from(17u32), + &BigUint::from(77u32), + ) + .unwrap(); + // Can verify this manually or with known RSA test vectors + assert_eq!(result, BigUint::from(70u32)); + } + + #[test] + fn test_fermat_little_theorem() { + // p = 7 (prime), a = 3 + // 3^6 mod 7 should equal 1 (Fermat's Little Theorem: a^(p-1) ≡ 1 mod p) + let result = mod_exp( + &BigUint::from(3u32), + &BigUint::from(6u32), + &BigUint::from(7u32), + ) + .unwrap(); + assert_eq!(result, BigUint::one()); + } + + #[test] + fn test_very_large_numbers() { + // Test with very large numbers + let base = BigUint::parse_bytes(b"123456789012345678901234567890", 10).unwrap(); + let exp = BigUint::from(1000u32); + let modulus = BigUint::parse_bytes(b"987654321098765432109876543210987654321", 10).unwrap(); + + let result = mod_exp(&base, &exp, &modulus); + assert!(result.is_ok()); + // Result should be less than modulus + assert!( + result.unwrap() + < BigUint::parse_bytes(b"987654321098765432109876543210987654321", 10).unwrap() + ); + } + + #[test] + fn test_error_zero_modulus() { + // Division by zero should return error + let result = mod_exp(&BigUint::from(5u32), &BigUint::from(3u32), &BigUint::zero()); + assert!(result.is_err()); + } + + #[test] + fn test_zero_zero_case() { + // 0^0 is mathematically undefined, should handle appropriately + let result = mod_exp(&BigUint::zero(), &BigUint::zero(), &BigUint::from(5u32)); + // Depending on your implementation, this might return 1 or error + // Common convention is 0^0 = 1 in many contexts + assert!(result.is_ok() || result.is_err()); // Just ensure it's handled + } + + #[test] + fn test_equal_base_modulus() { + // base == modulus, so base mod modulus = 0 + // 0^exp mod modulus = 0 (for exp > 0) + let result = mod_exp( + &BigUint::from(7u32), + &BigUint::from(3u32), + &BigUint::from(7u32), + ) + .unwrap(); + assert_eq!(result, BigUint::zero()); + } +} diff --git a/problems/p9/Cargo.toml b/problems/p9/Cargo.toml index 0860c29..3ebd1f5 100644 --- a/problems/p9/Cargo.toml +++ b/problems/p9/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = { workspace = true } diff --git a/problems/p9/src/main.rs b/problems/p9/src/main.rs index 2bbb7f4..d7f34ea 100644 --- a/problems/p9/src/main.rs +++ b/problems/p9/src/main.rs @@ -1,3 +1,6 @@ +use anyhow::{Result, anyhow}; +use core::panic; + fn pkcs7_padding(data: &mut Vec, block_size: usize) { if block_size == 0 { panic!("Block size must be greater than zero"); @@ -12,6 +15,22 @@ fn pkcs7_padding(data: &mut Vec, block_size: usize) { data.extend(vec![padding_length as u8; padding_length]); } +fn pkcs7_unpadding(input: &[u8]) -> Result> { + if input.is_empty() { + return Err(anyhow!("Input cannot be empty")); + } + let padding_length = *input.last().unwrap(); + if padding_length == 0 || padding_length > input.len() as u8 { + return Err(anyhow!("Invalid PKCS#7 padding")); + } + for &byte in input.iter().rev().take(padding_length as usize) { + if byte != padding_length { + return Err(anyhow!("Invalid PKCS#7 padding")); + } + } + Ok(input[..input.len() - padding_length as usize].to_vec()) +} + fn main() { let mut plain_text = b"YELLOW SUBMARINE".to_vec(); pkcs7_padding(&mut plain_text, 20); @@ -76,4 +95,43 @@ mod tests { assert_eq!(data.len(), 255); assert_eq!(&data[1..], vec![254; 254]); } + + #[test] + fn test_pkcs7_unpadding_valid() { + let result = pkcs7_unpadding(&[65, 66, 67, 4, 4, 4, 4]).unwrap(); + assert_eq!(result, vec![65, 66, 67]); + } + + #[test] + fn test_pkcs7_unpadding_single_byte() { + let result = pkcs7_unpadding(&[65, 66, 67, 68, 69, 70, 1]).unwrap(); + assert_eq!(result, vec![65, 66, 67, 68, 69, 70]); + } + + #[test] + fn test_pkcs7_unpadding_full_padding_block() { + let result = pkcs7_unpadding(&[8, 8, 8, 8, 8, 8, 8, 8]).unwrap(); + assert_eq!(result, vec![]); + } + + #[test] + fn test_pkcs7_unpadding_invalid_padding_byte_zero() { + assert!(pkcs7_unpadding(&[65, 66, 67, 0]).is_err()); + } + + #[test] + fn test_pkcs7_unpadding_invalid_padding_too_large() { + assert!(pkcs7_unpadding(&[65, 66, 67, 5]).is_err()); + } + + #[test] + fn test_pkcs7_unpadding_inconsistent_padding() { + assert!(pkcs7_unpadding(&[65, 66, 67, 3, 3, 2]).is_err()); + } + + #[test] + #[should_panic(expected = "Input cannot be empty")] + fn test_pkcs7_unpadding_empty_data() { + pkcs7_unpadding(&[]).unwrap(); + } }