I am having problems generating a valid signature key for doing HTTP Post in browser- I keep getting errors like
The request signature we calculated does not match the signature you provided. Check your key and signing method
and since that doesn't tell me anything much about where the problem lies, i decided to debug if my signature calculation method is correct.
So I decided to replicate the example in the documentation using the given String to sign and the Secret Access Key. My generated signature doesn't match the signature given on the documentation page.
What am I doing wrong here ? The below is my existing code (I am using AWS Signature V4)
private void debugSignatureGeneration() throws Exception {
byte[] testSigningKey = getSigningKey("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "20151229", "us-east-1",
"s3");
String testStringToSign = "eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLA0KICAiY29uZGl0aW9ucyI6IFsNCiAgICB7ImJ1Y2tldCI6ICJzaWd2NGV4YW1wbGVidWNrZXQifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwNCiAgICB7ImFjbCI6ICJwdWJsaWMtcmVhZCJ9LA0KICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tL3N1Y2Nlc3NmdWxfdXBsb2FkLmh0bWwifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAiaW1hZ2UvIl0sDQogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwNCiAgICB7IngtYW16LXNlcnZlci1zaWRlLWVuY3J5cHRpb24iOiAiQUVTMjU2In0sDQogICAgWyJzdGFydHMtd2l0aCIsICIkeC1hbXotbWV0YS10YWciLCAiIl0sDQoNCiAgICB7IngtYW16LWNyZWRlbnRpYWwiOiAiQUtJQUlPU0ZPRE5ON0VYQU1QTEUvMjAxNTEyMjkvdXMtZWFzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LA0KICAgIHsieC1hbXotYWxnb3JpdGhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwNCiAgICB7IngtYW16LWRhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfQ0KICBdDQp9";
String testSignature = getSignatureV4(testStringToSign, testSigningKey);
System.out.println("test signature " + testSignature);
}
static String getSignatureV4(String stringToSign, byte[] signingKey) throws Exception {
byte[] signature = HmacSHA256(stringToSign, signingKey);
return Hex.encodeHexString(signature);
}
static byte[] HmacSHA256(String policy, byte[] key) throws Exception {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(policy.getBytes("UTF8"));
}
static byte[] getSigningKey(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;
}
If it helps, the signature that is given in the documentation is
46503978d3596de22955b4b18d6dfb1d54e8c5958727d5bdcd02cc1119c60fc9
while the signature I am able to get is
8afdbf4008c03f22c2cd3cdb72e4afbb1f6a588f3255ac628749a66d7f09699e
Related
I'm able to successfully get a HMAC SHA256 using the following code:
public static String getHac(String dataUno, String keyUno) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
SecretKey secretKey = null;
Mac mac = Mac.getInstance("HMACSHA256");
byte[] keyBytes = keyUno.getBytes("UTF-8");
secretKey = new SecretKeySpec(keyBytes,mac.getAlgorithm());
mac.init(secretKey);
byte[] text = dataUno.getBytes("UTF-8");
System.out.println("Hex encode: " + Hex.encode(keyUno.getBytes()));
byte[] encodedText = mac.doFinal(text);
return new String(Base64.encode(encodedText)).trim();
}
which yields:
HMAC: 9rH0svSCPHdbc6qUhco+nlkt2O7HE0rThV4M9Hbv5aY=
However, i would like getting this:
HMAC:eVXBY4RZmFQcOHHZ5FMRjDLOJ8vCuVGTjy7cHN7pqfo=
I tried an online tool and it appears that the difference between my code and online tool is that I am working with a text in the key type.
Test values:
String data = "5515071604000fAIkwJtkeiA:APA91bH_Pb5xB2lrmKWUst5xRuJ3joVE-sb9KoT0zXZuupIEfdHjii-cODj-JMnjyy7hFJUbIRAre9o2yaCU43KaFDmxKlhJhE36Dw0bZ2VntDUn_Zd1EJBuSyCYiUtmmkHfRvRy3hIb";
String key = "fc67bb2ee0648a72317dcc42f232fc24f3964a9ebac0dfab6cf47521e121dc6e";
getHac("5515071604000fAIkwJtkeiA:APA91bH_Pb5xB2lrmKWUst5xRuJ3joVE-sb9KoT0zXZuupIEfdHjii-cODj-JMnjyy7hFJUbIRAre9o2yaCU43KaFDmxKlhJhE36Dw0bZ2VntDUn_Zd1EJBuSyCYiUtmmkHfRvRy3hIb", "fc67bb2ee0648a72317dcc42f232fc24f3964a9ebac0dfab6cf47521e121dc6e"));
the execution of my method return
9rH0svSCPHdbc6qUhco+nlkt2O7HE0rThV4M9Hbv5aY=
(the online returns the same value with key type text selected)
and i expected
eVXBY4RZmFQcOHHZ5FMRjDLOJ8vCuVGTjy7cHN7pqfo=
(the online returns the same value with key type hex selected)
Assuming that you are using Apache Commons Codec 1.11, use the following:
byte[] keyBytes = Hex.decodeHex(keyUno);
getHac Method
You code just slightly modified looks like this then:
public static String getHac(String dataUno, String keyUno)
throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, DecoderException {
SecretKey secretKey;
Mac mac = Mac.getInstance("HMACSHA256");
byte[] keyBytes = Hex.decodeHex(keyUno);
secretKey = new SecretKeySpec(keyBytes, mac.getAlgorithm());
mac.init(secretKey);
byte[] text = dataUno.getBytes("UTF-8");
byte[] encodedText = mac.doFinal(text);
return new String(Base64.encodeBase64(encodedText)).trim();
}
Test
This Java method gives then expected result:
eVXBY4RZmFQcOHHZ5FMRjDLOJ8vCuVGTjy7cHN7pqfo=
I'm trying to verify some signed content using ECDSA and spongycastle. Here is the code I'm using to generate the keypair with the brainpoolP512t1 ec curve:
public static KeyPair getKeyPairbrainpoolP512t1() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "SC");
ECNamedCurveParameterSpec curveParameterSpec = ECNamedCurveTable.getParameterSpec("brainpoolP512t1");
keyPairGenerator.initialize(curveParameterSpec, new SecureRandom());
return keyPairGenerator.generateKeyPair();
}
Here is the code I'm using to sign and verify signatures:
private byte[] ecdsaSign(ECPrivateKey key, byte[] content) throws Exception {
Signature ecdsaSign = Signature.getInstance("SHA256withECDSA", "SC");
ecdsaSign.initSign(key);
ecdsaSign.update(content);
byte[] signature = ecdsaSign.sign();
return signature;
}
public static boolean ecdsaVerify(ECPublicKey key, byte[] content, byte[] signature) throws Exception {
Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", "SC");
ecdsaVerify.initVerify(key);
ecdsaVerify.update(content);
boolean result = ecdsaVerify.verify(signature);
return result;
}
I'm passing in the bytes of a simple string message that was signed using the private key, and also the public key in order to verify. I'm always getting false however. What am I doing wrong? Any help is deeply appreciated.
Figured out what was wrong. I was exporting the keys to PEM in order to have them in string format prior to calling sign and verify, and then decoding back to private key original format. When I omitted this conversion and called Verify directly with the private key (without the PEM string back and forth conversion), the content got verified.
Java:
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes(),HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); mac.init(signingKey);
byte[] bytes = mac.doFinal(signatureString.getBytes()); return BASE64.encodeBAse64String(bytes);
C#:
public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, signatureTypes signatureType, out string normalizedUrl, out string normalizedRequestParameters) {
string signatureBase = GenerateSignatureBase(url, consumerKey, token, tokenSecret, httpMethod, timeStamp, nonce, HMACSHA1SignatureType, out normalizedUrl, out normalizedRequestParameters);
HMACSHA1 hmacsha1 = new HMACSHA1();
hmacsha1.key = Encoding.ASCII.GetBytes(string.Format("{0} & {1}", UrlEncode(consumerSecret), string.IsNullEmpty(tokenSecret) ? "" : UrlEncode(tokenSecret)));
//data is generated signature base string with attaching all parameters.
byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(data);
byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer);
return Convert.ToBase64String(hashBytes);
}
I am creating REST services where I am using OAuth 1.0 signature and for generating Oauth I need to use HMACSHA1 algorithm. My client's API is in C# and my API is in Java, and I have to generate a signature that should be encrypted by my client successfully.
I'm trying to publish an app on Shopify marketplace by following this documentation. And I'm stuck on step-3 of the oauth documentation wherein you have to do 'HMAC Signature Validation'.
Documentation states that you have to process the string (specified below) through HMAC-SHA256 using app's shared secret key.
String = "shop=some-shop.myshopify.com×tamp=1337178173"
I'm trying to implement the steps using Java. Following is gist of the code that I have used.
private static final String HMAC_ALGORITHM = "HmacSHA256";
String key = "hush";
String data = "shop=some-shop.myshopify.com×tamp=1337178173";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(),HMAC_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
mac.init(keySpec);
byte[] rawHmac = mac.doFinal(data.getBytes());
System.out.println(Hex.encodeHexString(rawHmac));
The code produces the following string:
c2812f39f84c32c2edaded339a1388abc9829babf351b684ab797f04cd94d4c7
Through some random search on Shopify developer forum I found the link to a question.
The last message from #Shayne suggests that we have to make changes in data variable by adding protocol field.
But it didn't work out :(
Can anyone tell me what should be done?Do I have to make modifications in my code or the process in the documentation have changed.
Please help.
Here's the java code you need to verify Shopify HMAC. The protocol parameter isn't required unless it was in the result from shopify, which it wasn't from me.
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String HMAC_ALGORITHM = "HmacSHA256";
resp.setContentType("text/html;charset=UTF-8");
Map<String,String[]> parameters = req.getParameterMap();
String data = null;
SortedSet<String> keys = new TreeSet<String>(parameters.keySet());
for (String key : keys) {
if (!key.equals("hmac")&&!key.equals("signature")){
if (data == null){
data = key + "=" +req.getParameter(key);
}
else {
data = data + "&" + key + "=" + req.getParameter(key);
}
}
}
SecretKeySpec keySpec = new SecretKeySpec(SHARED_KEY.getBytes(),HMAC_ALGORITHM);
Mac mac = null;
try {
mac = Mac.getInstance(HMAC_ALGORITHM);
mac.init(keySpec);
byte[] rawHmac = mac.doFinal(data.getBytes());
if (Hex.encodeHexString(rawHmac).equals(req.getParameter("hmac"))){
//THE HMAC IS VERIFIED
} else {
//THE HMAC IS NOT VERIFIED
}
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
}
Interestingly, the timestamp parameter in data turns into
×tamp=1459537704
instead of
×tamp=1459537704
The example is wrong apparently. Your hash code is OK. You'll need to make sure you include all parameters from the Shopify response e.g. the input for verification of a response would look like:
code={code}&protocol=https://&store={store}×tamp={timestamp}
See: https://ecommerce.shopify.com/c/shopify-apis-and-technology/t/you-broke-my-build-hmac-verification-broken-282951
here is my prod code:
public class HMACValidator {
public static String sha256HMAC(String key, String data) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, DecoderException {
Mac hmac = Mac.getInstance("HmacSHA256");
System.out.println("data "+data);
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
hmac.init(secret_key);
return Hex.encodeHexString(hmac.doFinal(data.getBytes("UTF-8")));
}
public static boolean validateShopifyAskForPermission(String key, String hmac, String shop, String timestamp) throws Exception {
return (sha256HMAC(key, "shop="+shop+"×tamp="+timestamp).compareTo(hmac) == 0);
}
}
This is the code am using on the Java side
private String encodedHexString(String secretKey, String payload)
throws NoSuchAlgorithmException, InvalidKeyException {
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(),"HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(keySpec);
byte[] payloadDigest = mac.doFinal(payload.getBytes());
String encodedDigest = DatatypeConverter.printHexBinary(payloadDigest);
return encodedDigest;
}
where secretKey is the Secret token entered on GitHub side as well on my side and the payload is what I am getting in the request.getParameter("payload") for application/x-www-form-urlencoded Content-type.
matching this with request.getHeader("X-Hub-Signature") don't match unfortunately, even after appending "sha1=" to it on Java side.
I had success with simply encoding the whole request body, without any kind of parameter parsing, unescaping, etc:
String secret = "73fcce1ecaeeb4136f27854eaaacb785929bb9a3";
String payload = "payload=%7B%22zen%22%3A%22It%27s+not+fully+shipped+until+it%27s+fast.%22%2C%22hook_id%2...";
System.out.println(encodedHexString(secret, payload));
The result was:
0F026F9E5107F4BF377CA032C1431BFED687B6E9
This matched the X-Hub-Signature header very well:
X-Hub-Signature: sha1=0f026f9e5107f4bf377ca032c1431bfed687b6e9