完成API签名工具的基础结构,包含Cargo配置、README文档及核心库实现,支持多种签名算法和命令行工具功能。

This commit is contained in:
SF-bytebytebrew
2025-05-21 14:00:33 +08:00
parent f94df2bdbb
commit b92d406bac
4 changed files with 791 additions and 0 deletions

373
rust/src/lib.rs Normal file
View File

@@ -0,0 +1,373 @@
use hmac::{Hmac, Mac};
use md5::{Digest, Md5};
use regex::Regex;
use sha1::Sha1;
use sha2::Sha256;
use std::{
collections::HashMap,
fmt,
time::{SystemTime, UNIX_EPOCH},
};
use thiserror::Error;
use url::form_urlencoded;
type HmacSha256 = Hmac<Sha256>;
/// API签名错误类型
#[derive(Error, Debug)]
pub enum SignError {
#[error("无效的签名算法: {0}")]
InvalidAlgorithm(String),
#[error("缺少必要参数: {0}")]
MissingParameter(String),
#[error("参数解析失败: {0}")]
ParseError(String),
#[error("时间戳过期")]
TimestampExpired,
#[error("签名验证失败")]
SignatureVerificationFailed,
#[error("HMAC错误: {0}")]
HmacError(String),
}
/// 签名算法类型
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureAlgorithm {
/// MD5算法默认、最快
Md5,
/// SHA1算法
Sha1,
/// SHA256算法
Sha256,
/// HMAC-SHA256算法最安全
HmacSha256,
}
impl SignatureAlgorithm {
/// 从字符串解析算法类型
pub fn from_str(s: &str) -> Result<Self, SignError> {
match s.to_uppercase().as_str() {
"MD5" => Ok(SignatureAlgorithm::Md5),
"SHA1" => Ok(SignatureAlgorithm::Sha1),
"SHA256" => Ok(SignatureAlgorithm::Sha256),
"HMAC_SHA256" | "HMACSHA256" | "HMAC-SHA256" => Ok(SignatureAlgorithm::HmacSha256),
_ => Err(SignError::InvalidAlgorithm(s.to_string())),
}
}
}
impl fmt::Display for SignatureAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SignatureAlgorithm::Md5 => write!(f, "MD5"),
SignatureAlgorithm::Sha1 => write!(f, "SHA1"),
SignatureAlgorithm::Sha256 => write!(f, "SHA256"),
SignatureAlgorithm::HmacSha256 => write!(f, "HMAC-SHA256"),
}
}
}
impl Default for SignatureAlgorithm {
fn default() -> Self {
Self::Md5
}
}
/// 签名选项
#[derive(Debug, Clone)]
pub struct SignOptions {
/// 签名算法
pub algorithm: SignatureAlgorithm,
/// AccessKeyId参数名
pub key_name: String,
/// 合作渠道方ID参数名
pub channel_id_name: String,
/// 时间戳参数名
pub timestamp_name: String,
/// 随机字符串参数名
pub nonce_name: String,
/// 签名参数名
pub signature_name: String,
}
impl Default for SignOptions {
fn default() -> Self {
Self {
algorithm: SignatureAlgorithm::default(),
key_name: "AccessKeyId".to_string(),
channel_id_name: "channelId".to_string(),
timestamp_name: "timestamp".to_string(),
nonce_name: "nonce".to_string(),
signature_name: "sign".to_string(),
}
}
}
/// API签名工具
#[derive(Debug, Clone)]
pub struct ApiSigner {
options: SignOptions,
}
impl ApiSigner {
/// 创建API签名工具
///
/// # 参数
/// * `options` - 签名选项如为None则使用默认选项
///
/// # 返回
/// * 签名工具实例
pub fn new(options: Option<SignOptions>) -> Self {
Self {
options: options.unwrap_or_default(),
}
}
/// 生成随机字符串
///
/// # 返回
/// * 一个基于当前时间的随机字符串
pub fn generate_nonce(&self) -> String {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
format!("{}{}", now.as_nanos(), now.as_secs() % 1000)
}
/// 获取当前时间戳(毫秒)
///
/// # 返回
/// * 当前的Unix时间戳毫秒
pub fn get_timestamp(&self) -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64
}
/// 对请求进行签名
///
/// # 参数
/// * `params` - 请求参数
/// * `access_key_id` - 访问密钥ID
/// * `secret_key` - 密钥
/// * `channel_id` - 渠道ID
///
/// # 返回
/// * 添加了签名的完整参数
pub fn sign_request(
&self,
params: &HashMap<String, String>,
access_key_id: &str,
secret_key: &str,
channel_id: &str,
) -> HashMap<String, String> {
let mut signed_params = params.clone();
let timestamp = self.get_timestamp();
signed_params.insert(self.options.key_name.clone(), access_key_id.to_string());
signed_params.insert(self.options.channel_id_name.clone(), channel_id.to_string());
signed_params.insert(self.options.timestamp_name.clone(), timestamp.to_string());
signed_params.insert(self.options.nonce_name.clone(), self.generate_nonce());
let signature = self.calculate_signature(&signed_params, secret_key);
signed_params.insert(self.options.signature_name.clone(), signature);
signed_params
}
/// 对URL进行签名
///
/// # 参数
/// * `base_url` - 基础URL地址
/// * `params` - 请求参数
/// * `access_key_id` - 访问密钥ID
/// * `secret_key` - 密钥
/// * `channel_id` - 渠道ID
///
/// # 返回
/// * 添加了签名的完整URL
pub fn sign_url(
&self,
base_url: &str,
params: &HashMap<String, String>,
access_key_id: &str,
secret_key: &str,
channel_id: &str,
) -> String {
let signed_params = self.sign_request(params, access_key_id, secret_key, channel_id);
let query_string = self.params_to_query_string(&signed_params);
if base_url.contains('?') {
format!("{}&{}", base_url, query_string)
} else {
format!("{}?{}", base_url, query_string)
}
}
/// 将参数转换为查询字符串
fn params_to_query_string(&self, params: &HashMap<String, String>) -> String {
let mut pairs = Vec::with_capacity(params.len());
for (key, value) in params {
let encoded = form_urlencoded::Serializer::new(String::new())
.append_pair(key, value)
.finish();
pairs.push(encoded);
}
pairs.sort();
pairs.join("&")
}
/// 计算签名
///
/// # 参数
/// * `params` - 请求参数
/// * `secret_key` - 密钥
///
/// # 返回
/// * 签名字符串
pub fn calculate_signature(
&self,
params: &HashMap<String, String>,
secret_key: &str,
) -> String {
let signing_string = self.create_signing_string(params);
let signing_string = format!("{}&key={}", signing_string, secret_key);
match self.options.algorithm {
SignatureAlgorithm::Md5 => {
let mut hasher = Md5::new();
hasher.update(signing_string.as_bytes());
hex::encode(hasher.finalize())
}
SignatureAlgorithm::Sha1 => {
let mut hasher = Sha1::new();
hasher.update(signing_string.as_bytes());
hex::encode(hasher.finalize())
}
SignatureAlgorithm::Sha256 => {
let mut hasher = Sha256::new();
hasher.update(signing_string.as_bytes());
hex::encode(hasher.finalize())
}
SignatureAlgorithm::HmacSha256 => {
let mut mac =
HmacSha256::new_from_slice(secret_key.as_bytes()).expect("HMAC初始化失败");
mac.update(signing_string.as_bytes());
hex::encode(mac.finalize().into_bytes())
}
}
}
/// 创建用于签名的规范化字符串
///
/// # 参数
/// * `params` - 请求参数
///
/// # 返回
/// * 按键名排序并拼接的字符串
pub fn create_signing_string(&self, params: &HashMap<String, String>) -> String {
let mut sorted_params = HashMap::new();
for (k, v) in params {
if k != &self.options.signature_name {
sorted_params.insert(k.clone(), v.clone());
}
}
let mut keys: Vec<String> = sorted_params.keys().cloned().collect();
keys.sort();
let mut parts = Vec::with_capacity(keys.len());
for key in keys {
let value = sorted_params.get(&key).unwrap();
let value = if self.needs_url_encode(value) {
form_urlencoded::Serializer::new(String::new())
.append_pair("", value)
.finish()
.trim_start_matches('=')
.to_string()
} else {
value.clone()
};
parts.push(format!("{}={}", key, value));
}
parts.join("&")
}
/// 判断是否需要对字符串进行URL编码
fn needs_url_encode(&self, s: &str) -> bool {
let re = Regex::new(r"^[a-zA-Z0-9]*$").unwrap();
!re.is_match(s)
}
/// 验证签名
///
/// # 参数
/// * `params` - 所有请求参数,包括签名
/// * `secret_key` - 密钥
/// * `max_age_ms` - 允许的最大时间差(毫秒)
///
/// # 返回
/// * 验证结果成功为Ok(())失败为Err
pub fn verify_signature(
&self,
params: &HashMap<String, String>,
secret_key: &str,
max_age_ms: i64,
) -> Result<(), SignError> {
if !params.contains_key(&self.options.key_name) {
return Err(SignError::MissingParameter(self.options.key_name.clone()));
}
let timestamp_str = params
.get(&self.options.timestamp_name)
.ok_or_else(|| SignError::MissingParameter(self.options.timestamp_name.clone()))?;
let timestamp = timestamp_str
.parse::<i64>()
.map_err(|e| SignError::ParseError(e.to_string()))?;
let now = self.get_timestamp();
if (now - timestamp).abs() > max_age_ms {
return Err(SignError::TimestampExpired);
}
if !params.contains_key(&self.options.nonce_name) {
return Err(SignError::MissingParameter(self.options.nonce_name.clone()));
}
let provided_signature = params
.get(&self.options.signature_name)
.ok_or_else(|| SignError::MissingParameter(self.options.signature_name.clone()))?;
let expected_signature = self.calculate_signature(params, secret_key);
if expected_signature == *provided_signature {
Ok(())
} else {
Err(SignError::SignatureVerificationFailed)
}
}
}
/// 默认创建签名选项
pub fn default_sign_options() -> SignOptions {
SignOptions {
algorithm: SignatureAlgorithm::Md5,
key_name: String::from("AccessKeyId"),
channel_id_name: String::from("channelId"),
timestamp_name: String::from("timestamp"),
nonce_name: String::from("nonce"),
signature_name: String::from("sign"),
}
}

