1
0

Compare commits

...

4 Commits

Author SHA1 Message Date
74e9959c6d chore: update dependency 2025-08-27 11:32:48 +08:00
944a68c18c feat: finish p21. Implment mt19937. 2025-08-25 17:22:07 +08:00
55bfa38d1c chore: update dependency 2025-08-24 22:29:01 +08:00
b2424c1fba feat: finish p18, implment aes ctr mode 2025-08-24 22:28:40 +08:00
13 changed files with 521 additions and 21 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
site

49
CLAUDE.md Normal file
View File

@ -0,0 +1,49 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a Rust implementation of the Cryptopal challenges - a collection of cryptography exercises. The codebase follows a workspace structure with individual problem solutions and shared utilities.
## Workspace Structure
- **Root workspace**: Defined in `Cargo.toml` with `edition = "2024"`
- **Individual problems**: Located in `problems/p1/`, `problems/p2/`, etc. Each has its own `Cargo.toml` and `src/main.rs`
- **Common utilities**: Located in `common/` crate with shared cryptographic functions like `is_valid_english()` and `xor_with_key()`
- **Documentation**: `cryptopal_book/` contains mdBook documentation for each challenge
## Key Commands
### Building and Running
- `cargo build` - Build the entire workspace
- `cargo run -p p1` - Run a specific problem (e.g., problem 1)
- `cargo build -p p1` - Build a specific problem
- `cargo test` - Run tests across the workspace
- `cargo check` - Quick syntax check without full compilation
### Documentation
- `mdbook build cryptopal_book/` - Build the challenge documentation
- `mdbook serve cryptopal_book/` - Serve documentation locally
## Architecture Notes
- Each problem is isolated in its own binary crate under `problems/`
- Common cryptographic utilities are shared via the `common` crate
- Problems use dependencies like `hex`, `base64`, and `anyhow` for encoding/error handling
- Code includes Chinese comments for educational purposes
- The `common` crate provides utilities for English text validation and XOR operations
## Dependencies Pattern
Individual problems typically use:
- `hex` for hexadecimal encoding/decoding
- `base64` for Base64 operations
- `anyhow` for error handling (via common crate)
- Local `common` crate for shared utilities
## Development Notes
- Each challenge solution should be self-contained in its respective `problems/pN/` directory
- Use the `common` crate for shared cryptographic functions
- Follow the existing pattern of having a simple `main.rs` that demonstrates the solution

169
Cargo.lock generated
View File

@ -20,6 +20,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.1" version = "1.0.1"
@ -31,21 +37,53 @@ name = "common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"rand", "rand 0.9.2",
] ]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]] [[package]]
name = "cryptopal_rs" name = "cryptopal_rs"
version = "0.1.0" version = "0.1.0"
[[package]] [[package]]
name = "getrandom" name = "either"
version = "0.2.16" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi",
"wasi", "wasi",
] ]
@ -69,7 +107,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [ dependencies = [
"num-integer", "num-integer",
"num-traits", "num-traits",
"rand", "rand 0.8.5",
] ]
[[package]] [[package]]
@ -118,7 +156,7 @@ name = "p11"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"common", "common",
"rand", "rand 0.9.2",
] ]
[[package]] [[package]]
@ -128,7 +166,7 @@ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
"common", "common",
"rand", "rand 0.9.2",
] ]
[[package]] [[package]]
@ -146,7 +184,7 @@ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
"common", "common",
"rand", "rand 0.9.2",
] ]
[[package]] [[package]]
@ -167,6 +205,22 @@ version = "0.1.0"
[[package]] [[package]]
name = "p18" name = "p18"
version = "0.1.0" version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"common",
"rayon",
]
[[package]]
name = "p19"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"common",
"rayon",
]
[[package]] [[package]]
name = "p2" name = "p2"
@ -175,6 +229,37 @@ dependencies = [
"hex", "hex",
] ]
[[package]]
name = "p20"
version = "0.1.0"
[[package]]
name = "p21"
version = "0.1.0"
[[package]]
name = "p22"
version = "0.1.0"
dependencies = [
"common",
"rand 0.9.2",
]
[[package]]
name = "p23"
version = "0.1.0"
dependencies = [
"rand 0.9.2",
]
[[package]]
name = "p24"
version = "0.1.0"
[[package]]
name = "p28"
version = "0.1.0"
[[package]] [[package]]
name = "p3" name = "p3"
version = "0.1.0" version = "0.1.0"
@ -191,7 +276,7 @@ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
"once_cell", "once_cell",
"rand", "rand 0.9.2",
] ]
[[package]] [[package]]
@ -270,25 +355,39 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha", "rand_chacha",
"rand_core", "rand_core 0.9.3",
] ]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.3.1" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.9.3",
] ]
[[package]] [[package]]
@ -296,10 +395,36 @@ name = "rand_core"
version = "0.6.4" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "rayon"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.103" version = "2.0.103"
@ -319,9 +444,21 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.1+wasi-snapshot-preview1" version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 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",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"

