how to generate s3 style access/secret key using java - java

I am trying to generate access keys and secret keys in the same fashion as S3 using java but am having some trouble.
As a starting point I am looking at this bouncy castle example , I have this code up and running but am not sure of two things 1) how to set it up to use the same key generation as s3 which uses HMAC-SHA1 as outlined here and 2) how to get the friendly public/private key strings out for the the user.
You may have guessed I am new to java encryption and the bouncy castle libraries, however I did find JCEKeyGenerator.HMACSHA1 in the bc docs but am unable to find an example of its use. Any help would be greatly appreciated.
Thanks.

You'll need to make use of javax.crypto.KeyGenerator to create the AWSAccessKeyId and the AWSSecretAccessKey:
javax.crypto.KeyGenerator generator = javax.crypto.KeyGenerator.getInstance("HMACSHA1");
generator.init(120);
byte[] awsAccessKeyId = generator.generateKey().getEncoded();
generator.init(240);
byte[] awsSecretAccessKey = generator.generateKey().getEncoded();
Then, you'll want to base64 encode the bytes (this uses MimeUtility from mail.jar):
final ByteArrayOutputStream encoded = new ByteArrayOutputStream();
final OutputStream encoder = javax.mail.internet.MimeUtility.encode(encoded, "base64");
encoder.write(awsAccessKeyId);
encoder.flush();
encoder.close();
String accessKeyId = new String(encoded.toByteArray(), encoding).replaceAll("[\\r\\n]", "");

Related

GOST 3411 Algorithm using Bouncy Castle

Am using GOST algorithm to create a hash based on key and input using Bouncy castle libs. Below code generates Hash that doesn't match with the one generated through online tools such as http://beautifytools.com/hmac-generator.php
HMac gMac= new HMac(new GOST3411Digest());
byte[] key = "keyphrase".getBytes();
byte[] input = "macinput".getBytes();
gMac.init(new KeyParameter(key));
gMac.update(input, 0, input.length);
byte[] mac = new byte[gMac.getMacSize()];
gMac.doFinal(mac, 0);
BigInteger in = new BigInteger(1, mac);
System.out.println(in.toString(16));
When this works, Need to use inside Mulesoft to call API which validates the Hash and gives authentication. Basically, The encyption is for generation of authentication token.
Have been referring to stackoverflow for a long time but never had an opportunity to post any questions. This is my first :)
Thanks,
Sudeep
GOST3411 algorithm has several SBox options, so try using different upon initialization. I had some luck with "D-Test" SBox, it matches with most if not all online tools. You can initialize different SBox like this:
HMac mac = new HMac(new GOST3411Digest(GOST28147Engine.getSBox("D-Test")));

Signing a message digest using BouncyCastle

