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"
Related
import android.util.Log;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Date;
import java.util.Iterator;
import static android.support.constraint.Constraints.TAG;
public class RSAEncryption {
static dataToBeEncrypt = "Hello";
static String publickey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"mI0EXEr27gEEALybxOEubZ39PX1t+oU9iXhTTmqrc/+ha+J6TYd+yvYcNEBIOzPI\n" +
"1fG2Tz30zJYwT9uZOyW7LAGKRcsIH6p4SiKGaEYBLEWuCQrGfXPAAXaH+WNsKOmz\n" +
"SAZilPwugEcWknIUDZzfU0AD8PPCLqhJM6Ij7AJ/SNa2xUIsyUW+PwBdABEBAAG0\n" +
"BWplZXRziM4EEwEKADgWIQRp0+dcK5ZTXht9TIcw0CiFbn2j6gUCXEr27gIbAwUL\n" +
"CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAw0CiFbn2j6um7A/9Ttzl8xHa4MvuF\n" +
"htOEAb6sXOk17B9BQyd3bkUCYtBjmFA3Q7XzWln5lm2QQTxW+AnlVJYwQS5zvVC0\n" +
"8WgPh6XP9sCi6/2ar748wpS8dL1wEAwaMOSYGEDCUpmW2uEGsrsAKfiMlKGhLiPC\n" +
"uUvPJ0x2jjHn9HHO1e9BES9tlkRPJLiNBFxK9u4BBADQtF6EajJMi2IPsVDR6PTX\n" +
"swjGgVbYKqwZRUwfLecJ95VeiQzznWUMpWFze4ESlAjCYeJf37voDazs6Ol5Uqz7\n" +
"7d5GurEk4kNjQp4Pm+chFQWLWU1seMghyJqsXCjWApy0lNJ2YPNbYKUqdJsZpJgT\n" +
"7qofPvEEPlGRRqzZxdN8oQARAQABiLYEGAEKACAWIQRp0+dcK5ZTXht9TIcw0CiF\n" +
"bn2j6gUCXEr27gIbDAAKCRAw0CiFbn2j6ky9BACgADAD/VIFIUjuQtNa4GEcAS0T\n" +
"vtJvsL26qW/Gohl5nb1ix7MLBwiH/l1Co6K52GKAVUrZBCnjkJW5zyGTKUkPGbGY\n" +
"Loh481phsvpYgXHqol7UQivBoF14EO1dNmn61QFRn7D3zhNirziOKQl3kLSuEdc5\n" +
"VpRF7ubkHL3jRrdQFA==\n" +
"=/lmV\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
public static void main (String[] args){
String encrypted = null;
try {
encrypted = encryptToFile(dataToBeEncrypt, publicKey);
} catch (Exception e) {
Log.i(TAG, "Exception: " + e);
}
return encrypted;
}
public static String encryptToFile(String inputdata, String keyFile) throws Exception {
Security.addProvider(new BouncyCastleProvider());
byte[] original = inputdata.getBytes();
InputStream secKey = new ByteArrayInputStream(keyFile.getBytes("UTF-8"));
byte[] encrypted = encrypt(original, readPublicKey(secKey), true, true);
return new String(encrypted);
}
private static PGPPublicKey readPublicKey(InputStream in) throws IOException, PGPException {
PGPPublicKey k = null;
in = PGPUtil.getDecoderStream(in);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in);
Iterator rIt = pgpPub.getKeyRings();
while (rIt.hasNext()) {
PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
Iterator kIt = kRing.getPublicKeys();
while (kIt.hasNext()) {
k = (PGPPublicKey) kIt.next();
if (k.isEncryptionKey()) {
break;
}
}
}
return k;
}
/**
* Simple PGP encryptor between byte[].
*
* #param clearData
* The test to be encrypted
* #param //passPhrase
* The pass phrase (key). This method assumes that the key is a
* simple pass phrase, and does not yet support RSA or more
* sophisiticated keying.
* #param //fileName
* File name. This is used in the Literal Data Packet (tag 11)
* which is really inly important if the data is to be related to
* a file to be recovered later. Because this routine does not
* know the source of the information, the caller can set
* something here for file name use that will be carried. If this
* routine is being used to encrypt SOAP MIME bodies, for
* example, use the file name from the MIME type, if applicable.
* Or anything else appropriate.
*
* #param armor
* #return encrypted data.
* #exception IOException
* #exception PGPException
* #exception NoSuchProviderException
*/
public static byte[] encrypt(byte[] clearData, PGPPublicKey encKey, boolean withIntegrityCheck, boolean armor)
throws IOException, PGPException, NoSuchProviderException {
ByteArrayOutputStream encOut = new ByteArrayOutputStream();
OutputStream out = encOut;
if (armor) {
out = new ArmoredOutputStream(out);
}
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(
PGPCompressedDataGenerator.ZIP);
OutputStream cos = comData.open(bOut); // open it with the final
// destination
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
// we want to generate compressed data. This might be a user option
// later,
// in which case we would pass in bOut.
OutputStream pOut = lData.open(cos, // the compressed output stream
PGPLiteralData.BINARY, "", // "filename" to store
clearData.length, // length of clear data
new Date() // current time
);
pOut.write(clearData);
lData.close();
comData.close();
PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(
PGPEncryptedData.CAST5, withIntegrityCheck, new SecureRandom(),
"BC");
cPk.addMethod(encKey);
byte[] bytes = bOut.toByteArray();
OutputStream cOut = cPk.open(out, bytes.length);
cOut.write(bytes); // obtain the actual bytes from the compressed stream
cOut.close();
out.close();
return encOut.toByteArray();
}
}
I was trying to achieve encryption using RSA algorithm with given public key and text to be encrypted. Below is my code which I was trying where I am passing my public key and string and getting the encrypted data. The problem is below code works fine when I run it as Java Application but when I use it in my android code I always get org.bouncycastle.openpgp.PGPException: Exception creating cipher as exception for line OutputStream cOut = cPk.open(out, bytes.length);. Please suggest me how can I make it work with my Android application.
Also,
OutputStream pOut = lData.open(cos, // the compressed output stream PGPLiteralData.BINARY, "", // "filename" to store clearData.length, // length of clear data new Date() // current time );
I am passing file name as null. Is this creating issue?
I expect Encrypted Message Without Version: BCPG v1.46, but I am getting org.bouncycastle.openpgp.PGPException: Exception creating cipher at line OutputStream cOut = cPk.open(out, bytes.length);
Editted
Exception:
org.bouncycastle.openpgp.PGPException: Exception creating cipher
at org.bouncycastle.openpgp.PGPEncryptedDataGenerator.open(Unknown Source)
at org.bouncycastle.openpgp.PGPEncryptedDataGenerator.open(Unknown Source)
at com.example.Resources.RSAEncryption.encrypt(RSAEncryption.java:157)
at com.example.Resources.RSAEncryption.encryptToFile(RSAEncryption.java:74)
at com.example.Resources.RSAEncryption.encryption(RSAEncryption.java:62)
at com.example.EQR.QRUserCredentials.onActivityResult(QRUserCredentials.java:140)
at android.app.Activity.dispatchActivityResult(Activity.java:6931)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4090)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4137)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1529)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
Caused by: java.security.NoSuchAlgorithmException: Provider BC does not provide CAST5/CFB/NoPadding
at javax.crypto.Cipher.createCipher(Cipher.java:921)
at javax.crypto.Cipher.getInstance(Cipher.java:901)
... 17 more
Finally, I found the solution.
Instead of CAST5 I am using AES_128 PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(PGPEncryptedData.AES_128, withIntegrityCheck, new SecureRandom(),"BC");
Below is the working code
import android.util.Log;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Date;
import java.util.Iterator;
import static android.support.constraint.Constraints.TAG;
public class RSAEncryption {
static dataToBeEncrypt = "Hello";
static String publickey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"mI0EXEr27gEEALybxOEubZ39PX1t+oU9iXhTTmqrc/+ha+J6TYd+yvYcNEBIOzPI\n" +
"1fG2Tz30zJYwT9uZOyW7LAGKRcsIH6p4SiKGaEYBLEWuCQrGfXPAAXaH+WNsKOmz\n" +
"SAZilPwugEcWknIUDZzfU0AD8PPCLqhJM6Ij7AJ/SNa2xUIsyUW+PwBdABEBAAG0\n" +
"BWplZXRziM4EEwEKADgWIQRp0+dcK5ZTXht9TIcw0CiFbn2j6gUCXEr27gIbAwUL\n" +
"CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAw0CiFbn2j6um7A/9Ttzl8xHa4MvuF\n" +
"htOEAb6sXOk17B9BQyd3bkUCYtBjmFA3Q7XzWln5lm2QQTxW+AnlVJYwQS5zvVC0\n" +
"8WgPh6XP9sCi6/2ar748wpS8dL1wEAwaMOSYGEDCUpmW2uEGsrsAKfiMlKGhLiPC\n" +
"uUvPJ0x2jjHn9HHO1e9BES9tlkRPJLiNBFxK9u4BBADQtF6EajJMi2IPsVDR6PTX\n" +
"swjGgVbYKqwZRUwfLecJ95VeiQzznWUMpWFze4ESlAjCYeJf37voDazs6Ol5Uqz7\n" +
"7d5GurEk4kNjQp4Pm+chFQWLWU1seMghyJqsXCjWApy0lNJ2YPNbYKUqdJsZpJgT\n" +
"7qofPvEEPlGRRqzZxdN8oQARAQABiLYEGAEKACAWIQRp0+dcK5ZTXht9TIcw0CiF\n" +
"bn2j6gUCXEr27gIbDAAKCRAw0CiFbn2j6ky9BACgADAD/VIFIUjuQtNa4GEcAS0T\n" +
"vtJvsL26qW/Gohl5nb1ix7MLBwiH/l1Co6K52GKAVUrZBCnjkJW5zyGTKUkPGbGY\n" +
"Loh481phsvpYgXHqol7UQivBoF14EO1dNmn61QFRn7D3zhNirziOKQl3kLSuEdc5\n" +
"VpRF7ubkHL3jRrdQFA==\n" +
"=/lmV\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
public static void main (String[] args){
String encrypted = null;
try {
encrypted = encryptToFile(dataToBeEncrypt, publicKey);
} catch (Exception e) {
Log.i(TAG, "Exception: " + e);
}
return encrypted;
}
public static String encryptToFile(String inputdata, String keyFile) throws Exception {
Security.addProvider(new BouncyCastleProvider());
byte[] original = inputdata.getBytes();
InputStream secKey = new ByteArrayInputStream(keyFile.getBytes("UTF-8"));
byte[] encrypted = encrypt(original, readPublicKey(secKey), true, true);
return new String(encrypted);
}
private static PGPPublicKey readPublicKey(InputStream in) throws IOException, PGPException {
PGPPublicKey k = null;
in = PGPUtil.getDecoderStream(in);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in);
Iterator rIt = pgpPub.getKeyRings();
while (rIt.hasNext()) {
PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
Iterator kIt = kRing.getPublicKeys();
while (kIt.hasNext()) {
k = (PGPPublicKey) kIt.next();
if (k.isEncryptionKey()) {
break;
}
}
}
return k;
}
/**
* Simple PGP encryptor between byte[].
*
* #param clearData
* The test to be encrypted
* #param //passPhrase
* The pass phrase (key). This method assumes that the key is a
* simple pass phrase, and does not yet support RSA or more
* sophisiticated keying.
* #param //fileName
* File name. This is used in the Literal Data Packet (tag 11)
* which is really inly important if the data is to be related to
* a file to be recovered later. Because this routine does not
* know the source of the information, the caller can set
* something here for file name use that will be carried. If this
* routine is being used to encrypt SOAP MIME bodies, for
* example, use the file name from the MIME type, if applicable.
* Or anything else appropriate.
*
* #param armor
* #return encrypted data.
* #exception IOException
* #exception PGPException
* #exception NoSuchProviderException
*/
public static byte[] encrypt(byte[] clearData, PGPPublicKey encKey, boolean withIntegrityCheck, boolean armor)
throws IOException, PGPException, NoSuchProviderException {
ByteArrayOutputStream encOut = new ByteArrayOutputStream();
OutputStream out = encOut;
if (armor) {
out = new ArmoredOutputStream(out);
}
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(
PGPCompressedDataGenerator.ZIP);
OutputStream cos = comData.open(bOut); // open it with the final
// destination
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
// we want to generate compressed data. This might be a user option
// later,
// in which case we would pass in bOut.
OutputStream pOut = lData.open(cos, // the compressed output stream
PGPLiteralData.BINARY, "", // "filename" to store
clearData.length, // length of clear data
new Date() // current time
);
pOut.write(clearData);
lData.close();
comData.close();
PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(
PGPEncryptedData.AES_128, withIntegrityCheck, new SecureRandom(),
"BC");
cPk.addMethod(encKey);
byte[] bytes = bOut.toByteArray();
OutputStream cOut = cPk.open(out, bytes.length);
cOut.write(bytes); // obtain the actual bytes from the compressed stream
cOut.close();
out.close();
return encOut.toByteArray();
}
}
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;
}
I am generating Keypair for PGP Encryption using this class and saving the Files to the disk.
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Date;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.sig.Features;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
public class RSAGen {
public static void Generate(String ID,String PassPhrase , String FileName) throws Exception {
char pass[] = PassPhrase.toCharArray();
PGPKeyRingGenerator krgen = generateKeyRingGenerator(ID, pass);
// Generate public key ring, dump to file.
PGPPublicKeyRing pkr = krgen.generatePublicKeyRing();
ArmoredOutputStream pubout = new ArmoredOutputStream(new BufferedOutputStream(new FileOutputStream(FileName + ".asc")));
pkr.encode(pubout);
pubout.close();
// Generate private key, dump to file.
PGPSecretKeyRing skr = krgen.generateSecretKeyRing();
BufferedOutputStream secout = new BufferedOutputStream(new FileOutputStream(FileName + ".skr"));
skr.encode(secout);
secout.close();
}
public final static PGPKeyRingGenerator generateKeyRingGenerator(String id, char[] pass) throws Exception{
return generateKeyRingGenerator(id, pass, 0xc0);
}
// Note: s2kcount is a number between 0 and 0xff that controls the number of times to iterate the password hash before use. More
// iterations are useful against offline attacks, as it takes more time to check each password. The actual number of iterations is
// rather complex, and also depends on the hash function in use. Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give
// you more iterations. As a rough rule of thumb, when using SHA256 as the hashing function, 0x10 gives you about 64
// iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0, or about 1 million iterations. The maximum you can go to is
// 0xff, or about 2 million iterations. I'll use 0xc0 as a default -- about 130,000 iterations.
public final static PGPKeyRingGenerator generateKeyRingGenerator(String id, char[] pass, int s2kcount) throws Exception {
// This object generates individual key-pairs.
RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();
// Boilerplate RSA parameters, no need to change anything
// except for the RSA key-size (2048). You can use whatever key-size makes sense for you -- 4096, etc.
kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001), new SecureRandom(), 2048, 12));
// First create the master (signing) key with the generator.
PGPKeyPair rsakp_sign = new BcPGPKeyPair(PGPPublicKey.RSA_SIGN, kpg.generateKeyPair(), new Date());
// Then an encryption subkey.
PGPKeyPair rsakp_enc = new BcPGPKeyPair(PGPPublicKey.RSA_ENCRYPT, kpg.generateKeyPair(), new Date());
// Add a self-signature on the id
PGPSignatureSubpacketGenerator signhashgen = new PGPSignatureSubpacketGenerator();
// Add signed metadata on the signature.
// 1) Declare its purpose
signhashgen.setKeyFlags(false, KeyFlags.SIGN_DATA|KeyFlags.CERTIFY_OTHER);
// 2) Set preferences for secondary crypto algorithms to use when sending messages to this key.
signhashgen.setPreferredSymmetricAlgorithms
(false, new int[] {
SymmetricKeyAlgorithmTags.AES_256,
SymmetricKeyAlgorithmTags.AES_192,
SymmetricKeyAlgorithmTags.AES_128
});
signhashgen.setPreferredHashAlgorithms
(false, new int[] {
HashAlgorithmTags.SHA256,
HashAlgorithmTags.SHA1,
HashAlgorithmTags.SHA384,
HashAlgorithmTags.SHA512,
HashAlgorithmTags.SHA224,
});
// 3) Request senders add additional checksums to the message (useful when verifying unsigned messages.)
signhashgen.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
// Create a signature on the encryption subkey.
PGPSignatureSubpacketGenerator enchashgen = new PGPSignatureSubpacketGenerator();
// Add metadata to declare its purpose
enchashgen.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS|KeyFlags.ENCRYPT_STORAGE);
// Objects used to encrypt the secret key.
PGPDigestCalculator sha1Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1);
PGPDigestCalculator sha256Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA256);
// bcpg 1.48 exposes this API that includes s2kcount. Earlier versions use a default of 0x60.
PBESecretKeyEncryptor pske = (new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha256Calc, s2kcount)).build(pass);
// Finally, create the keyring itself. The constructor takes parameters that allow it to generate the self signature.
PGPKeyRingGenerator keyRingGen =
new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsakp_sign,
id, sha1Calc, signhashgen.generate(), null,
new BcPGPContentSignerBuilder(rsakp_sign.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), pske);
// Add our encryption subkey, together with its signature.
keyRingGen.addSubKey(rsakp_enc, enchashgen.generate(), null);
return keyRingGen;
}
}
i found this class but it won't work with the latest version which is :
-bcpg-jdk15on-1.54
-bcprov-jdk15on-1.54
i got lot's of error message when using this class :
https://github.com/matthewmccullough/encryption-jvm-bootcamp/blob/master/bc-pgp/src/main/java/com/ambientideas/cryptography/KeyBasedFileProcessorUtil.java
package com.ambientideas.cryptography;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Iterator;
//Matthew McCullough: Rediculous as it sounds, many of the functions such as
// private static void encryptFile()
// private static void decryptFile()
// private static PGPPrivateKey findSecretKey()
// private static PGPPublicKey readPublicKey()
// for PGP in BouncyCastle are private, thus making it unbearable to use
// in a simple manner against whole file contents. Thus, this class is duplicated from the
// core of BouncyCastle (KeyBasedFileProcessor being the original name), but with the
// methods made public so that the test can use them.
/**
* A simple utility class that encrypts/decrypts public key based
* encryption files.
* <p>
* To encrypt a file: KeyBasedFileProcessor -e [-a|-ai] fileName publicKeyFile.<br>
* If -a is specified the output file will be "ascii-armored".
* If -i is specified the output file will be have integrity checking added.
* <p>
* To decrypt: KeyBasedFileProcessor -d fileName secretKeyFile passPhrase.
* <p>
* Note 1: this example will silently overwrite files, nor does it pay any attention to
* the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
* will have been used.
* <p>
* Note 2: if an empty file name has been specified in the literal data object contained in the
* encrypted packet a file with the name filename.out will be generated in the current working directory.
*/
public class KeyBasedFileProcessorUtil
{
/**
* A simple routine that opens a key ring file and loads the first available key suitable for
* encryption.
*
* #param in
* #return
* #throws IOException
* #throws PGPException
*/
public static PGPPublicKey readPublicKey(
InputStream in)
throws IOException, PGPException
{
in = PGPUtil.getDecoderStream(in);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in);
//
// we just loop through the collection till we find a key suitable for encryption, in the real
// world you would probably want to be a bit smarter about this.
//
//
// iterate through the key rings.
//
Iterator<?> rIt = pgpPub.getKeyRings();
while (rIt.hasNext())
{
PGPPublicKeyRing kRing = (PGPPublicKeyRing)rIt.next();
Iterator<?> kIt = kRing.getPublicKeys();
while (kIt.hasNext())
{
PGPPublicKey k = (PGPPublicKey)kIt.next();
if (k.isEncryptionKey())
{
return k;
}
}
}
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
/**
* Search a secret key ring collection for a secret key corresponding to
* keyID if it exists.
*
* #param pgpSec a secret key ring collection.
* #param keyID keyID we want.
* #param pass passphrase to decrypt secret key with.
* #return
* #throws PGPException
* #throws NoSuchProviderException
*/
public static PGPPrivateKey findSecretKey(
PGPSecretKeyRingCollection pgpSec,
long keyID,
char[] pass)
throws PGPException, NoSuchProviderException
{
PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
if (pgpSecKey == null)
{
return null;
}
return pgpSecKey.extractPrivateKey(pass, "BC");
}
/**
* decrypt the passed in message stream
*/
public static void decryptFile(
InputStream in,
InputStream keyIn,
char[] passwd,
String defaultFileName,
String outputPath)
throws Exception
{
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
//
// the first object might be a PGP marker packet.
//
if (o instanceof PGPEncryptedDataList)
{
enc = (PGPEncryptedDataList)o;
}
else
{
enc = (PGPEncryptedDataList)pgpF.nextObject();
}
//
// find the secret key
//
Iterator <?> it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
PGPUtil.getDecoderStream(keyIn));
while (sKey == null && it.hasNext())
{
pbe = (PGPPublicKeyEncryptedData)it.next();
sKey = findSecretKey(pgpSec, pbe.getKeyID(), passwd);
}
if (sKey == null)
{
throw new IllegalArgumentException("secret key for message not found.");
}
InputStream clear = pbe.getDataStream(sKey, "BC");
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
Object message = plainFact.nextObject();
if (message instanceof PGPCompressedData)
{
PGPCompressedData cData = (PGPCompressedData)message;
PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream());
message = pgpFact.nextObject();
}
if (message instanceof PGPLiteralData)
{
PGPLiteralData ld = (PGPLiteralData)message;
String outFileName = ld.getFileName();
if (ld.getFileName().length() == 0)
{
outFileName = defaultFileName;
}
//MJM: Enhancement to allow targeting of output folder for decrypted files
if (outputPath == null || outputPath.length() > 0) {
outFileName = outputPath + outFileName;
}
FileOutputStream fOut = new FileOutputStream(outFileName);
InputStream unc = ld.getInputStream();
int ch;
while ((ch = unc.read()) >= 0)
{
fOut.write(ch);
}
}
else if (message instanceof PGPOnePassSignatureList)
{
throw new PGPException("encrypted message contains a signed message - not literal data.");
}
else
{
throw new PGPException("message is not a simple encrypted file - type unknown.");
}
if (pbe.isIntegrityProtected())
{
if (!pbe.verify())
{
System.err.println("message failed integrity check");
}
else
{
System.err.println("message integrity check passed");
}
}
else
{
System.err.println("no message integrity check");
}
}
public static void encryptFile(
OutputStream out,
String fileName,
PGPPublicKey encKey,
boolean armor,
boolean withIntegrityCheck)
throws IOException, NoSuchProviderException
{
if (armor)
{
out = new ArmoredOutputStream(out);
}
try
{
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(
PGPCompressedData.ZIP);
PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName));
comData.close();
PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, withIntegrityCheck, new SecureRandom(), "BC");
cPk.addMethod(encKey);
byte[] bytes = bOut.toByteArray();
OutputStream cOut = cPk.open(out, bytes.length);
cOut.write(bytes);
cOut.close();
out.close();
}
catch (PGPException e)
{
System.err.println(e);
if (e.getUnderlyingException() != null)
{
e.getUnderlyingException().printStackTrace();
}
}
}
public static void main(
String[] args)
throws Exception
{
Security.addProvider(new BouncyCastleProvider());
if (args.length == 0)
{
System.err.println("usage: KeyBasedFileProcessor -e|-d [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
return;
}
if (args[0].equals("-e"))
{
if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
{
FileInputStream keyIn = new FileInputStream(args[3]);
FileOutputStream out = new FileOutputStream(args[2] + ".asc");
encryptFile(out, args[2], readPublicKey(keyIn), true, (args[1].indexOf('i') > 0));
}
else if (args[1].equals("-i"))
{
FileInputStream keyIn = new FileInputStream(args[3]);
FileOutputStream out = new FileOutputStream(args[2] + ".bpg");
encryptFile(out, args[2], readPublicKey(keyIn), false, true);
}
else
{
FileInputStream keyIn = new FileInputStream(args[2]);
FileOutputStream out = new FileOutputStream(args[1] + ".bpg");
encryptFile(out, args[1], readPublicKey(keyIn), false, false);
}
}
else if (args[0].equals("-d"))
{
FileInputStream in = new FileInputStream(args[1]);
FileInputStream keyIn = new FileInputStream(args[2]);
decryptFile(in, keyIn, args[3].toCharArray(), new File(args[1]).getName() + ".out", null);
}
else
{
System.err.println("usage: KeyBasedFileProcessor -d|-e [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
}
}
}
Error MEssages
here is the error messages got when running in intelliJ
I had similar issue, then i use THIS implementation for encrypting or decrypting PGP files and it worked fine.
Using the standard modern Java Core classes that have the JCE included (e.g. Java 1.8_303+), BouncyCastle Core, and Bouncy Castle provider, I have developed a Spring-based Service that can handle PGP encryption and decryption from public/private keys contained within Resource files. If you are not using Spring, you can strip out the Spring specific code and just leverage the public encrypt/decrypt methods along with the private support methods:
package com.your.organization.impl;
import com.your.organization.exception.EncryptionException; // Your own Exception class
import com.your.organization.service.PgpEncryptionService; // Your own Interface class
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Iterator;
#Service("pgpEncryptionService")
public final class PgpEncryptionServiceImpl implements PgpEncryptionService {
#PostConstruct
public void initializeSecurityProviders() {
// Add the Bouncy Castle security Provider to the JVM
Security.addProvider(new BouncyCastleProvider());
}
/**
* Encrypts a cleared message {#link String} using the classpath PGPPublicKey using
* {#link ArmoredOutputStream} to further protect the encrypted message.
*
* #param message {#link String}
* #return Encrypted String with, or without, armoring
* #throws EncryptionException is thrown if the {#link PGPEncryptedDataGenerator} could not be initialized
* from the provided PGPPublicKey or if the encoded message {#link OutputStream}
* could not be opened
*/
public String encrypt(String message) throws EncryptionException {
/*
* Initialize an OutputStream or ArmoredOutputStream for the encrypted message based on the armor
* function input
*/
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStream armoredOutputStream = byteArrayOutputStream;
armoredOutputStream = new ArmoredOutputStream(armoredOutputStream);
// Initialize and configure the encryption generator using the provided PGPPublicKey
PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256)
.setSecureRandom(new SecureRandom())
.setProvider("BC"));
pgpEncryptedDataGenerator.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(getPublicKey())
.setProvider("BC"));
// Convert message String to byte[] using standard UTF-8
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
// Open the PGPEncryptedDataGenerator from the ArmoredOutputStream initialized to the message body length
OutputStream encryptedOutputStream;
try {
encryptedOutputStream = pgpEncryptedDataGenerator.open(armoredOutputStream, messageBytes.length);
} catch (IOException | PGPException e) {
throw new EncryptionException("Could not open an OutputStream from the PGPEncryptedDataGenerator " +
"using the provided message body", e);
}
// Write the encrypted message to the encryptedOutputStream
try {
encryptedOutputStream.write(messageBytes);
} catch (IOException e) {
throw new EncryptionException("Could not write the message body to the encrypted OutputStream", e);
} finally {
// Close the encrypted message OutputStream
try {
encryptedOutputStream.close();
} catch (IOException e) {
// TODO: Log this
}
// Close the ArmoredOutputStream
try {
armoredOutputStream.close();
} catch (IOException e) {
// TODO: Log this
}
}
// Return the encrypted message OutputStream to a String
return byteArrayOutputStream.toString();
}
/**
* Decrypts an encrypted message {#link String} using the {#link PGPSecretKey} on the classpath and its
* password {#link String}
*
* #param encryptedMessage {#link String}
* #param password {#link String}
* #return String
* #throws EncryptionException is thrown if an encrypted message InputStream cannot be initialized from the
* encryptedMessage {#link String}, if the PGPEncryptedDataList from that stream
* contains no data, or if the password {#link String} for the
* {#link PGPSecretKey} is incorrect
*/
public String decrypt(String encryptedMessage, String password) throws EncryptionException {
// Convert the encrypted String into an InputStream
InputStream encryptedStream = new ByteArrayInputStream(encryptedMessage.getBytes(StandardCharsets.UTF_8));
try {
encryptedStream = PGPUtil.getDecoderStream(encryptedStream);
} catch (IOException e) {
throw new EncryptionException("Could not initialize the DecoderStream", e);
}
// Retrieve the PGPEncryptedDataList from the encryptedStream
JcaPGPObjectFactory jcaPGPObjectFactory = new JcaPGPObjectFactory(encryptedStream);
PGPEncryptedDataList pgpEncryptedDataList;
/*
* Evaluate the first object for a leading PGP marker packet and then return the encrypted
* message body as a PGPEncryptedDataList
*/
try {
Object nextDataObject = jcaPGPObjectFactory.nextObject();
if (nextDataObject instanceof PGPEncryptedDataList) {
pgpEncryptedDataList = (PGPEncryptedDataList) nextDataObject;
} else {
pgpEncryptedDataList = (PGPEncryptedDataList) jcaPGPObjectFactory.nextObject();
}
} catch (IOException e) {
throw new EncryptionException("Could not retrieve the encrupted message body", e);
}
// Retrieve the public key encrypted data from the encrypted message body
PGPPublicKeyEncryptedData pgpPublicKeyEncryptedData =
(PGPPublicKeyEncryptedData) pgpEncryptedDataList.getEncryptedDataObjects().next();
// Use the PGPPublicKeyEncryptedData and Secret Key password to decrypt the encoded message
InputStream decryptedInputStream;
try {
decryptedInputStream =
pgpPublicKeyEncryptedData.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder()
.setProvider("BC")
.build(getPrivateKey(getSecretKey(), password)));
} catch (PGPException e) {
throw new EncryptionException("Could not decrypt the encoded message from the application " +
"Secret Key or the embedded Private Key", e);
}
// Convert the InputStream of the decrypted message to a String
try {
return IOUtils.toString(decryptedInputStream, StandardCharsets.UTF_8.name());
} catch (IOException e) {
throw new EncryptionException("Could not convert the decrypted InputStream to a UTF-8 String", e);
}
}
/**
* Helper method for retrieving the {#link PGPPublicKey} from the application classpath.
*
* #return PGPPublicKey
* #throws EncryptionException is thrown in the event that the PGP Public Key file does not contain a
* Public Key or if the Public Key cannot be located on the file system
*/
private PGPPublicKey getPublicKey() throws EncryptionException {
// Retrieve the application PGP public key file from the classpath
File publicKeyFile;
try {
publicKeyFile = new ClassPathResource("keys/yourpublickey-pub.asc").getFile();
} catch (IOException e) {
throw new EncryptionException("Could not retrieve the PGP Public Key from the classpath", e);
}
// Read Public Key from the file
FileInputStream pubKey;
try {
pubKey = new FileInputStream(publicKeyFile);
} catch (FileNotFoundException e) {
throw new EncryptionException("Could not retrieve the PGP Public Key from the file system", e);
}
// Load PGPPublicKey FileInputStream into the PGPPublicKeyRingCollection
PGPPublicKeyRingCollection pgpPub;
try {
pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(pubKey),
new JcaKeyFingerprintCalculator());
} catch (IOException | PGPException e) {
throw new EncryptionException("Could not initialize the PGPPublicKeyRingCollection", e);
}
// Retrieve Public Key and evaluate if for the encryption key
Iterator<PGPPublicKeyRing> keyRingIter = pgpPub.getKeyRings();
while (keyRingIter.hasNext()) {
PGPPublicKeyRing keyRing = keyRingIter.next();
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext()) {
PGPPublicKey key = keyIter.next();
if (key.isEncryptionKey()) {
return key;
}
}
}
throw new EncryptionException("The application PGPPublicKey is not an allowable encryption key");
}
/**
* Helper method for retrieving the signing key {#link PGPSecretKey} from the classpath.
*
* #return Signing key {#link PGPSecretKey}
* #throws EncryptionException is thrown if the Secret Key is not a signing key or if the Secret Key file
* could not be located on the file system
*/
private PGPSecretKey getSecretKey() throws EncryptionException {
// Retrieve the application PGP secret key file from the classpath
File secretKeyFile;
try {
secretKeyFile = new ClassPathResource("keys/yoursecretkey-sec.asc").getFile();
} catch (IOException e) {
throw new EncryptionException("Could not retrieve the PGP Secret Key from the classpath", e);
}
// Read Secret Key file and load it into a PGPPublicKeyRingCollection for evaluation
FileInputStream secKey;
try {
secKey = new FileInputStream(secretKeyFile);
} catch (FileNotFoundException e) {
throw new EncryptionException("Could not retrieve the PGP Secret Key from the file system", e);
}
// Load PGPSecretKey FileInputStream into the PGPSecretKeyRingCollection
PGPSecretKeyRingCollection pgpSec;
try {
pgpSec = new PGPSecretKeyRingCollection(
PGPUtil.getDecoderStream(secKey), new JcaKeyFingerprintCalculator());
} catch (IOException | PGPException e) {
throw new EncryptionException("Could not initialize the PGPSecretKeyRingCollection", e);
}
// Retrieve signing Secret Key
Iterator<PGPSecretKeyRing> secretKeyRingIterator = pgpSec.getKeyRings();
while (secretKeyRingIterator.hasNext()) {
PGPSecretKeyRing keyRing = secretKeyRingIterator.next();
Iterator<PGPSecretKey> keyIter = keyRing.getSecretKeys();
while (keyIter.hasNext()) {
PGPSecretKey key = keyIter.next();
if (key.isSigningKey()) {
return key;
}
}
}
throw new EncryptionException("The application PGPSecretKey is not a signing key");
}
/**
* Retrieves the {#link PGPPrivateKey} from the provided {#link PGPSecretKey} and its password.
*
* #param secretKey {#link PGPSecretKey}
* #param password {#link String}
* #return PGPPrivateKey
* #throws EncryptionException is thrown in the event that the password for the {#link PGPSecretKey}
* is incorrect
*/
private PGPPrivateKey getPrivateKey(PGPSecretKey secretKey, String password) throws EncryptionException {
PBESecretKeyDecryptor decryptorFactory = new BcPBESecretKeyDecryptorBuilder(
new BcPGPDigestCalculatorProvider()).build(password.toCharArray());
try {
return secretKey.extractPrivateKey(decryptorFactory);
} catch (PGPException e) {
throw new EncryptionException("Could not extract the Private Key from the application Secret Key", e);
}
}
}
I had issues using the implementation recommended by #TomazStoiljkovic,
so I ended up using this one.
I first had some issues when generating the keys with gpg --gen-key.
It is because the default kind of key used by gpg is ECC (sign and encrypt). And this implementation does not support it. Instead, use gpg --full-generate-key. Then when prompted :
Please select what kind of key you want:
(1) RSA and RSA
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(9) ECC (sign and encrypt) *default*
(10) ECC (sign only)
Select (1) or (2) or (4).
Then export to file using:
gpg --output key.pub --armor --export <username>; gpg --output key.prv --armor --export-secret-key <username>;
We have to encrypt our data with HMAC-SHA256 that needs randomly generated salt. We are generating the salt this way:
public String generateSalt() throws Exception
{
KeyGenerator keyGen;
String salt = null;
try
{
keyGen = KeyGenerator.getInstance( "HmacSHA256" );
keyGen.init( 128 );
SecretKey key = keyGen.generateKey();
byte[] encodedKey = key.getEncoded();
salt = Base64.encodeBase64String( key.getEncoded() );
LOG.info( "Salt : " + salt );
}
catch ( NoSuchAlgorithmException )
{
e.printStackTrace();
throw e;
}
return salt;
}
According to our test this salt generation part is right. I have issue with the next part:
Now I have to write this salt in binary format in a file ( say named as pie_raw) and that's been done as:
private void writeToFile( byte[] saltBytes, String fileName ) throws FileNotFoundException, IOException
{
DataOutputStream out = new DataOutputStream( new FileOutputStream( enviro.getOutputFilePath()
+ fileName ) );
out.write( saltBytes );
out.close();
LOG.info( " Raw file created : " + enviro.getOutputFilePath() + fileName );
}
And then, I have to encrypt this salt with a supplied public RSA key in ".pem" and for Java implementation, the cipher will be "RSA/ECB/OAEPWithSHA1AndMGF1Padding". And finally the binary ciphertext should be written to a file named "pie_key". This part has been implemented this way:
private byte[] encryptSalt( String salt ) throws Exception
{
byte[] cipheredKey = null;
try
{
String keyString= readKeyFile( enviro.getPublicKeyFile() );
byte[] pem = pemToDer(keyString);
PublicKey publicKey = derToPublicKey(pem);
//PublicKey publicKey = getPublicKey( enviro.getPublicKeyFile() );
// Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
rsaCipher.init( Cipher.ENCRYPT_MODE, publicKey );
cipheredKey = rsaCipher.doFinal( salt.getBytes( "UTF-8" ) );//"UTF-8"
LOG.info( "Cyphered key : " + cipheredKey.toString() );
}
catch ( IOException | GeneralSecurityException e )
{
e.printStackTrace();
throw e;
}
return cipheredKey;
}
static String readKeyFile( String path )
throws IOException
{
String line = null;
try (BufferedReader br =
new BufferedReader( new FileReader( path ) ))
{
StringBuilder sb = new StringBuilder();
line = br.readLine();
while ( line != null )
{
sb.append( line );
sb.append( "\n" );
line = br.readLine();
}
return sb.toString();
}
}
public static byte[] pemToDer( String pemKey ) throws GeneralSecurityException
{
String[] parts = pemKey.split( "-----" );
return DatatypeConverter.parseBase64Binary( parts[ parts.length / 2 ] );
}
public static PublicKey derToPublicKey( byte[] asn1key ) throws GeneralSecurityException
{
X509EncodedKeySpec spec = new X509EncodedKeySpec( asn1key );
KeyFactory keyFactory = KeyFactory.getInstance( "RSA" );
return keyFactory.generatePublic( spec );
}
Writing this encrypted salt to a file named as "pie_key" in binary format by calling the "writeToFile" method above.
Now the content of the file "pie_key" should match the out put of the cmd :
openssl rsautl -encrypt -pubin -inkey wrap_pie_key_rsa.pem -oaep -in pie_key.raw -out pie_key
But whatever I tried ( you may find some signs of the ways, I tried ) did not work means that the final binary-encrypted-salt did not match with the output of openssl cmd.
Any idea what I am doing wrong?
I am using Java 7. And the .pem (partial) looks like
-----BEGIN PUBLIC KEY-----
MIIBIjANBgk345iG9w0BAQEFAA54328AMIIBCgKCAQEAt4GLJGPmvYdxwwAe59n3
.
.
.
.
7QIDNQAB
-----END PUBLIC KEY-----
Thanks in advance.
First of all, as Artjom already mentioned, the padding for OAEP or PKCS#1 v1.5 compatible padding is randomized. So even if you encrypt the same salt multiple times you would not get the same value. You can only decrypt the result to see if encryption succeeded.
Furthermore, you say you need a binary salt, but you first encode the salt to base64. It's unlikely that your encryption should contain an encoded salt. Maybe you need to encode the output of the encryption, not the salt.
The spurious encoding happens in the following line:
salt = Base64.encodeBase64String( key.getEncoded() );
Finally, although a new HMAC key generally consists of fully random bytes, I would say that it is not the right way to generate a salt. Instead just use:
SecureRandom rngForSalt = new SecureRandom();
byte[] salt = new byte[SALT_SIZE];
rngForSalt.nextBytes(salt);
Note too that the Bouncy Castle lightweight API (i.e. calling org.bouncycastle functionality directly) contains a PEM codec. No need to program or hack that yourself.
Try this Java 8 code. Bouncy castle provider classes required (no need to register the provider, this is just for the PEM handling).
package nl.maartenbodewes.stackoverflow;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
public class GenerateAndWrapHMACKey {
public static SecretKey generateHMACKey() throws Exception {
final KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("HMAC KeyGeneration should be available");
}
keyGen.init(128);
SecretKey key = keyGen.generateKey();
return key;
}
public static void writeToFile(SecretKey key, String filename)
throws IOException {
// file handling probably should be in a separate class
Files.write((new File(filename)).toPath(), key.getEncoded());
}
public static RSAPublicKey readRSAPublicKey(String filename) throws IOException, InvalidKeySpecException {
try (PemReader reader = new PemReader(new FileReader(filename))) {
PemObject pemObject = reader.readPemObject();
KeyFactory kf;
try {
kf = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("RSA key factory not available", e);
}
KeySpec keySpec = new X509EncodedKeySpec(pemObject.getContent());
try {
return (RSAPublicKey) kf.generatePublic(keySpec);
} catch (ClassCastException e) {
throw new InvalidKeySpecException("That's no RSA key", e);
}
}
}
public static byte[] wrapKey(Key key, RSAPublicKey wrappingKey) throws InvalidKeyException, IllegalBlockSizeException {
Cipher rsaWrapper;
try {
rsaWrapper = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
rsaWrapper.init(Cipher.WRAP_MODE, wrappingKey);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException("RSA OAEP should be available for RSA public key", e);
}
return rsaWrapper.wrap(key);
}
public static void main(String[] args) throws Exception {
// we need an RSA PEM key first I guess :)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024, new SecureRandom());
KeyPair kp = kpg.generateKeyPair();
String publicKeyFilename = "rsa_pub.pem";
try (PemWriter pemWriter = new PemWriter(new FileWriter(publicKeyFilename))) {
pemWriter.writeObject(new PemObject("PUBLIC KEY", kp.getPublic().getEncoded()));
}
RSAPublicKey wrappingRSAPublicKey = readRSAPublicKey(publicKeyFilename);
SecretKey hmacKey = generateHMACKey();
byte[] wrappedKey = wrapKey(hmacKey, wrappingRSAPublicKey);
System.out.println(Base64.getEncoder().encodeToString(wrappedKey));
}
}
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.