19

When I invoke API endpoints from REST client, I got error by concerning with Signature.

Request:

Host: https://xxx.execute-api.ap-southeast-1.amazonaws.com/latest/api/name

Authorization: AWS4-HMAC-SHA256 Credential={AWSKEY}/20160314/ap-southeast-1/execute-api/aws4_request,SignedHeaders=host;range;x-amz-date,Signature={signature}

X-Amz-Date: 20160314T102915Z

Response:

{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details. The Canonical String for this request should have been 'xxx' "
}

From Java code, I followed AWS reference of how to generate Signature.

    String secretKey = "{mysecretkey}";
    String dateStamp = "20160314";
    String regionName = "ap-southeast-1";
    String serviceName = "execute-api";

    byte[] signature = getSignatureKey(secretKey, dateStamp, regionName, serviceName);
    System.out.println("Signature : " + Hex.encodeHexString(signature));

    static byte[] HmacSHA256(String data, byte[] key) throws Exception  {
         String algorithm="HmacSHA256";
         Mac mac = Mac.getInstance(algorithm);
         mac.init(new SecretKeySpec(key, algorithm));
         return mac.doFinal(data.getBytes("UTF8"));
    }

    static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception  {
         byte[] kSecret = ("AWS4" + key).getBytes("UTF8");
         byte[] kDate    = HmacSHA256(dateStamp, kSecret);
         byte[] kRegion  = HmacSHA256(regionName, kDate);
         byte[] kService = HmacSHA256(serviceName, kRegion);
         byte[] kSigning = HmacSHA256("aws4_request", kService);
         return kSigning;
    }

May I know what I was wrong while generating Signature?

Reference how to generate Signature : http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java

Tun Lin Aung
  • 339
  • 1
  • 4
  • 17

6 Answers6

21

You can use classes from aws-java-sdk-core: https://github.com/aws/aws-sdk-java/tree/master/aws-java-sdk-core

More specifically, Request, Aws4Signer and a few other ones:

//Instantiate the request
Request<Void> request = new DefaultRequest<Void>("es"); //Request to ElasticSearch
request.setHttpMethod(HttpMethodName.GET);
request.setEndpoint(URI.create("http://..."));

//Sign it...
AWS4Signer signer = new AWS4Signer();
signer.setRegionName("...");
signer.setServiceName(request.getServiceName());
signer.sign(request, new AwsCredentialsFromSystem());

//Execute it and get the response...
Response<String> rsp = new AmazonHttpClient(new ClientConfiguration())
    .requestExecutionBuilder()
    .executionContext(new ExecutionContext(true))
    .request(request)
    .errorResponseHandler(new SimpleAwsErrorHandler())
    .execute(new SimpleResponseHandler<String>());

If you want a cleaner design, you can use the Decorator pattern to compose some elegant classes and hide the above mess. An example for that here: http://www.amihaiemil.com/2017/02/18/decorators-with-tunnels.html

amihaiemil
  • 623
  • 8
  • 19
  • Quite suprisingly it does not work with other HTTP clients. The same set of headers generates 'The request signature we calculated does not match the signature you provided." error. AmazonHttp client goes smoothly. – Łukasz Jun 09 '20 at 11:16
  • 3
    What is `AwsCredentialsFromSystem`? I can't find a reference to that class the AWS Java API. – totsubo Aug 08 '20 at 19:37
  • AwsCredentialsFromSystem is my class, which implements interface AwsCredentials (from the SDK). It's just a class that reads the required credentials from System properties: https://github.com/opencharles/charles-rest/blob/5c4b99c575e73dec6c7afe120ce0c9e15a8b8b61/src/main/java/com/amihaiemil/charles/aws/requests/SignedRequest.java#L107 – amihaiemil Aug 11 '20 at 13:24
  • I'm trying to use your answer as an example but I'm having trouble with the "Execute it and get the response" section. It's telling me "The method AmazonHttpClient(ClientConfiguration) is undefined". I'm using eclipse. I was able to automatically import everything before that using the quick fix option. But I'm not sure how to get AmazonHttpClient working. ...also it's telling me that "SimpleAwsErrorHandler()" is not defined. here are my import statements: (in the next comment) – benino Oct 28 '22 at 19:05
  • import com.amazonaws.ClientConfiguration; import com.amazonaws.DefaultRequest; import com.amazonaws.Request; import com.amazonaws.Response; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.http.ExecutionContext; import com.amazonaws.http.HttpMethodName; – benino Oct 28 '22 at 19:06
3

From the code example above it looks like you are not creating a canonical request and including it in the string that gets signed as per http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

Instead of implementing this yourself have you looked at using a third-party library.

