Create a signed AWS API request - AWS Identity and Access Management

Create a signed AWS API request

Important

If you use an AWS SDK (see Sample Code and Libraries) or AWS Command Line Interface (AWS CLI) tool to send API requests to AWS, you can skip this section because the SDK and CLI clients authenticate your requests by using the access keys that you provide. Unless you have a good reason not to, we recommend that you always use an SDK or the CLI.

In Regions that support multiple signature versions, manually signing requests means you must specify which signature version is used. When you supply requests to Multi-Region Access Points, SDKs and the CLI automatically switch to using Signature Version 4A without additional configuration.

You can use the AWS SigV4 signing protocol to create a signed request for AWS API requests.

  1. Creating a canonical request based on the request details.

  2. Calculating a signature using your AWS credentials.

  3. Adding this signature to the request as an Authorization header.

AWS then replicates this process and verifies the signature, granting or denying access accordingly.

To see how you can use AWS SigV4 to sign API requests, see Request signature examples

The following diagram illustrates the SigV4 signing process, including the various components of the string that you create for signing.

An image of the parts of a canonical request, string to sign, signing key, and signature.

The following table describes the functions that are shown in the diagram. You need to implement code for these functions. For more information, see the code examples in the AWS SDKs.

Function Description

Lowercase()

Convert the string to lowercase.

Hex()

Lowercase base 16 encoding.

SHA256Hash()

Secure Hash Algorithm (SHA) cryptographic hash function.

HMAC-SHA256()

Computes HMAC by using the SHA256 algorithm with the signing key provided. This is the final signature.

Trim()

Remove any leading or trailing whitespace.

UriEncode()

URI encode every byte. UriEncode() must enforce the following rules:

  • URI encode every byte except the unreserved characters: 'A'-'Z', 'a'-'z', '0'-'9', '-', '.', '_', and '~'.

  • The space character is a reserved character and must be encoded as "%20" (and not as "+").

  • Each URI encoded byte is formed by a '%' and the two-digit hexadecimal value of the byte.

  • Letters in the hexadecimal value must be uppercase, for example "%1A".

  • Encode the forward slash character, '/', everywhere except in the object key name. For example, if the object key name is photos/Jan/sample.jpg, the forward slash in the key name is not encoded.

Important

The standard UriEncode functions provided by your development platform may not work because of differences in implementation and related ambiguity in the underlying RFCs. We recommend that you write your own custom UriEncode function to ensure that your encoding will work.

To see an example of a UriEncode function in Java, see Java Utilities on the GitHub website.

Note

When signing your requests, you can use either AWS Signature Version 4 or AWS Signature Version 4A. The key difference between the two is determined by how the signature is calculated. With AWS Signature Version 4A, the signature does not include Region-specific information and is calculated using the AWS4-ECDSA-P256-SHA256 algorithm.

Signing requests with temporary security credentials

Instead of using long-term credentials to sign a request, you can use temporary security credentials provided by AWS Security Token Service (AWS STS).

When you use temporary security credentials, you must add X-Amz-Security-Token to the Authorization header or include it in the query string to hold the session token. Some services require that you add X-Amz-Security-Token to the canonical request. Other services require only that you add X-Amz-Security-Token at the end, after you calculate the signature. Check the documentation for each AWS service for specific requirements.

Summary of signing steps

Create a canonical request:

Arrange the contents of your request (host, action, headers, etc.) into a standard canonical format. The canonical request is one of the inputs used to create the string to sign. For details on creating the canonical request, see Elements of an AWS API request signature.

Create a hash of the canonical request

Hash the canonical request using the same algorithm that you used to create the hash of the payload. The hash of the canonical request is a string of lowercase hexadecimal characters.

Create a String to Sign

Create a string to sign with the canonical request and extra information such as the algorithm, request date, credential scope, and the hash of the canonical request.

Derive a signing key

Perform a succession of keyed hash operations (HMAC) on the request date, Region, and service, with your AWS secret access key as the key for the initial hashing operation.

Calculate the signature

Perform a keyed hash operation (HMAC) on the string to sign using the derived signing key as the hash key.

Add the signature to the request

Add the calculated signature to an HTTP header or to the query string of the request.

Create a canonical request

To create a canonical request, concatenate the following strings, separated by newline characters. This helps ensure that the signature that you calculate can match the signature that AWS calculates.

