Skip to content

Instantly share code, notes, and snippets.

@Kr328
Last active April 14, 2026 18:06
Show Gist options
  • Select an option

  • Save Kr328/30d465f4877a1c57ebda2afb6832823f to your computer and use it in GitHub Desktop.

Select an option

Save Kr328/30d465f4877a1c57ebda2afb6832823f to your computer and use it in GitHub Desktop.
适用于 Rust 的阿里云 API 的签名函数
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