arthrop0d

Brief articles on varied topics.

AWS Signature Version 4 Request Authentication

amazon web servicescomputer securitycryptography

AWS Signature Version 4 (SigV4) is AWS's method for authenticating an HTTP request by signing a precise representation of it with a secret access key. The receiving service independently rebuilds that representation and checks the signature.

The problem SigV4 solves

An HTTPS connection encrypts a request and authenticates the server, but it does not by itself tell AWS which IAM identity is making the request. AWS also needs to detect changes to the method, path, parameters, headers, or body while the request is being sent.

SigV4 solves both problems without putting the secret access key on the wire. The client proves that it knows the secret by producing a signature. AWS can verify that signature using the corresponding secret, while also checking that the signed request has not changed.

You have probably encountered the result in an error such as SignatureDoesNotMatch, InvalidSignatureException, or RequestTimeTooSkewed. Those messages usually mean that the client and AWS constructed different bytes to sign, used different credentials or scope, or disagreed about the request time.

First, canonicalize the request

The client starts by converting the HTTP request into a canonical request: a deterministic text form in which equivalent formatting choices are removed. This matters because HTTP permits some variation, while a cryptographic signature requires both sides to hash exactly the same bytes.

The canonical request has six newline-separated parts:

HTTPMethod
CanonicalURI
CanonicalQueryString
CanonicalHeaders
SignedHeaders
HexEncode(Hash(Payload))

For example, the method is GET or PUT, written in uppercase. The canonical URI is the encoded path, and the canonical query string contains query parameters sorted by encoded name and then encoded value. Parameter names and values must use AWS's required percent-encoding rules. Do not assume that a general-purpose URL library will produce the right result for every AWS service; path normalization and encoding rules can be service-specific. Amazon S3, in particular, treats the object path carefully.

Canonical headers are the headers selected for signing. Their names are lowercase and sorted, whitespace around values is removed, and runs of whitespace inside values are normalized. Each appears as name:value, followed by a newline. The next line, SignedHeaders, lists those same lowercase names, separated by semicolons.

The final line is the hexadecimal digest of the exact payload bytes that will be sent. A digest is a fixed-length result from a cryptographic hash function; changing one byte changes the digest. For many requests this is the SHA-256 hash of the body. Some services support a documented unsigned-payload mode, but that is an explicit protocol choice, not something to assume.

A common signed-header set looks like this:

host;x-amz-content-sha256;x-amz-date

The important rule is: sign what you actually send. If a signed header changes after signing, or a proxy rewrites a signed value, AWS calculates a different canonical request.

Build the string to sign

The canonical request is not signed directly with the long-term secret key. First, the client creates a string to sign, another newline-separated value:

AWS4-HMAC-SHA256
20250214T103015Z
20250214/us-east-1/s3/aws4_request
HexEncode(Hash(CanonicalRequest))

The first line identifies the SigV4 algorithm. The second is the request timestamp in UTC, formatted as YYYYMMDD'T'HHMMSS'Z'. The third is the credential scope: the calendar date, AWS Region, service name, and the fixed terminator aws4_request. The last line is the hexadecimal SHA-256 digest of the complete canonical request.

This structure binds the signature to a particular request, time, Region, and service. A signature made for S3 in us-east-1 cannot simply be reused as a signature for another service or Region.

Derive a scoped signing key

SigV4 uses HMAC, a keyed hash construction, rather than using the secret access key directly on the request. The client derives a short-lived, purpose-specific signing key through four HMAC operations:

kDate    = HMAC("AWS4" + secretAccessKey, date)
kRegion  = HMAC(kDate, region)
kService = HMAC(kRegion, service)
kSigning = HMAC(kService, "aws4_request")

It then computes the final signature as:

signature = HexEncode(HMAC(kSigning, stringToSign))

The secret access key never appears in the request. The derived key is constrained by the date, Region, and service, which limits the usefulness of a key if it is exposed and prevents signatures from being freely transferred between contexts.

Send the signature and credentials

For a normal header-authenticated request, the client sends an Authorization header similar to:

AWS4-HMAC-SHA256 Credential=ACCESS_KEY_ID/20250214/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=HEX_SIGNATURE

It also sends x-amz-date and the headers named in SignedHeaders. Temporary IAM credentials require the session token as well, usually in x-amz-security-token; omitting it commonly causes an otherwise correct signature to fail authentication.

AWS looks up the access key, determines the associated secret and identity, reconstructs the canonical request from the received HTTP message, derives the same scoped key, and compares signatures. A mismatch means either the request changed or the client and server canonicalized different values.

Timestamp validation and replay resistance

The timestamp has two jobs. It participates in the signed data, and it limits how long a captured request remains acceptable. AWS normally requires the request time to be close to its current time—commonly within about five minutes. A machine with an incorrect clock can therefore produce failures even when every credential and header is correct. The date in the credential scope must also match the date portion of x-amz-date.

This time window is a replay defense: an attacker who captures a signed request generally cannot reuse it indefinitely. It is not a replacement for IAM permissions, TLS, or credential rotation; it is one part of the authentication protocol.

SigV4 can also put the signature in query parameters for a presigned URL. In that form, X-Amz-Date, X-Amz-Expires, credential scope, signed headers, and the signature travel in the URL. The expiration controls the URL's lifetime, subject to AWS limits and the lifetime of the underlying credentials. The same canonicalization, string-to-sign, and scoped-key process still applies.

← All articles