Invalid appsecret_proof provided in the API argument on python server - java

I'm having trouble fetching mutual friends for two users of my app who are not friends.
As per all_mutual_friends permission, I need to make the request along with the appsecret_proof parameter.
I generated the app_access_token using this GET call:
GET /oauth/access_token
?client_id={app-id}
&client_secret={app-secret}
&grant_type=client_credentials
I've triple checked the app_id and app_secret, they are correct. I generated the appsecret_proof by SHA256 hashing the app_access_token with app_secret in Java.
Now when I request the mutual friends (sending the appsecret_proof as query parameter), it responds saying
"Invalid appsecret_proof provided in the API argument"
with a GraphMethodException. The original request (without appsecret_proof) is working fine for users who are friends. Any pointers here?
Here is the java code I'm using to generate appsecret_proof:
public static String hashMac(String text, String secretKey)
throws SignatureException {
try {
Key sk = new SecretKeySpec(secretKey.getBytes(), HASH_ALGORITHM);
Mac mac = Mac.getInstance(sk.getAlgorithm());
mac.init(sk);
final byte[] hmac = mac.doFinal(text.getBytes());
return toHexString(hmac);
} catch (NoSuchAlgorithmException e1) {// throw an exception or pick a different encryption method
throw new SignatureException(
"error building signature, no such algorithm in device "
+ HASH_ALGORITHM);
} catch (InvalidKeyException e) {
throw new SignatureException(
"error building signature, invalid key " + HASH_ALGORITHM);
}
}
private static final String HASH_ALGORITHM = "HmacSHA256";
public static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
Formatter formatter = new Formatter(sb);
for (byte b : bytes) {
formatter.format("%02x", b);
}
return sb.toString();
}
My server is python based.

I was able to fetch the mutual friends. I was using app_access_token to generate the appsecret_proof but the access_token of sessioned user needs to be used to generate the appsecret_proof. Apparently, this was not documented by Facebook.

Related

Translate Java string signing to OpenSSL