<HTTPMethod>\n <CanonicalURI>\n <CanonicalQueryString>\n <CanonicalHeaders>\n <SignedHeaders>\n <HashedPayload>
  • HTTPMethod – The HTTP method, such as GET, PUT, HEAD, and DELETE.

  • CanonicalUri – The URI-encoded version of the absolute path component URI, starting with the / that follows the domain name and up to the end of the string or to the question mark character (?) if you have query string parameters. If the absolute path is empty, use a forward slash character (/). The URI in the following example, /amzn-s3-demo-bucket/myphoto.jpg, is the absolute path and you don't encode the / in the absolute path:

    http://s3.amazonaws.com/amzn-s3-demo-bucket/myphoto.jpg
  • CanonicalQueryString – The URI-encoded query string parameters. You URI-encode each name and value individually. You must also sort the parameters in the canonical query string alphabetically by key name. The sorting occurs after encoding. The query string in the following URI example is:

    http://s3.amazonaws.com/amzn-s3-demo-bucket?prefix=somePrefix&marker=someMarker&max-keys=2

    The canonical query string is as follows (line breaks are added to this example for readability):

    UriEncode("marker")+"="+UriEncode("someMarker")+"&"+ UriEncode("max-keys")+"="+UriEncode("20") + "&" + UriEncode("prefix")+"="+UriEncode("somePrefix")

    When a request targets a subresource, the corresponding query parameter value will be an empty string (""). For example, the following URI identifies the ACL subresource on the amzn-s3-demo-bucket bucket:

    http://s3.amazonaws.com/amzn-s3-demo-bucket?acl

    In this case, the CanonicalQueryString would be:

    UriEncode("acl") + "=" + ""

    If the URI does not include a ?, there is no query string in the request, and you set the canonical query string to an empty string (""). You will still need to include the newline character ("\n").

  • CanonicalHeaders – A list of request headers with their values. Individual header name and value pairs are separated by the newline character ("\n"). The following is an example of a CanonicalHeader:

    Lowercase(<HeaderName1>)+":"+Trim(<value>)+"\n" Lowercase(<HeaderName2>)+":"+Trim(<value>)+"\n" ... Lowercase(<HeaderNameN>)+":"+Trim(<value>)+"\n"

    CanonicalHeaders list must include the following:

    • HTTP host header.

    • If the Content-Type header is present in the request, you must add it to the CanonicalHeaders list.

    • Any x-amz-* headers that you plan to include in your request must also be added. For example, if you are using temporary security credentials, you need to include x-amz-security-token in your request. You must add this header in the list of CanonicalHeaders.

    Note

    The x-amz-content-sha256 header is required for Amazon S3 AWS requests. It provides a hash of the request payload. If there is no payload, you must provide the hash of an empty string.

    Each header name must:

    • use lowercase characters.

    • appear in alphabetical order.

    • be followed by a colon (:).

    For values, you must:

    • trim any leading or trailing spaces.

    • convert sequential spaces to a single space.

    • separate the values for a multi-value header using commas.

    • You must include the host header (HTTP/1.1) or the :authority header (HTTP/2), and any x-amz-* headers in the signature. You can optionally include other standard headers in the signature, such as content-type.

    The Lowercase() and Trim() functions used in this example are described in the preceding section.

    The following is an example CanonicalHeaders string. The header names are in lowercase and sorted.

    host:s3.amazonaws.com x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 x-amz-date:20130708T220855Z

    Note

    For the purpose of calculating an authorization signature, only the host and any x-amz-* headers are required; however, in order to prevent data tampering, you should consider including all the headers in the signature calculation.

  • SignedHeaders – An alphabetically sorted, semicolon-separated list of lowercase request header names. The request headers in the list are the same headers that you included in the CanonicalHeaders string. For the previous example, the value of SignedHeaders would be as follows:

    host;x-amz-content-sha256;x-amz-date
  • HashedPayload – A string created using the payload in the body of the HTTP request as input to a hash function. This string uses lowercase hexadecimal characters.

    Hex(SHA256Hash(<payload>>))

    If there is no payload in the request, you compute a hash of the empty string, such as when you retrieve an object by using a GET request, there is nothing in the payload.

    Hex(SHA256Hash(""))
    Note

    For Amazon S3, include the literal string UNSIGNED-PAYLOAD when constructing a canonical request, and set the same value as the x-amz-content-sha256 header value when sending the request.

    Hex(SHA256Hash("UNSIGNED-PAYLOAD"))