View File

@ -10,7 +10,8 @@ members = ["problems/*", "common"]
hex = "0.4.3" hex = "0.4.3"
base64 = "0.22.1" base64 = "0.22.1"
anyhow = "1.0.98" anyhow = "1.0.98"
rand = "0.8" rand = "0.9"
num-bigint = { version = "0.4", features = ["rand"] } num-bigint = { version = "0.4", features = ["rand"] }
num-traits = "0.2" num-traits = "0.2"
once_cell = "1.21" once_cell = "1.21"
rayon = "1.11.0"

View File

@ -482,3 +482,67 @@ pub fn gen_random_key() -> [u8; 16] {
rng.fill(&mut key); rng.fill(&mut key);
key key
} }
pub fn aes_ctr_enc(input: &[u8], key: &[u8; 16], nonce: u64) -> Result<Vec<u8>> {
let mut key_stream = Vec::new();
for round in 0..=(input.len() / 16) as u64 {
let input: Vec<u8> = nonce
.to_le_bytes()
.into_iter()
.chain(round.to_le_bytes())
.collect();
let stream_block = aes_ecb_enc(&input, key)?;
key_stream.extend(stream_block);
}
let output: Vec<u8> = input.iter().zip(key_stream).map(|(&a, b)| a ^ b).collect();
Ok(output)
}
pub fn aes_ctr_dec(input: &[u8], key: &[u8; 16], nonce: u64) -> Result<Vec<u8>> {
aes_ctr_enc(input, key, nonce)
}
pub struct MT19937 {
mt: [u32; 624],
index: usize,
}
impl MT19937 {
pub fn new(seed: u32) -> Self {
let mut mt = [0; 624];
mt[0] = seed;
for i in 1..624 {
mt[i] = 0x6c078965u32
.wrapping_mul(mt[i - 1] ^ (mt[i - 1] >> 30))
.wrapping_add(i as u32)
}
let index = 0;
MT19937 { mt, index }
}
pub fn extract_number(&mut self) -> u32 {
if self.index == 0 {
self.generate_numbers();
}
let mut result = self.mt[self.index];
result ^= result >> 11;
result ^= (result << 7) & 0x9d2c5680;
result ^= (result << 15) & 0xefc60000;
result ^= result >> 18;
self.index = (self.index + 1) % 624;
result
}
fn generate_numbers(&mut self) {
for i in 0..624 {
let y: u32 = (self.mt[i] & 0x80000000) + (self.mt[(i + 1) % 624] & 0x7fffffff);
self.mt[i] = self.mt[(i + 397) % 624] ^ (y >> 1);
if y % 2 != 0 {
self.mt[i] ^= 0x9908b0df;
}
}
}
}

View File

@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
common = { path = "../../common" }
rand = { workspace = true }
base64 = { workspace = true }
anyhow = { workspace = true }

View File