At the moment in C# I'm signing a challenge like this:
RSACryptoServiceProvider rsa;
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(rsa);
RSAFormatter.SetHashAlgorithm("SHA1");
byte[] SignedHash = RSAFormatter.CreateSignature(paramDataToSign);
Then I give the SignedHash to Windows, it accepts it and everything is OK. But I need to move this part to Android and there's the problem, that I just can't get the same signed hash value.
In Android I tried to make the signed hash but they differ from the one generated in C#.
Signature signer = Signature.getInstance("SHA1withRSA", "BC");
signer.initSign(privateKey);
signer.update(paramDataToSign);
signer.sign();
In C# - using the following piece of code - I get the same result as in Android, but it is not an option cause then Windows does not accept the signed hash.
ISigner signer = SignerUtilities.GetSigner("SHA1withRSA");
signer.Init(true, privateKey);
signer.BlockUpdate(paramDataToSign, 0, paramDataToSign.Length);
signer.GenerateSignature();
Here's written that C# PKCS1SignatureFormatter and Java Signature should give the same result, but they do not. http://www.jensign.com/JavaScience/dotnet/VerifySig/
What could be the problem?
Here are the base 64 (WebSafe) values that I get:
Challenge = zHyz12Tk4m151nssYIBWqBCAxhQ
RSAPKCS1SignatureFormatter SignedHash = kmu39keplCAV4Qnu22wdprLz4nGSsrVtHbxQ5YMUG7p-0YwReCG4ROIlFvYs4CGfjCiAGFPw4PLrLx7mrlAA6iuhJMkgm_PMTW9alQYTH612hLEUP4EmK0M2kw8CveLcjI3HA08z8bByllIzRyAlM8bcR438vw2uhx_CbgvOOHn8vwBPnvWbFqpi2doYoq2xEuFBRe7eBPrxbMRqEd3ExdQ9c9rYT4ivOJ4pbioyi6D5i5_1crvGwM6nQanMZCmooRYJO65NP3B4wWnvQZpJLRD0U08wWcvyGBFWp188ZovDjnkTQZku6lzmwGXfqQwtBz9uNvLcTbp7cVyt5EyQxw
Signature and ISigner SignedHash = Vt-b5QfGPnSPpZuIB8-H4N1K5hQXpImS4e8k56_HruDSqy3DLsz96QKUrccshjr1z9nTK3Mwvd5yPdyTJOqSUcDQqxV46LPhWQNsubqKxAz97ePpeslIH1gHdnzkh46ixsWqgDrhR7egQtDkU8PPsph1qahCxaVkRYspQBV0jPZ-LK4EjoGGnuWTCihVKjruXJZ2VY8yZ9QRAsHVptr0Nv-mldO2MFK-oEVbtVbHqUPf5So8im3oRSm68OqY4g56bCdFNSbhcFBjrZ1QPjnxiIk43-_5tevafqoOB2D_E_mQHCJwmRg3MrNij6IdAdloCejnhCWzgMHdcG1Ug_Qmig
EDIT:
So the simplest solution is using Bouncy Castle API:
AsymmetricBlockCipher rsaEngine = new PKCS1Encoding(new RSABlindedEngine());
rsaEngine.init(true, privateKey);
DigestInfo dInfo = new DigestInfo(new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, DERNull.INSTANCE), paramDataToSign);
byte[] digestInfo = dInfo.getEncoded(ASN1Encoding.DER);
rsaEngine.processBlock(digestInfo, 0, digestInfo.length);
The problem is that RSAFormatter.CreateSignature(paramDataToSign); passes the hash value, while signer.update(paramDataToSign); passes the data before it is hashed. So it is likely that you have to remove a MessageDigest calculation for your Java code for this to work.
Alternatively, if you only have the hash value, you may have a look into the Bouncy Castle lightweight API to find a method that accepts a value that is pre-hashed. This can probably be performed using new RSADigestSigner(new StaticDigest(paramDataToSign, "SHA-1")).generateSignature().
Problem is that StaticDigest does not exist, so you'll have to comment here if you really require it. Alternative, mirror the implementation of RSADigestSigner but substitute a pre-calculated hash.

How to encode and decode SecretKey in JAVA?

KeyStore ks = KeyStore.getInstance("JCEKS");
ks.load(null, null);
SecretKey skInput = new SecretKeySpec("input".getBytes(), "DESede");
SecretKeyEntry skeInput = new KeyStore.SecretKeyEntry(skInput);
ks.setEntry("input_key", skeInput, new KeyStore.PasswordProtection("banana".toCharArray()));
FileOutputStream fos = new FileOutputStream("my.keystore");
pambks.store(fos, "password".toCharArray());
fos.flush();
fos.close();
The code shown above is trying to encode the input into a SecretKey and store into keystore. The code shown below is how I retrieve the key from keystore. But I do not know how to decode it back to original value?
FileInputStream fin = new FileInputStream("my.keystore");
KeyStore ks = KeyStore.getInstance("JCEKS");
ks.load(fin, "password".toCharArray());
SecretKeyEntry ske = (SecretKeyEntry) readks.getEntry("input_key", new KeyStore.PasswordProtection("banana".toCharArray()));
SecretKey sk = ske.getSecretKey();
I wasn't sure whether this is the correct way of doing encryption and decryption to SecretKey, please correct me if I'm wrong.
Reading back this Q/A I think I've misread the question.
You can get the byte representation of the key back by calling the getEncoded method on you key. After that it is possible to use one of the String constructors to revert it back to text. As said, you should not use a string as key. Note that DES keys are contain parity in the last bit, so this may change the result. To use strings for keys it is more advisable to use hexadecimals. Note that keys should have enough entropy, and a string is not likely to provide that.
There are several things not entirely correct in the code above:
You should not (ever) use "input".getBytes(). First of all, getBytes() uses a platform specific default character encoding. If you want to use a DES key as a String, decode it with a decoder of hexadecimals (e.g. in Apache Commons codec or Bouncy Castle).
You should use 24 bytes for DESede keys. "input".getBytes() does not return enough bytes.
For DES keys it pays to put the key specification through a KeyFactory if only to make sure that the parity bits are set. They you will be sure that the key is valid.
To get a good amount of key data, use PBKDF2 for passwords or use randomly generated keys.
If an encrypted key store is good enough storage for an encrypted key depends on the use case and the other security measures.

Amazon S3 Policy Signing in Java

For some reason, I'm struggling with the signature generation for my Amazon S3 upload policy. I swear I had this working at one point but no longer. Any help would be much appreciated. I need a fresh set of eyes.
When comparing to the output from Amazon S3 Signature Tester, I am not getting the same signature. However, when I directly use the signature coming out of that tool, everything works fine. So the issue is definitely in my signing process. Also, the "String to be signed" hex-decoded coming out of that tool is identical to my input policy being signed.
The AWS docs say the process for constructing a policy signature should go like this:
Encode the policy using UTF-8.
Encode those UTF-8 bytes using Base64.
Sign the policy with your Secret Access Key using HMAC SHA-1.
Encode the SHA-1 signature using Base64.
Seems straight-forward enough. The only place for ambiguity might be in #3. The AWS docs show a sample snippet for generating HMAC-SHA1 and this is consistent with other Java cryptography examples I've seen.
I'm using v1.6 of Apache Commons implementation of Base64. My signing code basically looks like this:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
/* ... */
private static final String UTF8 = "UTF-8";
private static final String HMACSHA1 = "HmacSHA1";
public static String sign(String secret, String data) {
byte[] dataBytes = data.getBytes(UTF8);
byte[] secretBytes = secret.getBytes(UTF8);
SecretKeySpec signingKey = new SecretKeySpec(secretBytes, HMACSHA1);
Mac mac = Mac.getInstance(HMACSHA1);
mac.init(signingKey);
byte[] signature = mac.doFinal(dataBytes);
return Base64.encodeBase64String(signature);
}
And then my usage of this signing looks like:
String signature = sign(
/* AWS Secret Access Key copied directly out of the AWS Console */,
/* policy properly serialized as JSON */);
Okay, I found it. Apparently today I've been effectively skipping step #2. I did encode the policy JSON as Base64 but then I am directly signing the JSON string not the Base64 string.
Step #3 should probably be reworded to "Sign the Base64 policy with your Secret Access Key using HMAC SHA-1."
I guess I'll leave this up in case anyone else comes across a similar issue.
Now, this procedure is officially supported.
http://aws.amazon.com/articles/1434
import sun.misc.BASE64Encoder;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
String policy = (new BASE64Encoder()).encode(
policy_document.getBytes("UTF-8")).replaceAll("\n","").replaceAll("\r","");
Mac hmac = Mac.getInstance("HmacSHA1");
hmac.init(new SecretKeySpec(
aws_secret_key.getBytes("UTF-8"), "HmacSHA1"));
String signature = (new BASE64Encoder()).encode(
hmac.doFinal(policy.getBytes("UTF-8")))
.replaceAll("\n", "");
*Beware the window implementation for this example as some found problem from the comments of the post and solution to the problem was also provided there.
The result can be verified by this
http://s3.amazonaws.com/doc/s3-example-code/post/post_sample.html
However, some said this "org.apache.commons.codec.binary.Base64" is better because of this.
http://www.asgarli.net/2011/03/replacing-sunmiscbase64encoder-and.html
String policy_document =
"{\"expiration\": \"2009-01-01T00:00:00Z\"," +
"\"conditions\": [" +
"{\"bucket\": \"s3-bucket\"}," +
"[\"starts-with\", \"$key\", \"uploads/\"]," +
"{\"acl\": \"private\"}," +
"{\"success_action_redirect\": \"http://localhost/\"}," +
"[\"starts-with\", \"$Content-Type\", \"\"]," +
"[\"content-length-range\", 0, 1048576]" +
"]" +
"}";
// Calculate policy and signature values from the given policy document and AWS credentials.
String policy = new String(
Base64.encodeBase64(policy_document.getBytes("UTF-8")), "ASCII");
Mac hmac = Mac.getInstance("HmacSHA1");
hmac.init(new SecretKeySpec(
aws_secret_key.getBytes("UTF-8"), "HmacSHA1"));
String signature = new String(
Base64.encodeBase64(hmac.doFinal(policy.getBytes("UTF-8"))), "ASCII");

