When I invoke API endpoints from REST client, I got error by concerning with Signature.
Request:
Host: https://xxx.execute-api.ap-southeast-1.amazonaws.com/latest/api/name
Authorization: AWS4-HMAC-SHA256 Credential={AWSKEY}/20160314/ap-southeast-1/execute-api/aws4_request,SignedHeaders=host;range;x-amz-date,Signature={signature}
X-Amz-Date: 20160314T102915Z
Response:
{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details. The Canonical String for this request should have been 'xxx' "
}
From Java code, I followed AWS reference of how to generate Signature.
String secretKey = "{mysecretkey}";
String dateStamp = "20160314";
String regionName = "ap-southeast-1";
String serviceName = "execute-api";
byte[] signature = getSignatureKey(secretKey, dateStamp, regionName, serviceName);
System.out.println("Signature : " + Hex.encodeHexString(signature));
static byte[] HmacSHA256(String data, byte[] key) throws Exception {
String algorithm="HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data.getBytes("UTF8"));
}
static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
byte[] kSecret = ("AWS4" + key).getBytes("UTF8");
byte[] kDate = HmacSHA256(dateStamp, kSecret);
byte[] kRegion = HmacSHA256(regionName, kDate);
byte[] kService = HmacSHA256(serviceName, kRegion);
byte[] kSigning = HmacSHA256("aws4_request", kService);
return kSigning;
}
May I know what I was wrong while generating Signature?
Reference how to generate Signature : http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java
You can use classes from aws-java-sdk-core: https://github.com/aws/aws-sdk-java/tree/master/aws-java-sdk-core
More specifically, Request, Aws4Signer and a few other ones:
//Instantiate the request
Request<Void> request = new DefaultRequest<Void>("es"); //Request to ElasticSearch
request.setHttpMethod(HttpMethodName.GET);
request.setEndpoint(URI.create("http://..."));
//Sign it...
AWS4Signer signer = new AWS4Signer();
signer.setRegionName("...");
signer.setServiceName(request.getServiceName());
signer.sign(request, new AwsCredentialsFromSystem());
//Execute it and get the response...
Response<String> rsp = new AmazonHttpClient(new ClientConfiguration())
.requestExecutionBuilder()
.executionContext(new ExecutionContext(true))
.request(request)
.errorResponseHandler(new SimpleAwsErrorHandler())
.execute(new SimpleResponseHandler<String>());
If you want a cleaner design, you can use the Decorator pattern to compose some elegant classes and hide the above mess. An example for that here: http://www.amihaiemil.com/2017/02/18/decorators-with-tunnels.html
From the code example above it looks like you are not creating a canonical request and including it in the string that gets signed as per http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
Instead of implementing this yourself have you looked at using a third-party library.
aws-v4-signer-java is a lightweight, zero-dependency library that makes it easy to generate AWS V4 signatures.
String contentSha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
HttpRequest request = new HttpRequest("GET", new URI("https://examplebucket.s3.amazonaws.com?max-keys=2&prefix=J"));
String signature = Signer.builder()
.awsCredentials(new AwsCredentials(ACCESS_KEY, SECRET_KEY))
.header("Host", "examplebucket.s3.amazonaws.com")
.header("x-amz-date", "20130524T000000Z")
.header("x-amz-content-sha256", contentSha256)
.buildS3(request, contentSha256)
.getSignature();
Disclaimer: I'm the libraries author.
This is possible using 100% java libraries without additional dependencies, just use the query parameters generated here:
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Formatter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
...
private static final String ACCESS_KEY = "...";
private static final String SECRET_KEY = "...";
private static final int expiresTime = 1 * 24 * 60 * 60;
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
public void sign(String protocol, String bucketName, String contentPath) throws Exception {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR_OF_DAY, 24);
String host = bucketName + ".s3-us-west-2.amazonaws.com";
long expireTime = cal.getTimeInMillis() / 1000;
String signString = "GET\n" +
"\n" +
"\n" +
expireTime + "\n" +
"/" + bucketName + contentPath;
SecretKeySpec signingKey = new SecretKeySpec(SECRET_KEY.getBytes(), HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
String signature = URLEncoder.encode(new String(Base64.getEncoder().encode(mac.doFinal(signString.getBytes()))));
System.out.println(signature);
String fullPayload = "?AWSAccessKeyId=" + ACCESS_KEY +
"&Expires=" + expireTime +
"&Signature=" + signature;
System.out.println(protocol + "://" + host + contentPath + fullPayload);
}
...
The signing process is lengthy and error-prone, here are some tips
Make sure your access key and secret is correct, try to use Postman to test the request at first, it's easy and fast, see Use Postman to Call a REST API
Make sure you use UTC time
The signing process uses both timestamp(YYYYMMDD'T'HHMMSS'Z') and datetime(YYYYMMDD), so double check your implementation for that
Use any online hash tool to verify your hash algorithm behaves as expected
Read the python implementation carefully, see Examples of the Complete Version 4 Signing Process (Python)
See my fully working java implementation on Github - A Java(SpringBoot) template for Java and AWS SageMaker DeepAR model endpoint invocation integration
You may investigate code samples that is shared by AWS web site. I used some of the util classes and a few java class I need. So you don't have to use all classes and other stuff. I left the link below.
AWS Java Samples in doc of Amazon
For me, in Java, the following code worked to generate a signed request to sent to web socket client via api gateway -
Request<Void> request = new DefaultRequest<Void>("execute-api"); //Request to API gateway
request.setHttpMethod(HttpMethodName.POST);
request.setEndpoint(URI.create(url));
String bodyContnt= "test data";
InputStream targetStream = new ByteArrayInputStream(bodyContnt.getBytes());
request.setContent(targetStream);
//Sign it...
AWS4Signer signer = new AWS4Signer();
signer.setRegionName("ap-south-1");
signer.setServiceName(request.getServiceName());
signer.sign(request, new Creds());
signer.setOverrideDate(new Date()); // needed as current ts is required
//Execute it and get the response...
Response<String> rsp = new AmazonHttpClient(new ClientConfiguration())
.requestExecutionBuilder()
.executionContext(new ExecutionContext(true))
.request(request)
.errorResponseHandler(new SimpleAwsErrorHandler(true))
.execute(new SimpleResponseHandler());
Related
I'm new to JMeter & Java and now writing Authorization script for testing API.
I had some troubles with updating variable with vars.put(key,value)
Here is my code example:
import java.util.Arrays;
import java.security.MessageDigest;
import java.util.Base64;
public class StringToByte {
public void main(String[] args) {
String str_salt = "${salt}";
byte[] b_salt = str_salt.getBytes();
String str_pass = "c3000Hub";
byte[] b_pass = str_pass.getBytes();
byte[] b_pass_hash = new byte[b_salt.length + b_pass.length];
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(b_pass_hash);
String pass_hash = Base64.getEncoder().encodeToString(hash);
vars.put("passhash", pass_hash);
}
}
Variable in User Defined Variables just not updating and I've got no idea why?
You need to explicitly call this main() function in order to get it working, you declare it but I fail to see where it's being invoked
Change String str_salt = "${salt}"; to String str_salt = vars.get("salt");, as per JSR223 Sampler Documentation:
The JSR223 test elements have a feature (compilation) that can significantly increase performance. To benefit from this feature:
Use Script files instead of inlining them. This will make JMeter compile them if this feature is available on ScriptEngine and cache them.
Or Use Script Text and check Cache compiled script if available property.
When using this feature, ensure your script code does not use JMeter variables or JMeter function calls directly in script code as caching would only cache first replacement. Instead use script parameters.
Suggested code change (if you want to keep this class/method approach):
import org.apache.jmeter.threads.JMeterVariables
import java.security.MessageDigest
public class StringToByte {
public void main(JMeterVariables vars) {
String str_salt = vars.get("salt");
byte[] b_salt = str_salt.getBytes();
String str_pass = "c3000Hub";
byte[] b_pass = str_pass.getBytes();
byte[] b_pass_hash = new byte[b_salt.length + b_pass.length];
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(b_pass_hash);
String pass_hash = Base64.getEncoder().encodeToString(hash);
vars.put("passhash", pass_hash);
}
}
new StringToByte().main(vars)
or you can just do something like:
import java.security.MessageDigest
String str_salt = vars.get("salt");
byte[] b_salt = str_salt.getBytes();
String str_pass = "c3000Hub";
byte[] b_pass = str_pass.getBytes();
byte[] b_pass_hash = new byte[b_salt.length + b_pass.length];
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(b_pass_hash);
String pass_hash = Base64.getEncoder().encodeToString(hash);
vars.put("passhash", pass_hash);
More information on Groovy scripting in JMeter: Apache Groovy - Why and How You Should Use It
I have a vendor that I wish to exchange data with. They want me to take the username and password that they gave me and use it on an Authorization header for a get request.
And now my dirty little secret. I've never created an Authorization header before.
So I do a bunch of research and figure out the following code.
public static final String AUTH_SEPARATOR = ":";
private static final String AUTH_TYPE = "Basic ";
public static final String HEADER_AUTHORIZATION = "Authorization";
public static void addAuthHeader(Map<String, String> headers, String user, String password) {
String secretKey = user
+ AUTH_SEPARATOR
+ password;
byte[] tokenBytes = secretKey.getBytes();
String token64 = Base64.getEncoder().encodeToString(tokenBytes);
String auth = AUTH_TYPE + token64;
headers.put(HEADER_AUTHORIZATION, auth);
}
I run it and I get no response back. Hmmm. So I go to open a support request with them and I want to create an example, so I open postman and use the APIs they gave me for postman. First, I run the one for the API I'm replicating and it works. Hmm.
So then I modify that API and use my username and password instead of the one included in the example and it works fine. Crikey!
So I bang around a bit and notice that the Base64 string in the auth created by postman is slightly different at the end than the one I created.
So, back to the research and all the code I find looks a lot like mine, although I had to update it some because of version differences. The string is still different and now I'm asking for help. Surely someone has solved this problem.
String from postman "Basic THVKZ...FvTg=="
String from code above "Basic THVKZ...FvTiA="
How did I do something wrong and end up with only a three byte difference?
Tg== is base64 for N.
TiA= is base64 for N (as in, N, then a space).
Sooo, it sounds like postman is sticking a space up there and you aren't. Hopefully, if you add a space at the very end of whatever you are base64 encoding, you should get the exact same string as postman is giving you, and, hopefully, it'll all just work out at that point :)
Postman using UTF-8 for basic auth encoding, check from https://github.com/postmanlabs/postman-app-support/issues/4070
change your code like this
secretKey.getBytes(StandardCharsets.UTF_8);
https://www.base64encode.org/ for test
The problem is caused by padding. I still don't understand exactly why, but the string I'm encoding is 49 bytes long, which is not evenly divisible by 3, which means that padding comes into play.
When I changed my code to the following:
String token64 = Base64.getEncoder().withoutPadding().encodeToString(tokenBytes);
and then ran it, I got the same string minus the two == at the end that base64 uses as a pad character. Sending that to the server got the answer I was looking for.
Why do they call it software when it's so damned hard?
Esteemed developer, are they not running on OAuth2? If so, kindly make use of the code blocks below and kill it
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Bearer " + token you are either generating or getting from Eureka discovery service);
return headers;
}
AuthResponseObjectType getAuthorization() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
String auth = basicAuthUsername + ":" + basicAuthPassword;
byte[] encodedAuth = org.apache.commons.net.util.Base64.encodeBase64(auth.getBytes(StandardCharsets.US_ASCII));
headers.add("Authorization", "Basic " + new String(encodedAuth));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", grantType);
map.add("username", username);
map.add("password", password);
map.add("scope", scope);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return restTemplate.exchange(authUrl, HttpMethod.POST, request, AuthResponseObjectType.class).getBody();
}
Let me know if you are facing any challenges then we can do it together
Trying to get a signed url for downloading a file in Google storage is problematic. Getting SignatureDoesNotMatch Error.
private String signString(GoogleCredential credential, String stringToSign) throws Exception {
// sign data
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(credential.getServiceAccountPrivateKey());
signer.update(stringToSign.getBytes("UTF-8"));
byte[] rawSignature = signer.sign();
return new String(org.apache.commons.codec.binary.Base64.encodeBase64(rawSignature, false), "UTF-8");
}
//Here is the code to get signed url
private getFileUrl(GoogleCredential credential,String bucketName, String filePath) {
String signedParam = signString(credential, "GET\n\n\n"+expiration+"\n"+bucketName+"/"+filePath);
// construct URL
String url = "http://storage.googleapis.com/" + bucketName + "/" + filePath +
"?GoogleAccessId=" + credential.getServiceAccountId() +
"&Expires=" + expiration +
"&Signature=" + URLEncoder.encode(signedParam, "UTF-8");
return url;
}
Am I missing anything here? Struggling with this for a while. Any help is much appreciated.
Thanks.
The bucket name in the GET path of the signed url should be preceeded by "/". This resolved the issue.
String signedParam = signString(credential, "GET\n\n\n"+expiration+"\n/"+bucketName+"/"+filePath);
I'm working on this project in which I'm using a Google-App-Engine backend connected to an Android app via Google-Cloud-Endpoints. For Google-Cloud-Datastore access I'm using Objectify and everything works fine.
Now I decided to add the functionality to upload images to Google-Cloud-Storage but I couldn't find a clear explanation on how to do this using the Google-Cloud-Endpoints setup.
I found the following explanation how to use Google-Cloud-Storage with Google-App-Engine:
https://cloud.google.com/appengine/docs/java/googlecloudstorageclient/app-engine-cloud-storage-sample
but instead of adding it to the Endpoints Api the article writes an additional servlet.
Furthermore I found this example of upload/download for Android:
github.com /thorrism/GoogleCloudExample
Sadly this is using the Google Cloud Storage API for direct access to the Google-Cloud-Storage and you need to add a P12-file to the asset folder, which seems unsecure.
My Google-App-Engine code looks like that:
#Api(
name = "example",
version = "v1",
scopes = { Constants.EMAIL_SCOPE },
clientIds = { Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID, Constants.API_EXPLORER_CLIENT_ID },
audiences = {Constants.ANDROID_AUDIENCE},
description = "API for the Example Backend application."
)
public class ExampleApi{
#ApiMethod(name = "doSomething", path = "dosomething", httpMethod = HttpMethod.POST)
public String doSomething(#Named("text") String text){
TestEntity test = new TestEntity(text);
ofy().save().entity(test).now();
return test;
}
After I uploaded it I generated the Endpoints Client Library and imported it into my android project.
Then I'm calling Endpoints from Android like explained here:
https://cloud.google.com/appengine/docs/java/endpoints/calling-from-android#creating_the_service_object
public static com.appspot.******.example.Example buildServiceHandler(Context context, String email) {
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(
context, AppConstants.AUDIENCE);
credential.setSelectedAccountName(email);
com.appspot.******.example.Example.Builder builder = new com.appspot.******.example.Example.Builder(
AppConstants.HTTP_TRANSPORT,
AppConstants.JSON_FACTORY, null);
builder.setApplicationName("example-server");
return builder.build();
}
sApiServiceHandler = buildServiceHandlerWithAuth(context,email);
And each Api-Method I call like this:
com.appspot.******.example.Example.DoSomething doSomething = sApiServiceHandler.doSomething(someString);
doSomething.execute();
All of this works fine, but only for storing/receiving Datastore Entities. How would I go about uploading/downloading files to Google Cloud Storage using the Google Cloud Endpoints setup?
Is it somehow possible to send a POST with my image data via Endpoints to the UploadServlet using the already build ServiceHandler ?
Is it possible to call a servlet from an Endpoints Method? How am I supposed to send the Post to the Servlet and how would I go about the authentication?
Any help or advice would be greatly appreciated!
There are different ways to do this, but the most recommended way is to use Signed URLs, so that your Android app can upload the file securely to Google Cloud Storage directly, without going through your Endpoints backend. The basic process is:
1) Create an Endpoints method that creates a new signed URL and returns it to the Android client. Signing the URL on the server still requires a P12 key but is stored on App Engine, not on the client, so is secure. Try to use a short expiration for the URL, for example no more than 5 minutes.
2) Have the Android client upload the file directly to the signed URL, as you would doing a normal HTTP PUT to the Cloud Storage XML API to upload a file (resumable uploads with the JSON API are also supported, but not covered here).
Your Endpoints method might look like this:
#ApiMethod(name = "getUploadUrl", path = "getuploadurl", httpMethod = HttpMethod.GET)
public MyApiResponse getUploadUrl(#Named("fileName") String fileName
#Named("contentType" String contentType)
{
String stringToSign
= "PUT\n" + contentType
+ "\n" + EXPIRATION_TIMESTAMP_EPOCH_SECONDS + "\n"
+ YOUR_GCS_BUCKET + "/" + fileName;
// Load P12 key
FileInputStream fileInputStream = new FileInputStream(PATH_TO_P12_KEY);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(fileInputStream, password);
PrivateKey key = keyStore.getKey(privatekey", YOUR_P12_KEY_PASSWORD);
// Get signature
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(key);
signer.update(stringToSign.getBytes("UTF-8"));
byte[] rawSignature = signer.sign();
String signature = new String(Base64.encodeBase64(rawSignature, false), "UTF-8");
// Construct signed url
String url
= "http://storage.googleapis.com/" + YOUR_GCS_BUCKET + fileName
+ "?GoogleAccessId=" + P12_KEY_SERVICE_ACCOUNT_CLIENT_ID
+ "&Expires=" + EXPIRATION_TIMESTAMP_EPOCH_SECONDS
+ "&Signature=" + URLEncoder.encode(signature, "UTF-8");
// Endpoints doesn't let you return 'String' directly
MyApiResponse response = new MyApiResponse();
response.setString(url);
return response;
}
On the Android side, you might use the method like this:
// Get the upload URL from the API
getUploadUrl = sApiServiceHandler.getUploadUrl(fileName, contentType);
MyApiResponse response = getUploadUrl.execute();
String uploadUrl = response.getString();
// Open connection to GCS
URL url = new URL(uploadUrl);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setDoOutput(true);
httpConnection.setRequestMethod("PUT");
httpConnection.setRequestProperty("Content-Type", contentType);
// Write file data
OutputStreamWriter out = new OutputStreamWriter(httpConnection.getOutputStream());
out.write(fileData);
out.flush();
// Get response, check status code etc.
InputStreamReader in = new InputStreamReader(httpConnection.getInputStream());
// ...
(Disclaimer: I'm just typing code freely into a text editor but not actually testing it, but it should be enough to give you a general idea.)
I recently got the book "Pro Paypal E-Commerce" by Damon Williams. Its a 2007 copy, so its to be expected that some things, like the code, would change over time.
I'm trying to get this code below to work. I downloaded the paypal_base.jar file and also the paypal_wpstoolkit.jar and put them into my lib folder under jakarta-tomcat (where all my other jars are). I'm having trouble compiling the code.
This code example comes from the book and also http://en.csharp-online.net/Encrypted_Website_Payments%E2%80%94Using_the_PayPal_Java_SDK
I modified it slightly.
import com.paypal.sdk.profiles.EWPProfile;
import com.paypal.sdk.profiles.ProfileFactory;
import com.paypal.wpstoolkit.services.EWPServices;
import com.paypal.sdk.exceptions.PayPalException;
public class PaypalTest {
// path to your PKCS12 file
public static final String PKCS12 = "./Certs/my_pkcs12.p12";
// path to PayPal's public certificate
public static final String PAYPAL_CERT = "./Certs/paypal_cert_pem.txt";
// use https://www.sandbox.paypal.com if testing
//public static final String URL = "https://www.paypal.com";
public static final String URL = "https://sandbox.paypal.com";
public static void main (String args[]) {
// Check to see if the user provided a password
if (args.length != 1) {
System.out.println("You must provide a password.");
System.exit(0);
}
// password used to encrypt your PKCS12 files
// obtained from the command line
String USER_PASSWORD = args[0];
// First we will create the EWPProfile object
try {
com.paypal.sdk.profiles.EWPProfile ewpProfile = ProfileFactory.createEWPProfile();
ewpProfile.setCertificateFile(PKCS12);
ewpProfile.setPayPalCertificateFile(PAYPAL_CERT);
ewpProfile.setPrivateKeyPassword(USER_PASSWORD);
ewpProfile.setUrl(URL);
String buttonParameters = "cmd=_xclick\nbusiness=buyer#hotmail.com\nitem_name=vase\nitemprice=25.00";
// Next we will create the EWPServices object
// and tell it which EWPProfile object to use
EWPServices ewpServices = new EWPServices();
ewpServices.setEWPProfile(ewpProfile);
// Finally we are ready to call the method to perform the button encryption
String encryptedButton = ewpServices.encryptButton(buttonParameters.getBytes());
System.out.println(encryptedButton);
} catch (PayPalException ppe) {
System.out.println("An exception occurred when creating the button.");
ppe.printStackTrace();
}
}
}//class
The errors I'm getting during compilation are as follows -
java:51: cannot find symbol
symbol: method setEWPProfile(com.paypal.sdk.profiles.EWPProfile)
location: class com.paypal.wpstoolkit.services.EWPServices
ewpServices.setEWPProfile(ewpProfile);
java:55: encryptButton(byte[],java.lang.String,java.lang.String.,java.lang.String.,java.lang.String) in com.paypal.wpstoolkit.services.EWPServices cannot be applied to (byte[])
ewpServices.encryptButton(buttonParameters.getBytes());
The paypal_base jar only has NVPCallerServices.class in it, and not EWPServices. EWPServices is in the wpstoolkit jar.
How do I fix my errors? I'm having trouble finding documentation on the paypal classes.
The updated Java SDK + API documenatation can be found here:
https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_Java_NVP_SDK.zip
Extract that .zip and open docs/index.html
That is where you can find all of the API documentation. It looks like you are trying to make calls to methods that no longer exist. Have a look through the new classes and see what will work for you.
Looks like with the newer API's Paypal want you to have all button code generated from their web service, as they seem to have removed the EWPService class from the SDK. But then I noticed they still provide the client side utility with with you can manually generate the code here. After a little tweaking, I got the code in there to do what I needed (encrypt an upload cart button locally).
Assuming you use Java 5+, just make sure you this and this in your classpath. Now the code isn't perfect, as it contains a bunch of deprecated methods, but for such a trivial task as encrypting the button code, it works just fine.
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<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
keyAlias = 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<X509Certificate> certList = new ArrayList<X509Certificate>();
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();
}
An easier way to do this is not to encrypt, but use an unencrypted button and then a hash trick to detect tampering. I explain this here with PHP, but you can translate to Java.
How do I make a PayPal encrypted buy now button with custom fields?