We've encountered a weird situation where the encryption method we're using in Java produces different output to openssl, despite them appearing identical in configuration.
Using the same key and IV, the text "The quick BROWN fox jumps over the lazy dog!" encrypts to base64'd strings...
openssl: A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTeHGPWyRqJK+UQxvJ1B/1L
Java: A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTEVySz5yJLGzGd7qsAkzuQ
This is our openssl call...
#!/bin/bash
keySpec="D41D8CD98F00B2040000000000000000"
ivSpec="03B13BBE886F00E00000000000000000"
plainText="The quick BROWN fox jumps over the lazy dog!"
echo "$plainText">plainText
openssl aes-128-cbc -nosalt -K $keySpec -iv $ivSpec -e -in plainText -out cipherText
base64 cipherText > cipherText.base64
printf "Encrypted hex dump = "
xxd -p cipherText | tr -d '\n'
printf "\n\n"
printf "Encrypted base64 = "
cat cipherText.base64
And this is our Java...
private static void runEncryption() throws Exception
{
String plainText = "The quick BROWN fox jumps over the lazy dog!";
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(hexToBytes("D41D8CD98F00B2040000000000000000"), 0, 16, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(hexToBytes("03B13BBE886F00E00000000000000000"));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
String encryptedHexDump = bytesToHex(encrypted);
String encryptedBase64 = new String(DatatypeConverter.printBase64Binary(encrypted));
System.out.println("Encrypted hex dump = " + encryptedHexDump);
System.out.println("");
System.out.println("Encrypted base64 = " + encryptedBase64);
}
private static byte[] hexToBytes(String s)
{
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2)
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
return data;
}
final protected static char[] hexArray = "0123456789abcdef".toCharArray();
public static String bytesToHex(byte[] bytes)
{
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++)
{
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
oopenssl output
Encrypted hex dump = 03c70c448ac35670588f6f9710a68c381435b2e7e3a6db007f9f2c951dfbdc94de1c63d6c91a892be510c6f27507fd4b
Encrypted base64 = A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTeHGPWyRqJK+UQxvJ1B/1L
Java output
Encrypted hex dump = 03c70c448ac35670588f6f9710a68c381435b2e7e3a6db007f9f2c951dfbdc94c45724b3e7224b1b319deeab00933b90
Encrypted base64 = A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTEVySz5yJLGzGd7qsAkzuQ
Are we missing something obvious? Or is there some hidden complexity?
I believe the difference is the padding, not the actual encrypted data.
Have you tried to decrypt the strings?
I believe they will show up as the same.
Why is the padding different? because they are either implementing it differently, or because one is provided a file, while the other a string, which in the end, when you read them, they are not the same thing (one has an EoF marker, for example).
BTW: Since it is CBC, Cipher Block Chaining, the whole last block is affected by this padding difference
It is indeed a problem of providing a string or a file. If you put a "\n" at the end of your Java code the result will be the same as in openSSL.
There are several reasons why these divergences can occur:
If you are providing OpenSSL and Java a password instead of a key, the key derivation from the password is different, unless you reimplement OpenSSL's algorithm in Java.
Still related to key derivation, the message digest used by OpenSSL by default depends on OpenSSL's version. Different versions can thus lead to different keys, and keys that differ from that computed by Java.
Finally, if you are sure to be using the same key through OpenSSL and Java, one reason why it can differ is because OpenSSL prepends Salted__<yoursalt> to the encrypted string.
Thus, in order to have the same output from Java as from OpenSSL, you need to prepend this to your result, like so:
byte[] rawEncryptedInput = cipher.doFinal(input.getBytes());
byte[] encryptedInputWithPrependedSalt = ArrayUtils.addAll(ArrayUtils.addAll(
"Salted__".getBytes(), SALT), rawEncryptedInput);
return Base64.getEncoder()
.encodeToString(encryptedInputWithPrependedSalt);
Related
I want to encrypt a challenge (like 162236fe0bec620827958c8fdf7e4bc7 ) using this key C6864E7696C686 with the DES algorithm.
Here is my code :
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.xml.bind.DatatypeConverter;
import javax.crypto.spec.DESKeySpec;
def data = prev.getResponseData();
String challenge = javax.xml.bind.DatatypeConverter.printHexBinary(data);
final String strPassPhrase = "C6864E7696C686";
String param = challenge;
System.out.println("Text : " + param);
SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");
SecretKey key = factory.generateSecret(new DESKeySpec(hexStringToByteArray(strPassPhrase)));
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
String str = DatatypeConverter.printBase64Binary(cipher.doFinal(param.getBytes()));
System.out.println("Text Encryted : " + str);
cipher.init(Cipher.DECRYPT_MODE, key);
String str2 = new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(str)));
System.out.println("Text Decryted : " + str2);
But i get this exception :
java.security.InvalidKeyException: Wrong key size
Edit :
I have copy this function to convert my hex string to bytes :
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
But I get the same exception ...
Your DES key should be 8 bytes (56 bits + 8 parity bits).
The string you're using as a key looks like a hexadecimal representation of 7 bytes, but instead of decoding it as hex, you're getting the bytes for the characters in the hex string.
Since there are 14 characters, you will most likely (depending on your encoding) end up with 14 bytes, which is too long for DES.
There are a couple of approaches described in this question that explain how to convert the hex string to a byte array.
That will however only get you so far, because you're still one byte short. A traditional approach seems to be to take the 56 bits you have and spread them out over 8 bytes, adding one parity bit to each byte. A Java example of how to do that is described in this answer. Another approach could be to just add a null byte at the end of the key. Which approach you should take depends on the key's intended usage, especially the way it is used by the other parties you're exchanging information with.
I'm trying to convert below java code into nodejs.
public static String encrypt(String accessToken) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
String merchantKey = "11111111111111111111";
String st = StringUtils.substring(merchantKey, 0, 16);
System.out.println(st);
Key secretKey = new SecretKeySpec(st.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedByte = cipher.doFinal(accessToken.getBytes());
// convert the byte to hex format
StringBuffer sb = new StringBuffer();
for (int i = 0; i < encryptedByte.length; i++) {
sb.append(Integer.toString((encryptedByte[i] & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
Here is what I was able to figure out-
function freeChargeEncryptAES(token){
var fcKey = "11111111111111111111".substring(0, 16);
var cipher = crypto.createCipher('aes-128-ecb', fcKey, "");
var encrypted = cipher.update(token,'ascii','hex');
encrypted += cipher.final('hex');
return encrypted;
}
I'm not able to get same output. For example if
token = "abcdefgh"
Java Code output - bc02de7c1270a352a98faa686f155df3
Nodejs Code output - eae7ec6943953aca94594641523c3c6d
I've read from this answer that by default encryption algorithm is aes-ecb which does not need IV. As the key length is 16, I'm assuming aes-128-ecb (16*8 = 128) is the algorithm that I should use.
Can someone help me figure out the problem ??
Just need to change -
crypto.createCipher('aes-128-ecb', fcKey, "");
to
crypto.createCipheriv('aes-128-ecb', fcKey, "");
Reason is simple - createCipher method treats second parameter as Encryption Password while it is an Encryption Key.
My bad, even after reading this answer, I've used wrong method (crypto.createCipher instead of crypto.createCipheriv). Below is proper working code in nodejs. That was all needed.
function freeChargeEncryptAES(token){
var fcKey = "11111111111111111111".substring(0, 16);
var cipher = crypto.createCipheriv('aes-128-ecb', fcKey, "");
var encrypted = cipher.update(token,'ascii','hex');
encrypted += cipher.final('hex');
return encrypted;
}
So this particular exception is pretty common, yet my problem is slightly different from what is usually asked.
I've got a AES decrytpion and encryption function defined as followed:
public static byte[] encrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes)
throws java.io.UnsupportedEncodingException,
NoSuchAlgorithmException,
NoSuchPaddingException,
InvalidKeyException,
InvalidAlgorithmParameterException,
IllegalBlockSizeException,
BadPaddingException {
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher;
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
return cipher.doFinal(textBytes);
}
public static byte[] decrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes)
throws java.io.UnsupportedEncodingException,
NoSuchAlgorithmException,
NoSuchPaddingException,
InvalidKeyException,
InvalidAlgorithmParameterException,
IllegalBlockSizeException,
BadPaddingException {
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
return cipher.doFinal(textBytes);
}
Now if I perform a single decryption like this:
System.out.println(Arrays.toString(
AES256Cipher.decrypt(ivBytes, HexBytePlainWriter.hexStringToByteArray(aKeys[a]),
AES256Cipher.encrypt(ivBytes, HexBytePlainWriter.hexStringToByteArray(aKeys[a]),
AES256Cipher.encrypt(ivBytes, HexBytePlainWriter.hexStringToByteArray(bKeys[b]), HexBytePlainWriter.hexStringToByteArray(zkeys[a^b]))
)
)));
The byte array outputs just fine. Whereas if I perform double encryption/decryption:
System.out.println("dec: " + HexBytePlainWriter.ByteToHexString(
AES256Cipher.decrypt(ivBytes, HexBytePlainWriter.hexStringToByteArray(aKeys[a]),
AES256Cipher.decrypt(ivBytes, HexBytePlainWriter.hexStringToByteArray(bKeys[b]),
AES256Cipher.encrypt(ivBytes, HexBytePlainWriter.hexStringToByteArray(aKeys[a]),
AES256Cipher.encrypt(ivBytes, HexBytePlainWriter.hexStringToByteArray(bKeys[b]), HexBytePlainWriter.hexStringToByteArray(zkeys[a^b]))
)
))));
I get the famous javax.crypto.BadPaddingException: Given final block not properly padded Exception. Note that a and b are just integers (assume they are both 0). Currently, the IVBytes is just an empty byte array of size 16, declared with new byte[16]. and aKeys and bKeys are both String arrays with AES encypted (random) strings (length 32 bytes).
These are the helper functions I use (to turn byte[] into a hex string and vice versa):
public static String ByteToHexString (byte[] data) {
StringBuilder buf = new StringBuilder();
for (byte b : data) {
int halfbyte = (b >>> 4) & 0x0F;
int two_halfs = 0;
do {
buf.append((0 <= halfbyte) && (halfbyte <= 9) ? (char) ('0' + halfbyte) : (char) ('a' + (halfbyte - 10)));
halfbyte = b & 0x0F;
} while (two_halfs++ < 1);
}
return buf.toString();
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
I suspect that the output of the first decryption malforms the ciphertext in such a way that the outer part throw the exception. I've check the size and the outer part outputs 32 bytes, so that should be OK. Does this not comply with the PKC5Padding?
Any help is much appreciated.
Edit:
It looks like my minimal example was faulty: I used key B first instead of A. Jon Skeet did give me an idea though. I will edit if I've got anything new.
Edit2:
The idea was correct. I was looping over a Garbled truth table (for those interested, check this Wikipedia article) and checking all possible ciphertexts (CT). The problem was if you pick an incorrect CT and use double decryption on it, it will throw the exception because the first decryption returns garbage. A simple check on the keys in the table fixed this.
You're using the wrong keys for decrypting. You're encrypting with key B, then encrypting the result with key A. You're then trying to decrypt that result with key B, and the final result with key A.
Each time you decrypt, you should be specifying the same key that was used to perform the "most recent" encryption operation. So:
Encrypt Encrypt Decrypt Decrypt
with B with A with A with B
Plain text -> encrypted I -> encrypted II -> encrypted I -> plain text
I'd also suggest that performing these operations one statement at a time, rather than having to read from the bottom up due to doing everything in a single statement, would make it a lot easier to understand what's going on.
I have data encrypted in ColdFusion that I need to be able to decrypt and encrypt to the same exact value using Java. I was hoping someone may be able to help me with this. I will specify everything used in ColdFusion except for the actual PasswordKey, which I must keep secret for security purposes. The PasswordKey is 23 characters long. It uses upper and lowercase letters, numbers, and the + and = signs. I know this is a lot to ask, but any help would be greatly appreciated.
I tried using a Java encryption example I found online and just replacing the line below with the 23 characters used in our CF app:
private static final byte[] keyValue = new byte[] {'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' };`
But I get the error:
java.security.InvalidKeyException: Invalid AES key length: 23 bytes
The CF code is:
Application.PasswordKey = "***********************";
Application.Algorithm = "AES";
Application.Encoding = "hex";
<cffunction name="encryptValue" access="public" returntype="string">
<cfargument name="strEncryptThis" required="yes">
<cfreturn Encrypt(TRIM(strEncryptThis), Application.PasswordKey, Application.Algorithm, Application.Encoding)>
</cffunction>
<cffunction name="decryptValue" access="public" returntype="string">
<cfargument name="strDecryptThis" required="yes">
<cfreturn Decrypt(TRIM(strDecryptThis), Application.PasswordKey, Application.Algorithm, Application.Encoding)>
</cffunction>
Your secret key is most likely a Base64 encoded key (23 chars should decode to about 16 bytes, which is the right length for a 128 bit key for AES).
So, in your java code, first run your secret key string through a Base64 decoder to get a byte[] of the appropriate length (16 bytes) for the AES algorithm.
128 but AES Encryption supports key key size of 16 bytes.
16 * 8 = 128 bits, even in the example the key is 16 bytes.
Sounds like your key is in Base64 so use Base64.decode(key or key.getBytes()) to get the byte array, check its in 16 bytes otherwise make it 16 bytes by padding.
Thank you everyone for your help. I wanted to post my final solution for others to use. I am including my entire encryption package code minus the specific password key (again for security). This code creates the same hex string as the CF code listed in the question, and decrypts it back to the proper english text string.
I found the bytesToHex and hexStringToByteArray functions in other question on stackoverflow, so my thanks to users maybeWeCouldStealAVan and Dave L. respectively also. I think I will look into other base 64 encoders/decoders in case the one from sun is ever made unavailable, but this definitely works for now. Thanks again.
package encryptionpackage;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.*;
public class encryption
{
// Note: The full CF default is "AES/ECB/PKCS5Padding"
private static final String ALGORITHM = "AES";
// The 24 character key from my CF app (base64 encoded)
// typically generated with: generateSecretKey("AES")
private static final String passKey = "***********************";
public static String encrypt(String valueToEnc) throws Exception
{
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encValue = c.doFinal(valueToEnc.getBytes());
String encryptedValue = bytesToHex(encValue);
return encryptedValue;
}
public static String decrypt(String encryptedValue) throws Exception
{
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = hexStringToByteArray(encryptedValue);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
private static Key generateKey() throws Exception
{
byte[] keyValue;
keyValue = new BASE64Decoder().decodeBuffer(passKey);
Key key = new SecretKeySpec(keyValue, ALGORITHM);
return key;
}
public static String bytesToHex(byte[] bytes)
{
final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ )
{
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] hexStringToByteArray(String s)
{
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2)
{
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}
AES Encryption only supports a key-size of 128 bits, 192 bits or 256 bits.
http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
You can't just take any byte array and use it as an AES key. In the sample code you see above, the example cleverly used 16 characters, which corresponds to a 128-bit key.
This is because 1 character or rather 1 byte corresponds to 8 bits.
A 16-value byte array will then correspond to 16 * 8 = 128 bits
23 characters = 23 * 8 = 184 bits, thus it is an invalid key-size.
You need either 16 characters, 24 characters, or 32 characters.
That being said, using merely characters for AES encryption is extremely insecure. Do use a proper and secure random key for encrypt purposes.
To generate a secure and random AES key:
SecureRandom random = new SecureRandom();
byte [] secret = new byte[16];
random.nextBytes(secret);
http://docs.oracle.com/javase/6/docs/api/java/security/SecureRandom.html
I'm working on a project and I need to encrypt a String with AES. The program needs to take be able to either take in a String and output an encrypted string in hex, along with a key, or, using a user-specified key and string, output unencrypted text (that is, the program needs to be able to do both of these things in different instances i.e. I should be able to put in "1234" on my machine and get out "Encrypted text: asdf Key: ghjk"; my friend should be able to put in "Encrypted text: asdf KEy: ghjk" on his and get out "1234" )
Here's what I have so far:
package betterencryption;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.util.Scanner;
public class BetterEncryption {
public static String asHex (byte buf[]) { //asHex works just fine, it's the main that's
//giving me trouble
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if (((int) buf[i] & 0xff) < 0x10)
strbuf.append("0");
strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
}
return strbuf.toString();
}
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
KeyGenerator kgen = KeyGenerator.getInstance("AES");kgen.init(128);
SecretKey skey = kgen.generateKey();
byte[] bytes = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(bytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
System.out.print("Do you want to encrypt or unencrypt?\n");/*This is a weird way of doing it,*/
String choice = sc.next(); char cc = choice.charAt(2); /*I know, but this part checks to see if*/
if(cc=='c'){ /*the program is to encrypt or unencrypt*/
System.out.print("Enter a string to encrypt: "); /* a string. The 'encrypt' function works.*/
String message = sc.next();
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal((args.length == 0 ? message : args[0]).getBytes());
System.out.println("Encrypted string: " + asHex(encrypted)+"\nKey: "+asHex(bytes));
//^This^ section actually works! The code outputs an encrypted string and everything.
//It's beautiful
//Unfortunately getting that string back into readable text has been problematic
//Which is where you guys come in!
//Hopefully
}
if(true){
System.out.print("\nEnter the encrypted string: "); String encryptedString = sc.next();
System.out.print("\nEnter the key: "); String keyString = sc.next();
int len = encryptedString.length(); /*this section converts the user-input string*/
byte[] encrypted = new byte[len / 2]; /*into an array of bytes*/
for (int i = 0; i < len; i += 2) { /*I'm not sure if it works, though*/
encrypted[i / 2] = (byte) ((Character.digit(encryptedString.charAt(i), 16) << 4)+
Character.digit(encryptedString.charAt(i+1), 16));
cipher.init(Cipher.DECRYPT_MODE, skeySpec); /*as you can see, I haven't even begun to implement*/
byte[] original = cipher.doFinal(encrypted);/*a way to allow the user-input key to be used.*/
String originalString = new String(original);
System.out.println("\nOriginal string: "+originalString); //I'm really quite stuck.
//can you guys help?
}
}
}
}
Well, hopefully someone can help me.
EDIT:
My biggest problems are converting String encryptedString into an sKeySpec and figuring out how to prevent the 'unencrypt' function from giving the user an error saying that the String they input was not properly padded. I know this isn't true because I've tried encrypting a String and then pasting what the encrypted form of it is into the unencryptor only to get an error. The program works fine if I eliminate all the "if" conditions and just have it encrypt a String and then unencrypt it in the same instance; I think this is due to the preservation of keyGen's Random Key
Your problem is this:
KeyGenerator kgen = KeyGenerator.getInstance("AES");kgen.init(128);
SecretKey skey = kgen.generateKey();
byte[] bytes = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(bytes, "AES");
As you've written it, your program is generating a new, random key every time it's run, which is never saved or displayed anywhere. Anything that you're encrypting with this key is effectively impossible to decrypt.
What you'll need to do is come up with some scheme for generating a secret key from the user input, rather than randomly generating it using KeyGenerator. How that scheme will work is up to you.
Depending on which AES Variant you use, your key needs to be 128, 192 or 256Bit long.
You can use a HashAlgorithm to generate a key with the specific length from the user-input.
String key;
byte[] keydata = hashFunctionToMakeToKeytheRightSize(key);
SecretKeySpec secretKeySpec = new SecretKeySpec(keydata, "AES");
Also see: java-aes-and-using-my-own-key