| Crates.io | vigenere-demo |
| lib.rs | vigenere-demo |
| version | 0.1.1 |
| created_at | 2025-10-29 09:22:22.081564+00 |
| updated_at | 2025-10-29 09:29:43.89112+00 |
| description | 优雅的维吉尼亚密码实现,采用泛型设计,支持自定义字符集 |
| homepage | https://github.com/ShaoG-R/vigenere |
| repository | https://github.com/ShaoG-R/vigenere |
| max_upload_size | |
| id | 1906292 |
| size | 41,385 |
一个优雅的维吉尼亚密码实现,采用泛型设计,支持自定义字符集。
核心特性:使用类型系统在编译期保证字符集和密钥非空,而非运行时检查。
src/
├── core.rs # 核心密码算法(泛型实现)
├── lib.rs # 库接口(元素类型 + StringCipher)
└── main.rs # 交互式命令行程序
core.rs: 定义 CipherElement trait 和泛型 VigenereCipher<T>
NonEmptyVec 和 NonEmptySliceRef 提供编译期非空保证lib.rs: 提供具体实现和便捷接口
CharElement 和 DigitElement 预定义元素类型StringCipher 便捷的字符串加密接口main.rs: 用户交互界面
[dependencies]
nonempty_tools = "0.1.0"
使用 nonempty_tools 在类型层面保证集合非空性。
use nonempty_tools::{NonEmptyVec, NonEmptySliceRef};
/// 维吉尼亚密码元素 trait
/// 元素自带索引,无需外部映射表
pub trait CipherElement: Clone + Debug {
/// 值类型 - 需要支持相等性比较
type Value: PartialEq;
/// 获取元素索引(算法核心)
fn index(&self) -> usize;
/// 获取元素值(用于比较、显示)
fn value(&self) -> Self::Value;
}
/// 泛型密码器 - 使用 NonEmptyVec 保证字符集非空
pub struct VigenereCipher<T: CipherElement> {
charset: Vec<T>,
modulus: usize,
}
impl<T: CipherElement> VigenereCipher<T> {
/// 编译期保证字符集非空
pub fn new(charset: NonEmptyVec<T>) -> Self;
/// 编译期保证密钥非空
pub fn encrypt(&self, plaintext: &[T], key: NonEmptySliceRef<T>) -> Vec<T>;
pub fn decrypt(&self, ciphertext: &[T], key: NonEmptySliceRef<T>) -> Vec<T>;
}
核心加密/解密逻辑使用纯粹的数学运算和高阶函数:
// 加密:E(M, K) = (M + K) mod n
cipher.process(plaintext, key, |m, k, n| (m + k) % n)
// 解密:D(C, K) = (C - K + n) mod n
cipher.process(ciphertext, key, |c, k, n| (c + n - k) % n)
NonEmptyVec 和 NonEmptySliceRef,将运行时检查提升到编译期Value: PartialEq 而非 Element: PartialEq,更清晰的语义use vigenere::{CharElement, VigenereCipher, NonEmptyVec, NonEmptySliceRef};
// 创建字符元素字符集
let charset: Vec<CharElement> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
.chars()
.enumerate()
.map(|(i, c)| CharElement::new(c, i))
.collect();
// 使用 NonEmptyVec 保证字符集非空(编译期检查)
let ne_charset = NonEmptyVec::try_from_vec(charset.clone()).unwrap();
let cipher = VigenereCipher::new(ne_charset);
// 构建明文和密钥(使用元素)
let plaintext = vec![
charset[7].clone(), // H
charset[4].clone(), // E
charset[11].clone(), // L
charset[11].clone(), // L
charset[14].clone(), // O
];
let key = vec![
charset[10].clone(), // K
charset[4].clone(), // E
charset[24].clone(), // Y
];
// 使用 NonEmptySliceRef 保证密钥非空(编译期检查)
let ne_key = NonEmptySliceRef::new(&key).unwrap();
// 加密 - 不会再有运行时空检查!
let encrypted = cipher.encrypt(&plaintext, ne_key);
// 结果: RIJVS
// 解密
let decrypted = cipher.decrypt(&encrypted, ne_key);
// 结果: HELLO
use vigenere::{DigitElement, VigenereCipher, NonEmptyVec, NonEmptySliceRef};
// 数字密码器(0-9)
let charset: Vec<DigitElement> = (0..10)
.map(|i| DigitElement::new(i).unwrap())
.collect();
let cipher = VigenereCipher::new(NonEmptyVec::try_from_vec(charset.clone()).unwarp());
let plaintext = vec![
charset[1].clone(), // 1
charset[2].clone(), // 2
charset[3].clone(), // 3
];
let key = vec![
charset[4].clone(), // 4
charset[5].clone(), // 5
charset[6].clone(), // 6
];
let encrypted = cipher.encrypt(
&plaintext,
NonEmptySliceRef::new(&key).unwrap()
).unwrap();
// 结果: [5, 7, 9] (对应 '5', '7', '9')
// 使用预定义字符集
let cipher = StringCipher::uppercase_alpha();
let encrypted = cipher.encrypt("HELLO", "KEY");
// 结果: "RIJVS"
// 自定义字符集
let cipher = StringCipher::new("0123456789");
let encrypted = cipher.encrypt("123", "456");
// 结果: "579"
// 处理混合文本(保留不在字符集中的字符)
let cipher = StringCipher::uppercase_alpha();
let encrypted = cipher.encrypt("HELLO, WORLD!", "KEY");
// 结果: "RIJVS, UYVJN!" (逗号、空格、感叹号保持不变)
┌─────────────────────────────────────┐
│ main.rs │
│ 交互式命令行界面 │
│ • 用户输入/输出 │
│ • 菜单选择 │
└──────────────┬──────────────────────┘
│ uses
┌──────────────▼─────────────────────┐
│ lib.rs │
│ ┌─────────────────────────────┐ │
│ │ StringCipher │ │
│ │ 字符串便捷接口 │ │
│ └──────────┬──────────────────┘ │
│ │ │
│ ┌──────────▼──────────────────┐ │
│ │ CharElement, DigitElement │ │
│ │ 具体元素类型实现 │ │
│ └──────────┬──────────────────┘ │
│ │ implements │
└─────────────┼──────────────────────┘
│
┌─────────────▼──────────────────────┐
│ core.rs │
│ ┌─────────────────────────────┐ │
│ │ trait CipherElement │ │
│ │ • type Value: PartialEq │ │
│ │ • fn index() -> usize │ │
│ │ • fn value() -> Value │ │
│ └──────────┬──────────────────┘ │
│ │ │
│ ┌──────────▼──────────────────┐ │
│ │ VigenereCipher<T> │ │
│ │ 泛型密码器 - 纯数学运算 │ │
│ │ E(M,K) = (M+K) mod n │ │
│ │ D(C,K) = (C-K+n) mod n │ │
│ └─────────────────────────────┘ │
└────────────────────────────────────┘
cargo run
cargo test
程序提供了友好的交互式界面:
=== 维吉尼亚密码加解密程序 ===
架构设计:
• core.rs - 泛型 VigenereCipher<T: CipherElement>
• lib.rs - CharElement, DigitElement, StringCipher
• main.rs - 交互式用户界面
• 元素自带索引,值实现 PartialEq
选择字符集:
1. 大写英文字母 (A-Z)
2. 小写英文字母 (a-z)
3. 大小写英文字母
4. 字母+数字
5. 可打印ASCII字符
6. 自定义字符集
0. 退出
注意:StringCipher 为了用户友好性,仍然接受 &str 并在内部进行检查。而底层的 VigenereCipher<T> 则使用 NonEmptyVec 和 NonEmptySliceRef 提供类型安全保证。这是便利性和类型安全的良好平衡。
| 方法 | 字符集 | 说明 |
|---|---|---|
uppercase_alpha() |
A-Z | 大写英文字母(26个) |
lowercase_alpha() |
a-z | 小写英文字母(26个) |
mixed_alpha() |
A-Za-z | 大小写英文字母(52个) |
alphanumeric() |
A-Za-z0-9 | 字母和数字(62个) |
printable_ascii() |
ASCII 32-126 | 可打印ASCII字符(95个) |
new(charset) |
自定义 | 任意自定义字符集 |
传统实现需要在运行时检查空值:
// ❌ 传统方式 - 运行时检查
pub fn new(charset: Vec<T>) -> Result<Self, String> {
if charset.is_empty() {
return Err("字符集不能为空".to_string());
}
// ...
}
pub fn encrypt(&self, text: &[T], key: &[T]) -> Result<Vec<T>, String> {
if key.is_empty() {
return Err("密钥不能为空".to_string());
}
// ...
}
我们的实现将检查提升到类型层面:
// ✅ 现代方式 - 编译期保证
pub fn new(charset: NonEmptyVec<T>) -> Self {
// 无需检查 - 类型系统已保证非空!
}
pub fn encrypt(&self, text: &[T], key: NonEmptySliceRef<T>) -> Vec<T> {
// 无需检查 - 类型系统已保证非空!
}
优势:
if 检查和错误处理传统实现需要 HashMap<char, usize> 来映射字符到索引,而我们的设计中元素本身就包含索引:
pub struct CharElement {
value: char,
index: usize, // 索引是元素的固有属性
}
优势:
我们要求 Value: PartialEq 而非 Element: PartialEq:
pub trait CipherElement: Clone + Debug {
type Value: PartialEq; // 值需要可比较
fn index(&self) -> usize;
fn value(&self) -> Self::Value;
}
为什么这样设计?
加密和解密的唯一区别是运算方式,我们使用高阶函数优雅地抽象这一差异:
fn process<F>(&self, input: &[T], key: &[T], operation: F) -> Vec<T>
where
F: Fn(usize, usize, usize) -> usize,
{
input.iter().enumerate().map(|(i, element)| {
let key_element = &key[i % key.len()];
let new_index = operation(element.index(), key_element.index(), self.modulus);
self.charset[new_index].clone()
}).collect()
}
只需实现 CipherElement trait,就能支持任意类型的密码系统:
use vigenere::{CipherElement, VigenereCipher, NonEmptyVec, NonEmptySliceRef};
// 示例:二进制元素
#[derive(Debug, Clone, PartialEq)]
pub struct BinaryElement {
value: bool,
}
impl CipherElement for BinaryElement {
type Value = bool;
fn index(&self) -> usize {
self.value as usize // 0 或 1
}
fn value(&self) -> Self::Value {
self.value
}
}
// 创建二进制密码器
let charset = NonEmptyVec::try_from_vec(vec![
BinaryElement { value: false },
BinaryElement { value: true },
]).unwrap();
let cipher = VigenereCipher::new(charset);
// 使用类型安全的密钥
let key = NonEmptyVec::new(BinaryElement { value: true }, vec![]);
cipher.encrypt(&plaintext, NonEmptySliceRef::from_nonempty_vec(&key));
项目包含全面的单元测试:
运行 cargo test 查看所有测试结果。
以下代码无法编译,体现类型安全:
// ❌ 编译错误:类型不匹配
let empty_vec: Vec<CharElement> = vec![];
let cipher = VigenereCipher::new(empty_vec);
// 期望 NonEmptyVec<CharElement>,得到 Vec<CharElement>
// ❌ 编译错误:无法创建空的 NonEmptySliceRef
let empty_key: &[CharElement] = &[];
cipher.encrypt(&plaintext, NonEmptySliceRef::new(empty_key).unwrap());
// unwrap() 会 panic,但类型系统已强制检查
维吉尼亚密码是一种多表替换密码:
C[i] = (P[i] + K[i mod len(K)]) mod nP[i] = (C[i] - K[i mod len(K)] + n) mod n其中:
P[i] 是明文的第 i 个元素的索引K[i] 是密钥的第 i 个元素的索引C[i] 是密文的第 i 个元素的索引n 是字符集大小(modulus)⚠️ 注意:维吉尼亚密码是一种历史密码,已被现代密码分析技术攻破(如 Kasiski 测试、重合指数分析等)。
本项目仅用于教学和演示目的,请勿用于任何实际的安全加密需求。
对于实际应用,请使用现代加密算法,如:
MIT License
维吉尼亚密码的数学定义要求:
modulus = |charset|,若 |charset| = 0 则除以零传统方法是运行时检查:
if charset.is_empty() {
return Err("字符集不能为空");
}
但这有几个问题:
使用类型系统表达约束:
pub fn new(charset: NonEmptyVec<T>) -> Self
这样:
NonEmptyVec 在运行时就是普通 Vecuse nonempty_tools::{NonEmptyVec, NonEmptySliceRef};
// 方式 1: 从已知非空的 Vec 创建
let charset = vec![/* ... */];
let ne_charset = NonEmptyVec::try_from_vec(charset)?; // 运行时检查一次
// 方式 2: 直接构造(编译期保证)
let ne_charset = NonEmptyVec::new(first_elem, rest_elems);
// 方式 3: 从切片引用
let slice = &[1, 2, 3];
let ne_slice = NonEmptySliceRef::new(slice)?; // 运行时检查一次
一旦拥有 NonEmptyVec 或 NonEmptySliceRef,就可以在整个程序中传递,不再需要任何检查。
欢迎提交 Issue 和 Pull Request!