Last active
April 14, 2026 18:06
-
-
Save Kr328/30d465f4877a1c57ebda2afb6832823f to your computer and use it in GitHub Desktop.
适用于 Rust 的阿里云 API 的签名函数
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::collections::BTreeMap; | |
| use aws_lc_rs::{digest, hmac}; | |
| use http::header::{HeaderName, HeaderValue}; | |
| use percent_encoding::{AsciiSet, CONTROLS}; | |
| use time::format_description::well_known::iso8601::TimePrecision; | |
| // 阿里云要求的 RFC3986 编码规则 | |
| const RFC3986: &AsciiSet = &CONTROLS | |
| .add(b' ') | |
| .add(b'!') | |
| .add(b'"') | |
| .add(b'#') | |
| .add(b'$') | |
| .add(b'%') | |
| .add(b'&') | |
| .add(b'\'') | |
| .add(b'(') | |
| .add(b')') | |
| .add(b'*') | |
| .add(b'+') | |
| .add(b',') | |
| .add(b'/') | |
| .add(b':') | |
| .add(b';') | |
| .add(b'<') | |
| .add(b'=') | |
| .add(b'>') | |
| .add(b'?') | |
| .add(b'@') | |
| .add(b'[') | |
| .add(b'\\') | |
| .add(b']') | |
| .add(b'^') | |
| .add(b'`') | |
| .add(b'{') | |
| .add(b'|') | |
| .add(b'}'); | |
| /// 对字符串进行 RFC3986 编码 | |
| pub fn encode_rfc3986(input: &str) -> String { | |
| percent_encoding::utf8_percent_encode(input, RFC3986).to_string() | |
| } | |
| /// 对请求签名 | |
| pub fn sign( | |
| parts: &mut http::request::Parts, | |
| body: &[u8], | |
| access_key_id: &str, | |
| access_key_secret: &str, | |
| security_token: Option<&str>, | |
| ) -> Result<(), http::Error> { | |
| // 0. 配置必要的 Headers | |
| parts.headers.insert( | |
| "x-acs-date", | |
| HeaderValue::try_from( | |
| time::UtcDateTime::now() | |
| .format( | |
| &time::format_description::well_known::Iso8601::< | |
| { | |
| time::format_description::well_known::iso8601::Config::DEFAULT | |
| .set_time_precision(TimePrecision::Second { decimal_digits: None }) | |
| .encode() | |
| }, | |
| >, | |
| ) | |
| .unwrap(), | |
| )?, | |
| ); | |
| parts.headers.insert( | |
| "x-acs-signature-nonce", | |
| HeaderValue::try_from(hex::encode({ | |
| let mut nonce = [0u8; 16]; | |
| aws_lc_rs::rand::fill(&mut nonce).unwrap(); | |
| nonce | |
| }))?, | |
| ); | |
| if let Some(security_token) = security_token { | |
| parts | |
| .headers | |
| .insert("x-acs-security-token", HeaderValue::from_str(security_token)?); | |
| } | |
| // 1. 计算 HashedRequestPayload (Body 的 SHA256) | |
| let body_hash = hex::encode(digest::digest(&digest::SHA256, body)); | |
| parts.headers.insert( | |
| HeaderName::from_static("x-acs-content-sha256"), | |
| HeaderValue::from_str(&body_hash)?, | |
| ); | |
| // 2. 构造 CanonicalHeaders 和 SignedHeaders | |
| let mut headers_to_sign = BTreeMap::new(); | |
| for (name, value) in parts.headers.iter() { | |
| let name_lower = name.as_str().to_lowercase(); | |
| if name_lower.starts_with("x-acs-") || name_lower == "host" || name_lower == "content-type" { | |
| let val_str = String::from_utf8_lossy(value.as_bytes()).trim().to_string(); | |
| headers_to_sign.insert(name_lower, val_str); | |
| } | |
| } | |
| let canonical_headers = headers_to_sign | |
| .iter() | |
| .map(|(k, v)| format!("{}:{}\n", k, v)) | |
| .collect::<String>(); | |
| let signed_headers = headers_to_sign.keys().cloned().collect::<Vec<String>>().join(";"); | |
| // 3. 构造 CanonicalQueryString | |
| let mut query_params = BTreeMap::new(); | |
| if let Some(query) = parts.uri.query() { | |
| for pair in query.split('&') { | |
| let (k, v) = pair.split_once("=").unwrap_or((pair, "")); | |
| query_params.insert(encode_rfc3986(k), encode_rfc3986(v)); | |
| } | |
| } | |
| let canonical_query_string = query_params | |
| .iter() | |
| .map(|(k, v)| format!("{}={}", k, v)) | |
| .collect::<Vec<String>>() | |
| .join("&"); | |
| // 4. 构造 CanonicalRequest | |
| // 注意:RPC 风格 CanonicalURI 通常为 "/" | |
| let canonical_uri = if parts.uri.path().is_empty() { "/" } else { parts.uri.path() }; | |
| let canonical_request = format!( | |
| "{}\n{}\n{}\n{}\n{}\n{}", | |
| parts.method.as_str().to_uppercase(), | |
| canonical_uri, | |
| canonical_query_string, | |
| canonical_headers, | |
| signed_headers, | |
| body_hash | |
| ); | |
| // 5. 构造 StringToSign | |
| let hashed_canonical_request = hex::encode(digest::digest(&digest::SHA256, canonical_request.as_bytes())); | |
| let string_to_sign = format!("ACS3-HMAC-SHA256\n{}", hashed_canonical_request); | |
| // 6. 计算 HMAC-SHA256 签名 | |
| let signature_key = hmac::Key::new(hmac::HMAC_SHA256, access_key_secret.as_bytes()); | |
| let signature = hex::encode(hmac::sign(&signature_key, string_to_sign.as_bytes())); | |
| // 7. 设置 Authorization Header | |
| parts.headers.insert( | |
| http::header::AUTHORIZATION, | |
| HeaderValue::try_from(format!( | |
| "ACS3-HMAC-SHA256 Credential={},SignedHeaders={},Signature={}", | |
| access_key_id, signed_headers, signature | |
| ))?, | |
| ); | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment