I'm trying to write Ruby code that copies functionality of Java code that I received as a working example. I believe I got most of it, but in the end when I'm trying to verify Signature with server, I'm getting this:
IAM Exception: IAM-1000:Signature length not correct: got 256 but was expecting 384
Here is the Java example:
import java.io.ObjectStreamException;
import java.security.KeyRep;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.commons.codec.binary.Base64;
public class SignatureGenerator {
public static void main(String[] args) {
SignatureGenerator generator = new SignatureGenerator();
String consumerId = "xxxxxx";
String priviateKeyVersion = "1";
String privateKey = ""; // omitted for security reasons
long intimestamp = System.currentTimeMillis();
System.out.println("consumerId: " + consumerId);
System.out.println("intimestamp: " + intimestamp);
Map<String, String> map = new HashMap<>();
map.put("AA_CONSUMER.ID", consumerId);
map.put("AA_CONSUMER.INTIMESTAMP", Long.toString(intimestamp));
map.put("AA_SEC.KEY_VERSION", priviateKeyVersion);
String[] array = canonicalize(map);
String data = null;
try {
data = generator.generateSignature(privateKey, array[1]);
} catch(Exception e) { }
System.out.println("Signature: " + data);
}
public String generateSignature(String key, String stringToSign) throws Exception {
Signature signatureInstance = Signature.getInstance("SHA256WithRSA");
ServiceKeyRep keyRep = new ServiceKeyRep(KeyRep.Type.PRIVATE, "RSA", "PKCS#8", Base64.decodeBase64(key));
PrivateKey resolvedPrivateKey = (PrivateKey) keyRep.readResolve();
signatureInstance.initSign(resolvedPrivateKey);
byte[] bytesToSign = stringToSign.getBytes("UTF-8");
signatureInstance.update(bytesToSign);
byte[] signatureBytes = signatureInstance.sign();
String signatureString = Base64.encodeBase64String(signatureBytes);
return signatureString;
}
protected static String[] canonicalize(Map<String, String> headersToSign) {
StringBuffer canonicalizedStrBuffer=new StringBuffer();
StringBuffer parameterNamesBuffer=new StringBuffer();
Set<String> keySet=headersToSign.keySet();
// Create sorted key set to enforce order on the key names
SortedSet<String> sortedKeySet=new TreeSet<String>(keySet);
for (String key :sortedKeySet) {
Object val=headersToSign.get(key);
parameterNamesBuffer.append(key.trim()).append(";");
canonicalizedStrBuffer.append(val.toString().trim()).append("\n");
}
return new String[] {parameterNamesBuffer.toString(), canonicalizedStrBuffer.toString()};
}
class ServiceKeyRep extends KeyRep {
private static final long serialVersionUID = -7213340660431987616L;
public ServiceKeyRep(Type type, String algorithm, String format, byte[] encoded) {
super(type, algorithm, format, encoded);
}
protected Object readResolve() throws ObjectStreamException {
return super.readResolve();
}
}
}
And here is my Ruby code that tries to do the same + request in the end
consumer_id = 'xxxxx'
key_version = '1'
private_key = '' # omitted for security
to_encrypt = {
"AA_CONSUMER.ID" => consumer_id,
"AA_CONSUMER.INTIMESTAMP" => DateTime.now.strftime('%Q'),
"AA_SEC.KEY_VERSION" => key_version
}
names = ''
canstr = ''
# sort
sorted_keys = to_encrypt.keys.sort
sorted_keys.each do |key|
value = to_encrypt[key]
names += "#{key};"
canstr += "#{value}\n"
end
final_arr = [names, canstr]
key = OpenSSL::PKey::RSA.new(Base64.decode64(private_key))
signature = key.sign('SHA256', final_arr[1])
signature_string = Base64.urlsafe_encode64(signature)
headers = to_encrypt.merge("AA_SEC.AUTH_SIGNATURE" => signature_string)
response = Faraday.send(:get, url, {}, headers)
Any suggestion?
It seems that the server expects a signature 384 bytes long, and you are sending a signature 256 bytes long.
It is possibly worth to try
key.sign('SHA384', final_arr[1])
in order for your digest to be 384 bytes.
Related
I want to exactly build a function which produces a HMAC with a secret key like this site provides:
http://www.freeformatter.com/hmac-generator.html
The Java 8 lib only provides MessageDigest and KeyGenerator which both only support up to SH256.
Also, Google doesn't give me any result for an implementation to generate a HMAC.
Does someone know a implementation?
I have this code to generate an ordinary SH256 but I guess this doesn't help me much:
public static String get_SHA_512_SecurePassword(String passwordToHash) throws Exception {
String generatedPassword = null;
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] bytes = md.digest(passwordToHash.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
generatedPassword = sb.toString();
System.out.println(generatedPassword);
return generatedPassword;
}
The simplest way can be -
private static final String HMAC_SHA512 = "HmacSHA512";
private static String toHexString(byte[] bytes) {
Formatter formatter = new Formatter();
for (byte b : bytes) {
formatter.format("%02x", b);
}
return formatter.toString();
}
public static String calculateHMAC(String data, String key)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException
{
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), HMAC_SHA512);
Mac mac = Mac.getInstance(HMAC_SHA512);
mac.init(secretKeySpec);
return toHexString(mac.doFinal(data.getBytes()));
}
public static void main(String[] args) throws Exception {
String hmac = calculateHMAC("data", "key");
System.out.println(hmac);
}
You can change the HMAC_SHA512 variable to any of the Mac algorithm and the code will work the same way.
Hope this helps:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Test1 {
private static final String HMAC_SHA512 = "HmacSHA512";
public static void main(String[] args) {
Mac sha512Hmac;
String result;
final String key = "Welcome1";
try {
final byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
sha512Hmac = Mac.getInstance(HMAC_SHA512);
SecretKeySpec keySpec = new SecretKeySpec(byteKey, HMAC_SHA512);
sha512Hmac.init(keySpec);
byte[] macData = sha512Hmac.doFinal("My message".getBytes(StandardCharsets.UTF_8));
// Can either base64 encode or put it right into hex
result = Base64.getEncoder().encodeToString(macData);
//result = bytesToHex(macData);
} catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) {
e.printStackTrace();
} finally {
// Put any cleanup here
System.out.println("Done");
}
}
}
For converting from byte array to hex refer this stackoverflow answer : here
Also you can use a little wrapper by Apache Commons codec:
import static org.apache.commons.codec.digest.HmacAlgorithms.HMAC_SHA_512;
import org.apache.commons.codec.digest.HmacUtils;
public class HmacService {
private String sharedSecret;
public HmacService(String sharedSecret) {
this.sharedSecret = sharedSecret;
}
public String calculateHmac(String data) {
return new HmacUtils(HMAC_SHA_512, sharedSecret).hmacHex(data);
}
public boolean checkHmac(String data, String hmacHex) {
return calculateHmac(data).equals(hmacHex);
}
}
You could use Caesar (version 0.6.0 or later):
int blockSize = 128;
String secretKey = "My secret key";
String message = "Message to hash";
System.out.println(
new Hmac(
new ImmutableMessageDigest(
MessageDigest.getInstance("SHA-512")
),
blockSize,
new PlainText(secretKey),
new PlainText(message)
).asHexString()
);
Error : javax.crypto.IllegalBlockSizeException: Input length must be
multiple of 16 when decrypting with padded cipher
Tried Solutions: I've tried to to change the padding to "AES/ECB/NoPadding", "AES/ECB/PKCS5", "AES/CBC/NoPadding", "AES/CBC/PKCS5Padding" and still received the same error or an error stating only AES or Rijndael required. Then I tried making the key use "AES" parameter and ALGO set to "AES/CBC/PKCS5Padding", but I recieved a missing parameter error which I tried to fix my adding new IvParameterSpec(new byte[16]) to cipher.init. It still resulted into the 16 bit issue. So I'm stuck now.
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.security.Key;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.*;
import java.io.PrintWriter;
import java.io.FileWriter;
import java.util.*;
import java.io.*;
// Don't forget to import any supporting classes you plan to use.
public class Crypto
{
private Scanner fileText;
private PrintWriter fileEncrypt;
private Scanner inputFile;
private PrintWriter outputFile;
private static final String ALGO = "AES/CBC/PKCS5Padding";
private byte[] keyValue;
public Crypto(String key)
{
keyValue = key.getBytes();
}
public String encrypt(String Data) throws Exception
{
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = c.doFinal(Data.getBytes());
String encryptedValue = new BASE64Encoder().encode(encVal);
return encryptedValue;
}
public String decrypt(String encryptedData) throws Exception
{
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.DECRYPT_MODE, key);
byte[] decodedValue = new BASE64Decoder().decodeBuffer(encryptedData);
byte[] decValue = c.doFinal(decodedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
public Key generateKey() throws Exception
{
Key key = new SecretKeySpec(keyValue, "AES");
return key;
}
// encrypt_decrypt("ENCRYPT", "CryptoPlaintext.txt", "CryptoCiphertext.txt" )
// encrypt_decrypt("DECRYPT", "CryptoCiphertext.txt", "CryptoDeciphered.txt")
public void encrypt_decrypt(String function_type , String source_file , String
target_file)
{
String lineValue = "";
String convertedValue = "";
try
{
inputFile = new Scanner(new File(source_file));
}
catch(Exception e)
{
System.out.println("( " + source_file + ") - File Opening Error");
}
try
{
outputFile = new PrintWriter(new FileWriter(target_file));
}
catch(Exception e)
{
System.out.println("( " + target_file + ") - File Opening Error");
}
while(inputFile.hasNext())
{
lineValue = inputFile.nextLine();
System.out.println("Source Line: " + lineValue);
try
{
if (function_type == "ENCRYPT")
{
convertedValue = encrypt(lineValue);
}
else if (function_type == "DECRYPT")
{
convertedValue = decrypt(lineValue);
}
}
catch(Exception e)
{
System.out.println(e);
}
System.out.println("Converted Line : " + convertedValue);
outputFile.write(convertedValue);
}
inputFile.close();
outputFile.close();
}
public static void main( String args[] ) throws IOException
{
// Write your code here...
// You will read from CryptoPlaintext.txt and write to
CryptoCiphertext.txt.
Crypto c = new Crypto("dk201anckse29sns");
c.encrypt_decrypt("ENCRYPT", "CryptoPlaintext.txt", "CryptoCiphertext.txt"
);
c.encrypt_decrypt("DECRYPT", "CryptoCiphertext.txt",
"CryptoDeciphered.txt");
//
// And then read from CryptoCiphertext.txt and write to
CryptoDeciphered.txt.
//
// DON'T forget your comments!
// =============================== DO NOT MODIFY ANY CODE BELOW HERE
==============================
// Compare the files
System.out.println(compareFiles() ? "The files are identical!" : "The
files are NOT identical.");
}
/**
* Compares the Plaintext file with the Deciphered file.
*
* #return true if files match, false if they do not
*/
public static boolean compareFiles() throws IOException
{
Scanner pt = new Scanner(new File("CryptoPlaintext.txt")); // Open the
plaintext file
Scanner dc = new Scanner(new File("CryptoDeciphered.txt")); // Open the
deciphered file
// Read through the files and compare them record by record.
// If any of the records do not match, the files are not identical.
while(pt.hasNextLine() && dc.hasNextLine())
if(!pt.nextLine().equals(dc.nextLine())) return false;
// If we have any records left over, then the files are not identical.
if(pt.hasNextLine() || dc.hasNextLine()) return false;
// The files are identical.
return true;
}
}
There are two errors in your code:
You forgot to generate a random IV and prefix it to your ciphertext (before encoding it to base 64). You'd need to find the IV from your ciphertext and then retrieve it back again during decryption.
Note that CBC code requires an IV indistinguishable from random. You can create it using new SecureRandom (only during encryption, of course) and IvParameterSpec. The code will probably run without this as the default implementation in Java defaults on an all-zero IV. Possibly that is enough for this assignment.
But that's not what generates the error; that's much more of a banality:
You're calling outputFile.write instead of outputFile.println which means that newlines aren't inserted, and all base 64 encodings are put in a single line.
Note that you should not use any classes from sun.misc. Those are private to the Java implementation and are not part of the Java API. The new Java versions have java.util.Base64 for your convenience. Actually, the sun.misc version may insert line endings within the base 64 encoding that will break your code for longer lines.
For example:
package nl.owlstead.stackoverflow;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Key;
import java.util.Base64;
import java.util.Scanner;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Crypto {
private Scanner inputFile;
private PrintWriter outputFile;
private static final String ALGO = "AES/CBC/PKCS5Padding";
private byte[] keyValue;
public Crypto(String key) {
keyValue = key.getBytes();
}
public String encrypt(String Data) throws Exception {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.ENCRYPT_MODE, key,
new IvParameterSpec(new byte[c.getBlockSize()]));
byte[] encVal = c.doFinal(Data.getBytes());
String encryptedValue = Base64.getEncoder().encodeToString(encVal);
return encryptedValue;
}
public String decrypt(String encryptedData) throws Exception {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.DECRYPT_MODE, key,
new IvParameterSpec(new byte[c.getBlockSize()]));
byte[] decodedValue = Base64.getDecoder().decode(encryptedData);
byte[] decValue = c.doFinal(decodedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
public Key generateKey() throws Exception {
Key key = new SecretKeySpec(keyValue, "AES");
return key;
}
public void encrypt_decrypt(String function_type, String source_file,
String target_file) {
String lineValue = "";
String convertedValue = "";
try {
inputFile = new Scanner(new File(source_file));
} catch (Exception e) {
System.out.println("( " + source_file + ") - File Opening Error");
}
try {
outputFile = new PrintWriter(new FileWriter(target_file));
} catch (Exception e) {
System.out.println("( " + target_file + ") - File Opening Error");
}
while (inputFile.hasNext()) {
lineValue = inputFile.nextLine();
System.out.println("Source Line: " + lineValue);
try {
if (function_type == "ENCRYPT") {
convertedValue = encrypt(lineValue);
} else if (function_type == "DECRYPT") {
convertedValue = decrypt(lineValue);
}
} catch (Exception e) {
System.out.println(e);
}
System.out.println("Converted Line : " + convertedValue);
outputFile.println(convertedValue);
}
inputFile.close();
outputFile.close();
}
public static void main(String args[]) throws IOException {
Crypto c = new Crypto("dk201anckse29sns");
c.encrypt_decrypt("ENCRYPT", "CryptoPlaintext.txt",
"CryptoCiphertext.txt");
c.encrypt_decrypt("DECRYPT", "CryptoCiphertext.txt",
"CryptoDeciphered.txt");
System.out.println(compareFiles() ? "The files are identical!"
: "The files are NOT identical.");
}
/**
* Compares the Plaintext file with the Deciphered file.
*
* #return true if files match, false if they do not
*/
public static boolean compareFiles() throws IOException {
Scanner pt = new Scanner(new File("CryptoPlaintext.txt")); // Open the
Scanner dc = new Scanner(new File("CryptoDeciphered.txt")); // Open the
// Read through the files and compare them record by record.
// If any of the records do not match, the files are not identical.
while (pt.hasNextLine() && dc.hasNextLine()) {
String ptl = pt.nextLine();
String dcl = dc.nextLine();
if (!ptl.equals(dcl))
{
System.out.println(ptl);
System.out.println(dcl);
continue;
// return false;
}
}
// If we have any records left over, then the files are not identical.
if (pt.hasNextLine() || dc.hasNextLine())
return false;
// The files are identical.
return true;
}
}
A working solution for you:
Just added a random IV value while initiating your cipher during encrypting and decrypting.
c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[16]));
package com.samples;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.io.PrintWriter;
import java.io.FileWriter;
// Don't forget to import any supporting classes you plan to use.
public class Crypto
{
private Scanner fileText;
private PrintWriter fileEncrypt;
private Scanner inputFile;
private PrintWriter outputFile;
private static final String ALGO = "AES/CBC/PKCS5Padding";
private byte[] keyValue;
public Crypto(String key)
{
keyValue = key.getBytes();
}
public String encrypt(String Data) throws Exception
{
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[16]));
byte[] encVal = c.doFinal(Data.getBytes());
String encryptedValue = new BASE64Encoder().encode(encVal);
return encryptedValue;
}
public String decrypt(String encryptedData) throws Exception
{
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(new byte[16]));
byte[] decodedValue = new BASE64Decoder().decodeBuffer(encryptedData);
byte[] decValue = c.doFinal(decodedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
public Key generateKey() throws Exception
{
Key key = new SecretKeySpec(keyValue, "AES");
return key;
}
// encrypt_decrypt("ENCRYPT", "CryptoPlaintext.txt", "CryptoCiphertext.txt" )
// encrypt_decrypt("DECRYPT", "CryptoCiphertext.txt", "CryptoDeciphered.txt")
public void encrypt_decrypt(String function_type, String source_file, String target_file)
{
String lineValue = "";
String convertedValue = "";
try
{
inputFile = new Scanner(new File(source_file));
}
catch(Exception e)
{
System.out.println("( " + source_file + ") - File Opening Error");
}
try
{
outputFile = new PrintWriter(new FileWriter(target_file));
}
catch(Exception e)
{
System.out.println("( " + target_file + ") - File Opening Error");
}
while(inputFile.hasNext())
{
lineValue = inputFile.nextLine();
System.out.println("Source Line: " + lineValue);
try
{
if (function_type == "ENCRYPT")
{
convertedValue = encrypt(lineValue);
}
else if (function_type == "DECRYPT")
{
convertedValue = decrypt(lineValue);
}
}
catch(Exception e)
{
System.out.println(e);
}
System.out.println("Converted Line : " + convertedValue);
outputFile.write(convertedValue);
}
inputFile.close();
outputFile.close();
}
public static void main( String args[] ) throws IOException
{
// Write your code here...
// You will read from CryptoPlaintext.txt and write to CryptoCiphertext.txt.
Crypto c = new Crypto("dk201anckse29sns");
c.encrypt_decrypt("ENCRYPT", "C:\\Users\\mundrap\\Eclipse_Workspace\\Java-8\\src\\com\\samples\\CryptoPlaintext.txt", "C:\\Users\\mundrap\\Eclipse_Workspace\\Java-8\\src\\com\\samples\\CryptoCiphertext.txt"
);
c.encrypt_decrypt("DECRYPT", "C:\\Users\\mundrap\\Eclipse_Workspace\\Java-8\\src\\com\\samples\\CryptoCiphertext.txt",
"C:\\Users\\mundrap\\Eclipse_Workspace\\Java-8\\src\\com\\samples\\CryptoDeciphered.txt");
//
// And then read from CryptoCiphertext.txt and write to CryptoDeciphered.txt.
//
// DON'T forget your comments!
// =============================== DO NOT MODIFY ANY CODE BELOW HE ==============================
// Compare the files
System.out.println(compareFiles() ? "The files are identical!" : "The files are NOT identical.");
}
/**
* Compares the Plaintext file with the Deciphered file.
*
* #return true if files match, false if they do not
*/
public static boolean compareFiles() throws IOException
{
Scanner pt = new Scanner(new File("C:\\Users\\mundrap\\Eclipse_Workspace\\Java-8\\src\\com\\samples\\CryptoPlaintext.txt")); // Open the plaintext file
Scanner dc = new Scanner(new File("C:\\Users\\mundrap\\Eclipse_Workspace\\Java-8\\src\\com\\samples\\CryptoDeciphered.txt")); // Open the deciphered file
// Read through the files and compare them record by record.
// If any of the records do not match, the files are not identical.
while(pt.hasNextLine() && dc.hasNextLine())
if(!pt.nextLine().equals(dc.nextLine())) return false;
// If we have any records left over, then the files are not identical.
if(pt.hasNextLine() || dc.hasNextLine()) return false;
// The files are identical.
return true;
}
}
I want to exactly build a function which produces a HMAC with a secret key like this site provides:
http://www.freeformatter.com/hmac-generator.html
The Java 8 lib only provides MessageDigest and KeyGenerator which both only support up to SH256.
Also, Google doesn't give me any result for an implementation to generate a HMAC.
Does someone know a implementation?
I have this code to generate an ordinary SH256 but I guess this doesn't help me much:
public static String get_SHA_512_SecurePassword(String passwordToHash) throws Exception {
String generatedPassword = null;
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] bytes = md.digest(passwordToHash.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
generatedPassword = sb.toString();
System.out.println(generatedPassword);
return generatedPassword;
}
The simplest way can be -
private static final String HMAC_SHA512 = "HmacSHA512";
private static String toHexString(byte[] bytes) {
Formatter formatter = new Formatter();
for (byte b : bytes) {
formatter.format("%02x", b);
}
return formatter.toString();
}
public static String calculateHMAC(String data, String key)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException
{
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), HMAC_SHA512);
Mac mac = Mac.getInstance(HMAC_SHA512);
mac.init(secretKeySpec);
return toHexString(mac.doFinal(data.getBytes()));
}
public static void main(String[] args) throws Exception {
String hmac = calculateHMAC("data", "key");
System.out.println(hmac);
}
You can change the HMAC_SHA512 variable to any of the Mac algorithm and the code will work the same way.
Hope this helps:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Test1 {
private static final String HMAC_SHA512 = "HmacSHA512";
public static void main(String[] args) {
Mac sha512Hmac;
String result;
final String key = "Welcome1";
try {
final byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
sha512Hmac = Mac.getInstance(HMAC_SHA512);
SecretKeySpec keySpec = new SecretKeySpec(byteKey, HMAC_SHA512);
sha512Hmac.init(keySpec);
byte[] macData = sha512Hmac.doFinal("My message".getBytes(StandardCharsets.UTF_8));
// Can either base64 encode or put it right into hex
result = Base64.getEncoder().encodeToString(macData);
//result = bytesToHex(macData);
} catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) {
e.printStackTrace();
} finally {
// Put any cleanup here
System.out.println("Done");
}
}
}
For converting from byte array to hex refer this stackoverflow answer : here
Also you can use a little wrapper by Apache Commons codec:
import static org.apache.commons.codec.digest.HmacAlgorithms.HMAC_SHA_512;
import org.apache.commons.codec.digest.HmacUtils;
public class HmacService {
private String sharedSecret;
public HmacService(String sharedSecret) {
this.sharedSecret = sharedSecret;
}
public String calculateHmac(String data) {
return new HmacUtils(HMAC_SHA_512, sharedSecret).hmacHex(data);
}
public boolean checkHmac(String data, String hmacHex) {
return calculateHmac(data).equals(hmacHex);
}
}
You could use Caesar (version 0.6.0 or later):
int blockSize = 128;
String secretKey = "My secret key";
String message = "Message to hash";
System.out.println(
new Hmac(
new ImmutableMessageDigest(
MessageDigest.getInstance("SHA-512")
),
blockSize,
new PlainText(secretKey),
new PlainText(message)
).asHexString()
);
I'm pretty familiar with PGP, and the way it works. I'd like to include some functionality in a project, but so far my research has left my head spinning. What I'd like to do is create a public key with a defined private pass phrase. From there, I'd share the public key with someone where they can then encrypt a message using the key and return it to me where I can decrypt. I envision the code looking something like this.
To generate my private pgp key:
PGPKey key = new PGPKey();
key.setPassPhrase("MySecretPassword!!!1");
key.generateRandomSharedKey();
key.build();
To encrypt I'd give my shared key to a friend:
String encryptedText = PGPTools.Encrypt("Text to encrypt", getSharedKey());
To decrypt the encrypted string after being sent back to me:
String decryptedText = PGPTools.Decrypt(encryptedText, key, "MySecretPassword!!!1")
Obviously I know I'm skipping out on a ton of details. Through my research I've seen references to libraries like Bouncy Castle and Spongy Castle. Any help would be hugely appreciated!
I want to post my solution because A. it was extremely difficult to get this working, and B. If any crypto pro's want to audit my code I'd be eternally grateful.
I included the 4 following libraries:
compile 'com.madgag.spongycastle:core:1.50.0.0'
compile 'com.madgag.spongycastle:pg:1.50.0.0'
compile 'com.madgag.spongycastle:pkix:1.50.0.0'
compile 'com.madgag.spongycastle:prov:1.50.0.0'
Bouncy Castle needs to be added as a security provider. I included this code in a class that initializes some other objects when the app loads.
static {
Security.addProvider(new BouncyCastleProvider());
}
Here's the utils class I created that really contains the nuts and bolts. Edited slightly:
import com.example.Device;
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.crypto.generators.RSAKeyPairGenerator;
import org.spongycastle.crypto.params.RSAKeyGenerationParameters;
import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyPair;
import org.spongycastle.openpgp.PGPKeyRingGenerator;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
import org.spongycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
import org.spongycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.spongycastle.openpgp.operator.bc.BcPGPKeyPair;
import org.spongycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Iterator;
public class PgpUtils {
private static final String PROVIDER = "SC";
private static final String KEY_RING_ID = "asdf#asdf.com";
public static String decrypt(String encryptedText, String password) throws Exception {
byte[] encrypted = encryptedText.getBytes();
InputStream in = new ByteArrayInputStream(encrypted);
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
while (sKey == null && enc.getEncryptedDataObjects().hasNext()) {
pbe = (PGPPublicKeyEncryptedData)enc.getEncryptedDataObjects().next();
sKey = getPrivateKey(getPGPSecretKeyRing(), pbe.getKeyID(), password.toCharArray());
}
if (pbe != null) {
InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
PGPObjectFactory pgpFact = new PGPObjectFactory(clear);
PGPCompressedData cData = (PGPCompressedData) pgpFact.nextObject();
pgpFact = new PGPObjectFactory(cData.getDataStream());
PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();
InputStream unc = ld.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
int ch;
while ((ch = unc.read()) >= 0) {
out.write(ch);
}
byte[] returnBytes = out.toByteArray();
out.close();
return new String(returnBytes);
}
return null;
}
private static PGPPublicKey getPublicKey(PGPPublicKeyRing publicKeyRing) {
Iterator<?> kIt = publicKeyRing.getPublicKeys();
while (kIt.hasNext()) {
PGPPublicKey k = (PGPPublicKey) kIt.next();
if (k.isEncryptionKey()) {
return k;
}
}
return null;
}
private static PGPPrivateKey getPrivateKey(PGPSecretKeyRing keyRing, long keyID, char[] pass) throws PGPException {
PGPSecretKey secretKey = keyRing.getSecretKey(keyID);
PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass);
return secretKey.extractPrivateKey(decryptor);
}
public static String encrypt(String msgText) throws IOException, PGPException {
byte[] clearData = msgText.getBytes();
PGPPublicKey encKey = getPublicKey(getPGPPublicKeyRing());
ByteArrayOutputStream encOut = new ByteArrayOutputStream();
OutputStream out = new ArmoredOutputStream(encOut);
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedDataGenerator.ZIP);
OutputStream cos = comData.open(bOut);
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut = lData.open(cos, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, clearData.length, new Date());
pOut.write(clearData);
lData.close();
comData.close();
PGPEncryptedDataGenerator encGen =
new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_256).setWithIntegrityPacket(true).setSecureRandom(
new SecureRandom()).setProvider(PROVIDER));
if (encKey != null) {
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider(PROVIDER));
byte[] bytes = bOut.toByteArray();
OutputStream cOut = encGen.open(out, bytes.length);
cOut.write(bytes);
cOut.close();
}
out.close();
return new String(encOut.toByteArray());
}
public final static PGPKeyRingGenerator generateKeyRingGenerator (char[] pass) throws PGPException{
RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();
kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001), new SecureRandom(), 2048, 12));
PGPKeyPair rsakp_sign = new BcPGPKeyPair(PGPPublicKey.RSA_SIGN, kpg.generateKeyPair(), new Date());
PGPKeyPair rsakp_enc = new BcPGPKeyPair(PGPPublicKey.RSA_ENCRYPT, kpg.generateKeyPair(), new Date());
PGPSignatureSubpacketGenerator signhashgen = new PGPSignatureSubpacketGenerator();
signhashgen.setKeyFlags(false, KeyFlags.SIGN_DATA|KeyFlags.CERTIFY_OTHER|KeyFlags.SHARED);
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});
signhashgen.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
PGPSignatureSubpacketGenerator enchashgen = new PGPSignatureSubpacketGenerator();
enchashgen.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
PGPDigestCalculator sha1Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1);
PGPDigestCalculator sha256Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA256);
PBESecretKeyEncryptor pske = (new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha256Calc, 0xc0)).build(pass);
PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator (PGPSignature.POSITIVE_CERTIFICATION, rsakp_sign,
KEY_RING_ID, sha1Calc, signhashgen.generate(), null, new BcPGPContentSignerBuilder(rsakp_sign.getPublicKey().getAlgorithm(),
HashAlgorithmTags.SHA1), pske);
keyRingGen.addSubKey(rsakp_enc, enchashgen.generate(), null);
return keyRingGen;
}
private static PGPPublicKeyRing getPGPPublicKeyRing() throws IOException {
ArmoredInputStream ais = new ArmoredInputStream(new ByteArrayInputStream(Device.getDevice().getPgpPublicKey().getBytes()));
return (PGPPublicKeyRing) new PGPObjectFactory(ais).nextObject();
}
private static PGPSecretKeyRing getPGPSecretKeyRing() throws IOException {
ArmoredInputStream ais = new ArmoredInputStream(new ByteArrayInputStream(Device.getDevice().getPgpSecretKey().getBytes()));
return (PGPSecretKeyRing) new PGPObjectFactory(ais).nextObject();
}
public final static String genPGPPublicKey (PGPKeyRingGenerator krgen) throws IOException {
ByteArrayOutputStream baosPkr = new ByteArrayOutputStream();
PGPPublicKeyRing pkr = krgen.generatePublicKeyRing();
ArmoredOutputStream armoredStreamPkr = new ArmoredOutputStream(baosPkr);
pkr.encode(armoredStreamPkr);
armoredStreamPkr.close();
return new String(baosPkr.toByteArray(), Charset.defaultCharset());
}
public final static String genPGPPrivKey (PGPKeyRingGenerator krgen) throws IOException {
ByteArrayOutputStream baosPriv = new ByteArrayOutputStream ();
PGPSecretKeyRing skr = krgen.generateSecretKeyRing();
ArmoredOutputStream armoredStreamPriv = new ArmoredOutputStream(baosPriv);
skr.encode(armoredStreamPriv);
armoredStreamPriv.close();
return new String(baosPriv.toByteArray(), Charset.defaultCharset());
}
}
Here is how I create the private and public keys:
final PGPKeyRingGenerator krgen = PgpUtils.generateKeyRingGenerator("password".toCharArray());
String pgpPublicKey = PgpUtils.genPGPPublicKey(krgen);
String pgpSecretKey = PgpUtils.genPGPPrivKey(krgen);
And finally encrypting and decrypting using your own public key:
String encrypted = PgpUtils.encrypt("message text");
String decrypted = PgpUtils.decrypt(encrypted, "Password");
I don't have enough rep to comment on joey_g216 excellent answer above. Initially it worked for me, but then failed on decrypting various files. This is because the structure of a PGPObject can change.
To get decryption to work I had to adjust:
public static String decrypt(String encryptedText, String password) throws Exception {
to include:
// Could be
// signature + compressed -> data
// signature + data
// data
// compressed -> data
Object z = pgpFact.nextObject();
while (!(z instanceof PGPLiteralData))
{
if (z instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) z;
pgpFact = new PGPObjectFactory(cData.getDataStream(), bcKeyFingerprintCalculator);
}
if (z instanceof PGPOnePassSignatureList) {
// ignore for now!
}
z = pgpFact.nextObject();
}
PGPLiteralData ld = (PGPLiteralData) z;
Looks like you want to generate a public key pair using BouncyCastle?
This one asks how to do it without a password, but you can take a look how to do it with a password too:
How to generate OpenPGP KeyPair without passphrase using BouncyCastle?
SO I am testing a Rest OAuth implementation.
My testing tool will send the HTTP Request, but I need to prepare the Authorization header.
What I need: I want a valid Authorization Header
What I have: All the headers except the oauth_signature
I also have the 2 secrets, the token_secret and the consumer_secret. I also posses the access_token. So It really boils down to, having to sign this request. How do I do that?
Summary: I simply need to populate the oauth_signature portion of the Authorization header for a RESTful service. How do I do it?
Basically:
oAuthHeader="OAuth";
oAuthHeader=oAuthHeader+" oauth_signature_method="+oauth_signature_method;
oAuthHeader=oAuthHeader+",oauth_version="+oauth_version;
oAuthHeader=oAuthHeader+",oauth_nonce="+oauth_nonce;
oAuthHeader=oAuthHeader+",oauth_timestamp="+oauth_timestamp;
oAuthHeader=oAuthHeader+",oauth_consumer_key="+oauth_consumer_key;
oAuthHeader=oAuthHeader+",oauth_token="+oauth_token;
oAuthHeader=oAuthHeader+",oauth_signature="+**oauth_signature**;
Authorization = oAuthHeader;
My problem is I do not have the oauth_signature portion of it. And I do not know how to get it. Help please?
Here is my code for Flickr OAuth. NOTICE: I REFERED some logic from SignPost. It is really very tricky to generate it signature.... OK. This is just an example for generate the "oauth_signature"
package oauthflickr;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
/**
* a simple program to get flickr token and token secret.
*
* #author Mark Zang
*
*/
public class OAuthForFlickr {
private static String key = "_________________________";
private static String secret = "___________";
private static final String HMAC_SHA1 = "HmacSHA1";
private static final String ENC = "UTF-8";
private static Base64 base64 = new Base64();
/**
*
* #param url
* the url for "request_token" URLEncoded.
* #param params
* parameters string, URLEncoded.
* #return
* #throws UnsupportedEncodingException
* #throws NoSuchAlgorithmException
* #throws InvalidKeyException
*/
private static String getSignature(String url, String params)
throws UnsupportedEncodingException, NoSuchAlgorithmException,
InvalidKeyException {
/**
* base has three parts, they are connected by "&": 1) protocol 2) URL
* (need to be URLEncoded) 3) Parameter List (need to be URLEncoded).
*/
StringBuilder base = new StringBuilder();
base.append("GET&");
base.append(url);
base.append("&");
base.append(params);
System.out.println("Stirng for oauth_signature generation:" + base);
// yea, don't ask me why, it is needed to append a "&" to the end of
// secret key.
byte[] keyBytes = (secret + "&").getBytes(ENC);
SecretKey key = new SecretKeySpec(keyBytes, HMAC_SHA1);
Mac mac = Mac.getInstance(HMAC_SHA1);
mac.init(key);
// encode it, base64 it, change it to string and return.
return new String(base64.encode(mac.doFinal(base.toString().getBytes(
ENC))), ENC).trim();
}
/**
* #param args
* #throws IOException
* #throws ClientProtocolException
* #throws URISyntaxException
* #throws NoSuchAlgorithmException
* #throws InvalidKeyException
*/
public static void main(String[] args) throws ClientProtocolException,
IOException, URISyntaxException, InvalidKeyException,
NoSuchAlgorithmException {
HttpClient httpclient = new DefaultHttpClient();
List<NameValuePair> qparams = new ArrayList<NameValuePair>();
// These params should ordered in key
qparams.add(new BasicNameValuePair("oauth_callback", "oob"));
qparams.add(new BasicNameValuePair("oauth_consumer_key", key));
qparams.add(new BasicNameValuePair("oauth_nonce", ""
+ (int) (Math.random() * 100000000)));
qparams.add(new BasicNameValuePair("oauth_signature_method",
"HMAC-SHA1"));
qparams.add(new BasicNameValuePair("oauth_timestamp", ""
+ (System.currentTimeMillis() / 1000)));
qparams.add(new BasicNameValuePair("oauth_version", "1.0"));
// generate the oauth_signature
String signature = getSignature(URLEncoder.encode(
"http://www.flickr.com/services/oauth/request_token", ENC),
URLEncoder.encode(URLEncodedUtils.format(qparams, ENC), ENC));
// add it to params list
qparams.add(new BasicNameValuePair("oauth_signature", signature));
// generate URI which lead to access_token and token_secret.
URI uri = URIUtils.createURI("http", "www.flickr.com", -1,
"/services/oauth/request_token",
URLEncodedUtils.format(qparams, ENC), null);
System.out.println("Get Token and Token Secrect from:"
+ uri.toString());
HttpGet httpget = new HttpGet(uri);
// output the response content.
System.out.println("oken and Token Secrect:");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int len;
byte[] tmp = new byte[2048];
while ((len = instream.read(tmp)) != -1) {
System.out.println(new String(tmp, 0, len, ENC));
}
}
}
}
For Twitter oAuth: In case anyone needs to generate oAuth signature and header to connect to Twitter API, here is the code. This requires minimum of Java 8 and NO 3rd party library.
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Class to generate Oauth 1.0a header for Twitter
*
*/
public class TwitterOauthHeaderGenerator {
private String consumerKey;
private String consumerSecret;
private String signatureMethod;
private String token;
private String tokenSecret;
private String version;
public TwitterOauthHeaderGenerator(String consumerKey, String consumerSecret, String token, String tokenSecret) {
this.consumerKey = consumerKey;
this.consumerSecret = consumerSecret;
this.token = token;
this.tokenSecret = tokenSecret;
this.signatureMethod = "HMAC-SHA1";
this.version = "1.0";
}
private static final String oauth_consumer_key = "oauth_consumer_key";
private static final String oauth_token = "oauth_token";
private static final String oauth_signature_method = "oauth_signature_method";
private static final String oauth_timestamp = "oauth_timestamp";
private static final String oauth_nonce = "oauth_nonce";
private static final String oauth_version = "oauth_version";
private static final String oauth_signature = "oauth_signature";
private static final String HMAC_SHA1 = "HmacSHA1";
/**
* Generates oAuth 1.0a header which can be passed as Authorization header
*
* #param httpMethod
* #param url
* #param requestParams
* #return
*/
public String generateHeader(String httpMethod, String url, Map<String, String> requestParams) {
StringBuilder base = new StringBuilder();
String nonce = getNonce();
String timestamp = getTimestamp();
String baseSignatureString = generateSignatureBaseString(httpMethod, url, requestParams, nonce, timestamp);
String signature = encryptUsingHmacSHA1(baseSignatureString);
base.append("OAuth ");
append(base, oauth_consumer_key, consumerKey);
append(base, oauth_token, token);
append(base, oauth_signature_method, signatureMethod);
append(base, oauth_timestamp, timestamp);
append(base, oauth_nonce, nonce);
append(base, oauth_version, version);
append(base, oauth_signature, signature);
base.deleteCharAt(base.length() - 1);
System.out.println("header : " + base.toString());
return base.toString();
}
/**
* Generate base string to generate the oauth_signature
*
* #param httpMethod
* #param url
* #param requestParams
* #return
*/
private String generateSignatureBaseString(String httpMethod, String url, Map<String, String> requestParams, String nonce, String timestamp) {
Map<String, String> params = new HashMap<>();
requestParams.entrySet().forEach(entry -> {
put(params, entry.getKey(), entry.getValue());
});
put(params, oauth_consumer_key, consumerKey);
put(params, oauth_nonce, nonce);
put(params, oauth_signature_method, signatureMethod);
put(params, oauth_timestamp, timestamp);
put(params, oauth_token, token);
put(params, oauth_version, version);
Map<String, String> sortedParams = params.entrySet().stream().sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));
StringBuilder base = new StringBuilder();
sortedParams.entrySet().forEach(entry -> {
base.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
});
base.deleteCharAt(base.length() - 1);
String baseString = httpMethod.toUpperCase() + "&" + encode(url) + "&" + encode(base.toString());
return baseString;
}
private String encryptUsingHmacSHA1(String input) {
String secret = new StringBuilder().append(encode(consumerSecret)).append("&").append(encode(tokenSecret)).toString();
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
SecretKey key = new SecretKeySpec(keyBytes, HMAC_SHA1);
Mac mac;
try {
mac = Mac.getInstance(HMAC_SHA1);
mac.init(key);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
return null;
}
byte[] signatureBytes = mac.doFinal(input.getBytes(StandardCharsets.UTF_8));
return new String(Base64.getEncoder().encode(signatureBytes));
}
/**
* Percentage encode String as per RFC 3986, Section 2.1
*
* #param value
* #return
*/
private String encode(String value) {
String encoded = "";
try {
encoded = URLEncoder.encode(value, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
String sb = "";
char focus;
for (int i = 0; i < encoded.length(); i++) {
focus = encoded.charAt(i);
if (focus == '*') {
sb += "%2A";
} else if (focus == '+') {
sb += "%20";
} else if (focus == '%' && i + 1 < encoded.length() && encoded.charAt(i + 1) == '7' && encoded.charAt(i + 2) == 'E') {
sb += '~';
i += 2;
} else {
sb += focus;
}
}
return sb.toString();
}
private void put(Map<String, String> map, String key, String value) {
map.put(encode(key), encode(value));
}
private void append(StringBuilder builder, String key, String value) {
builder.append(encode(key)).append("=\"").append(encode(value)).append("\",");
}
private String getNonce() {
int leftLimit = 48; // numeral '0'
int rightLimit = 122; // letter 'z'
int targetStringLength = 10;
Random random = new Random();
String generatedString = random.ints(leftLimit, rightLimit + 1).filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)).limit(targetStringLength)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
return generatedString;
}
private String getTimestamp() {
return Math.round((new Date()).getTime() / 1000.0) + "";
}
}
Sample usage using Spring RestTemplate to get details of a Twitter user:
TwitterOauthHeaderGenerator generator = new TwitterOauthHeaderGenerator("consumerKey", "consumerSecret", "token", "tokenSecret");
Map<String, String> requestParams = new HashMap<>();
requestParams.put("usernames", "some_handle");
String header = generator.generateHeader("GET", "https://api.twitter.com/labs/1/users", requestParams);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", header);
HttpEntity<String> httpEntity = new HttpEntity<String>("body", headers);
ResponseEntity<SomeModel> someModelEntity= restTemplate.exchange("https://api.twitter.com/labs/1/users?usernames=some_handle",
HttpMethod.GET, httpEntity, SomeModel.class);
System.out.println(someModelEntity.getBody());
Complete code and working demo available at Twitter-Play
For OAuth 1.0a with JIRA. I was able to benefit from the Twitter example above, except that in case of JIRA I had to digitally sign the generatedSignatureBaseString with RSA-SHA1 instead of HMACSHA1.
Below are the snippets to do the signing with RSA-SHA1. This is in Groovy and uses Java 8 without any external libraries:
import java.security.SignatureException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Signature;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
//-------- generate RSA-SHA1 signature from request data
def encryptUsingRSASHA1(String data, String key) throws
java.security.SignatureException
{
String result
try {
// get an rsa_sha1 key from the raw key bytes
//SHA1withRSA
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(getPrivateKey(key));
signature.update(data.getBytes("UTF-8"));
byte[] rawRSAsigned = signature.sign();
result= rawRSAsigned.encodeBase64()
} catch (Exception e) {
throw new SignatureException("Failed to generate Signature : " +
e.getMessage());
}
return result
}
//get PrivateKey from key string
def getPrivateKey(String privateKey) throws NoSuchAlgorithmException,
InvalidKeySpecException {
String privateKeyPEM = privateKey.replace("-----BEGIN PRIVATE KEY-----\n",
"").replace("-----END PRIVATE KEY-----", "").replaceAll("\n","");
byte[] privateKeyBytes = privateKeyPEM.decodeBase64();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}