IVParameter Spec Problem in Java

I am currently devloping a Windows application using C# and looking at developing a mobile app using Java.
The windows software and the mobile app will work together, the windows app will store information and encrypt certain information before storing it in an online database.
The mobile app will pull the information from the online database and will need to decrypt the encrypted string that is retrieved from the datbase.
The encryption method I am using in C# is below
byte[] clearTextBytes = Encoding.UTF8.GetBytes(encryptionString);
SymmetricAlgorithm rijn = SymmetricAlgorithm.Create();
MemoryStream ms = new MemoryStream();
byte[] rgbIV = Encoding.ASCII.GetBytes("ryojvlzmdalyglrj");
byte[] key = Encoding.ASCII.GetBytes("hcxilkqbbhczfeultgbskdmaunivmfuo");
CryptoStream cs = new CryptoStream(ms, rijn.CreateEncryptor(key, rgbIV), CryptoStreamMode.Write);
cs.Write(clearTextBytes, 0, clearTextBytes.Length);
cs.Close();
return Convert.ToBase64String(ms.ToArray());
The Windows method works fine.
The code I am using in Java is as follows:
KeySpec ks = new DESKeySpec("hcxilkqbbhczfeultgbskdmaunivmfuo".getBytes("UTF-8"));
SecretKey key = SecretKeyFactory.getInstance("DES").generateSecret(ks);
String ivString = "ryojvlzmdalyglrj";
byte[] ivByte = ivString.getBytes("UTF-8");
IvParameterSpec iv = new IvParameterSpec(ivByte);
//RC5ParameterSpec iv = new RC5ParameterSpec(ivByte);
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encoded = cipher.doFinal(Base64.decodeBase64("iNtaFme3B/e6DppNSp9QLg=="));
Log.d("Decoded Password", encoded.toString());
As both methods need to encrypt and decrypt the same string together it has to use the same key and IV. The only problem that I am having is in the java method the IVParameterSpec is going into the catch with an error that says IV must be 8 bytes long.
How can I resolve this to ensure that I have the same IV as C#.
Thanks for the help
The problem is that you are encrypting (in C#) with AES (also known as Rjindael), but trying to decrypt in Java with DES. If you change your Java code to use AES then it should all work fine.
DES uses an 8-byte IV because it works on 64-bit blocks. AES uses a 16-byte IV because it works on 128-bit blocks.
You should also make sure you use the same character encoding. In C# you are using ASCII, but in java you're using UTF-8. In your case they will be the same, but you should really fix it now to prevent strange bugs in future. You can change the character set name in Java to "US-ASCII" and it'll work.
You have to use the same algorithm of course. The default algorithm is for .NET is AES, so that is what you should be using on the Java side as well.

Categories