Rust 命令行密码管理器工具开发

文章目录

    • 一、项目概述
      • [1.1 项目目标](#1.1 项目目标)
      • [1.2 核心功能](#1.2 核心功能)
      • [1.3 技术栈清单](#1.3 技术栈清单)
    • 二、项目初始化
      • [2.1 创建项目](#2.1 创建项目)
      • [2.2 配置 Cargo.toml](#2.2 配置 Cargo.toml)
    • 三、核心模块实现
      • [3.1 主程序(src/main.rs)](#3.1 主程序(src/main.rs))
      • [3.2 密码管理器模块(src/password_manager.rs)](#3.2 密码管理器模块(src/password_manager.rs))
      • [3.3 加密模块( src/crypto.rs)](#3.3 加密模块( src/crypto.rs))
      • [3.4 创建数据模型(src/models.rs)](#3.4 创建数据模型(src/models.rs))
      • [3.5 创建 lib.rs 文件](#3.5 创建 lib.rs 文件)
    • 四、测试与使用
      • [4.1 子命令详解](#4.1 子命令详解)
      • [4.2 测试](#4.2 测试)
    • 五、项目总结

一、项目概述

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.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 测试

  1. 初始化密码库:
bash 复制代码
cargo run -- init

我们设置密码为123456789

  1. 添加密码:
bash 复制代码
cargo run -- add github myusername
  1. 查询密码:
bash 复制代码
cargo run -- get github --username myusername --copy

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

  1. 列出所有服务:
bash 复制代码
cargo run -- list
  1. 更新密码:
bash 复制代码
cargo run -- update github myusername
  1. 删除密码:
bash 复制代码
cargo run -- delete github --username myusername
  1. 生成随机密码:
bash 复制代码
cargo run -- generate --length 20 --special

五、项目总结

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

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

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

相关推荐
u0119608232 小时前
java 不可变集合讲解
java·开发语言
翔云 OCR API2 小时前
NFC护照鉴伪查验流程解析-ICAO9303护照真伪查验接口技术方案
开发语言·人工智能·python·计算机视觉·ocr
2501_941111682 小时前
模板编译期哈希计算
开发语言·c++·算法
Creeper.exe2 小时前
【C语言】分支与循环(上)
c语言·开发语言
jllllyuz2 小时前
基于粒子群优化(PSO)的特征选择与支持向量机(SVM)分类
开发语言·算法·matlab
树下水月2 小时前
使用python 发送数据到第三方接口,同步等待太慢
开发语言·python
njsgcs2 小时前
pyautocad获得所选圆弧的弧长总和
开发语言·windows·python
xiaoxue..2 小时前
深入理解JavaScript中的深拷贝与浅拷贝:内存管理的艺术
开发语言·前端·javascript·面试
多多*2 小时前
分布式中间件 消息队列Rocketmq 详解
java·开发语言·jvm·数据库·mysql·maven·java-rocketmq