@ -1,3 +1,96 @@
fn main() { use std::process::exit;
println!("Hello, world!");
use anyhow::Result;
use base64::{Engine, engine::general_purpose::STANDARD};
use common::{aes_ecb_enc, gen_random_key, pkcs7_padding};
use rand::random;
fn oracle(
unknown_prefix: &[u8],
controlled_input: &[u8],
key: &[u8; 16],
unknown_string: &[u8],
) -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(unknown_prefix);
data.extend_from_slice(controlled_input);
data.extend_from_slice(unknown_string);
pkcs7_padding(&mut data, 16);
aes_ecb_enc(&data, key).unwrap()
} }
fn main() -> Result<()> {
let b64_unknown_string = "Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK";
let unknown_string = STANDARD.decode(b64_unknown_string)?;
let key = gen_random_key();
let prefix_len = random::<u8>();
let unknown_prefix = vec![6u8; prefix_len as usize];
// step1. find padding length and unknown_string length.
let mut padding_len = 0;
let prev_cipher = oracle(
&unknown_prefix,
&vec![0; padding_len],
&key,
&unknown_string,
);
while prev_cipher.len() + 16
!= oracle(
&unknown_prefix,
&vec![0; padding_len],
&key,
&unknown_string,
)
.len()
{
padding_len += 1;
}
println!("padding_len: {padding_len}"); // 6
let unknown_str_len = prev_cipher.len() - padding_len;
println!("unknown_str_len: {unknown_str_len}");
assert_eq!(unknown_str_len, unknown_string.len()); // debug use
let mut cracked: Vec<u8> = Vec::new();
let mut flag = false;
while cracked.len() != unknown_str_len {
padding_len += 1;
for i in 0..=255u8 {
let mut block = Vec::new();
block.push(i);
block.extend_from_slice(&cracked[..cracked.len().min(15)]);
pkcs7_padding(&mut block, 16);
block.extend(b"1".repeat(padding_len));
let cipher = oracle(&unknown_prefix, &block, &key, &unknown_string);
let chunks: Vec<&[u8]> = cipher.chunks(16).collect();
let first_16 = chunks[0];
let need_crack_16 = chunks[chunks.len() - 1 - ((cracked.len() + 1) / 16)];
if first_16 == need_crack_16 {
cracked.insert(0, i);
flag = true;
break;
}
}
if flag {
flag = false;
} else {
println!("Error. Some byte not found.");
println!("Cerrent length: {}", cracked.len());
exit(0)
}
}
println!("{}", String::from_utf8_lossy(&cracked));
// println!("{}", String::from_utf8_lossy(&plaintext));
assert_eq!(cracked, unknown_string);
Ok(())
}

View File

