Rust API密钥管理库prefixed-api-key的使用,实现安全可识别的带前缀API密钥生成与验证

Rust API密钥管理库prefixed-api-key的使用,实现安全可识别的带前缀API密钥生成与验证

Prefixed API Key (Seam风格)

示例密钥: mycompany_BRTRKFsL_51FwqftsmMDHHbJAMEXXHCgG

Seam风格的API密钥有许多优点:

  • 双击API密钥会选择整个API密钥
  • 字母表标准化,感谢base58 RFC及其在加密货币中的使用
  • 比hex和base32 API密钥更短
  • 有前缀,允许GitHub进行秘密扫描
  • 有哈希组件,服务器不需要存储API密钥(减少攻击面)
  • 有未哈希的短令牌,服务器和密钥持有者/客户可以共同使用来识别API密钥
  • 默认与UUIDv4大致相同的熵位数

格式

Seam风格的API密钥看起来像这样:

mycompany_BRTRKFsL_51FwqftsmMDHHbJAMEXXHCgG

分解API密钥的组件:

mycompany ..._...  BRTRKFsL ..._...  51FwqftsmMDHHbJAMEXXHCgG
^                  ^                 ^
前缀               短令牌            长令牌
  • 前缀用于识别创建API密钥的公司或服务
  • 短令牌由服务器和密钥持有者/客户存储,可用于识别API密钥
  • 长令牌用于验证密钥,服务器只存储其哈希值

完整示例代码

use prefixed_api_key::PrefixedApiKeyController;
use sha2::Sha256;
use std::collections::HashMap;

// 模拟数据库存储
struct ApiKeyDatabase {
    // short_token -> hash 的映射
    keys: HashMap<String, String>,
}

impl ApiKeyDatabase {
    fn new() -> Self {
        Self {
            keys: HashMap::new(),
        }
    }

    fn store_key(&mut self, short_token: String, hash: String) {
        self.keys.insert(short_token, hash);
    }

    fn get_hash_by_short_token(&self, short_token: &str) -> Option<&String> {
        self.keys.get(short_token)
    }
}

fn main() {
    // 1. 创建API密钥控制器
    let controller = PrefixedApiKeyController::<_, Sha256>::configure()
        .prefix("myapp".to_owned())  // 设置API密钥前缀
        .rng_osrng()                 // 使用操作系统随机数生成器
        .short_token_length(8)       // 短令牌长度
        .long_token_length(24)       // 长令牌长度
        .finalize()
        .expect("Failed to create API key controller");

    // 2. 创建模拟数据库
    let mut db = ApiKeyDatabase::new();

    // 3. 生成并存储API密钥
    let (api_key, hash) = controller.try_generate_key_and_hash()
        .expect("Failed to generate API key");
    
    db.store_key(api_key.short_token().to_string(), hash);

    // 4. 将API密钥发送给客户端
    let api_key_str = api_key.to_string();
    println!("Generated API key for client: {}", api_key_str);

    // 模拟客户端请求 - 使用API密钥访问API
    let client_api_key_str = api_key_str.clone();

    // 5. 服务器端验证
    // 解析API密钥
    let client_api_key = client_api_key_str.parse::<prefixed_api_key::PrefixedApiKey>()
        .expect("Failed to parse API key string");

    // 从数据库获取存储的hash
    let stored_hash = db.get_hash_by_short_token(client_api_key.short_token())
        .expect("API key not found in database");

    // 验证密钥
    let is_valid = controller.check_hash(&client_api_key, stored_hash);
    println!("API key validation result: {}", is_valid);

    // 6. 展示API密钥信息
    println!("\nAPI Key Details:");
    println!("Prefix: {}", client_api_key.prefix());
    println!("Short Token: {}", client_api_key.short_token());
    println!("Long Token: {}", client_api_key.long_token());
    println!("Full Key: {}", client_api_key);
}

测试说明

库测试命令:

cargo test --all-features

代码质量检查:

cargo fmt --all -- --check
cargo clippy -- -D warnings

1 回复

Prefixed API Key: Rust中安全可识别的API密钥管理库

prefixed_api_key是一个Rust库,用于生成和验证带有前缀的安全API密钥。这种密钥格式既方便识别又保证了安全性,是现代API开发的理想选择。

主要特性

  • 生成带有可读前缀的API密钥
  • 使用加密安全的随机数生成器
  • 密钥格式:prefix_xxxxxxxxxxxxxxxx_secret_xxxxxxxxxxxxxxxx
  • 支持密钥验证
  • 可自定义密钥长度和前缀

安装

在Cargo.toml中添加依赖:

[dependencies]
prefixed_api_key = "0.3"

基本用法

生成新API密钥

use prefixed_api_key::PrefixedApiKey;

fn main() {
    // 生成一个新密钥
    let pak = PrefixedApiKey::generate("myapp");
    
    println!("API Key: {}", pak.to_string());
    println!("Key ID: {}", pak.key_id());
    println!("Secret: {}", pak.secret());
}

示例输出:

