perf(signer): cache signing key to avoid redundant HMAC-SHA256 (#6651)

* perf(signer): cache signing key to avoid redundant HMAC-SHA256

Cache the AWS4 signing key per (secret, region, date, service_type)
tuple. The signing key is derived from 4 HMAC-SHA256 calls and is
constant for a given user within the same UTC day, so caching it
eliminates ~0.5-1ms of redundant crypto per request.

The cache uses a LazyLock<Mutex<HashMap>> with automatic daily
rotation (cache entries naturally expire when the date component
of the key changes).

Refs: https://github.com/rustfs/backlog/issues/2005

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(signer): bound signing key cache

* fix(signer): satisfy cache lint

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: overtrue <anzhengchao@gmail.com>
This commit is contained in:
houseme
2026-08-26 17:00:00 +08:00
committed by GitHub
parent 2ef9519bea
commit 5a424219d2
+35 -2
View File
@@ -28,6 +28,13 @@ use super::utils::{HostAddrError, sign_v4_trim_all, try_get_host_addr};
use rustfs_utils::crypto::{hex, hex_sha256, hmac_sha256};
use s3s::Body;
const SIGNING_KEY_CACHE_CAPACITY: usize = 1024;
type SigningKeyCacheKey = ([u8; 32], String, String, String);
// Keep the cache bounded and avoid retaining raw secret access keys.
static SIGNING_KEY_CACHE: LazyLock<std::sync::Mutex<HashMap<SigningKeyCacheKey, [u8; 32]>>> =
LazyLock::new(|| std::sync::Mutex::new(HashMap::new()));
pub const SIGN_V4_ALGORITHM: &str = "AWS4-HMAC-SHA256";
pub const SERVICE_TYPE_S3: &str = "s3";
pub const SERVICE_TYPE_STS: &str = "sts";
@@ -88,14 +95,40 @@ fn format_amz_datetime(t: OffsetDateTime) -> SignResult<String> {
}
pub fn get_signing_key(secret: &str, loc: &str, t: OffsetDateTime, service_type: &str) -> [u8; 32] {
let date_value = format_yyyymmdd(t);
let cache_key = (
hmac_sha256(b"rustfs-signing-key-cache", secret),
loc.to_string(),
date_value.clone(),
service_type.to_string(),
);
// Check cache first
if let Ok(cache) = SIGNING_KEY_CACHE.lock()
&& let Some(&key) = cache.get(&cache_key)
{
return key;
}
// Cache miss: compute signing key
let mut s = "AWS4".to_string();
s.push_str(secret);
let date_value = format_yyyymmdd(t);
let date = hmac_sha256(s.into_bytes(), date_value.into_bytes());
let location = hmac_sha256(date, loc);
let service = hmac_sha256(location, service_type);
let signing_key = hmac_sha256(service, "aws4_request");
hmac_sha256(service, "aws4_request")
if let Ok(mut cache) = SIGNING_KEY_CACHE.lock() {
if cache.len() >= SIGNING_KEY_CACHE_CAPACITY
&& !cache.contains_key(&cache_key)
&& let Some(evicted_key) = cache.keys().next().cloned()
{
cache.remove(&evicted_key);
}
cache.insert(cache_key, signing_key);
}
signing_key
}
pub fn get_signature(signing_key: [u8; 32], string_to_sign: &str) -> String {