Coinbase Api Java POST request "Invalid Signature" - java

I am trying to send a POST request to the coinbase sandbox endpoint. When signing the request I always get an "Invalid Signature" response. It seems that coinbase requires the JSON message be base 64 encoded and sent as a part of the signature. I am fairly new to POST requests and have never signed a message before. Can someone please let me know what I am doing wrong. I have been stuck on this issue for a week so any input is much appreciated.
The relevant part of my code is below
public void postOrder() throws InvalidKeyException, NoSuchAlgorithmException, CloneNotSupportedException, ClientProtocolException, IOException {
String message = "{ \n"+
" \"size\":\"1.00\", \n"+
" \"price\":\"0.80\", \n"+
" \"side\":\"buy\", \n"+
" \"product_id\":\"BTC-USD\" \n"+
"}";
JSONObject json = new JSONObject(message);
message = json.toString();
try
{
String timestamp= Instant.now().getEpochSecond()+"";
String accessSign = getAccess(timestamp,"POST","/orders",message);
String apiKey = properties.getProperty("key");
String passphrase = properties.getProperty("passphrase");
URL url = new URL("https://api-public.sandbox.pro.coinbase.com/orders");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("accept", "application/json");
connection.setRequestProperty("content-type", "application/json; charset=UTF-8");
connection.setRequestProperty("CB-ACCESS-KEY", apiKey);
connection.setRequestProperty("CB-ACCESS-SIGN", accessSign);
connection.setRequestProperty("CB-ACCESS-TIMESTAMP", timestamp);
connection.setRequestProperty("CB-ACCESS-PASSPHRASE", passphrase);
connection.setRequestProperty("User-Agent", "Java Client");
try {
connection.getOutputStream().write(message.getBytes("UTF-8"));
OutputStream output = connection.getOutputStream();
output.write(param.getBytes("UTF-8"));
} catch (Exception e) {
System.out.println(e.getMessage());
}
String status = connection.getResponseMessage();
System.out.println("STATUS: "+status);
}catch(Exception e) {
System.out.println(e.getMessage());
}
return;
}
private String getAccess(String timestamp, String method, String path, String param) throws NoSuchAlgorithmException, InvalidKeyException, CloneNotSupportedException, IllegalStateException, UnsupportedEncodingException {
String secretKeyString = properties.getProperty("secret");
String prehash = timestamp+method+path+param;
byte[] secretKeyDecoded = Base64.getDecoder().decode(secretKeyString);
SecretKey secretKey = new SecretKeySpec(secretKeyDecoded, "HmacSHA256");
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
hmacSha256.init(secretKey);
return Base64.getEncoder().encodeToString(hmacSha256.doFinal(prehash.getBytes()));
}

I was able to get this working with using javax.crypto.* library functions. The changes I did were
Took the API Key for the sandbox.
To encoded the signature, I explicitly used UTF_8
Below is the code that worked for me with Coinbase sandbox API_KEY -
public String getCoinbaseHeaderSignature(
String timeStamp,
String method,
String requestPath,
String body
) throws NoSuchAlgorithmException, InvalidKeyException {
String data = timeStamp + method.toUpperCase() + requestPath + body;
byte[] key = Base64.getDecoder().decode(API_KEY);
SecretKey keySpec = new SecretKeySpec(key, "HmacSHA256");
// Get HmacSHA256 instance
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
For more details, refer to documentation https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-key-authentication

In my case, I utilized gdax-java example. I solved this issue by removing decimal values from timestamp that means I just used integer part of the timestamp value.

Related

Azure Notification Hub - Invalid authorization token signature with Java

Hi all I'm trying to send a notification with Azure Notification Hub in Java.
This is the code I used:
NotificationHub hub = new NotificationHub("Endpoint=sb://XXXXXXXXXX;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=XXXXXXXXXXXXXXXXX", "myHubName");
String message = "{\"data\":{\"msg\":\"Hello from Java!\"}}";
Notification n = Notification.createGcmNotification(message);
NotificationOutcome result = hub.sendNotification(n);
return result.getNotificationId();
The problem is that I get an Invalid authorization token signature error when I try to get the NotificationOutcome object.
I assume that the problem is in the generation of the token, which is generated with the following method of NotificationHub class:
private String generateSasToken(URI uri) {
String targetUri;
try {
targetUri = URLEncoder
.encode(uri.toString().toLowerCase(), "UTF-8")
.toLowerCase();
long expiresOnDate = System.currentTimeMillis();
expiresOnDate += SdkGlobalSettings.getAuthorizationTokenExpirationInMinutes() * 60 * 1000;
long expires = expiresOnDate / 1000;
String toSign = targetUri + "\n" + expires;
// Get an hmac_sha1 key from the raw key bytes
byte[] keyBytes = SasKeyValue.getBytes("UTF-8");
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");
// Get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
// Compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(toSign.getBytes("UTF-8"));
// Convert raw bytes to Hex
String signature = URLEncoder.encode(
Base64.encodeBase64String(rawHmac), "UTF-8");//
// construct authorization string
String token = "SharedAccessSignature sr=" + targetUri + "&sig="
+ signature + "&se=" + expires + "&skn=" + SasKeyName;
System.out.println(token);
return token;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
I tried the same example using php and it worked. Any suggestions? Thanks in advance.

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);
}
}

Need help creating a valid nonce

