So I currently working on an implementation of the Kraken API for Java. I am using this sample code I found on http://pastebin.com/nHJDAbH8.
The general usage as described by Kraken (https://www.kraken.com/help/api) is:
API-Key = API key
API-Sign = Message signature using HMAC-SHA512 of
( URI path + SHA256( nonce + POST data ) ) and base64 decoded secret API
key
and
nonce = always increasing unsigned 64 bit integer
otp = two-factor password ( if two-factor enabled, otherwise not required )
however I am facing the following response:
{"error":["EAPI:Invalid key"]}
I already tried a couple of ways ( getting a new API, trying to change the sha256 methods, because I thought something is wrong with the way it is hashed )
So this is the code:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class KrakenClient {
protected static String key = "myAPIKey"; // API key
protected static String secret = "MySecret===="; // API secret
protected static String url = "api.kraken.com"; // API base URL
protected static String version = "0"; // API version
public static void main(String[] args) throws Exception {
queryPrivateMethod("Balance");
}
public static void queryPrivateMethod(String method) throws NoSuchAlgorithmException, IOException{
long nonce = System.currentTimeMillis();
String path = "/" + version + "/private/" + method; // The path like "/0/private/Balance"
String urlComp = "https://"+url+path; // The complete url like "https://api.kraken.com/0/private/Balance"
String postdata = "nonce="+nonce;
String sign = createSignature(nonce, path, postdata);
postConnection(urlComp, sign, postdata);
}
/**
* #param nonce
* #param path
* #param postdata
* #return
* #throws NoSuchAlgorithmException
* #throws IOException
*/
private static String createSignature(long nonce, String path,
String postdata) throws NoSuchAlgorithmException, IOException {
return hmac(path+sha256(nonce + postdata), new String(Base64.decodeBase64(secret)));
}
public static String sha256Hex(String text) throws NoSuchAlgorithmException, IOException{
return org.apache.commons.codec.digest.DigestUtils.sha256Hex(text);
}
public static byte[] sha256(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException{
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(text.getBytes());
byte[] digest = md.digest();
return digest;
}
public static void postConnection(String url1, String sign, String postData) throws IOException{
URL url = new URL( url1 );
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.addRequestProperty("API-Key", key);
connection.addRequestProperty("API-Sign", Base64.encodeBase64String(sign.getBytes()));
// connection.addRequestProperty("API-Sign", sign);
connection.addRequestProperty("User-Agent", "Mozilla/4.0");
connection.setRequestMethod( "POST" );
connection.setDoInput( true );
connection.setDoOutput( true );
connection.setUseCaches( false );
// connection.setRequestProperty( "Content-Type",
// "application/x-www-form-urlencoded" );
connection.setRequestProperty( "Content-Length", String.valueOf(postData.length()) );
OutputStreamWriter writer = new OutputStreamWriter( connection.getOutputStream() );
writer.write( postData );
writer.flush();
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()) );
for ( String line; (line = reader.readLine()) != null; )
{
System.out.println( line );
}
writer.close();
reader.close();
}
public static String hmac(String text, String secret){
Mac mac =null;
SecretKeySpec key = null;
// Create a new secret key
try {
key = new SecretKeySpec( secret.getBytes( "UTF-8"), "HmacSHA512" );
} catch( UnsupportedEncodingException uee) {
System.err.println( "Unsupported encoding exception: " + uee.toString());
return null;
}
// Create a new mac
try {
mac = Mac.getInstance( "HmacSHA512" );
} catch( NoSuchAlgorithmException nsae) {
System.err.println( "No such algorithm exception: " + nsae.toString());
return null;
}
// Init mac with key.
try {
mac.init( key);
} catch( InvalidKeyException ike) {
System.err.println( "Invalid key exception: " + ike.toString());
return null;
}
// Encode the text with the secret
try {
return new String( mac.doFinal(text.getBytes( "UTF-8")));
} catch( UnsupportedEncodingException uee) {
System.err.println( "Unsupported encoding exception: " + uee.toString());
return null;
}
}
}
Here is a working example:
static String key = "---myKey---";
static String secret = "---mySecret---";
String nonce, signature, data, path;
static String domain = "https://api.kraken.com";
void account_balance() {
nonce = String.valueOf(System.currentTimeMillis());
data = "nonce=" + nonce;
path = "/0/private/Balance";
calculateSignature();
String answer = post(domain + path, data);
// on empty accounts, returns {"error":[],"result":{}}
// this is a known Kraken bug
...
}
String post(String address, String output) {
String answer = "";
HttpsURLConnection c = null;
try {
URL u = new URL(address);
c = (HttpsURLConnection)u.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("API-Key", key);
c.setRequestProperty("API-Sign", signature);
c.setDoOutput(true);
DataOutputStream os = new DataOutputStream(c.getOutputStream());
os.writeBytes(output);
os.flush();
os.close();
BufferedReader br = null;
if(c.getResponseCode() >= 400) {
System.exit(1);
}
br = new BufferedReader(new InputStreamReader((c.getInputStream())));
String line;
while ((line = br.readLine()) != null)
answer += line;
} catch (Exception x) {
System.exit(1);
} finally {
c.disconnect();
}
return answer;
}
void calculateSignature() {
signature = "";
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update((nonce + data).getBytes());
Mac mac = Mac.getInstance("HmacSHA512");
mac.init(new SecretKeySpec(Base64.decodeBase64(secret.getBytes()), "HmacSHA512"));
mac.update(path.getBytes());
signature = new String(Base64.encodeBase64(mac.doFinal(md.digest())));
} catch(Exception e) {}
return;
}
Related
I need to send nullbyte as 1 character this code send its as 4 characters so not a nullbyte (\x00) , it can't be sending it as plain text. it's sending to a flash client. I'm using AsynchronousSocketChannel to send the packets.
the nullbyte is to tell the server that the packet has ended.
for example when I send test\x00 it sends it as test\x00 which is wrong.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception {
String connect = "gfdg";
System.out.println(connect);
String request = connect;
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
SocketAddress serverAddr = new InetSocketAddress("artix.aqw.aq.com", 5588);
Future<Void> result = channel.connect(serverAddr);
result.get();
Attachment attach = new Attachment();
attach.channel = channel;
attach.buffer = ByteBuffer.allocate(2048);
attach.isRead = false;
attach.mainThread = Thread.currentThread();
Charset cs = Charset.forName("UTF-8");
String msg = request;
byte[] data = msg.getBytes(cs);
attach.buffer.put(data);
attach.buffer.flip();
ReadWriteHandler readWriteHandler = new ReadWriteHandler();
channel.write(attach.buffer, attach, readWriteHandler);
attach.mainThread.join();
}
}
class Attachment {
AsynchronousSocketChannel channel;
ByteBuffer buffer;
Thread mainThread;
boolean isRead;
}
class ReadWriteHandler implements CompletionHandler<Integer, Attachment> {
#Override
public void completed(Integer result, Attachment attach) {
if (attach.isRead) {
attach.buffer.flip();
Charset cs = Charset.forName("UTF-8");
int limits = attach.buffer.limit();
byte bytes[] = new byte[limits];
attach.buffer.get(bytes, 0, limits);
String msg = new String(bytes, cs);
String str = new String(bytes,cs).split("\0")[0];
System.out.format("Server Responded: " + str + "\n");
try {
msg = this.getTextFromUser();
} catch (Exception e) {
e.printStackTrace();
}
if (msg.equalsIgnoreCase("bye")) {
attach.mainThread.interrupt();
return;
}
attach.buffer.clear();
byte[] data = msg.getBytes(cs);
attach.buffer.put(data);
attach.buffer.flip();
attach.isRead = false; // It is a write
attach.channel.write(attach.buffer, attach, this);
} else {
attach.isRead = true;
attach.buffer.clear();
attach.channel.read(attach.buffer, attach, this);
}
}
#Override
public void failed(Throwable e, Attachment attach) {
e.printStackTrace();
}
private String getTextFromUser() throws Exception {
System.out.println("Please enter a message:");
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in));
String msg = consoleReader.readLine() + "\\x00";
return msg;
}
}
You should write a single null byte (0x00) after writing your string to the channel. What you're doing is not it: you're appending the string \x00 instead (a backslash followed by an x and two 0s).
Against my first instincts, it seems it will work if you append the unicode character \u0000 to your string, but the optimal way to do it is simply to put a byte with value 0 into the ByteBuffer after putting your string.
To be clear, I expected the null byte to be doubled when you append \u0000, as Java encodes chars as UTF-16, hence on 2 bytes. But we're explicitly encoding the String to UTF-8 to get it as bytes, so the null char is indeed encoded as a single null byte.
Here's a small demo of this, showing for each method the length of data written to the channel, then its value as bytes:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Buffer {
public static void main(String[] args) throws IOException {
try (Scanner scan = new Scanner(System.in)) {
boolean done = false;
while(!done) {
System.out.println("Enter string to encode, 'bye' to exit:");
String s = scan.nextLine();
if ("bye".equals(s.toLowerCase())) {
done = true;
break;
}
System.out.println("withNullChar");
String withNullChar = s + '\u0000';
ByteBuffer buff = ByteBuffer.allocate(1024);
buff.put(withNullChar.getBytes(StandardCharsets.UTF_8));
System.out.println("Length: " + buff.position());
buff.flip();
byte[] result = readBack(buff);
printArray(result);
System.out.println("withNullCharFaulty");
String withNullCharFaulty = s + "\\x00";
buff = ByteBuffer.allocate(1024);
buff.put(withNullCharFaulty.getBytes(StandardCharsets.UTF_8));
System.out.println("Length: " + buff.position());
buff.flip();
result = readBack(buff);
printArray(result);
System.out.println("with null byte");
buff = ByteBuffer.allocate(1024);
buff.put(s.getBytes(StandardCharsets.UTF_8)).put((byte) 0);
System.out.println("Length: " + buff.position());
buff.flip();
result = readBack(buff);
printArray(result);
}
}
}
public static byte[] readBack(ByteBuffer buff) throws IOException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
try (WritableByteChannel channel = Channels.newChannel(bos)) {
channel.write(buff);
return bos.toByteArray();
}
}
}
public static void printArray(byte[] arr) {
StringBuilder sb = new StringBuilder();
for (byte b : arr)
sb.append(String.format("%02X ", b));
System.out.println(sb);
}
}
I am working on Microsoft Sharepoint integration with SAP. The sharepoint supports 'kerberos fallback to ntlm' method for authentication.
I wish to take advantage of ntlm authentication, as I dont wish to go into kerberos set up. My understanding is that ntlm authentication can be managed with following piece of code however, I am getting '401 Unauthorized' error.
Probably something needs to be added to following line in password authentication.
if (getRequestingScheme().equalsIgnoreCase("negotiate") || getRequestingScheme().equalsIgnoreCase("ntlm") || getRequestingScheme().equalsIgnoreCase("kerberos"))
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.lang.Object;
import java.nio.charset.StandardCharsets;
import java.io.OutputStream;
import java.net.InetAddress;
public class Main {
public static void main(String[] argv) throws Exception {
Authenticator.setDefault(new MyAuthenticator());
URL url = new URL ("https://sharepointlink");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput( true );
//conn.setInstanceFollowRedirects( false );
conn.setRequestMethod( "POST" );
conn.setRequestProperty("Accept", "*/*");
//MyAuthenticator m = new MyAuthenticator();
String urlParameters = "tests";
byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 );
int postDataLength = postData.length;
byte[] data = ("").getBytes("UTF-8");
OutputStream out = conn.getOutputStream();
out.write(postData);
out.flush();
StringBuilder response = new StringBuilder();
InputStream stream = conn.getInputStream();
// InputStream estream = conn.getErrorStream();
BufferedReader in = new BufferedReader(new InputStreamReader(stream));
String str = "";
while ((str = in.readLine()) != null) {
response.append(str) ;
}
in.close();
System.out.println(response.toString());
// return response.toString() ;
}
}
class MyAuthenticator extends Authenticator {
// static String user = System.getProperty("user");
// static String pass = System.getProperty("pass");
// static String kuser = System.getProperty("kuser");
// static String kpass = System.getProperty("kpass");
// static String showhint = System.getProperty("showhint");
protected PasswordAuthentication getPasswordAuthentication() {
String promptString = getRequestingPrompt();
System.out.println("prompt string " + promptString);
String hostname = getRequestingHost();
System.out.println("host name " + hostname);
InetAddress ipaddr = getRequestingSite();
System.out.println(ipaddr);
int port = getRequestingPort();
RequestorType reqType = getRequestorType();
System.out.println ("reqeust type = " + reqType.toString());
System.out.println ("Protocol type = " + getRequestingProtocol());
System.out.println ("Scheme type = " + getRequestingScheme());
if (getRequestingScheme().equalsIgnoreCase("negotiate") || getRequestingScheme().equalsIgnoreCase("ntlm") || getRequestingScheme().equalsIgnoreCase("kerberos")) {
String krb5user="tp1\\user";
String krb5pass ="pwd";
// get krb5user and krb5pass in your own way
return (new PasswordAuthentication (krb5user, krb5pass.toCharArray()));
}
String username = "TP1\\user";
String password = "pwd";
return new PasswordAuthentication(username, password.toCharArray());
}
}
Can anyone help please.
Admin please don't mark it as duplicate read my question completely. I am encrypting and decrypting some text but while running in same file with main its running fine but when i call its encrypt and decrypt function from outside. Its giving an error at runtime. I am attaching the code.
package desede;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import security.SHA256Algo;
import shradhafinalwiddesign.UpdateFile;
import shradhafinalwiddesign.UserRegistration;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Simple TripleDES Encrypt/Decrypt Test
* sha1, utf-8, no padding
*
* uses commons-codec-1.6
* javac -cp :commons-codec-1.6.jar TripleDESTest.java
* java -cp :commons-codec-1.6.jar TripleDESTest
*/
public class TripleDesDemo {
public static void main(String[] args) throws Exception {
String text = "textToEncrypt";
UserRegistration user = new UserRegistration() ;
user.setlUsername("tarunv") ;
user.setAnswer("tommysdsfdsfsd") ;
user.setLastaccess("pets namesdfsfds") ;
user.setLpassword("computersdfdsfd") ;
String h1 = SHA256Algo.createHash(user.getlUsername()) ;
String h2 = SHA256Algo.createHash(user.getLpassword()) ;
String h3 = SHA256Algo.createHash(user.getAnswer()) ;
String hash1 = UpdateFile.modifyHashValue(h1).substring(0, 24) ;
String hash2 = UpdateFile.modifyHashValue(h2) ;
String hash3 = UpdateFile.modifyHashValue(h3) ;
System.out.println(" key1 : "+hash1.length()+" key2 : "+hash2.length()+" key3 : "+hash3.length());
byte[] arr = toByteArray(user) ;
byte[] codedtext = TripleDesDemo._encrypt(arr,"tarunvermacdac#gmail.com");
byte[] codedtext1 = TripleDesDemo._encrypt(codedtext,"tarun.spicyabc#gmail.com");
byte[] codedtext2 = TripleDesDemo._encrypt(codedtext1,"direct_tarun#yahoo.co.in");
writeSmallBinaryFile(codedtext2, "tarun.bat") ;
byte[] texttoDecrypt = readSmallBinaryFile("tarun.bat");
byte[] decodedtext = TripleDesDemo._decrypt(texttoDecrypt,"direct_tarun#yahoo.co.in");
byte[] decodedtext1 = TripleDesDemo._decrypt(decodedtext,"tarun.spicyabc#gmail.com");
byte[] decodedtext2 = TripleDesDemo._decrypt(decodedtext1,"tarunvermacdac#gmail.com");
System.out.println(codedtext + " ---> " + toObject(decodedtext2));
}
public static byte[] _encrypt(byte[] plainTextBytes, String secretKey) throws Exception {
byte[] keyBytes = secretKey.getBytes();
SecretKey key = new SecretKeySpec(keyBytes, "DESede");
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.ENCRYPT_MODE, key);
//byte[] plainTextBytes = message.getBytes("utf-8");
byte[] buf = cipher.doFinal(plainTextBytes);
byte [] base64Bytes = Base64.encodeBase64(buf);
//String base64EncryptedString = new String(base64Bytes);
return base64Bytes ;
}
public static byte[] _decrypt(byte[] encryptedText, String secretKey) throws Exception {
//byte[] message = Base64.decodeBase64(encryptedText);
byte[] message = Base64.decodeBase64(encryptedText);
byte[] keyBytes = secretKey.getBytes();
SecretKey key = new SecretKeySpec(keyBytes, "DESede");
Cipher decipher = Cipher.getInstance("DESede");
decipher.init(Cipher.DECRYPT_MODE, key);
byte[] plainText = decipher.doFinal(message);
return plainText ;
//return toObject(plainText);
}
public static byte[] toByteArray(UserRegistration obj) throws IOException {
byte[] bytes = null;
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
} finally {
if (oos != null) {
oos.close();
}
if (bos != null) {
bos.close();
}
}
return bytes;
}
public static UserRegistration toObject(byte[] bytes) throws IOException, ClassNotFoundException {
UserRegistration obj = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bis = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bis);
obj = (UserRegistration) ois.readObject();
} finally {
if (bis != null) {
bis.close();
}
if (ois != null) {
ois.close();
}
}
return obj;
}
public static byte[] readSmallBinaryFile(String aFileName) throws IOException {
Path path = Paths.get(aFileName);
return Files.readAllBytes(path);
}
public static void writeSmallBinaryFile(byte[] aBytes, String aFileName) throws IOException {
Path path = Paths.get(aFileName);
Files.write(path, aBytes); //creates, overwrites
}
}
The code is running fine with main but not when i call its function from other class which is in other package. Here is the exception.
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/apache/commons/codec/binary/Base64 at desede.TripleDesAlgo._encrypt(TripleDesAlgo.java:81)
And this is .classpath file
Thanks in advance for any help.
You are missing commons-codec.jar. Download it from http://commons.apache.org/proper/commons-codec/download_codec.cgi.
Then add it project build path. To do that right click the project, click Properties, click "Java Build Path", open "Library" tab, and click "Add External JARs...".
Or if you are using maven add dependency for
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.6</version>
</dependency>
I am trying to implement the API of the bitcoin exchange Kraken in Java. Unfortunately I got stuck at trying to execute an authentication in order to retrieve private user data.
In particular, I was playing with the following Implementation:
http://pastebin.com/nHJDAbH8
The documentation of Kraken's API is here: https://www.kraken.com/help/api
However, so far I only received {"error":["EAPI:Invalid key"]} . I couldn't find any mistake in the implementation and I tried several different API-keys. Could someone maybe have a quick look at the implementation and look for flaws in the code? Or has someone successfully implemented the Kraken API?
Many thanks!
The instructions for authentication are:
HTTP-Header: API-Key = API key API-Sign = Message signature using
HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) and base64
decoded secret API key
Post data: nonce = always increasing unsigned 64 bit integer otp =
two-factor password (if two-factor enabled, otherwise not required)
Note: in my case otp is disabled, so post-data consists only of nonce.
The implementation I was experimenting with is:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class KrakenClient {
protected static String key = "myAPIKey"; // API key
protected static String secret = "MySecret===="; // API secret
protected static String url = "api.kraken.com"; // API base URL
protected static String version = "0"; // API version
public static void main(String[] args) throws Exception {
queryPrivateMethod("Balance");
}
public static void queryPrivateMethod(String method) throws NoSuchAlgorithmException, IOException{
long nonce = System.currentTimeMillis();
String path = "/" + version + "/private/" + method; // The path like "/0/private/Balance"
String urlComp = "https://"+url+path; // The complete url like "https://api.kraken.com/0/private/Balance"
String postdata = "nonce="+nonce;
String sign = createSignature(nonce, path, postdata);
postConnection(urlComp, sign, postdata);
}
/**
* #param nonce
* #param path
* #param postdata
* #return
* #throws NoSuchAlgorithmException
* #throws IOException
*/
private static String createSignature(long nonce, String path,
String postdata) throws NoSuchAlgorithmException, IOException {
return hmac(path+sha256(nonce + postdata), new String(Base64.decodeBase64(secret)));
}
public static String sha256Hex(String text) throws NoSuchAlgorithmException, IOException{
return org.apache.commons.codec.digest.DigestUtils.sha256Hex(text);
}
public static byte[] sha256(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException{
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(text.getBytes());
byte[] digest = md.digest();
return digest;
}
public static void postConnection(String url1, String sign, String postData) throws IOException{
URL url = new URL( url1 );
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.addRequestProperty("API-Key", key);
connection.addRequestProperty("API-Sign", Base64.encodeBase64String(sign.getBytes()));
// connection.addRequestProperty("API-Sign", sign);
connection.addRequestProperty("User-Agent", "Mozilla/4.0");
connection.setRequestMethod( "POST" );
connection.setDoInput( true );
connection.setDoOutput( true );
connection.setUseCaches( false );
// connection.setRequestProperty( "Content-Type",
// "application/x-www-form-urlencoded" );
connection.setRequestProperty( "Content-Length", String.valueOf(postData.length()) );
OutputStreamWriter writer = new OutputStreamWriter( connection.getOutputStream() );
writer.write( postData );
writer.flush();
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()) );
for ( String line; (line = reader.readLine()) != null; )
{
System.out.println( line );
}
writer.close();
reader.close();
}
public static String hmac(String text, String secret){
Mac mac =null;
SecretKeySpec key = null;
// Create a new secret key
try {
key = new SecretKeySpec( secret.getBytes( "UTF-8"), "HmacSHA512" );
} catch( UnsupportedEncodingException uee) {
System.err.println( "Unsupported encoding exception: " + uee.toString());
return null;
}
// Create a new mac
try {
mac = Mac.getInstance( "HmacSHA512" );
} catch( NoSuchAlgorithmException nsae) {
System.err.println( "No such algorithm exception: " + nsae.toString());
return null;
}
// Init mac with key.
try {
mac.init( key);
} catch( InvalidKeyException ike) {
System.err.println( "Invalid key exception: " + ike.toString());
return null;
}
// Encode the text with the secret
try {
return new String( mac.doFinal(text.getBytes( "UTF-8")));
} catch( UnsupportedEncodingException uee) {
System.err.println( "Unsupported encoding exception: " + uee.toString());
return null;
}
}
}
Here is how I've got it working with Haskell:
signature body nonce path secret = convertToBase Base64 hmacsha512
where
sha256 = convert (hash $ nonce `append` body :: Digest SHA256)
hmacsha512 = hmac secretd (path `append` sha256) :: HMAC SHA512
secretd = fromRight $ convertFromBase Base64 secret :: ByteString
So you need to:
get SHA256 hash of nonce + body, i.e. SHA256("1487687774151000nonce=1487687774151000")
append raw bytes of digest to path (result would be unprintable, example path for balance method is "/0/private/Balance"),
get HMAC SHA512 digest using base64-decoded secret,
encode to Base64.
Remove the "/" prefix of your path variable.
String path = version + "/private/" + method; // The path like "0/private/Balance"
I've really been struggling to get working code, good examples, and most importantly, good documentation on how to use Paypal's Java SDK for Encrypting Website Payments. I've looked to Paypal for help (posted on their forum, contacted support), but haven't gotten any help thus far.
I went to https://www.paypal.com/us/cgi-bin/?cmd=p/xcl/rec/ewp-code and downloaded the Paypal Java SDK. Within the zip, there is a ReadMe.txt file with instructions for setup. The instructions are simple enough.
I went to Bouncy Castle's site - http://www.bouncycastle.org/latest_releases.html - to download the latest versions of the following jars :
bcmail-jdk16-146.jar
bcpg-jdk16-146.jar
bcprov-jdk16-146.jar
bctest-jdk16-146.jar
I then went to http://www.oracle.com/technetwork/java/javase/downloads/index.html to download the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files.
I put all the JARS in the appropriate folders, updated the classpath and then tried to compile the ClientSide.java class that came with the Paypal Java SDK.
The compiler tells me that there are deprecated classes, showing me the following errors after recompiling with -Xlint.
.\palmb\servlets\paypal\ClientSide.java:98: warning: [deprecation] addSigner(jav
a.security.PrivateKey,java.security.cert.X509Certificate,java.lang.String) in org.bouncycastle.cms.CMSSignedDataGenerator has been deprecated
signedGenerator.addSigner( privateKey, certificate, CMSSignedDataGenerator.DIGEST_SHA1 );
^
.\palmb\servlets\paypal\ClientSide.java:101: warning: [unchecked] unchecked call
to add(E) as a member of the raw type java.util.ArrayList
certList.add(certificate);
^
.\palmb\servlets\paypal\ClientSide.java:103: warning: [deprecation] addCertificatesAndCRLs(java.security.cert.CertStore) in org.bouncycastle.cms.CMSSignedGenerator has been deprecated
signedGenerator.addCertificatesAndCRLs(certStore);
^
.\palmb\servlets\paypal\ClientSide.java:110: warning: [deprecation] generate(org.bouncycastle.cms.CMSProcessable,boolean,java.lang.String) in org.bouncycastle.cms.CMSSignedDataGenerator has been deprecated
CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true, "BC");
^
.\palmb\servlets\paypal\ClientSide.java:115: warning: [deprecation] addKeyTransRecipient(java.security.cert.X509Certificate) in org.bouncycastle.cms.CMSEnvelopedGenerator has been deprecated envGenerator.addKeyTransRecipient(payPalCert);
^
.\palmb\servlets\paypal\ClientSide.java:116: warning: [deprecation] generate(org.bouncycastle.cms.CMSProcessable,java.lang.String,java.lang.String) in org.bouncycastle.cms.CMSEnvelopedDataGenerator has been deprecated
CMSEnvelopedData envData = envGenerator.generate( new CMSProcessableByteArray(signed),
^
6 warnings
I have Java 1.6 running on my machine. I'm disappointed in Paypal, in that they haven't provided nearly adequate, easy to understand documentation, and on to of that, for someone who needs an out of the box setup, their code doesn't work.
I went to Bouncy Castle's site (www.bouncycastle.org) and briefly looked at the documentation (http://www.bouncycastle.org/documentation.html) for version 1.6 - but I honestly have no clue how to use the methods that replace the deprecated ones.
Does anybody have experience with this Java Paypal code? Or experience with BouncyCastle and their code? If so, I'm in great need of some help.
ClientSide class
package palmb.servlets.paypal;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertStore;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.util.encoders.Base64;
/**
*/
public class ClientSide
{
private String keyPath;
private String certPath;
private String paypalCertPath;
private String keyPass;
public ClientSide( String keyPath, String certPath, String paypalCertPath, String keyPass )
{
this.keyPath = keyPath;
this.certPath = certPath;
this.paypalCertPath = paypalCertPath;
this.keyPass = keyPass;
}
public String getButtonEncryptionValue(String _data, String _privateKeyPath, String _certPath, String _payPalCertPath,
String _keyPass) throws IOException,CertificateException,KeyStoreException,
UnrecoverableKeyException,InvalidAlgorithmParameterException,NoSuchAlgorithmException,
NoSuchProviderException,CertStoreException,CMSException {
_data = _data.replace(',', '\n');
CertificateFactory cf = CertificateFactory.getInstance("X509", "BC");
// Read the Private Key
KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
ks.load( new FileInputStream(_privateKeyPath), _keyPass.toCharArray() );
String keyAlias = null;
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
keyAlias = (String) aliases.nextElement();
}
PrivateKey privateKey = (PrivateKey) ks.getKey( keyAlias, _keyPass.toCharArray() );
// Read the Certificate
X509Certificate certificate = (X509Certificate) cf.generateCertificate( new FileInputStream(_certPath) );
// Read the PayPal Cert
X509Certificate payPalCert = (X509Certificate) cf.generateCertificate( new FileInputStream(_payPalCertPath) );
// Create the Data
byte[] data = _data.getBytes();
// Sign the Data with my signing only key pair
CMSSignedDataGenerator signedGenerator = new CMSSignedDataGenerator();
signedGenerator.addSigner( privateKey, certificate, CMSSignedDataGenerator.DIGEST_SHA1 );
ArrayList certList = new ArrayList();
certList.add(certificate);
CertStore certStore = CertStore.getInstance( "Collection", new CollectionCertStoreParameters(certList) );
signedGenerator.addCertificatesAndCRLs(certStore);
CMSProcessableByteArray cmsByteArray = new CMSProcessableByteArray(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cmsByteArray.write(baos);
System.out.println( "CMSProcessableByteArray contains [" + baos.toString() + "]" );
CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true, "BC");
byte[] signed = signedData.getEncoded();
CMSEnvelopedDataGenerator envGenerator = new CMSEnvelopedDataGenerator();
envGenerator.addKeyTransRecipient(payPalCert);
CMSEnvelopedData envData = envGenerator.generate( new CMSProcessableByteArray(signed),
CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC" );
byte[] pkcs7Bytes = envData.getEncoded();
return new String( DERtoPEM(pkcs7Bytes, "PKCS7") );
}
public static byte[] DERtoPEM(byte[] bytes, String headfoot)
{
ByteArrayOutputStream pemStream = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(pemStream);
byte[] stringBytes = Base64.encode(bytes);
System.out.println("Converting " + stringBytes.length + " bytes");
String encoded = new String(stringBytes);
if (headfoot != null) {
writer.print("-----BEGIN " + headfoot + "-----\n");
}
// write 64 chars per line till done
int i = 0;
while ((i + 1) * 64 < encoded.length()) {
writer.print(encoded.substring(i * 64, (i + 1) * 64));
writer.print("\n");
i++;
}
if (encoded.length() % 64 != 0) {
writer.print(encoded.substring(i * 64)); // write remainder
writer.print("\n");
}
if (headfoot != null) {
writer.print("-----END " + headfoot + "-----\n");
}
writer.flush();
return pemStream.toByteArray();
}
}
ButtonEncryption class
package palmb.servlets.paypal;
//import com.paypal.crypto.sample.*;
import palmb.servlets.paypal.ClientSide;
import java.io.*;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateException;
import org.bouncycastle.cms.CMSException;
/**
*/
public class ButtonEncryption {
//path to public cert
private static String certPath = "C:/jakarta-tomcat/webapps/PlanB/Certs/public-cert.pem";
//path to private key in PKCS12 format
private static String keyPath = "C:/jakarta-tomcat/webapps/PlanB/Certs/my_pkcs12.p12";
//path to Paypal's public cert
private static String paypalCertPath = "C:/jakarta-tomcat/webapps/PlanB/Certs/paypal_cert_pem.txt";
//private key password
private static String keyPass = "password"; //will be replaced with actual password when compiled and executed
//the button command, properties/parameters
private static String cmdText = "cmd=_xclick\nbusiness=buyer#hotmail.com\nitem_name=vase\nitemprice=25.00"; //cmd=_xclick,business=sample#paypal.com,amount=1.00,currency_code=USD
//output file for form code
private static String output = "test.html";
public static void main(String[] args)
{
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String stage = "sandbox";
try
{
ClientSide client_side = new ClientSide( keyPath, certPath, paypalCertPath, keyPass );
String result = client_side.getButtonEncryptionValue( cmdText, keyPath, certPath, paypalCertPath, keyPass );
File outputFile = new File( output );
if ( outputFile.exists() )
outputFile.delete();
if ( result != null && result != "")
{
try {
OutputStream fout= new FileOutputStream( output );
OutputStream bout= new BufferedOutputStream(fout);
OutputStreamWriter out = new OutputStreamWriter(bout, "US-ASCII");
out.write( "<form action=\"https://www." );
out.write( stage );
out.write( "paypal.com/cgi-bin/webscr\" method=\"post\">" );
out.write( "<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\">" ); ;
out.write( "<input type=\"image\" src=\"https://www." );
out.write( stage );
out.write( "paypal.com/en_US/i/btn/x-click-but23.gif\" border=\"0\" name=\"submit\" " );
out.write( "alt=\"Make payments with PayPal - it's fast, free and secure!\">" );
out.write( "<input type=\"hidden\" name=\"encrypted\" value=\"" );
out.write( result );
out.write( "\">" );
out.write( "</form>");
out.flush(); // Don't forget to flush!
out.close();
}
catch (UnsupportedEncodingException e) {
System.out.println(
"This VM does not support the ASCII character set."
);
}
catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
catch (NoSuchAlgorithmException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (NoSuchProviderException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (CMSException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (CertificateException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (KeyStoreException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (UnrecoverableKeyException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (InvalidAlgorithmParameterException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (CertStoreException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Edited - Exception from running ButtonEncryption class
C:\jakarta-tomcat\webapps\PlanB\WEB-INF\classes>java palmb.servlets.paypal.ButtonEncryption
java.io.IOException: exception decrypting data - java.security.InvalidKeyException: Illegal key size
at org.bouncycastle.jce.provider.JDKPKCS12KeyStore.cryptData(Unknown Source)
at org.bouncycastle.jce.provider.JDKPKCS12KeyStore.engineLoad(Unknown Source)
at java.security.KeyStore.load(Unknown Source)
at palmb.servlets.paypal.ClientSide.getButtonEncryptionValue(ClientSide.
java:63)
at palmb.servlets.paypal.ButtonEncryption.main(ButtonEncryption.java:81)
You are getting the illegalKeySize error because you didn't install the JCE files in the correct location. You likely have multiple JREs on your system.
As for answering your question about the deprecated functions... I came up with the below replacement functions to PayPal's sample code which works great (based on bouncycastle javadoc):
private final static String getButtonEncryptionValue(String commandData, String keystorePath,
String keystorePassword, boolean sandbox) throws IOException, CertificateException, KeyStoreException,
UnrecoverableKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, CertStoreException, CMSException, OperatorCreationException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
commandData = commandData.replace(',', '\n');
CertificateFactory cf = CertificateFactory.getInstance("X509", "BC");
// Read the Private Key
KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
ks.load(new FileInputStream(keystorePath), keystorePassword.toCharArray());
String keyAlias = null;
Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
keyAlias = (String) aliases.nextElement();
}
PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, keystorePassword.toCharArray());
// Read the Certificate
X509Certificate certificate = (X509Certificate) cf.generateCertificate(ApplicationProxyService.class
.getResourceAsStream("/myCompanyPublicCert.pem.cer"));
// Read the PayPal Cert
X509Certificate payPalCert = (X509Certificate) cf.generateCertificate(ApplicationProxyService.class
.getResourceAsStream("/paypalPublicCert" + (sandbox ? "-sandbox" : "") + ".pem.cer"));
// Create the Data
// System.out.println(commandData);
byte[] data = commandData.getBytes();
// Sign the Data with my signing only key pair
CMSSignedDataGenerator signedGenerator = new CMSSignedDataGenerator();
List<X509Certificate> certList = new ArrayList<X509Certificate>();
certList.add(certificate);
//deprecated: Store certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList));
Store certStore = new JcaCertStore(certList);
// deprecated: signedGenerator.addCertificatesAndCRLs(certStore);
signedGenerator.addCertificates(certStore);
// deprecated: signedGenerator.addSigner(privateKey, certificate, CMSSignedDataGenerator.DIGEST_SHA1);
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privateKey);
signedGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, certificate));
CMSProcessableByteArray cmsByteArray = new CMSProcessableByteArray(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cmsByteArray.write(baos);
LOGGER.debug("CMSProcessableByteArray contains [" + baos.toString() + "]");
// deprecated: CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true, "BC");
CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true);
byte[] signed = signedData.getEncoded();
CMSEnvelopedDataGenerator envGenerator = new CMSEnvelopedDataGenerator();
// deprecated: envGenerator.addKeyTransRecipient(payPalCert);
envGenerator.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(payPalCert).setProvider("BC"));
// deprecated: CMSEnvelopedData envData = envGenerator.generate(new CMSProcessableByteArray(signed),
// CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC");
CMSEnvelopedData envData = envGenerator.generate(new CMSProcessableByteArray(signed),
new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider("BC").build());
byte[] pkcs7Bytes = envData.getEncoded();
return new String(DERtoPEM(pkcs7Bytes, "PKCS7"));
}
I would also like to note that the sample DERtoPEM() function had a defect in it that would truncate the last line of the encrypted value if it happened to be 64 characters long (0 % 64 == 0 AND 64 % 64 == 0). Below is the fix:
private static final byte[] DERtoPEM(byte[] bytes, String headfoot) {
byte[] stringBytes = Base64.encode(bytes);
// System.out.println("Converting " + stringBytes.length + " bytes");
StringBuilder sb = new StringBuilder();
sb.append("-----BEGIN " + headfoot + "-----\n");
String encoded = new String(stringBytes);
// write 64 chars per line till done
int i = 0;
while ((i + 1) * 64 < encoded.length()) {
sb.append(encoded.substring(i * 64, (i + 1) * 64));
sb.append("\n");
i++;
}
// if (encoded.length() % 64 != 0) { //FIXME (fixed via next line): this is a BUG that drops remaining data if data.length==64!
String remainder = encoded.substring(i * 64);
if (StringUtils.isNotEmpty(remainder)) {
sb.append(remainder); // write remainder
sb.append("\n");
}
sb.append("-----END " + headfoot + "-----\n");
return sb.toString().getBytes();
}
Couldn't get the classes from Paypal to work, so decided to give the Paypal Button API a try. This proved to be the best way to go. I could still use Java, and let Paypal take care of encrypting the buttons. It turned out to be a simple process once I got things coded correctly.
To view information about the Paypal Button API click here.