API Key: myapp_5Hj6u8P3Q2R9s4T7_secret_2w4z6x8y1a3b5c7d
Key ID: 5Hj6u8P3Q2R9s4T7
Secret: 2w4z6x8y1a3b5c7d

验证API密钥

use prefixed_api_key::PrefixedApiKey;

fn main() {
    let api_key = "myapp_5Hj6u8P3Q2R9s4T7_secret_2w4z6x8y1a3b5c7d";
    
    match PrefixedApiKey::parse(api_key) {
        Ok(pak) => {
            println!("Valid API key for prefix: {}", pak.prefix());
            println!("Key ID: {}", pak.key_id());
            println!("Secret: {}", pak.secret());
        }
        Err(e) => println!("Invalid API key: {}", e),
    }
}

高级用法

自定义密钥长度

use prefixed_api_key::{PrefixedApiKey, KeyConfig};

fn main() {
    let config = KeyConfig {
        prefix: "cust",
        short_key_length: 10,
        long_key_length: 20,
    };
    
    let pak = PrefixedApiKey::generate_with_config(config);
    println!("Custom API Key: {}", pak);
}

从组件构建密钥

use prefixed_api_key::PrefixedApiKey;

fn main() {
    let pak = PrefixedApiKey::from_components(
        "myapp",
        "5Hj6u8P3Q2R9s4T7",
        "2w4z6x8y1a3b5c7d",
    );
    
    println!("Reconstructed API Key: {}", pak);
}

实际应用示例

在Web服务中使用

use prefixed_api_key::PrefixedApiKey;
use actix_web::{get, App, HttpServer, HttpResponse, web};

#[get("/api/key")]
async fn generate_key() -> HttpResponse {
    let pak = PrefixedApiKey::generate("myapp");
    HttpResponse::Ok().body(pak.to_string())
}

#[get("/api/validate/{key}")]
async fn validate_key(key: web::Path<String>) -> HttpResponse {
    match PrefixedApiKey::parse(&key) {
        Ok(pak) => HttpResponse::Ok().json(serde_json::json!({
            "valid": true,
            "prefix": pak.prefix(),
            "key_id": pak.key_id()
        })),
        Err(_) => HttpResponse::BadRequest().json(serde_json::json!({
            "valid": false,
            "error": "Invalid API key format"
        })),
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(generate_key)
            .service(validate_key)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

安全建议

  1. 始终通过HTTPS传输API密钥
  2. 不要将生成的密钥记录在日志中
  3. 考虑为不同环境使用不同前缀(如dev_, prod_
  4. 定期轮换密钥

prefixed_api_key库提供了一种既安全又便于管理的API密钥解决方案,特别适合需要区分不同客户端或服务的应用场景。

完整示例代码

以下是一个完整的Rust项目示例,展示了如何使用prefixed_api_key库:

// Cargo.toml
/*
[dependencies]
prefixed_api_key = "0.3"
actix-web = "4.0"
serde_json = "1.0"
*/

// src/main.rs
use prefixed_api_key::{PrefixedApiKey, KeyConfig};
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde_json::json;

// 生成API密钥的端点
#[get("/generate")]
async fn generate() -> impl Responder {
    let pak = PrefixedApiKey::generate("prod"); // 生产环境使用prod前缀
    HttpResponse::Ok().json(json!({
        "api_key": pak.to_string(),
        "key_id": pak.key_id(),
        "prefix": pak.prefix()
    }))
}

// 验证API密钥的端点
#[post("/validate")]
async fn validate(req_body: String) -> impl Responder {
    match PrefixedApiKey::parse(&req_body) {
        Ok(pak) => HttpResponse::Ok().json(json!({
            "valid": true,
            "prefix": pak.prefix(),
            "key_id": pak.key_id(),
            "environment": if pak.prefix() == "dev" { 
                "development" 
            } else { 
                "production" 
            }
        })),
        Err(e) => HttpResponse::BadRequest().json(json!({
            "valid": false,
            "error": e.to_string()
        })),
    }
}

// 自定义密钥生成的端点
#[get("/custom")]
async fn custom() -> impl Responder {
    let config = KeyConfig {
        prefix: "test",
        short_key_length: 12,
        long_key_length: 24,
    };
    
    let pak = PrefixedApiKey::generate_with_config(config);
    HttpResponse::Ok().body(pak.to_string())
}

// 主函数
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Starting API key server at http://localhost:8080");
    
    HttpServer::new(|| {
        App::new()
            .service(generate)
            .service(validate)
            .service(custom)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

这个示例创建了一个完整的Web服务,提供三个端点:

  1. /generate - 生成新的API密钥
  2. /validate - 验证API密钥的有效性
  3. /custom - 生成自定义长度的API密钥

要运行这个示例:

  1. 创建新的Rust项目:cargo new api_key_demo
  2. 添加上述依赖到Cargo.toml
  3. 将代码复制到src/main.rs
  4. 运行:cargo run

测试方法:

  • 访问 http://localhost:8080/generate 获取新密钥
  • 使用POST请求 http://localhost:8080/validate 并发送密钥进行验证
  • 访问 http://localhost:8080/custom 获取自定义长度的密钥
回到顶部