@ -1,5 +1,6 @@
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
#[allow(dead_code)]
fn pkcs7_unpadding(input: &[u8]) -> Result<Vec<u8>> { fn pkcs7_unpadding(input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() { if input.is_empty() {
return Err(anyhow!("Input cannot be empty")); return Err(anyhow!("Input cannot be empty"));

View File

@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
anyhow = { workspace = true }
common = { path = "../../common" }
base64 = { workspace = true }
rayon = { workspace = true }

View File

@ -1,3 +1,42 @@
fn main() { use anyhow::Result;
println!("Hello, world!"); use base64::{Engine as _, engine::general_purpose::STANDARD};
use rayon::prelude::*;
use common::aes_ecb_enc;
fn aes_ctr_enc(input: &[u8], key: &[u8; 16], nonce: u64) -> Result<Vec<u8>> {
let mut key_stream = Vec::new();
for round in 0..=(input.len() / 16) as u64 {
let input: Vec<u8> = nonce
.to_le_bytes()
.into_iter()
.chain(round.to_le_bytes())
.collect();
let stream_block = aes_ecb_enc(&input, key)?;
key_stream.extend(stream_block);
}
let output: Vec<u8> = input
.par_iter()
.zip(key_stream)
.map(|(&a, b)| a ^ b)
.collect();
Ok(output)
}
fn aes_ctr_dec(input: &[u8], key: &[u8; 16], nonce: u64) -> Result<Vec<u8>> {
aes_ctr_enc(input, key, nonce)
}
fn main() -> Result<()> {
let cipher = STANDARD
.decode("L77na/nrFsKvynd6HzOoG7GHTLXsTVu9qvY/2syLXzhPweyyMTJULu/6/kXX0KSvoOLSFQ==")?;
let key: [u8; 16] = "YELLOW SUBMARINE".as_bytes().try_into()?;
let plain = aes_ctr_dec(&cipher, &key, 0)?;
let plain = String::from_utf8(plain)?;
println!("{plain}");
Ok(())
} }

6
problems/p21/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "p21"
version = "0.1.0"
edition = "2024"
[dependencies]

View File

@ -0,0 +1,8 @@
#include <iostream>
#include <random>
int main() {
std::mt19937 gen(12345);
std::cout << gen() << std::endl; // 第一个输出
return 0;
}

93
problems/p21/src/main.rs Normal file
View File

@ -0,0 +1,93 @@
// //創建一個長度為624的數組來存儲發生器的狀態
// int[0..623] MT
// int index = 0
//
// //初始化產生器,種子作為首項內容
// function initialize_generator(int seed) {
// i := 0
// MT[0] := seed
// for i from 1 to 623 { // 走訪剩下的每個元素
// MT[i] := last 32 bits of(1812433253 * (MT[i-1] xor (right shift by 30 bits(MT[i-1]))) + i) // 1812433253 == 0x6c078965
// }
// }
//
// // Extract a tempered pseudorandom number based on the index-th value,
// // calling generate_numbers() every 624 numbers
// function extract_number() {
// if index == 0 {
// generate_numbers()
// }
//
// int y := MT[index]
// y := y xor (right shift by 11 bits(y))
// y := y xor (left shift by 7 bits(y) and (2636928640)) // 2636928640 == 0x9d2c5680
// y := y xor (left shift by 15 bits(y) and (4022730752)) // 4022730752 == 0xefc60000
// y := y xor (right shift by 18 bits(y))
//
// index := (index + 1) mod 624
// return y
// }
//
// // Generate an array of 624 untempered numbers
// function generate_numbers() {
// for i from 0 to 623 {
// int y := (MT[i] & 0x80000000) // bit 31 (32nd bit) of MT[i]
// + (MT[(i+1) mod 624] & 0x7fffffff) // bits 0-30 (first 31 bits) of MT[...]
// MT[i] := MT[(i + 397) mod 624] xor (right shift by 1 bit(y))
// if (y mod 2) != 0 { // y is odd
// MT[i] := MT[i] xor (2567483615) // 2567483615 == 0x9908b0df
// }
// }
// }
struct MT19937 {
mt: [u32; 624],
index: usize,
}
impl MT19937 {
fn new(seed: u32) -> Self {
let mut mt = [0; 624];
mt[0] = seed;
for i in 1..624 {
mt[i] = 0x6c078965u32
.wrapping_mul(mt[i - 1] ^ (mt[i - 1] >> 30))
.wrapping_add(i as u32)
}
let index = 0;
MT19937 { mt, index }
}
fn extract_number(&mut self) -> u32 {
if self.index == 0 {
self.generate_numbers();
}
let mut result = self.mt[self.index];
result ^= result >> 11;
result ^= (result << 7) & 0x9d2c5680;
result ^= (result << 15) & 0xefc60000;
result ^= result >> 18;
self.index = (self.index + 1) % 624;
result
}
fn generate_numbers(&mut self) {
for i in 0..624 {
let y: u32 = (self.mt[i] & 0x80000000) + (self.mt[(i + 1) % 624] & 0x7fffffff);
self.mt[i] = self.mt[(i + 397) % 624] ^ (y >> 1);
if y % 2 != 0 {
self.mt[i] ^= 0x9908b0df;
}
}
}
}
fn main() {
// use C++ std::mt19937 to verify.
let mut mt19937 = MT19937::new(12345);
let result = mt19937.extract_number();
println!("{result}");
}