167
rust/src/main.rs Normal file
View File

@@ -0,0 +1,167 @@
use api_signer::{ApiSigner, SignOptions, SignatureAlgorithm};
use clap::Parser;
use dotenv::dotenv;
use std::{collections::HashMap, env, error::Error};
/// API签名工具CLI程序
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// 签名算法MD5, SHA1, SHA256, HMAC-SHA256
#[arg(short, long, default_value = "MD5")]
algorithm: String,
/// API操作类型sign_url, sign_params, verify
#[arg(short = 'm', long, default_value = "sign_url")]
action: String,
/// 基础URL地址仅sign_url模式需要
#[arg(short, long, default_value = "https://api.example.com/v1/data")]
url: String,
/// 请求参数格式为key=value可多次指定
#[arg(short, long)]
param: Vec<String>,
/// 访问密钥ID默认从环境变量ACCESS_KEY_ID读取
#[arg(short = 'k', long)]
access_key_id: Option<String>,
/// 密钥默认从环境变量SECRET_KEY读取
#[arg(short = 's', long)]
secret_key: Option<String>,
/// 合作渠道方ID默认从环境变量CHANNEL_ID读取
#[arg(short = 'c', long)]
channel_id: Option<String>,
/// 签名仅verify模式需要
#[arg(short = 'g', long)]
signature: Option<String>,
}
fn main() -> Result<(), Box<dyn Error>> {
// 加载环境变量
dotenv().ok();
// 解析命令行参数
let args = Args::parse();
// 获取配置
let access_key_id = args
.access_key_id
.unwrap_or_else(|| env::var("ACCESS_KEY_ID").unwrap_or("test-access-key-id".to_string()));
let secret_key = args
.secret_key
.unwrap_or_else(|| env::var("SECRET_KEY").unwrap_or("test-secret-key".to_string()));
let channel_id = args
.channel_id
.unwrap_or_else(|| env::var("CHANNEL_ID").unwrap_or("test-channel-id".to_string()));
// 解析签名算法
let algorithm =
SignatureAlgorithm::from_str(&args.algorithm).unwrap_or(SignatureAlgorithm::Md5);
// 创建自定义签名选项
let mut options = SignOptions::default();
options.algorithm = algorithm;
// 创建签名工具
let signer = ApiSigner::new(Some(options.clone()));
// 解析参数
let mut params = HashMap::new();
for param in &args.param {
if let Some((key, value)) = param.split_once('=') {
params.insert(key.to_string(), value.to_string());
}
}
// 如果没有参数,使用默认参数进行演示
if params.is_empty() {
params.insert("userId".to_string(), "12345".to_string());
params.insert("action".to_string(), "getData".to_string());
params.insert("data".to_string(), "测试数据".to_string()); // 包含非ASCII字符测试URL编码
}
println!("===================== API签名示例 =====================");
println!("AccessKeyId: {}", access_key_id);
println!("ChannelId: {}", channel_id);
println!("SecretKey: {}", secret_key);
println!("签名算法: {}", algorithm);
println!("基础URL: {}", args.url);
println!("请求参数:");
for (key, value) in &params {
println!(" {}: {}", key, value);
}
match args.action.as_str() {
"sign_url" => {
// 签名URL
let signed_url =
signer.sign_url(&args.url, &params, &access_key_id, &secret_key, &channel_id);
println!("\n签名后的URL:");
println!("{}", signed_url);
}
"sign_params" => {
// 获取签名后的参数
let signed_params =
signer.sign_request(&params, &access_key_id, &secret_key, &channel_id);
println!("\n签名后的参数:");
for (key, value) in &signed_params {
println!(" {}: {}", key, value);
}
}
"verify" => {
// 添加签名参数(如果提供)
if let Some(signature) = args.signature {
params.insert(options.signature_name.clone(), signature);
}
// 验证签名
match signer.verify_signature(&params, &secret_key, 300000) {
Ok(()) => println!("\n签名验证结果: 验证成功"),
Err(err) => println!("\n签名验证结果: 验证失败 - {}", err),
}
}
_ => {
return Err(format!("未知操作类型: {}", args.action).into());
}
}
// 演示不同算法的签名结果
demonstrate_algorithms(&params, &access_key_id, &secret_key);
Ok(())
}
/// 演示不同算法的签名结果
fn demonstrate_algorithms(params: &HashMap<String, String>, access_key_id: &str, secret_key: &str) {
println!("\n不同算法的签名结果:");
let algorithms = [
SignatureAlgorithm::Md5,
SignatureAlgorithm::Sha1,
SignatureAlgorithm::Sha256,
SignatureAlgorithm::HmacSha256,
];
let default_channel_id = "test-channel-id".to_string();
let channel_id = params.get("channelId").unwrap_or(&default_channel_id);
for alg in &algorithms {
let mut options = SignOptions::default();
options.algorithm = *alg;
let signer = ApiSigner::new(Some(options.clone()));
// 添加必要的参数用于签名
let mut sign_params = params.clone();
sign_params.insert("AccessKeyId".to_string(), access_key_id.to_string());
sign_params.insert("channelId".to_string(), channel_id.to_string());
let signature = signer.calculate_signature(&sign_params, secret_key);
println!(" {}: {}", alg, signature);
}
}