一、项目概述

1.1 项目目标

开发一款本地存储、安全加密的命令行密码管理工具,支持密码的添加、查询、修改、删除,以及高强度随机密码生成,核心满足 “隐私安全”“本地可控”“操作便捷” 三大需求。

1.2 核心功能

  1. 隐私安全:
    1. 使用 AES-256-CBC 加密算法加密存储所有密码
    2. 使用 PBKDF2 算法将主密码转换为加密密钥
    3. 100,000 轮密钥派生增强安全性
    4. 内存中的敏感数据使用 zeroize 安全清除
  2. 本地可控:
    1. 所有数据本地存储,不依赖网络
    2. 数据文件使用加密格式存储
    3. 可以完全控制数据存储位置
  3. 操作便捷:
    1. 命令行界面,支持多种操作
    2. 自动生成高强度随机密码
    3. 支持将密码复制到剪贴板
    4. 支持按服务和用户名查询

1.3 技术栈清单

依赖库版本要求用途说明
clap>=4.0命令行参数解析(定义 init/add/search 等子命令及选项)
aes>=0.8AES-256 加密算法核心实现,提供对称加密能力
cbc>=0.1支持 AES 算法的 CBC 加密模式,确保数据块关联性
pbkdf2>=0.12PBKDF2 密钥派生函数,从用户主密码生成高强度加密密钥(结合 SHA-256)
sha2>=0.10SHA-256 哈希算法,用于 PBKDF2 密钥派生的哈希环节
serde>=1.0数据序列化 / 反序列化框架(需启用 derive 特性,用于 JSON 处理)
serde_json>=1.0JSON 格式处理,实现密码数据的序列化存储与反序列化读取
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 测试

  1. 初始化密码库:
cargo run -- init

我们设置密码为123456789

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

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

当我们粘贴时就可以看见密码

  1. 列出所有服务:
cargo run -- list

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

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

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

五、项目总结

该项目成功实现了一个功能完整、安全可靠的命令行密码管理工具。通过采用现代加密技术和 Rust 语言的安全特性,确保了用户密码数据的高度安全性。同时,友好的命令行界面和丰富的功能使得密码管理变得更加便捷高效。

该工具不仅满足了基本的密码存储需求,还提供了密码生成、剪贴板集成等实用功能,真正实现了"隐私安全"、"本地可控"和"操作便捷"的设计目标。无论是个人用户还是企业用户,都可以通过该工具更好地管理自己的密码资产,提升整体的数字安全水平。

想了解更多关于Rust语言的知识及应用,可前往旋武开源社区(https://xuanwu.openatom.cn/),了解更多资讯~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