文章目录
一、项目概述
1.1 项目目标
开发一款本地存储、安全加密的命令行密码管理工具,支持密码的添加、查询、修改、删除,以及高强度随机密码生成,核心满足 "隐私安全""本地可控""操作便捷" 三大需求。
1.2 核心功能
- 隐私安全 :
- 使用 AES-256-CBC 加密算法加密存储所有密码
- 使用 PBKDF2 算法将主密码转换为加密密钥
- 100,000 轮密钥派生增强安全性
- 内存中的敏感数据使用 zeroize 安全清除
- 本地可控 :
- 所有数据本地存储,不依赖网络
- 数据文件使用加密格式存储
- 可以完全控制数据存储位置
- 操作便捷 :
- 命令行界面,支持多种操作
- 自动生成高强度随机密码
- 支持将密码复制到剪贴板
- 支持按服务和用户名查询
1.3 技术栈清单
| 依赖库 | 版本要求 | 用途说明 |
|---|---|---|
clap |
>=4.0 | 命令行参数解析(定义 init/add/search 等子命令及选项) |
aes |
>=0.8 | AES-256 加密算法核心实现,提供对称加密能力 |
cbc |
>=0.1 | 支持 AES 算法的 CBC 加密模式,确保数据块关联性 |
pbkdf2 |
>=0.12 | PBKDF2 密钥派生函数,从用户主密码生成高强度加密密钥(结合 SHA-256) |
sha2 |
>=0.10 | SHA-256 哈希算法,用于 PBKDF2 密钥派生的哈希环节 |
serde |
>=1.0 | 数据序列化 / 反序列化框架(需启用 derive 特性,用于 JSON 处理) |
serde_json |
>=1.0 | JSON 格式处理,实现密码数据的序列化存储与反序列化读取 |
rand |
>=0.8 | 生成高强度随机密码(控制字符类型、长度)及安全随机数(如加密盐值) |
clipboard |
>=0.5 | 跨平台剪贴板操作,支持密码一键复制到剪贴板 |
rpassword |
>=7.2 | 安全读取密码输入(隐藏终端输入字符,避免明文泄露) |
dirs |
>=4.0 | 获取系统默认配置目录,用于存储加密数据文件(跨平台适配) |
thiserror |
>=1.0 | 自定义错误类型,统一错误处理逻辑(如加密错误、文件错误、参数错误) |
chrono |
>=0.4 | 时间处理库,记录密码条目创建 / 更新时间、加密文件最后修改时间 |
colored |
>=2.0 | 终端输出美化(成功信息标绿、错误信息标红、提示信息标蓝) |
zeroize |
>=1.6 | 敏感数据内存清零,避免密码、密钥等信息在内存中残留 |
二、项目初始化
2.1 创建项目
bash
# 创建项目目录
cargo new rust-pass-manager
cd rust-pass-manager
2.2 配置 Cargo.toml
toml
[package]
name = "secure-password-manager"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.0", features = ["derive"] }
aes = "0.8"
cbc = { version = "0.1", features = ["alloc"] }
pbkdf2 = "0.12"
rand = "0.8"
sha2 = "0.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
rpassword = "7.2"
clipboard = "0.5"
zeroize = "1.6"
三、核心模块实现
3.1 主程序(src/main.rs)
rust
// src/main.rs
use clap::Parser;
use std::path::Path;
use std::fs;
use std::io::{self, Write};
mod password_manager;
mod crypto;
mod models;
use password_manager::PasswordManager;
use models::{PasswordEntry, Config};
/// 安全密码管理器 - 本地存储、加密保护
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// 要执行的操作
#[command(subcommand)]
command: Command,
}
#[derive(clap::Subcommand, Debug)]
enum Command {
/// 添加新密码
Add {
/// 服务名称
service: String,
/// 用户名
username: String,
/// 密码(如果未提供将生成随机密码)
#[arg(short, long)]
password: Option<String>,
/// 密码长度(用于生成随机密码)
#[arg(short = 'l', long, default_value = "16")]
length: usize,
},
/// 查询密码
Get {
/// 服务名称
service: String,
/// 用户名
#[arg(short, long)]
username: Option<String>,
/// 是否复制到剪贴板
#[arg(short, long)]
copy: bool,
},
/// 列出所有服务
List,
/// 修改密码
Update {
/// 服务名称
service: String,
/// 用户名
username: String,
/// 新密码
#[arg(short, long)]
password: Option<String>,
/// 密码长度(用于生成随机密码)
#[arg(short = 'l', long, default_value = "16")]
length: usize,
},
/// 删除密码
Delete {
/// 服务名称
service: String,
/// 用户名
#[arg(short, long)]
username: Option<String>,
},
/// 生成随机密码
Generate {
/// 密码长度
#[arg(short, long, default_value = "16")]
length: usize,
/// 是否包含特殊字符
#[arg(short, long)]
special: bool,
/// 是否复制到剪贴板
#[arg(short, long)]
copy: bool,
},
/// 初始化密码库
Init,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
// 检查是否已初始化
let config_path = "passwords.config";
let data_path = "passwords.data";
match &args.command {
Command::Init => {
init_password_manager(config_path)?;
return Ok(());
}
_ => {
if !Path::new(config_path).exists() {
println!("❌ 密码库尚未初始化,请先运行: secure-password-manager init");
return Ok(());
}
}
}
// 获取主密码
let master_password = rpassword::prompt_password("请输入主密码: ")?;
// 初始化密码管理器
let mut pm = PasswordManager::new(master_password, config_path, data_path)?;
// 执行命令
match &args.command {
Command::Add { service, username, password, length } => {
let password = match password {
Some(pwd) => pwd.clone(),
None => crypto::generate_password(*length, true),
};
let entry = PasswordEntry {
service: service.clone(),
username: username.clone(),
password,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
pm.add_password(entry)?;
println!("✅ 密码添加成功");
}
Command::Get { service, username, copy } => {
let entries = pm.get_passwords(service, username.as_deref())?;
if entries.is_empty() {
println!("📭 未找到相关密码");
return Ok(());
}
for entry in &entries {
println!("服务: {}", entry.service);
println!("用户名: {}", entry.username);
if *copy {
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
{
match clipboard::write(entry.password.as_str()) {
Ok(_) => println!("📋 密码已复制到剪贴板"),
Err(_) => println!("密码: {}", entry.password),
}
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
{
println!("密码: {}", entry.password);
}
} else {
println!("密码: {}", entry.password);
}
println!("创建时间: {}", entry.created_at.format("%Y-%m-%d %H:%M:%S"));
println!("更新时间: {}", entry.updated_at.format("%Y-%m-%d %H:%M:%S"));
println!("-------------------------");
}
}
Command::List => {
let services = pm.list_services()?;
if services.is_empty() {
println!("📭 暂无密码记录");
return Ok(());
}
println!("🔐 已保存的服务:");
for service in services {
println!("- {}", service);
}
}
Command::Update { service, username, password, length } => {
let password = match password {
Some(pwd) => pwd.clone(),
None => crypto::generate_password(*length, true),
};
pm.update_password(service, username, password)?;
println!("✅ 密码更新成功");
}
Command::Delete { service, username } => {
let deleted = pm.delete_password(service, username.as_deref())?;
if deleted > 0 {
println!("✅ 删除了 {} 条记录", deleted);
} else {
println!("📭 未找到要删除的记录");
}
}
Command::Generate { length, special, copy } => {
let password = crypto::generate_password(*length, *special);
if *copy {
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
{
match clipboard::write(password.as_str()) {
Ok(_) => println!("📋 生成的密码已复制到剪贴板: {}", password),
Err(_) => println!("生成的密码: {}", password),
}
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
{
println!("生成的密码: {}", password);
}
} else {
println!("生成的密码: {}", password);
}
}
Command::Init => unreachable!(),
}
Ok(())
}
fn init_password_manager(config_path: &str) -> Result<(), Box<dyn std::error::Error>> {
if Path::new(config_path).exists() {
println!("⚠️ 密码库已存在,是否要重新初始化?(y/N): ");
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if !input.trim().eq_ignore_ascii_case("y") {
println!("❌ 初始化已取消");
return Ok(());
}
}
let master_password = rpassword::prompt_password("请设置主密码: ")?;
let confirm_password = rpassword::prompt_password("请确认主密码: ")?;
if master_password != confirm_password {
println!("❌ 两次输入的密码不一致");
return Ok(());
}
let salt = crypto::generate_salt();
let config = Config { salt };
fs::write(config_path, serde_json::to_string(&config)?)?;
fs::write("passwords.data", "")?;
println!("✅ 密码库初始化成功!");
println!("🔑 请牢记您的主密码,丢失将无法恢复数据!");
Ok(())
}
3.2 密码管理器模块(src/password_manager.rs)
rust
// src/password_manager.rs
use std::collections::HashMap;
use std::fs;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use crate::crypto;
use crate::models::{PasswordEntry, Config};
pub struct PasswordManager {
master_key: Vec<u8>,
config: Config,
data_path: String,
passwords: HashMap<String, Vec<PasswordEntry>>,
}
impl PasswordManager {
pub fn new(
master_password: String,
config_path: &str,
data_path: &str,
) -> Result<Self, Box<dyn std::error::Error>> {
// 读取配置
let config_content = fs::read_to_string(config_path)?;
let config: Config = serde_json::from_str(&config_content)?;
// 生成主密钥
let master_key = crypto::derive_key(&master_password, &config.salt);
// 初始化实例
let mut pm = PasswordManager {
master_key,
config,
data_path: data_path.to_string(),
passwords: HashMap::new(),
};
// 加载现有数据
pm.load_data()?;
Ok(pm)
}
fn load_data(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let data = fs::read(&self.data_path)?;
if data.is_empty() {
return Ok(());
}
let decrypted_data = crypto::decrypt(&data, &self.master_key)?;
self.passwords = serde_json::from_slice(&decrypted_data)?;
Ok(())
}
fn save_data(&self) -> Result<(), Box<dyn std::error::Error>> {
let json_data = serde_json::to_vec(&self.passwords)?;
let encrypted_data = crypto::encrypt(&json_data, &self.master_key)?;
fs::write(&self.data_path, encrypted_data)?;
Ok(())
}
pub fn add_password(&mut self, entry: PasswordEntry) -> Result<(), Box<dyn std::error::Error>> {
self.passwords
.entry(entry.service.clone())
.or_insert_with(Vec::new)
.push(entry);
self.save_data()?;
Ok(())
}
pub fn get_passwords(
&self,
service: &str,
username: Option<&str>,
) -> Result<Vec<PasswordEntry>, Box<dyn std::error::Error>> {
let mut results = Vec::new();
if let Some(entries) = self.passwords.get(service) {
for entry in entries {
if let Some(target_username) = username {
if entry.username == target_username {
results.push(entry.clone());
}
} else {
results.push(entry.clone());
}
}
}
Ok(results)
}
pub fn list_services(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let mut services: Vec<String> = self.passwords.keys().cloned().collect();
services.sort();
Ok(services)
}
pub fn update_password(
&mut self,
service: &str,
username: &str,
new_password: String,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(entries) = self.passwords.get_mut(service) {
for entry in entries {
if entry.username == username {
entry.password = new_password;
entry.updated_at = Utc::now();
self.save_data()?;
return Ok(());
}
}
}
// 如果没有找到,添加新条目
let entry = PasswordEntry {
service: service.to_string(),
username: username.to_string(),
password: new_password,
created_at: Utc::now(),
updated_at: Utc::now(),
};
self.add_password(entry)?;
Ok(())
}
pub fn delete_password(
&mut self,
service: &str,
username: Option<&str>,
) -> Result<usize, Box<dyn std::error::Error>> {
let mut deleted_count = 0;
if let Some(username) = username {
// 删除特定用户名的条目
if let Some(entries) = self.passwords.get_mut(service) {
let original_len = entries.len();
entries.retain(|entry| entry.username != username);
deleted_count = original_len - entries.len();
// 如果服务下没有条目了,删除服务
if entries.is_empty() {
self.passwords.remove(service);
}
}
} else {
// 删除整个服务的所有条目
if let Some(entries) = self.passwords.remove(service) {
deleted_count = entries.len();
}
}
if deleted_count > 0 {
self.save_data()?;
}
Ok(deleted_count)
}
}
3.3 加密模块( src/crypto.rs)
rust
// src/crypto.rs
use aes::Aes256;
use cbc::{Cipher, Decryptor, Encryptor};
use pbkdf2::pbkdf2;
use rand::{Rng, rngs::OsRng};
use sha2::Sha256;
use std::num::NonZeroU32;
const KEY_SIZE: usize = 32;
const IV_SIZE: usize = 16;
const SALT_SIZE: usize = 32;
const PBKDF2_ROUNDS: u32 = 100_000;
pub fn generate_salt() -> Vec<u8> {
let mut salt = vec![0u8; SALT_SIZE];
OsRng.fill(&mut salt);
salt
}
pub fn derive_key(password: &str, salt: &[u8]) -> Vec<u8> {
let mut key = vec![0u8; KEY_SIZE];
pbkdf2::<Sha256>(
password.as_bytes(),
salt,
NonZeroU32::new(PBKDF2_ROUNDS).unwrap(),
&mut key,
);
key
}
pub fn encrypt(data: &[u8], key: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut iv = vec![0u8; IV_SIZE];
OsRng.fill(&mut iv);
let cipher = Encryptor::<Aes256>::new(key.into(), &iv);
let mut buffer = data.to_vec();
cipher.encrypt_padded_mut::<cbc::cipher::block_padding::Pkcs7>(&mut buffer, data.len())?;
let mut result = iv;
result.extend_from_slice(&buffer);
Ok(result)
}
pub fn decrypt(encrypted_data: &[u8], key: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
if encrypted_data.len() < IV_SIZE {
return Err("Invalid encrypted data".into());
}
let (iv, ciphertext) = encrypted_data.split_at(IV_SIZE);
let cipher = Decryptor::<Aes256>::new(key.into(), iv.into());
let mut buffer = ciphertext.to_vec();
let decrypted_len = cipher.decrypt_padded_mut::<cbc::cipher::block_padding::Pkcs7>(&mut buffer)?;
buffer.truncate(decrypted_len);
Ok(buffer)
}
pub fn generate_password(length: usize, with_special: bool) -> String {
let charset = if with_special {
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789\
!@#$%^&*()_+-=[]{}|;:,.<>?"
} else {
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789"
};
let mut rng = OsRng;
(0..length)
.map(|_| {
let idx = rng.gen_range(0..charset.len());
charset.chars().nth(idx).unwrap()
})
.collect()
}
3.4 创建数据模型(src/models.rs)
rust
// src/models.rs
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PasswordEntry {
pub service: String,
pub username: String,
pub password: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub created_at: DateTime<Utc>,
#[serde(with = "chrono::serde::ts_seconds")]
pub updated_at: DateTime<Utc>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub salt: Vec<u8>,
}
3.5 创建 lib.rs 文件
rust
// src/lib.rs
pub mod password_manager;
pub mod crypto;
pub mod models;
四、测试与使用
4.1 子命令详解
初始化密码库
cargo run -- init
- 功能:初始化密码库,创建配置文件和数据文件
- 参数:无
- 选项:无
- 说明:首次使用必须执行此命令
添加密码
cargo run -- add <service> <username> [OPTIONS]
- 功能:添加新密码记录
- 参数 :
<service>- 服务名称(必填),如 github、gmail、jira 等<username>- 用户名(必填),如 user@example.com、john_dev 等
- 选项 :
-p, --password <password>- 指定具体密码内容-l, --length <length>- 指定生成密码长度(默认16位)
- 说明:如果未指定密码,则自动生成高强度密码
查询密码
cargo run -- get <service> [OPTIONS]
- 功能:查询密码记录
- 参数 :
<service>- 服务名称(必填)
- 选项 :
-u, --username <username>- 指定用户名进行精确查询-c, --copy- 将密码复制到剪贴板
- 说明:如不指定用户名,将显示该服务下的所有账户
列出所有服务
cargo run -- list
- 功能:列出所有已保存的服务名称
- 参数:无
- 选项:无
- 说明:按字母顺序显示所有服务
更新密码
cargo run -- update <service> <username> [OPTIONS]
- 功能:更新现有密码记录
- 参数 :
<service>- 服务名称(必填)<username>- 用户名(必填)
- 选项 :
-p, --password <password>- 指定新的密码内容-l, --length <length>- 指定生成新密码的长度(默认16位)
- 说明:如果未指定密码,则自动生成新的高强度密码
删除密码
cargo run -- delete <service> [OPTIONS]
- 功能:删除密码记录
- 参数 :
<service>- 服务名称(必填)
- 选项 :
-u, --username <username>- 指定要删除的用户名
- 说明:如不指定用户名,将删除该服务下的所有账户记录
生成随机密码
cargo run -- generate [OPTIONS]
- 功能:生成高强度随机密码
- 参数:无
- 选项 :
-l, --length <length>- 指定密码长度(默认16位)-s, --special- 包含特殊字符-c, --copy- 将生成的密码复制到剪贴板
- 说明:仅生成密码,不保存到密码库
4.2 测试
- 初始化密码库:
bash
cargo run -- init
我们设置密码为123456789

- 添加密码:
bash
cargo run -- add github myusername

- 查询密码:
bash
cargo run -- get github --username myusername --copy

当我们粘贴时就可以看见密码
- 列出所有服务:
bash
cargo run -- list

- 更新密码:
bash
cargo run -- update github myusername

- 删除密码:
bash
cargo run -- delete github --username myusername

- 生成随机密码:
bash
cargo run -- generate --length 20 --special

五、项目总结
该项目成功实现了一个功能完整、安全可靠的命令行密码管理工具。通过采用现代加密技术和 Rust 语言的安全特性,确保了用户密码数据的高度安全性。同时,友好的命令行界面和丰富的功能使得密码管理变得更加便捷高效。
该工具不仅满足了基本的密码存储需求,还提供了密码生成、剪贴板集成等实用功能,真正实现了"隐私安全"、"本地可控"和"操作便捷"的设计目标。无论是个人用户还是企业用户,都可以通过该工具更好地管理自己的密码资产,提升整体的数字安全水平。
想了解更多关于Rust语言的知识及应用,可前往旋武开源社区(https://xuanwu.openatom.cn/),了解更多资讯~