How URL Signing Works: Principles, Implementation, and Secure Practices
This article explains the cryptographic foundations of URL signing, details a step‑by‑step construction and HMAC‑SHA256 generation process, showcases Java implementations for signing and verification, discusses key‑management options, security hardening techniques, production best practices, and compares the approach with AWS Signature V4 and Tencent COS.
Core Principles of URL Signing
URL signing embeds authentication data into a request URL, forming a self‑contained credential. It relies on an irreversible hash function and a shared secret key to provide integrity verification, source authentication, replay‑attack prevention via timestamps, and permission isolation.
Typical Signature Scheme Design
1. Parameter Construction
Parameter sorting – parameters are sorted by ASCII ascending order.
TreeMap<String, String> sortedParams = new TreeMap<>();
sortedParams.put("Expires", "1714142762");
sortedParams.put("OSSAccessKeyId", "TMP.3Kdrk8kmt...");String concatenation – build the canonical string.
StringBuilder canonicalString = new StringBuilder();
canonicalString.append("GET
"); // HTTP method
canonicalString.append("/file.pdf
"); // resource path
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
canonicalString.append(entry.getKey()).append("=")
.append(entry.getValue()).append("
");
}HMAC calculation – use SHA‑256 HMAC.
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(masterKey, "HmacSHA256");
mac.init(secretKey);
byte[] hash = mac.doFinal(canonicalString.toString().getBytes(StandardCharsets.UTF_8));2. Signature Encoding
Base64 URL‑safe encoding (replace + with -, / with _, remove padding =).
String signature = Base64.getUrlEncoder().withoutPadding()
.encodeToString(hash);Alibaba Cloud special handling – URL‑encode the Base64 result.
import urllib.parse;
signature = urllib.parse.quote(base64_signature);Key‑Management Schemes
Master‑key derivation : HMAC‑SHA256(masterKey, timestamp) – security level ★★★★☆.
Temporary credentials : STS (Security Token Service) – security level ★★★★★.
Multi‑level keys : Business‑dimensional + user‑dimensional double keys – security level ★★★☆☆.
Tencent Cloud COS recommends STS temporary keys, which add a security-token parameter to the signed URL.
Implementation (Java)
Signature Generator
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class UrlSigner {
private final byte[] masterKey;
public UrlSigner(byte[] masterKey) { this.masterKey = masterKey; }
public String signUrl(String httpMethod, String resourcePath, Map<String, String> params) throws Exception {
TreeMap<String, String> sortedParams = new TreeMap<>(params);
StringBuilder canonical = new StringBuilder();
canonical.append(httpMethod).append("
")
.append(resourcePath).append("
");
for (Map.Entry<String, String> e : sortedParams.entrySet()) {
canonical.append(e.getKey()).append("=").append(e.getValue()).append("
");
}
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec signingKey = new SecretKeySpec(masterKey, "HmacSHA256");
mac.init(signingKey);
byte[] hash = mac.doFinal(canonical.toString().getBytes(StandardCharsets.UTF_8));
return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
}
}Signature Verifier
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class UrlVerifier {
private final byte[] masterKey;
public UrlVerifier(byte[] masterKey) { this.masterKey = masterKey; }
public boolean verify(String httpMethod, String resourcePath, Map<String, String> params, String expectedSignature) throws Exception {
String actualSignature = new UrlSigner(masterKey).signUrl(httpMethod, resourcePath, params);
return MessageDigest.isEqual(actualSignature.getBytes(StandardCharsets.UTF_8),
expectedSignature.getBytes(StandardCharsets.UTF_8));
}
}Usage Example
public class Demo {
public static void main(String[] args) throws Exception {
byte[] masterKey = "my-secure-master-key-123456".getBytes();
Map<String, String> params = new HashMap<>();
params.put("Expires", String.valueOf(System.currentTimeMillis()/1000 + 3600));
params.put("OSSAccessKeyId", "TMP.3Kdrk8kmt...");
UrlSigner signer = new UrlSigner(masterKey);
String signature = signer.signUrl("GET", "/file.pdf", params);
StringBuilder url = new StringBuilder("https://example.com/file.pdf?");
params.forEach((k,v) -> url.append(k).append("=").append(v).append("&"));
url.append("Signature=").append(signature);
System.out.println("Generated URL: " + url);
}
}Security Enhancement Strategies
1. Dynamic Defense (sub‑key derivation)
public byte[] deriveSubKey(long timestamp) {
byte[] tsBytes = ByteBuffer.allocate(8).putLong(timestamp).array();
byte[] combined = new byte[masterKey.length + 8];
System.arraycopy(masterKey, 0, combined, 0, masterKey.length);
System.arraycopy(tsBytes, 0, combined, masterKey.length, 8);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return digest.digest(combined);
}2. Multi‑Factor Signing (IP binding example)
public boolean verifyWithIP(Map<String, String> params, String clientIP, String expectedSignature) {
params.put("ClientIP", clientIP);
// Re‑calculate signature including the IP parameter
// Placeholder for actual verification logic
return false;
}3. Performance Optimisation (signature cache)
private final Map<String, String> signatureCache = new ConcurrentHashMap<>();
public String getCachedSignature(Map<String, String> params) {
String cacheKey = params.toString();
return signatureCache.computeIfAbsent(cacheKey, k -> {
try {
return new UrlSigner(masterKey).signUrl("GET", "/file.pdf", params);
} catch (Exception e) {
throw new RuntimeException("Signature generation failed", e);
}
});
}Production Practices
Key rotation – rotate the master key every 90 days, limit temporary key validity to 15 minutes, and version keys (e.g., key‑v1, key‑v2).
Monitoring – count signature verification failures; trigger an alert when failures exceed 100.
public class SignatureMonitor {
private final AtomicLong failureCount = new AtomicLong(0);
public void logFailure() {
if (failureCount.incrementAndGet() > 100) {
AlertSystem.trigger("Potential signature attack detected");
}
}
}Defensive programming – enforce request rate limits (e.g., 1000 requests/min), log failed signatures with client fingerprints, and analyse bulk request behaviour.
Comparison with Existing Schemes
Hash algorithm : This scheme uses SHA‑256; AWS Signature V4 also uses SHA‑256; Tencent COS uses SHA‑1 (compatible mode).
Expiration control : Unix timestamp in this scheme; ISO‑8601 timestamp in AWS; fixed 15‑minute validity in Tencent COS.
Key quantity : Single master‑key derivation here; multi‑region keys in AWS; STS temporary keys in Tencent COS.
Applicable scenario : Enterprise‑wide key sharing for this scheme; general cloud storage services for AWS; object storage services for Tencent COS.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer1970
Formerly called 'Code to 35'. Add our main WeChat ID to access a wealth of shared resources (algorithms, interview prep, tech stacks: Java, Python, Go, big data). We mainly share serious development techniques, focusing on output-driven input. Occasionally we post life snippets and gossip. Our aim is to attract precise traffic and test advertising opportunities.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
