I want send a rest service call from Java using "GET" request.But i am getting the following error.I am able to use it in the postman but i am unable to send java application.
Exception in thread "main" java.lang.IllegalArgumentException: Key length not 128/192/256 bits.
at org.bouncycastle.crypto.engines.AESFastEngine.generateWorkingKey(Unknown Source)
at org.bouncycastle.crypto.engines.AESFastEngine.init(Unknown Source)
at org.bouncycastle.crypto.modes.CBCBlockCipher.init(Unknown Source)
at org.bouncycastle.crypto.macs.CMac.init(Unknown Source)
at com.rest.OAuth1.generateCmac(OAuth1.java:262)
at com.rest.OAuth1.generateSignature(OAuth1.java:180)
at com.rest.OAuth1.main(OAuth1.java:61)
This the my sample code
package com.rest;
// Java Libraries
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import javax.net.ssl.HttpsURLConnection;
// Apache Commons Libraries used for the Nonce & Base64
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.codec.binary.Base64;
// Bouncy Castle Libraries used for CMAC encryption
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.macs.CMac;
import org.bouncycastle.crypto.params.KeyParameter;
/**
* Very basic sample code that demonstrates how to make an OAuth 1.0 System-to-System
* request to the LearningStudio API
*/
public class OAuth1 {
public static void main(final String[] args) throws Exception
{
// Setup the variables necessary to create the OAuth 1.0 signature and make the request
String httpMethod = "GET";
String URI = "example.com/one/oauth1/userManagement/v5/users";
//String appID = "{applicationId}";
String consumerKey = "1234567-1234-4186-1234-1234567891011!mailid#example.com";
String secret = "12345678-1234-1234-1234-12345678";
String body = "{var:val}";
String signatureMethod = "HMAC-SHA1";
byte[] requestBody = null;
HttpsURLConnection request = null;
BufferedReader in = null;
URL url = new URL(String.format("https://api.example.com%s", URI));
// Set the Nonce and Timestamp parameters
String nonce = getNonce();
String timestamp = getTimestamp();
// Set the request body if making a POST or PUT request
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod))
{
requestBody = body.getBytes("UTF-8");
}
// Create the OAuth parameter name/value pair
Map<String, String> oauthParams = new LinkedHashMap<String, String>();
oauthParams.put("oauth_consumer_key", consumerKey);
//oauthParams.put("application_id", appID);
oauthParams.put("oauth_signature_method", signatureMethod);
oauthParams.put("oauth_timestamp", timestamp);
oauthParams.put("oauth_nonce", nonce);
// Get the OAuth 1.0 Signature
String signature = generateSignature(httpMethod, url, oauthParams, requestBody, secret);
System.out.println(String.format("OAuth 1.0 Signature = %s", signature));
// Add the oauth_signature parameter to the set of OAuth Parameters
oauthParams.put("oauth_signature", signature);
// Generate a string of comma delimited: keyName="URL-encoded(value)" pairs
StringBuilder sb = new StringBuilder();
String delimiter = "";
for (String keyName : oauthParams.keySet()) {
sb.append(delimiter);
String value = oauthParams.get((String) keyName);
sb.append(keyName).append("=\"").append(URLEncoder.encode(value, "UTF-8")).append("\"");
delimiter=",";
}
String urlString = url.toString();
// omit the queryString from the url
int startOfQueryString = urlString.indexOf('?');
if(startOfQueryString != -1) {
urlString = urlString.substring(0,startOfQueryString);
}
// Build the X-Authorization request header
String xauth = String.format("OAuth realm=\"%s\",%s", urlString, sb.toString());
System.out.println(String.format("X-Authorization request header = %s", xauth));
try
{
// Setup the Request
request = (HttpsURLConnection)url.openConnection();
request.setRequestMethod(httpMethod);
request.addRequestProperty("X-Authorization", xauth);
// Set the request body if making a POST or PUT request
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod))
{
request.setRequestProperty("Content-Length", "" + requestBody.length);
request.setDoOutput(true);
OutputStream postStream = request.getOutputStream();
postStream.write(requestBody, 0, requestBody.length);
postStream.close();
}
// Send Request & Get Response
InputStreamReader reader = new InputStreamReader(request.getInputStream());
in = new BufferedReader(reader);
// Get the response stream
String response = in.readLine();
System.out.println(String.format("Successful Response: \r\n%s", response));
} catch (IOException e )
{
// This exception will be raised if the serve didn't return 200 - OK
System.out.print(e.getMessage());
} finally
{
if (in != null) in.close();
if (request != null) request.disconnect();
}
}
/**
* Generates a random nonce
*
* #return A unique identifier for the request
*/
private static String getNonce()
{
return RandomStringUtils.randomAlphanumeric(32);
}
/**
* Generates an integer representing the number of seconds since the unix epoch using the
* date/time the request is issued
*
* #return A timestamp for the request
*/
private static String getTimestamp()
{
return Long.toString((System.currentTimeMillis() / 1000));
}
/**
* Generates an OAuth 1.0 signature
*
* #param httpMethod The HTTP method of the request
* #param URL The request URL
* #param oauthParams The associative set of signable oAuth parameters
* #param requestBody The serialized POST/PUT message body
* #param secret Alphanumeric string used to validate the identity of the education partner (Private Key)
*
* #return A string containing the Base64-encoded signature digest
*
* #throws UnsupportedEncodingException
*/
private static String generateSignature(
String httpMethod,
URL url,
Map<String, String> oauthParams,
byte[] requestBody,
String secret
) throws UnsupportedEncodingException
{
// Ensure the HTTP Method is upper-cased
httpMethod = httpMethod.toUpperCase();
// Construct the URL-encoded OAuth parameter portion of the signature base string
String encodedParams = normalizeParams(httpMethod, url, oauthParams, requestBody);
// URL-encode the relative URL
String encodedUri = URLEncoder.encode(url.getPath(), "UTF-8");
// Build the signature base string to be signed with the Consumer Secret
String baseString = String.format("%s&%s&%s", httpMethod, encodedUri, encodedParams);
return generateCmac(secret, baseString);
}
/**
* Normalizes all OAuth signable parameters and url query parameters according to OAuth 1.0
*
* #param httpMethod The upper-cased HTTP method
* #param URL The request URL
* #param oauthParams The associative set of signable oAuth parameters
* #param requstBody The serialized POST/PUT message body
*
* #return A string containing normalized and encoded oAuth parameters
*
* #throws UnsupportedEncodingException
*/
private static String normalizeParams(
String httpMethod,
URL url,
Map<String, String> oauthParams,
byte[] requestBody
) throws UnsupportedEncodingException
{
// Sort the parameters in lexicographical order, 1st by Key then by Value
Map<String, String> kvpParams = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
kvpParams.putAll(oauthParams);
// Place any query string parameters into a key value pair using equals ("=") to mark
// the key/value relationship and join each parameter with an ampersand ("&")
if (url.getQuery() != null)
{
for(String keyValue : url.getQuery().split("&"))
{
String[] p = keyValue.split("=");
kvpParams.put(p[0],p[1]);
}
}
// Include the body parameter if dealing with a POST or PUT request
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod))
{
String body = Base64.encodeBase64String(requestBody).replaceAll("\r\n", "");
// url encode the body 2 times now before combining other params
body = URLEncoder.encode(body, "UTF-8");
body = URLEncoder.encode(body, "UTF-8");
kvpParams.put("body", body);
}
// separate the key and values with a "="
// separate the kvp with a "&"
StringBuilder combinedParams = new StringBuilder();
String delimiter="";
for(String key : kvpParams.keySet()) {
combinedParams.append(delimiter);
combinedParams.append(key);
combinedParams.append("=");
combinedParams.append(kvpParams.get(key));
delimiter="&";
}
// url encode the entire string again before returning
return URLEncoder.encode(combinedParams.toString(), "UTF-8");
}
/**
* Generates a Base64-encoded CMAC-AES digest
*
* #param key The secret key used to sign the data
* #param msg The data to be signed
*
* #return A CMAC-AES hash
*
* #throws UnsupportedEncodingException
*/
private static String generateCmac(String key, String msg)
throws UnsupportedEncodingException
{
byte[] keyBytes = key.getBytes("UTF-8");
byte[] data = msg.getBytes("UTF-8");
CMac macProvider = new CMac(new AESFastEngine());
macProvider.init(new KeyParameter(keyBytes));
macProvider.reset();
macProvider.update(data, 0, data.length);
byte[] output = new byte[macProvider.getMacSize()];
macProvider.doFinal(output, 0);
// Convert the CMAC to a Base64 string and remove the new line the Base64 library adds
String cmac = Base64.encodeBase64String(output).replaceAll("\r\n", "");
return cmac;
}
}
Is there any thing else which i am missing.
Also if i need to do POST request i need to add json data in the body tag directly?
EDIT: Is this really your key? maybe you changed the secret key to another size for not posting the original key here. If so check if keyBytes.length really gives you 16,24 or 32
I really digged deep now... I can't find any error in any of your code.
Your key is 256 bits long:
byte[] keyBytes = "12345678-1234-1234-1234-12345678".getBytes("UTF-8");
int bits = keyBytes.length*8;
System.out.println(bits); //gives 256
So i checked the CMac.java and they basically just copy the key with System.arraycopyso there is no error there.
They check the key in https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/engines/AESFastEngine.java with
int keyLen = key.length;
if (keyLen < 16 || keyLen > 32 || (keyLen & 7) != 0)
{
throw new IllegalArgumentException("Key length not 128/192/256 bits.");
}
And since key.length is equal to 32 and 100000 & 111 is obviously 0 I really don't see anything wrong.
Please try to use a key with only 16 characters and tell us if the error still applies. Maybe you should also check if you really use the latest version of org.bouncycastle.crypto
If this doesn't help try just key.getBytes() without the charset UTF-8 for once.
Related
so learning still but i'm getting an Error 401 unauthorised back from the code below. I know that the OAuth header works as it works in postman so i'm assuming there is a problem with the POST request / Auth header? Any ideas?
//set timestamp
Long timestamp = System.currentTimeMillis()/1000;
//set nonce ***** call from main system*************************************************************
String aString = randomAlphaNumeric(11);
// other stuff
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
HttpHeaders headers = new HttpHeaders();
String url = "aURL";
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// String auth = Base64.getEncoder().encodeToString(credentials.getBytes());
List<NameValuePair> oauthHeaders = new ArrayList<>(9);
oauthHeaders.add(new BasicNameValuePair("oauth_consumer_key", "aKey"));
oauthHeaders.add(new BasicNameValuePair("oauth_nonce", aString));
oauthHeaders.add(new BasicNameValuePair("oauth_timestamp", String.valueOf(timestamp)));
oauthHeaders.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1"));
oauthHeaders.add(new BasicNameValuePair("oauth_version", "1.0"));
//generate signature
//encode
String encodedURL = encode(oauthHeaders.toString());
System.out.println("encoded URL:" +encodedURL);
//form base string
String baseString = "POST&"+encode(url).toString()+encodedURL;
System.out.println("Base String: "+baseString);
//form signature
byte[] byteHMAC = null;
try {
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec spec;
if (null == secretKey) {
String signingKey = encode(secretKey) + '&';
spec = new SecretKeySpec(signingKey.getBytes(), "HmacSHA1");
} else {
String signingKey = encode(secretKey) + '&' + encode(secretKey);
spec = new SecretKeySpec(signingKey.getBytes(), "HmacSHA1");
}
mac.init(spec);
byteHMAC = mac.doFinal(baseString.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
String signature = new BASE64Encoder().encode(byteHMAC);
System.out.println("oauth signature: "+signature);
//set signature to params
oauthHeaders.add(new BasicNameValuePair("oauth_signature", signature));
String test = "OAuth "+oauthHeaders.toString();
headers.set("Authorization", test);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("Name",name.toString());
map.add("Region",region.toString());
HttpEntity<MultiValueMap<String, String>> requestEntity= new HttpEntity<MultiValueMap<String, String>>(headers, map);
System.out.println(requestEntity);
ResponseEntity<String> response= restTemplate.exchange(url ,HttpMethod.POST, requestEntity, String.class);
System.out.println(response.toString());
HttpStatus status = response.getStatusCode();
status.toString();
if(status.equals("200")){
Notification.show("Employer" + name +" added successfully");
}
else{
Notification.show("Unsuccessful, error: "+status);
}
}
removed the URL and consumer key / signature for obvious reasons.
The following System out prints might help as well:
encoded params:
%5Boauth_consumer_key%3aKey%2C%20oauth_nonce%3DWZU8H1B5JA6%2C%20oauth_timestamp%3D1511621759%2C%20oauth_signature_method%3DHMAC-SHA1%2C%20oauth_version%3D1.0%5D
Base String: POST&https%3A%2F%2Fapi.test.payrun.io%2FEmployer%5Boauth_consumer_key%3aKey%2C%20oauth_nonce%3DWZU8H1B5JA6%2C%20oauth_timestamp%3D1511621759%2C%20oauth_signature_method%3DHMAC-SHA1%2C%20oauth_version%3D1.0%5D
oauth signature: DlRJGSzgRIItzz+LzMbgnIfbOqU=
The value of oauth_signature is wrong. You are using asignature as the value of oauth_signature, but you have to compute the correct value for your request and set it to oauth_signature. If the value of oauth_signature is wrong, the server will reject your request. See "3.4. Signature" in RFC 5849 (The OAuth 1.0 Protocol) for details.
For anyone who wants to make this work then please see below for a complete OAuth Generator example :):
public class oAuthGenerator {
private String httpMethod;
private String params;
private String url;
//Required for percent encoding
private static final String ENC = "ASCII";
//Required for nonce
private static final String ALPHA_NUMERIC_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
//As provided by Payrun.io
private static final String secretKey = "aSecretKey";
//As provided by Payrun.io
private static final String consumerKey ="aConsumerKey";
private List veriList;
//Constructor for setting signature base string values.
//#Param url needs to be percent encoded
//#Param params needs to be percent encoded, this is done #Method generateOAuth()
public oAuthGenerator(String httpMethod,String url )throws Exception {
veriList= new ArrayList<String>();
veriList.add("POST");
veriList.add("GET");
veriList.add("PUT");
veriList.add("DELETE");
if (veriList.contains(httpMethod)){
this.httpMethod = httpMethod+"&";}
this.url = URLEncoder.encode(url,ENC)+"&";
}
//A method used to generate the OAuth Authorization header
//#Method randomAlphaNumeric() calls internal method using instance variable ALPHA_NUMERIC_STRING
//#Method getSignature() returns String HMACSHA1 > Base64 encoded value of httpMethod,url,params
public String generateOAuth()throws Exception{
//Set timestamp as seconds from 01-01-1970
Timestamp timestamp = new Timestamp(System.currentTimeMillis()/1000);
Long aTimestamp = timestamp.getTime();
//Set nonce which is a 10 digit random, non repeating alpha-numeric value
String aNonce = randomAlphaNumeric(10);
//Normalize and form param string
String normalizedParams = "oauth_consumer_key="+consumerKey+"&"+"oauth_nonce="+aNonce+"&"+"oauth_signature_method="+"HMAC-SHA1"+"&"+"oauth_timestamp="+ aTimestamp.toString()+"&"+"oauth_version="+"1.0";
//Percent encoded params
params = URLEncoder.encode(normalizedParams,ENC);
//Set signature variable
String signature = getSignature();
//place into required format
String oAuthResult = "OAuth "+"oauth_version="+"\"1.0\""+","+"oauth_consumer_key="+"\"" + consumerKey + "\""+","+"oauth_signature_method="+"\"HMAC-SHA1\""+","+"oauth_timestamp="+"\""+aTimestamp+"\""+","+"oauth_nonce="+"\""+aNonce+"\""+","+"oauth_signature="+"\""+signature+"\"";
return oAuthResult;
}
// A method designed to return a hashed and base64 encoded value.
//#Param aString holds HMAC-SHA1 and Base 64 encoded value of variables httpMethod,url,params
//#Param result holds percent encoded value of aString
private String getSignature()
throws Exception {
//form base string
StringBuilder base = new StringBuilder();
base.append(httpMethod);
base.append(url);
base.append(params);
//Set SecretKey of variable secretKey using HMAC-SHA1 algorithm
SecretKey signingKey = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
// Get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// Compute the hmac on input data bytes, then encode to Base64
String aString = Base64.getEncoder().encodeToString(mac.doFinal(base.toString().getBytes(ENC))).trim();
//Percent encoded the Base64 value
String result = URLEncoder.encode(aString, ENC);
return new String(result);
}
//Required for nonce, returns a random alpha numeric value by using variable ALPHA_NUMERIC_STRING
private static String randomAlphaNumeric(int count) {
StringBuilder builder = new StringBuilder();
while (count-- != 0) {
int character = (int)(Math.random()*ALPHA_NUMERIC_STRING.length());
builder.append(ALPHA_NUMERIC_STRING.charAt(character));
}
return builder.toString();
}
}
I want to get HttpServletResponse return content for logging in custom interceptor.The develop enviroment is Spring Boot 1.5.6 + Java 8 + Embeded Tomcat 8.0.35,and return content is RESTful interface json string.This is my code to get http response content:
/**
* get Response return json content
*
* #param response
* #return
* #throws IOException
* #throws NoSuchFieldException
* #throws IllegalAccessException
*/
public String getResponseContent(HttpServletResponse response) throws IOException, NoSuchFieldException, IllegalAccessException {
String responseContent = null;
CoyoteOutputStream outputStream = (CoyoteOutputStream) response.getOutputStream();
Class<CoyoteOutputStream> coyoteOutputStreamClass = CoyoteOutputStream.class;
Field obField = coyoteOutputStreamClass.getDeclaredField("ob");
if (obField.getType().toString().endsWith("OutputBuffer")) {
obField.setAccessible(true);
org.apache.catalina.connector.OutputBuffer outputBuffer = (org.apache.catalina.connector.OutputBuffer) obField.get(outputStream);
Class<org.apache.catalina.connector.OutputBuffer> opb = org.apache.catalina.connector.OutputBuffer.class;
Field outputChunkField = opb.getDeclaredField("outputChunk");
outputChunkField.setAccessible(true);
if (outputChunkField.getType().toString().endsWith("ByteChunk")) {
ByteChunk bc = (ByteChunk) outputChunkField.get(outputBuffer);
Integer length = bc.getLength();
if (length == 0) return null;
responseContent = new String(bc.getBytes(), "UTF-8");
Integer responseLength = StringUtils.isBlank(responseContent) ? 0 : responseContent.length();
if (responseLength < length) {
responseContent = responseContent.substring(0, responseLength);
} else {
responseContent = responseContent.substring(0, length);
}
}
}
return responseContent;
}
When response json is short, the code running well.But when the return json is too long,the responseContent only having part of the response content,parsing the content failed before logging(need to parsing json and get some value write to database).
How to adapt the response and get full response content?
Increse tomcat defult buffer size:
//default buffer size is:8*1024,in OutputBuffer class
//public static final int DEFAULT_BUFFER_SIZE = 8*1024;
response.setBufferSize(2048 * 20);
This is not a perfect solution,when the response size beyond 2048 * 20,it will encount an exception.But could handle most response.
I have a wcf service which accepts byte[] serialData, now am developing a java client which needs to consume the same method.
When i sent bytearray to the service as a json post request , it is getting an exception as java.io.IOException: Server returned HTTP response code: 400
Here is my code:
wcf method:
[OperationContract]
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, UriTemplate = "saveSerialNumbers", BodyStyle = WebMessageBodyStyle.WrappedRequest)]
Dictionary<string, object> saveSerialNumbers(byte[] serialData);
Java Client:
for (int i = 1; i < 100; i++) {
sb.append(String.valueOf(gen()));
}
byte[] bytesEncoded = Base64.encodeBase64(sb.toString().getBytes());
String json = "{\"serialDataByte\":\""+sb.toString()+"\"}";
This is my postrequest method:
public String getResultPOST(String jsonObject,String uri,String method) throws Exception{
try {
URL url = new URL(uri+method);
System.out.println(url.toString());
URLConnection connection = url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
OutputStreamWriter out;
try {
out = new OutputStreamWriter(connection.getOutputStream());
out.write(jsonObject);
out.close();
} catch (Exception e) {
/
// TODO Auto-generated catch block
e.printStackTrace();
}
String line = "";
StringBuilder builder = new StringBuilder();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
while ((line = in.readLine()) != null) {
builder.append(line);
}
in.close();
return builder.toString();
} catch (Exception e) {
throw e;
//here is the exception
}
}
Here is my method call:
String json = "{\"serialData\":\""+ new String(bytesEncoded) +"\",\"guProductID\":\""+guProductID+"\",\"guStoreID\":\""+guStoreID+"\",\"securityToken\":\""+SecurityToken+"\"}";
String serialContract = serialClient.getResultPOST(json, "http://localhost:3361/EcoService.svc/Json/", "saveSerialNumbers");
Below, there's a simple working prototype to generate json from a string instance. Use this code snippet to update your client part. And it should work.
import java.util.Base64;
public class Test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
// composing string to be encoded
sb.append("Part 1 of some text to be encoded to base64 format\n");
sb.append("Part 2 of some text to be encoded to base64 format\n");
sb.append("Part 3 of some text to be encoded to base64 format");
// getting base64 encoded string bytes
byte[] bytesEncoded = Base64.getEncoder().encode(sb.toString().getBytes());
// composing json
String json = "{\"serialDataByte\":\""+ new String(bytesEncoded) +"\"}";
System.out.println(json);
}
}
UPDATE:
The code uses Java 8 SDK. If you are using pre-Java8 version, then consider Apache Commons Codec for this task.
Below there's a sample code, that uses Apache Commons Codec for Base64 encoding (please note that import directive has been changed):
import org.apache.commons.codec.binary.Base64;
public class Test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
// composing string to be encoded
sb.append("Part 1 of some text to be encoded to base64 format\n");
sb.append("Part 2 of some text to be encoded to base64 format\n");
sb.append("Part 3 of some text to be encoded to base64 format");
// getting base64 encoded string bytes
byte[] bytesEncoded = Base64.encodeBase64(sb.toString().getBytes());
// composing json
String json = "{\"serialDataByte\":\""+ new String(bytesEncoded) +"\"}";
System.out.println(json);
}
}
UPDATE 2:
Upon sending POST requests make sure that you have marked your request as a POST request. Do not forget this line of code, before making the request:
connection.setRequestMethod("POST");
and use HttpURLConnection instead of URLConnection:
import java.net.HttpURLConnection;
I'm using Play framework to develop consumer for Instagram real-time API. But still could not perform x-hub-signature verification properly. So, how can we perform Instagram x-hub-signature verification using Java and Play framework?
Here is my current code:
From the Play framework, I obtain the JSON payload using this method:
public static Result receiveInstaData(){
JsonNode json = request().body().asJson();
//obtain the x-hub-signature from the header
//obtain the corresponding client secret
VerificationResult verificationResult =
SubscriptionUtil.verifySubscriptionPostSignature(
clientSecret, json.toString(), xHubSignature);
if(verificationResult.isSuccess()){
//do something
}
}
Then inside the SubscriptionUtil, I perform verification using this following code:
public static VerificationResult verifySubscriptionPostSignature(String clientSecret, String rawJsonData, String xHubSignature) {
SecretKeySpec keySpec;
keySpec = new SecretKeySpec(clientSecret.getBytes("UTF-8"), HMAC_SHA1);
Mac mac;
mac = Mac.getInstance(HMAC_SHA1);
mac.init(keySpec);
byte[] result;
result = mac.doFinal(rawJsonData.getBytes("UTF-8"));
String encodedResult = Hex.encodeHexString(result);
return new VerificationResult(encodedResult.equals(xHubSignature), encodedResult);
}
I created a standalone Python script that copies the instagram-python implementation and both of them produce the same results for the same clientSecret and jsonString. Maybe I should provide with raw binary data instead of String.
If let's say we need a raw binary data for JSON request, then I need to create my custom BodyParser to parse the JSON request to raw binary data[5]
References:
[1-4]http://pastebin.com/g4uuDwzn (SO doesn't allow me to post more than 2 links, so I put all the references here. The links contain the signature verification in Ruby, Python and PHP)
[5]https://groups.google.com/forum/#!msg/play-framework/YMQb6yeDH5o/jU8FD--yVPYJ
[6]My standalone python script:
#! /usr/bin/env python
import sys
import hmac
import hashlib
hc_client_secret = "myclientsecret"
hc_raw_response = "[{\"subscription_id\":\"1\",\"object\":\"user\",\"object_id\":\"1234\",\"changed_aspect\":\"media\",\"time\":1297286541},{\"subscription_id\":\"2\",\"object\":\"tag\",\"object_id\":\"nofilter\",\"changed_aspect\":\"media\",\"time\":1297286541}]"
client_secret = hc_client_secret
raw_response = hc_raw_response
if len(sys.argv) != 3:
print 'Usage verify_signature <client_secret> <raw_response>.\nSince the inputs are invalid, use the hardcoded value instead!'
else:
client_secret = sys.argv[1]
raw_response = sys.argv[2]
print "client_secret = " + client_secret
print "raw_response = " + raw_response
digest = hmac.new(client_secret.encode('utf-8'), msg=raw_response.encode('utf-8'), digestmod=hashlib.sha1).hexdigest()
print digest
Finally I managed to find the solution. For the Controller in Play Framework, we need to use BodyParser.Raw so the we can extract the payload request as raw data, i.e. array of bytes.
Here's the code for the controller in Play Framework:
#BodyParser.Of(BodyParser.Raw.class)
public static Result receiveRawInstaData(){
Map<String, String[]> headers = request().headers();
RawBuffer jsonRaw = request().body().asRaw();
if(jsonRaw == null){
logger.warn("jsonRaw is null. Something is wrong with the payload");
return badRequest("Expecting serializable raw data");
}
String[] xHubSignature = headers.get(InstaSubscriptionUtils.HTTP_HEADER_X_HUB_SIGNATURE);
if(xHubSignature == null){
logger.error("Invalid POST. It does not contain {} in its header", InstaSubscriptionUtils.HTTP_HEADER_X_HUB_SIGNATURE);
return badRequest("You are not Instagram!\n");
}
String json;
byte[] jsonRawBytes;
jsonRawBytes = jsonRaw.asBytes();
json = new String(jsonRawBytes, StandardCharsets.UTF_8);
try {
String clientSecret = InstaSubscriptionUtils.getClientSecret(1);
VerificationResult verificationResult = SubscriptionUtil.verifySubscriptionPostRequestSignature
(clientSecret,jsonRawBytes, xHubSignature[0]);
if(verificationResult.isSuccess()){
logger.debug("Signature matches!. Received signature: {}, calculated signature: {}", xHubSignature[0], verificationResult.getCalculatedSignature());
}else{
logger.error("Signature doesn't match. Received signature: {}, calculated signature: {}", xHubSignature[0], verificationResult.getCalculatedSignature());
return badRequest("Signature does not match!\n");
}
} catch (InstagramException e) {
logger.error("Instagram exception.", e);
return internalServerError("Internal server error. We will attend to this problem ASAP!");
}
logger.debug("Received xHubSignature: {}", xHubSignature[0]);
logger.info("Sucessfully received json data: {}", json);
return ok("OK!");
}
And for the code for method verifySubscriptionPostRequestSignature in SubscriptionUtil
public static VerificationResult verifySubscriptionPostRequestSignature(String clientSecret, byte[] rawJsonData, String xHubSignature) throws InstagramException{
SecretKeySpec keySpec;
keySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1);
Mac mac;
try {
mac = Mac.getInstance(HMAC_SHA1);
mac.init(keySpec);
byte[] result = mac.doFinal(rawJsonData);
String encodedResult = Hex.encodeHexString(result);
return new VerificationResult(encodedResult.equals(xHubSignature), encodedResult);
} catch (NoSuchAlgorithmException e) {
throw new InstagramException("Invalid algorithm name!", e);
} catch (InvalidKeyException e){
throw new InstagramException("Invalid key: " + clientSecret, e);
}
}
I implemented this solution in jInstagram, here is the link to the source code: SubscriptionUtil
I am trying to get the request_token to sign in with twitter in an app, but I always get 401 Error. I have created my app in dev.twitter and I have the consumer key, secret and the callback.
This is my method:
def readSomething(String url){
StringBuffer buffer = new StringBuffer();
try {
/**
* get the time - note: value below zero
* the millisecond value is used for oauth_nonce later on
*/
int millis = (int) System.currentTimeMillis() * -1;
int time = (int) millis / 1000;
/**
* Listing of all parameters necessary to retrieve a token
* (sorted lexicographically as demanded)
*/
def data = [
"oauth_nonce": String.valueOf(millis),
"oauth_signature":"",
"oauth_signature_method":"HMAC-SHA1",
"oauth_timestamp": String.valueOf(time),
"oauth_version":"1.0"
]
/**
* Generation of the signature base string
*/
String signature_base_string =
"POST&"+URLEncoder.encode(url, "UTF-8")+"&";
data.each{
// ignore the empty oauth_signature field
if(it.key != "oauth_signature") {
signature_base_string +=
URLEncoder.encode(it.key, "UTF-8") + "%3D" +
URLEncoder.encode(it.value, "UTF-8") + "%26";
}
}
// cut the last appended %26
signature_base_string = signature_base_string.substring(0,
signature_base_string.length()-3);
/**
* Sign the request
*/
def signingKey=URLEncoder.encode(CONSUMER_SECRET, "UTF-8")+"&"
Mac m = Mac.getInstance("HmacSHA1");
m.init(new SecretKeySpec(signingKey.getBytes(), "HmacSHA1"));
m.update(signature_base_string.getBytes());
byte[] res = m.doFinal();
String sig = String.valueOf(res.encodeBase64());
data["oauth_signature"] = sig;
/**
* Create the header for the request
*/
String header = "OAuth ";
data.each {
header += it.key+"=\""+it.value+"\", ";
}
// cut off last appended comma
header = header.substring(0, header.length()-2);
System.out.println("Signature Base String: "+signature_base_string);
System.out.println("Authorization Header: "+header);
System.out.println("Signature: "+sig);
String charset = "UTF-8";
URLConnection connection = new URL(url).openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestProperty("Accept-Charset", charset);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
connection.setRequestProperty("Authorization", header);
OutputStream output = connection.getOutputStream();
output.write(header.getBytes(charset));
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
println"reader "+reader
String read;
while((read = reader.readLine()) != null) {
buffer.append(read);
}
}
catch(Exception e) {
e.printStackTrace();
}
println"buffer "+buffer
println"buffer "+buffer.getClass()
println"buffer "+buffer.toString()
return buffer.toString()
}
Is there any wrong with it?
I believe I am not signing correctly and the oauth_signature is wrong. Is there any way to check if this signature is ok?
I try to do this (http://stackoverflow.com/questions/2964392/implement-oauth-in-java) but is still not working, Any others ideas what could be happening?
Thanks in advance