I am trying to consume a web service that uses Password Digest mode, and I have these functions in my Java application to generate a random nonce, creation date and password digest. I can't get past the Authentication Failed error, and the documentation isn't overly clear on whether they want SHA-1 or MD5, as it mentions both in passing. I've tried MD5 instead of SHA-1 and I am getting the same result. I managed to get the requests to work via a test on SoapUI, but I have no idea how that application is generating the digest / nonce. Any help is appreciated.
Here's the code I am using to generate the nonce and the password digest:
private static SOAPMessage createSOAPRequest() throws Exception
{
String password = "FakePassword";
String nonce = generateNonce();
System.out.println("Nonce = " + nonce);
DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
Date today = Calendar.getInstance().getTime();
String created = dateFormatter.format(today);
System.out.println("Created = " + created);
String passwordDigest = buildPasswordDigest(nonce, created, password);
System.out.println("Password Digest = " + passwordDigest);
}
private static String buildPasswordDigest(String nonce, String created, String password) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
MessageDigest sha1;
String passwordDigest = null;
try
{
sha1 = MessageDigest.getInstance("SHA-1");
sha1.update(Base64.decodeBase64(nonce));
sha1.update(created.getBytes("UTF-8"));
passwordDigest = new String(Base64.encodeBase64(sha1.digest(password.getBytes("UTF-8"))));
sha1.reset();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
return passwordDigest;
}
private static String generateNonce() throws NoSuchAlgorithmException, NoSuchProviderException, UnsupportedEncodingException
{
String dateTimeString = Long.toString(new Date().getTime());
byte[] nonceByte = dateTimeString.getBytes();
return Base64.encodeBase64String(nonceByte);
}
The solution was to replace the line sha1.update(nonce.getBytes("UTF-8")); with sha1.update(Base64.decodeBase64(nonce));

Java Mail MimeUtility encodeText UnsupportedEncodingException BASE64

As part of my J2EE application's email service, I encode into BASE64
body = MimeUtility.encodeText(orig_mail_body,"UTF-8","BASE64");
but in some circumstances it's throwing an exception:
java.io.UnsupportedEncodingException: Unknown transfer encoding: BASE64
at javax.mail.internet.MimeUtility.encodeWord(MimeUtility.java:565)
at javax.mail.internet.MimeUtility.encodeText(MimeUtility.java:373)
I've been trying to uncover why I get this particular message, but to no avail.
Can someone illuminate me?
It seems like the only valid values for the 'encoding' argument are "B" or "Q"; so my code should be:
body = MimeUtility.encodeText(orig_mail_body,"UTF-8","B");
if you're using java 8 now there is a class that can solve that.
byte[] bytes = orig_mail_body.getBytes();
Base64.Encoder encoder = Base64.getEncoder();
String encode = encoder.encodeToString(bytes);
System.out.print(encode);
with spring: org.springframework.security.crypto.codec
public static String base64Encode(String token) {
byte[] encodedBytes = Base64.encode(token.getBytes());
return new String(encodedBytes, Charset.forName("UTF-8"));
}
public static String base64Decode(String token) {
byte[] decodedBytes = Base64.decode(token.getBytes());
return new String(decodedBytes, Charset.forName("UTF-8"));
}
with apache commons
org.apache.commons.codec.binary.Base64;
byte[] encodedBytes = Base64.encodeBase64("Test".getBytes());
System.out.println("encodedBytes " + new String(encodedBytes));
byte[] decodedBytes = Base64.decodeBase64(encodedBytes);
System.out.println("decodedBytes " + new String(decodedBytes));
Salud

AES decryption Android

I am working on AES encryption and decryption in Android, I post request using below Android code snippet.
Request Post
String urlParameters = "username=abc&password=abc";
String request = "http://abcd.co.uk/data_abc.php?";
String passkey = "mysecretkey";
URL url;
HttpURLConnection connection = null;
try {
//Create connection
url = new URL(request);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", "" +
Integer.toString(urlParameters.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
//Send request
DataOutputStream wr = new DataOutputStream(
connection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
//Get Response
InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while ((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (connection != null) {
connection.disconnect();
}
}
And I successfully got Base64 Encrypted response string from the above request but when I try to decrypt the response string using following code snippet, It return unreadable string like characters and boxes.
Decryption
String strDecriptedValue = decrypt(passkey, responseBase64);
public static String decrypt(String seed, String encrypted)
throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] enc = Base64.decode(encrypted.getBytes(), Base64.DEFAULT);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(seed);
keygen.init(128, random);
SecretKey key = keygen.generateKey();
byte[] raw = key.getEncoded();
return raw;
}
private static byte[] decrypt(byte[] raw, byte[] encrypted)
throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
Decrypted Output
��]ة*�]��O��Z���Q2�_
The response should be in JSON format but actual output is like above.
Please share the snippet which is used to decrypt the data with AES 256 bit, secure key using Base 64.
And also I tried using AES/CBC/NoPadding , AES/CBC/PKCS5Padding etc., but its not getting work.
You are using SecureRandom random to generated key using passed seed value.
But the actual implementation of SecureRandom on the server and the one used by your Android code be different (you are using some PHP code it seems) so the generated key value would be different.
Also you mentioned the 256 bit key but your code is using 128 instead: keygen.init(128, random);
So make sure that your are using the same keys on both side. You should try your code first with the 'fixed' key on both side and check if it works, otherwise you may still have bugs in your decryption/encryption process. You included only half of the process code in your question.

Categories