✨ 完成API签名工具的基础结构,包含Cargo配置、README文档及核心库实现,支持多种签名算法和命令行工具功能。
This commit is contained in:
373
rust/src/lib.rs
Normal file
373
rust/src/lib.rs
Normal 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
167
rust/src/main.rs
Normal 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 ¶ms {
|
||||
println!(" {}: {}", key, value);
|
||||
}
|
||||
|
||||
match args.action.as_str() {
|
||||
"sign_url" => {
|
||||
// 签名URL
|
||||
let signed_url =
|
||||
signer.sign_url(&args.url, ¶ms, &access_key_id, &secret_key, &channel_id);
|
||||
println!("\n签名后的URL:");
|
||||
println!("{}", signed_url);
|
||||
}
|
||||
"sign_params" => {
|
||||
// 获取签名后的参数
|
||||
let signed_params =
|
||||
signer.sign_request(¶ms, &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(¶ms, &secret_key, 300000) {
|
||||
Ok(()) => println!("\n签名验证结果: 验证成功"),
|
||||
Err(err) => println!("\n签名验证结果: 验证失败 - {}", err),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("未知操作类型: {}", args.action).into());
|
||||
}
|
||||
}
|
||||
|
||||
// 演示不同算法的签名结果
|
||||
demonstrate_algorithms(¶ms, &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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user