Rust 命令行密码管理器工具开发
文章目录
- 一、项目概述
- 1.1 项目目标
- 1.2 核心功能
- 1.3 技术栈清单
- 二、项目初始化
- 2.1 创建项目
- 2.2 配置 Cargo.toml
- 三、核心模块实现
- 3.1 主程序(src/main.rs)
- 3.2 密码管理器模块(_src/password_manager.rs_)
- 3.3 加密模块( _src/crypto.rs_)
- 3.4 创建数据模型(_src/models.rs_)
- 3.5 创建 lib.rs 文件
- 四、测试与使用
- 4.1 子命令详解
- 4.2 测试
- 五、项目总结
一、项目概述
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 创建项目
# 创建项目目录
cargo new rust-pass-manager
cd rust-pass-manager
2.2 配置 Cargo.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)
// 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)
// 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)
// 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)
// 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 文件
// 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 测试
- 初始化密码库:
cargo run -- init
我们设置密码为123456789

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

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

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

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

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

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

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