aws-v4-signer-java is a lightweight, zero-dependency library that makes it easy to generate AWS V4 signatures.

String contentSha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
HttpRequest request = new HttpRequest("GET", new URI("https://examplebucket.s3.amazonaws.com?max-keys=2&prefix=J"));
String signature = Signer.builder()
        .awsCredentials(new AwsCredentials(ACCESS_KEY, SECRET_KEY))
        .header("Host", "examplebucket.s3.amazonaws.com")
        .header("x-amz-date", "20130524T000000Z")
        .header("x-amz-content-sha256", contentSha256)
        .buildS3(request, contentSha256)
        .getSignature();

Disclaimer: I'm the libraries author.

lucasweb
  • 1,736
  • 5
  • 28
  • 42
  • Hiya Lucas, I'm curious if you have tested this library for a back-end server providing signatures to a front-end client for direct upload to S3. Is it necessary to compute the sha256 of the content client-side and then submit that has to the server as part of the signature calculation? – Charney Kaye Apr 21 '17 at 14:52
  • @CharneyKaye did you find the answer I am also looking for it – princebillyGK Nov 07 '20 at 07:09
  • @lucasweb Instead of hardcoding the header pairs, do we have any provision to pass a Map/List ? If so, then it would be useful for me to dynamically receive all the headers at once via method arguments and generate the signature – Deepak Prabhu Nov 12 '20 at 04:08
  • Is this available for SQS – Shamil Puthukkot Feb 02 '21 at 12:22
  • can I use aws-v4-signer for generating a signature (x-amz-signature) for an s3 form post request? I believe no headers are needed in this case and the signature is made of the policy and secret? – PathOfNeo Sep 17 '21 at 07:22
2

This is possible using 100% java libraries without additional dependencies, just use the query parameters generated here:

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Formatter;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

...

private static final String ACCESS_KEY = "...";
private static final String SECRET_KEY = "...";
private static final int expiresTime = 1 * 24 * 60 * 60;
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

public void sign(String protocol, String bucketName, String contentPath) throws Exception {
    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.HOUR_OF_DAY, 24);

    String host = bucketName + ".s3-us-west-2.amazonaws.com";
    long expireTime = cal.getTimeInMillis() / 1000;

    String signString = "GET\n" +
        "\n" +
        "\n" +
        expireTime + "\n" +
        "/" + bucketName + contentPath;

    SecretKeySpec signingKey = new SecretKeySpec(SECRET_KEY.getBytes(), HMAC_SHA1_ALGORITHM);
    Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
    mac.init(signingKey);
    String signature = URLEncoder.encode(new String(Base64.getEncoder().encode(mac.doFinal(signString.getBytes()))));

    System.out.println(signature);
    String fullPayload = "?AWSAccessKeyId=" + ACCESS_KEY +
        "&Expires=" + expireTime + 
        "&Signature=" + signature;

    System.out.println(protocol + "://" + host + contentPath + fullPayload);
}

...
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
OscarG
  • 385
  • 5
  • 11
1

The signing process is lengthy and error-prone, here are some tips

KryptonJ
  • 11
  • 1
1

You may investigate code samples that is shared by AWS web site. I used some of the util classes and a few java class I need. So you don't have to use all classes and other stuff. I left the link below.

AWS Java Samples in doc of Amazon

sopehl
  • 1,067
  • 1
  • 13
  • 18
0

For me, in Java, the following code worked to generate a signed request to sent to web socket client via api gateway -

Request<Void> request = new DefaultRequest<Void>("execute-api"); //Request to API gateway
request.setHttpMethod(HttpMethodName.POST);
request.setEndpoint(URI.create(url));
String bodyContnt= "test data";
InputStream targetStream = new ByteArrayInputStream(bodyContnt.getBytes());
request.setContent(targetStream);

//Sign it...
AWS4Signer signer = new AWS4Signer();
signer.setRegionName("ap-south-1");
signer.setServiceName(request.getServiceName());
signer.sign(request, new Creds());
signer.setOverrideDate(new Date()); // needed as current ts is required

//Execute it and get the response...
Response<String> rsp = new AmazonHttpClient(new ClientConfiguration())
.requestExecutionBuilder()
.executionContext(new ExecutionContext(true))
.request(request)
.errorResponseHandler(new SimpleAwsErrorHandler(true))
      .execute(new SimpleResponseHandler());
  • 1
    You should explain how your code improves on the previous answers to this question. – moken Dec 04 '22 at 12:24
  • @moken - My comment was also intended to make the date setting aspect clearer in the input. Without it, the call fails! – jswordfish Dec 05 '22 at 13:36