For a payment API I need to hash a string (a request payload) with SHA-512,
sign itwith "RSA" according to the documentation.
Hash the payload string (payout instruction) using SHA512 algorithm.
Sign the hashed payload string using RSA algorithm and the signing certificate private key.
Base64 encode the signed hash
Step 1 and 3 I've got no problems with. Step 2 is the problem. Support sent me a java example but we don't run java and I would like to do this in OpenSSL if at all possible.
private static String createSignature(String stringToBeSigned, PrivateKey privateKey) {
try {
byte[] hashString = hashString(stringToBeSigned);
Signature sign = Signature.getInstance("SHA512withRSA");
sign.initSign(privateKey);
sign.update(hashString);
byte[] signature = sign.sign();
return Base64.getEncoder().encodeToString(signature);
} catch (Exception e) {
throw new RuntimeException("Could not create signed payout request.", e);
}
}
private static byte[] hashString(String stringToBeHashed) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-512");
return digest.digest(stringToBeHashed.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
The Key is provided. How do I find what "SHA512withRSA" corresponds to using OpenSSL?

Comparing SHA256 Output in Java and PHP

This question might probably be a duplicate. But so far I haven't seen any response that could solve mine issue.
I have this piece of Java code doing encryption with SHA-256:
public static String hashIt(String msg, String key) {
MessageDigest m = null;
String hashText = null;
byte[] actualKeyBytes = TripleDES.hexStringToBytes(key);
try {
m = MessageDigest.getInstance("SHA-256");
m.update(actualKeyBytes, 0, actualKeyBytes.length);
try {
m.update(msg.getBytes("UTF-8"), 0, msg.length());
} catch (UnsupportedEncodingException ex) {
}
hashText = TripleDES.bytesToHexString( m.digest() ); //new BigInteger(1, m.digest()).toString(16);
} catch (NoSuchAlgorithmException ex) {
}
return hashText;
}
Using d38a5cd5 as key with "ewo10kalavanda" as the string to hash.
Utils.hashIt("ewo10kalavanda", "d38a5cd5");
I have the following output:
fc87c73012e11de3a57faabe4d852ce89ec3337504531c16
Using the same SHA256 in PHP
hash_hmac('SHA256', "ewo10kalavanda", "d38a5cd5", $raw=false)
The output is 1839412f79b9e33c2f810650f79f23f46173792f885dd8d8c9633675e28e792f which does not match that of Java.
Is there anything done wrong here? Been on this for some hours now.
In your PHP code you used HMAC which is more than just hashing the string obtained by joining key and the message body. I found a diagram from Wikipedia which explains how HMAC-SHA1 works:
I did manage to get a working version in Java:
public static String hashIt(String msg, String key) {
try {
byte[] keyBytes = key.getBytes("UTF-8");
SecretKeySpec spec = new SecretKeySpec(keyBytes, HMAC_SHA256);
Mac mac = Mac.getInstance(HMAC_SHA256);
mac.init(spec);
return TripleDES.bytesToHexString(mac.doFinal(msg.getBytes("UTF-8")));
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
I still think there is something wrong with msg.length(). If you ever hash a two byte character it will be buggy. I tested it and it's different. For example try use your previous code to hash 111111錒(message) and 1111(key) and then use my previously suggested code to hash the same string. Your code output 81e385eb2bf89f7494a4b0927a4f5d4105450eb4a21152d53d52ddb9c08ed0e1 and my code output ef7f82833c865ef4d6089ba7dfbec8ad4f05b58e3fd77ca242c5fd7e7757d8b4.
That chinese character is intended. It shows how the OP's code fails with two byte characters. DO NOT REMOVE.
The other answer from glee8e should have got you a long way. But just to be sure, here is how to generate the output:
$k = hex2bin("d38a5cd5");
$m = "ewo10kalavanda";
$in = $k.$m;
$h = hash ("SHA256", $in);
print $h;
It would be a bit better to first encode to UTF-8, but I haven't got the right module installed:
$m = mb_convert_encoding("ewo10kalavanda", "UTF-8");
for the test sting this of course doesn't matter as long as the platform encoding is compatible with UTF-8 for the input characters.
That's however half of the answer though: there is a reason why HMAC was defined, and the major reason is that hash functions on their own are not that secure for keyed hash or Message Authentication Code (MAC). So the use of HMAC as in the PHP function should be preferred.

fail to verify jwt signature in Java

I am trying to verify a JWT token by verifying its signature. But during the verification I get an error
java.security.SignatureException: Signature length not correct: got
342 but was expecting 256
. I assume that signature is the encrypted sha256 hash of the data. In my case data is base64(header)+"."+base64(body). Here is my code:
static String certificate = "MIIDBjCCAe4CCQDmPif23IJerzANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE1MDkyMjA3MDkwMFoXDTE2MDkyMTA3MDkwMFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYjKc8pLFkAa45j6w6PHsroe7ijOCfhZmVtMCvZ8lINaP9mR8irOJHpLdJs4vpbxEZMqqLMhKjO7iUmXBmml37QRlJXY6f25essPkTdUmhiIrU/rIrZrCanvegXUHkvf4xvOQ1BTx/p5b1iIq3Wrk5Fox3pMigzqYhk4YuiJho8uabC9zyecmS3zIoRgwx+Vacel/ZW6r6YOlB6mblN9IvasvqWgDalegmMKOIZvwkpo/3mfzcGi5haWZZ3ufUqQjb4B7raJmfyrLnwi6XI9UzzGc04pCfIAsxTb5yM8cJQcJ/5VHF3h21eFJdZKyD2210gSq7/Y8Oda0dDXQchmFcCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAmBm84PfwOe5sGemT9D4ImIYWp/1xrV6b0zeaDZ6Yi7sz9R3VKjfnAxUVOHA7GlMi8xHHdDu7VhVhAWyFCvITdG00K18xJuKap1bwzw3kr70lLQaFahV+zaWKfWAfV34tots3G/hMqdOv0a+I/5t/T7oKPCmm/IfCVKdC1tGbTji+hxVLpaAkn60RFNzLKGFwtSxv9ObxR5Hn88+wV48VAcEnwcUk2DjBi1fW6jnMcNJbVd+/oKBOwj7UK2Lk10Qaeet8KKh5fFKEpgx7D4ITwer0G/Je1NMv1/lfNzpKlTKoBureF5C6B+rJIesQ/dAfg6H/ggxbgVMuo6imIPVvrg==";
static String signedData = "Z5MwwjtXdypMQGNwmNNuCVmRcDVT24EgtwoDWalF4icxwz7jyB99Yg3262D7OsERewv4cOfdEz3bbOF-iG7YWXeSC9YZeO1tGapqlc8FRtAergSUZC7BcbFEx75MfSy7qLWYTOfdpJesQ23rOzjF7KdrAMJC_Y0T_r6RuBcZVyfT4P55kICETYyv7bBDXc9V8BJUf-QHDu6DaH7u6PSyeOmdzFInI_LwySnMlr3VahoUfUpJmauU8yHQUnFakJBgrMBe1Au9tS-HxtDVnHmoHQw8xGXsVQnEOa1aPAcVWy0v7hILUSmWNAG3IZ0JwUztQitgtnTTzXDszUxTbJ4YlQ";
static String data = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiJodHRwczovL2NpdHJpeHAuY29tOjg0NDMvIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZGQ5YjZhM2UtMjlkMS00MjU0LWE3NDYtZTAyOTQxNDQ0NTE3LyIsImlhdCI6MTQ0MjYyNjA0NSwibmJmIjoxNDQyNjI2MDQ1LCJleHAiOjE0NDI2Mjk5NDUsInZlciI6IjEuMCIsInRpZCI6ImRkOWI2YTNlLTI5ZDEtNDI1NC1hNzQ2LWUwMjk0MTQ0NDUxNyIsIm9pZCI6ImJkZDNmZDAyLTEyMzMtNGMxOC05NTRmLWJkNGFjMWYzOWU5OSIsInVwbiI6InNwQGNpdHJpeHAuY29tIiwic3ViIjoieUFfZTFzMmFOeFdvR3NSNzhfVWNYZkk5dERlYUM0QUozdXFZQXFDdllSbyIsImdpdmVuX25hbWUiOiJzIiwiZmFtaWx5X25hbWUiOiJwIiwibmFtZSI6InNwIiwiYW1yIjpbInB3ZCIsInJzYSJdLCJ1bmlxdWVfbmFtZSI6InNwQGNpdHJpeHAuY29tIiwiYXBwaWQiOiIyOWQ5ZWQ5OC1hNDY5LTQ1MzYtYWRlMi1mOTgxYmMxZDYwNWUiLCJhcHBpZGFjciI6IjAiLCJzY3AiOiJtZG1fZGVsZWdhdGlvbiIsImFjciI6IjEiLCJpcGFkZHIiOiI2My4xMTAuNTEuMTEiLCJkZXZpY2VpZCI6ImQyNzU3NzY0LWFiOTEtNDBiMS05MmM2LTViOWE4MWYxODNiYyJ9";
public static void main(String[] args) throws UnsupportedEncodingException {
// TODO Auto-generated method stub
String certString = "-----BEGIN CERTIFICATE-----\r\n" + certificate + "\r\n-----END CERTIFICATE-----";
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
CertificateFactory cf;
try {
cf = CertificateFactory.getInstance("X.509");
InputStream stream = new ByteArrayInputStream(certString.getBytes()); //StandardCharsets.UTF_8
java.security.cert.Certificate cert = cf.generateCertificate(stream);
PublicKey pk = cert.getPublicKey();
//verifying content with signature and content :
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify( pk );
sig.update(data.getBytes());
Boolean ret = sig.verify(signedData.getBytes());
} catch (CertificateException | SignatureException | NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
The signature part of a JWT is base64url encoded (see JWS §7.1) so you'll need to properly decode it before verification.
Replace the line Boolean ret = sig.verify(signedData.getBytes()); with something like the following (using the Base64 decoder from Apache Commons Codec),
byte[] signature = org.apache.commons.codec.binary.Base64.decodeBase64(signedData);
Boolean ret = sig.verify(signature);
And the signature on that JWT should verify for you.
Having said that, I'd very much recommend using a library for JWT processing. A decent library will handle stuff like that for you and can provide more JWT functionality too.
For example, the following using the jose4j JWT library could replace the stuff from the code in the question after getting the public key from the certificate. This not only verifies the signature per the spec but also validates the claims in the JWT to make sure it's still valid, was issued to you, etc..
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setVerificationKey(pk)
.setRequireExpirationTime()
.setExpectedAudience("https://citrixp.com:8443/")
.setExpectedIssuer("https://sts.windows.net/dd9b6a3e-29d1-4254-a746-e02941444517/")
.build();
JwtClaims claims = jwtConsumer.processToClaims(data + "." + signedData);
System.out.println("Subject: " + claims.getSubject());
System.out.println("UPN: " + claims.getStringClaimValue("upn")); // or whatever, etc....
That JWT expired a few days so the processToClaims will throw an exception, which is what you want. You can add a .setEvaluationTime(NumericDate.fromSeconds(1442626055)) when building the JwtConsumer to get it to work for the given JWT though.

HMAC-SHA256 issue in Shopify oauth (Output does not match)

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&timestamp=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&timestamp=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
&timestamp=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}&timestamp={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+"&timestamp="+timestamp).compareTo(hmac) == 0);
}
}

Generate SHA 256 Hash using multiple values in Java/Android

I have two objects based on which SHA 256 Hash needs to be generated.
First value is a JSONObject
Second value is a String variable.
Ideally, what i need is
Hash hash= new Hash(JSONObject, String);
I couldn't find any hash generation methods which takes two values.
Could anyone help me with this?.
SHA 256 works on a byte array as input. You need to convert your JSONObject and your String to byte arrays, then calculate the SHA 256 hash on the concatenation of these byte arrays.
The proper way of generating a sha256 hashcode using key and value
public static String hashMac(String text, String secretKey)
throws SignatureException {
try {
Key sk = new SecretKeySpec(secretKey.getBytes(), HASH_ALGORITHM);
Mac mac = Mac.getInstance(sk.getAlgorithm());
mac.init(sk);
final byte[] hmac = mac.doFinal(text.getBytes());
return toHexString(hmac);
} catch (NoSuchAlgorithmException e1) {
// throw an exception or pick a different encryption method
throw new SignatureException(
"error building signature, no such algorithm in device "
+ HASH_ALGORITHM);
} catch (InvalidKeyException e) {
throw new SignatureException(
"error building signature, invalid key " + HASH_ALGORITHM);
}
}
public static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
Formatter formatter = new Formatter(sb);
for (byte b : bytes) {
formatter.format("%02x", b);
}
return sb.toString();
}

Categories