Create a hash of the canonical request

Create a hash (digest) of the canonical request using the same algorithm that you used to create the hash of the payload. The hash of the canonical request is a string of lowercase hexadecimal characters.

Create a string to sign

To create a string to sign, concatenate the following strings, separated by newline characters. Do not end this string with a newline character.

Algorithm \n RequestDateTime \n CredentialScope \n HashedCanonicalRequest
  • Algorithm – The algorithm used to create the hash of the canonical request. For SHA-256, the algorithm is AWS4-HMAC-SHA256.

  • RequestDateTime – The date and time used in the credential scope. This value is the current UTC time in ISO 8601 format (for example, 20130524T000000Z).

  • CredentialScope – The credential scope, which restricts the resulting signature to the specified Region and service. The string has the following format: YYYYMMDD/region/service/aws4_request.

  • HashedCanonicalRequest – The hash of the canonical request, calculated in the previous step.

The following is an example string to sign.

"AWS4-HMAC-SHA256" + "\n" + timeStampISO8601Format + "\n" + <Scope> + "\n" + Hex(SHA256Hash(<CanonicalRequest>))

Derive a signing key

To derive a signing key, perform a succession of keyed hash operations (HMAC) on the request date, Region, and service, with your AWS secret access key as the key for the initial hashing operation.

For each step, call the hash function with the required key and data. The result of each call to the hash function becomes the input for the next call to the hash function.

The following example shows how you derive the SigningKey used in the next section of this procedure, showing the order in which your input is concatenated and hashed. HMAC-SHA256 is the hash function used to hash the data as shown.

DateKey = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>") DateRegionKey = HMAC-SHA256(<DateKey>, "<aws-region>") DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>") SigningKey = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")
Required input
  • Key, a string that contains your secret access key.

  • Date, a string that contains the date used in the credential scope, in the format YYYYMMDD.

  • Region, a string that contains the Region code (for example, us-east-1).

    For a list of Region strings, see Regional Endpoints in the AWS General Reference.

  • Service, a string that contains the service code (for example, ec2).

  • The string to sign that you created in the previous step.

To derive a signing key
  1. Concatenate "AWS4" and the secret access key. Call the hash function with the concatenated string as the key and the date string as the data.

    DateKey = hash("AWS4" + Key, Date)
  2. Call the hash function with the result of the previous call as the key and the Region string as the data.

    DateRegionKey = hash(kDate, Region)
  3. Call the hash function with the result of the previous call as the key and the service string as the data.

    The service code is defined by the service. You can use get-products in the AWS Pricing CLI to return the service code for a service.

    DateRegionServiceKey = hash(kRegion, Service)
  4. Call the hash function with the result of the previous call as the key and "aws4_request" as the data.

    SigningKey = hash(kService, "aws4_request")

Calculate the signature

Once you have derived the signing key, calculate the signature by performing a keyed hash operation on the string to sign. Use the derived signing key as the hash key for this operation.

To calculate a signature
  1. Call the hash function with the result of the previous call as the key and the string to sign as the data. The result is the signature as a binary value.

    signature = hash(SigningKey, string-to-sign)
  2. Convert the signature from binary to hexadecimal representation, in lowercase characters.

Add the signature to the request

Add the calculated signature to your request.

Example: Authorization header

The following example shows an Authorization header for the DescribeInstances action. For readability, this example is formatted with line breaks. In your code, this must be a continuous string. There is no comma between the algorithm and Credential. However, the other elements must be separated by commas.

Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220830/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-date, Signature=calculated-signature
Example: Request with authentication parameters in the query string

The following example shows a query for the DescribeInstances action that includes the authentication information. For readability, this example is formatted with line breaks and is not URL encoded. In your code, the query string must be a continuous string that is URL encoded.

https://ec2.amazonaws.com/? Action=DescribeInstances& Version=2016-11-15& X-Amz-Algorithm=AWS4-HMAC-SHA256& X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20220830/us-east-1/ec2/aws4_request& X-Amz-Date=20220830T123600Z& X-Amz-SignedHeaders=host;x-amz-date& X-Amz-Signature=calculated-signature