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
Related
I am building and Instagram bot which needs to login to Instagram. Now I know that Instagram uses an encryption system for its password, and I am trying to follow it but keep running into the
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: null
Basically Instagram needs a cipher text that follow the structure detailed in this github repo. My code is as follows to replicate it.
// Generate a random IV
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
// Get the system current time in milliseconds
long timestamp = System.currentTimeMillis();
// Converting public key to byte array from hex string
byte[] publicKeyBytes = hexStringToByteArray(publicKey);
// Create a random key
byte[] randomKey = new byte[32];
new SecureRandom().nextBytes(randomKey);
// rebuild key using SecretKeySpec
SecretKey publicKeyClass = new SecretKeySpec(randomKey,0, randomKey.length, "AES");
//generate the cipher text
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKeyClass, new GCMParameterSpec(128, iv));
// Add the current time in milliseconds as additional authenticated data
cipher.updateAAD(Long.toString(timestamp).getBytes(StandardCharsets.UTF_8));
byte[] cipherText = cipher.doFinal(pwd.getBytes(StandardCharsets.UTF_8));
// Get the tag from the end of the cipher text
byte[] tag = Arrays.copyOfRange(cipherText, cipherText.length - 16, cipherText.length);
// Now we encrypt a random key using the same public key
// use RSA encryption to encrypt the random key
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes, "RSA");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey encryptedKeyClass = keyFactory.generatePublic(keySpec);
Cipher cipher2 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher2.init(Cipher.ENCRYPT_MODE, encryptedKeyClass);
byte[] encryptedRandomKey = cipher2.doFinal(randomKey);
// Generate the final encrypted text
String encryptedText = publicKeyId+encryptedRandomKey.toString()+tag.toString()+cipherText.toString();
//Base64 encode the encrypted text
String encryptedTextBase64 = Base64.getEncoder().encodeToString(encryptedText.getBytes(StandardCharsets.UTF_8));
and the function hexStringtoByteArray
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 realize that it is a lot of code so here is a basic gist of what is going on
I pass a password and publickey(string) to the function.
We generate a random IV and a new random key.
I then use this random key to encrypt the password using AES_GCM_256 bit encryption.
Then the publickey(which was given by instagram) is 64 bytes long and appears to be hex encoded. So I decode it and use it as a key to encrypt the randomly generated key that I have created.
Finally I combine all of this in the format that insta wants and return the final cipher text.
Now my problem lies in the 4th step.I keep getting the error that the key is of invalid spec. The line that throws the error is
PublicKey encryptedKeyClass = keyFactory.generatePublic(keySpec);
Example Public Key that Instagram returns
c81814218a8fe89a6a5794ff5f2a192bf5ab9d3f7115bc8fbceb7b701079777c
I checked multiple online sources and they all seem to follow the same method I am doing. Am I doing something wrong? Thanks !
I am trying to encode in nodejs and decryption for the same in nodejs works well. But when I try to do the decryption in Java using the same IV and secret, it doesn't behave as expected.
Here is the code snippet:
Encryption in nodeJs:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
_ = require('lodash'),
secret = 'd6F3231q7d1942874322a#123nab#392';
function encrypt(text, secret) {
var iv = crypto.randomBytes(16);
console.log(iv);
var cipher = crypto.createCipheriv(algorithm, new Buffer(secret),
iv);
var encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
var encrypted = encrypt("8123497494", secret);
console.log(encrypted);
And the output is:
<Buffer 94 fa a4 f4 a1 3c bf f6 d7 90 18 3f 3b db 3f b9>
94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e
Code Snippet for decryption in JAVA:
public class Test {
public static void main(String[] args) throws Exception {
String s =
"94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e";
String seed = "d6F3231q7d1942874322a#123nab#392";
decrypt(s, seed);
}
private static void decrypt(String s, String seed)
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String parts[] = s.split(":");
String ivString = parts[0];
String encodedString = parts[1];
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] secretBytes = seed.getBytes("UTF-8");
IvParameterSpec ivSpec = new IvParameterSpec(hexStringToByteArray(ivString));
/*Removed after the accepted answer
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);*/
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
cipher.init(Cipher.DECRYPT_MODE, skey, ivSpec);
byte[] output = cipher.doFinal(hexStringToByteArray(encodedString));
System.out.println(new String(output));
}
}
Output: �s˸8ƍ�
I am getting some junk value in the response. Tried a lot of options, but none of them seem to be working. Any lead/help is appreciated.
In your JS code, you're using the 32-character string d6F3231q7d19428743234#123nab#234 directly as the AES key, with each ASCII character directly mapped to a single key byte.
In the Java code, you're instead first hashing the same string with MD5, and then using the MD5 output as the AES key. It's no wonder that they won't match.
What you probably should be doing, in both cases, is either:
randomly generating a string of 32 bytes (most of which won't be printable ASCII characters) and using it as the key; or
using a key derivation function (KDF) to take an arbitrary input string and turn it into a pseudorandom AES key.
In the latter case, if the input string is likely to have less than 256 bits of entropy (e.g. if it's a user-chosen password, most of which only have a few dozen bits of entropy at best), then you should make sure to use a KDF that implements key stretching to slow down brute force guessing attacks.
Ps. To address the comments below, MD5 outputs a 16-byte digest, which will yield an AES-128 key when used as an AES SecretKeySpec. To use AES-256 in Java, you will need to provide a 32-byte key. If trying to use a 32-byte AES key in Java throws an InvalidKeyException, you are probably using an old version of Java with a limited crypto policy that does not allow encryption keys longer than 128 bits. As described this answer to the linked question, you will either need to upgrade to Java 8 update 161 or later, or obtain and install an unlimited crypto policy file for your Java version.
In the Java code you are taking the MD5 hash of secret before using it as a key:
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
Whereas, in your NodeJS code, you don't do this anywhere. So you're using two different keys when encrypting and decrypting.
Don't copy and paste code without understanding it. Especially crypto code.
Faced with the same task (but with 128, it easy to adapt for 256), here is working Java/NodeJs code with comments.
It's additionally wrapped to Base64 to readability, but it's easy to remove if you would like.
Java side (encrypt/decrypt) :
import java.lang.Math; // headers MUST be above the first class
import java.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
// one class needs to have a main() method
public class MyClass
{
private static void log(String s)
{
System.out.print("\r\n"+s);
}
public static SecureRandom IVGenerator() {
return new SecureRandom();
}
// arguments are passed using the text field below this editor
public static void main(String[] args)
{
String valueToEncrypt = "hello, stackoverflow!";
String key = "3e$C!F)H#McQfTjK";
String encrypted = "";
String decrypted = "";
//ENCODE part
SecureRandom IVGenerator = IVGenerator();
byte[] encryptionKeyRaw = key.getBytes();
//aes-128=16bit IV block size
int ivLength=16;
byte[] iv = new byte[ivLength];
//generate random vector
IVGenerator.nextBytes(iv);
try {
Cipher encryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
encryptionCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKeyRaw, "AES"), new IvParameterSpec(iv));
//encrypt
byte[] cipherText = encryptionCipher.doFinal(valueToEncrypt.getBytes());
ByteBuffer byteBuffer = ByteBuffer.allocate(ivLength + cipherText.length);
//storing IV in first part of whole message
byteBuffer.put(iv);
//store encrypted bytes
byteBuffer.put(cipherText);
//concat it to result message
byte[] cipherMessage = byteBuffer.array();
//and encrypt to base64 to get readable value
encrypted = new String(Base64.getEncoder().encode(cipherMessage));
} catch (Exception e) {
throw new IllegalStateException(e);
}
//END OF ENCODE CODE
log("encrypted and saved as Base64 : "+encrypted);
///DECRYPT CODE :
try {
//decoding from base64
byte[] cipherMessageArr = Base64.getDecoder().decode(encrypted);
//retrieving IV from message
iv = Arrays.copyOfRange(cipherMessageArr, 0, ivLength);
//retrieving encrypted value from end of message
byte[] cipherText = Arrays.copyOfRange(cipherMessageArr, ivLength, cipherMessageArr.length);
Cipher decryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKeyRaw, "AES");
decryptionCipher.init(Cipher.DECRYPT_MODE,secretKeySpec , ivSpec);
//decrypt
byte[] finalCipherText = decryptionCipher.doFinal(cipherText);
//converting to string
String finalDecryptedValue = new String(finalCipherText);
decrypted = finalDecryptedValue;
} catch (Exception e) {
throw new IllegalStateException(e);
}
log("decrypted from Base64->aes128 : "+decrypted);
//END OF DECRYPT CODE
}
}
It could be easy be tested by online java compilers (this example prepared on https://www.jdoodle.com/online-java-compiler).
NodeJs decrypt side :
const crypto = require('crypto');
const ivLength = 16;
const algorithm = 'aes-128-ctr';
const encrypt = (value, key) => {
//not implemented, but it could be done easy if you will see to decrypt
return value;
};
function decrypt(value, key) {
//from base64 to byteArray
let decodedAsBase64Value = Buffer.from(value, 'base64');
let decodedAsBase64Key = Buffer.from(key);
//get IV from message
let ivArr = decodedAsBase64Value.slice(0, ivLength);
//get crypted message from second part of message
let cipherTextArr = decodedAsBase64Value.slice(ivLength, decodedAsBase64Value.length);
let cipher = crypto.createDecipheriv(algorithm, decodedAsBase64Key, ivArr);
//decrypted value
let decrypted = cipher.update(cipherTextArr, 'binary', 'utf8');
decrypted += cipher.final('utf8');
return decrypted;
}
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;
}
I'm making an app that encrypts some files. I want to use gnu's cryptix library. It says it is no longer developed since 2005, but I guess it has everything I need... should I use something else?
And I have a question about encrypting a single file. Right now I do it with a loop like this:
for(int i=0; i+block_size < bdata.length; i += block_size)
cipher.encryptBlock(bdata, i, cdata, i);
So my question is how to encrypt the last block that may not have the same size as the block_size. I was thinking maybe a should add some extra data to the last block, but than I don't know how to decrypt that...
I would strongly suggest using AES encryption and it too comes with the JAVA SDK. Have a look at: Using AES with Java Technology which will give you some great example. To read up more on AES see: Advanced Encryption Standard - Wikipedia.
Never use your own encryption scheme or an older form of an encryption scheme. AES has been tried and tested by people with far greater knowledge in that field then us, so you know it will work. Where as with your own or an old encryption scheme we might miss a fatal loop hole that will leave our data open to attacks.
See this question here to see the difference in the encryption schemes: Comparison of DES, Triple DES, AES, blowfish encryption for data
Addendum:
AES in java will work flawlessly for 192 and 256bit keys but you will have to install the newer JCE Policy Files. See here and here. You should also place the files in your JDK or else it wont work when executed from your IDE.
Note: Make sure you download the correct JCE policy files, depending on your Java version i.e 1.4, 1.5 1.6 or 7.
However if you use 128bit keys no need to install the newer JCE files.
Here is a template of some secure AES usage in java it use CBC/AES/PKCS5Padding and a random IV using RandomSecure.
Note you need both the key and IV for decrypting:
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* This program generates a AES key, retrieves its raw bytes, and then
* reinstantiates a AES key from the key bytes. The reinstantiated key is used
* to initialize a AES cipher for encryption and decryption.
*/
public class AES {
/**
* Encrypt a sample message using AES in CBC mode with a random IV genrated
* using SecyreRandom.
*
*/
public static void main(String[] args) {
try {
String message = "This string contains a secret message.";
System.out.println("Plaintext: " + message + "\n");
// generate a key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128); // To use 256 bit keys, you need the "unlimited strength" encryption policy files from Sun.
byte[] key = keygen.generateKey().getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
// build the initialization vector (randomly).
SecureRandom random = new SecureRandom();
byte iv[] = new byte[16];//generate random 16 byte IV AES is always 16bytes
random.nextBytes(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
// initialize the cipher for encrypt mode
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivspec);
System.out.println("Key: " + new String(key, "utf-8") + " This is important when decrypting");
System.out.println("IV: " + new String(iv, "utf-8") + " This is important when decrypting");
System.out.println();
// encrypt the message
byte[] encrypted = cipher.doFinal(message.getBytes());
System.out.println("Ciphertext: " + asHex(encrypted) + "\n");
// reinitialize the cipher for decryption
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivspec);
// decrypt the message
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Plaintext: " + new String(decrypted) + "\n");
} catch (IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
}
/**
* Turns array of bytes into string
*
* #param buf Array of bytes to convert to hex string
* #return Generated hex string
*/
public static String asHex(byte buf[]) {
StringBuilder strbuf = new StringBuilder(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();
}
}
I always use BouncyCastle
I also use the streaming framework instead of the for loop you were describing: it deals with the issue raised. Mostly I use that because when it comes to cryptography (and threading) I rarely trust my own code, I trust the people that live eat and breath it. Here is the code I use when I want "gash" cryptography. i.e. I have no particular threat model, and just want something "a little secure".
The hex encoding of the keys makes them much easier to manipulate / store and so on. I use "makeKey" to ... well ... make a key, then I can use the key in the encrypt and decrypt methods. You can obviously go back to using byte[] instead of hex strings for the keys.
private static boolean initialised;
private static void init() {
if (initialised)
return;
Security.addProvider(new BouncyCastleProvider());
initialised = true;
}
public static String makeKey() {
init();
KeyGenerator generator = KeyGenerator.getInstance(algorithm, provider);
generator.init(keySize);
Key key = generator.generateKey();
byte[] encoded = key.getEncoded();
return Strings.toHex(encoded);
}
public static String aesDecrypt(String hexKey, String hexCoded) {
init();
SecretKeySpec key = new SecretKeySpec(Strings.fromHex(hexKey), algorithm);
Cipher cipher = Cipher.getInstance(algorithm + "/ECB/PKCS5Padding", provider);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] codedBytes = Strings.fromHex(hexCoded);
CipherInputStream inputStream = new CipherInputStream(new ByteArrayInputStream(codedBytes), cipher);
byte[] bytes = getBytes(inputStream, 256);
String result = new String(bytes, "UTF-8");
return result;
}
public static String aesEncrypt(String hexKey, String input) {
init();
SecretKeySpec key = new SecretKeySpec(Strings.fromHex(hexKey), algorithm);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(input.length());
CipherOutputStream outputStream = new CipherOutputStream(byteArrayOutputStream, cipher);
setText(outputStream, input);
byte[] outputBytes = byteArrayOutputStream.toByteArray();
String output = new String(Strings.toHex(outputBytes));
return output;
}
public static void setText(OutputStream outputStream, String text, String encoding) {
try {
outputStream.write(text.getBytes(encoding));
outputStream.flush();
} finally {
outputStream.close();
}
}
public static byte[] getBytes(InputStream inputStream, int bufferSize) {
try {
List<ByteArrayAndLength> list = Lists.newList();
while (true) {
byte[] buffer = new byte[bufferSize];
int count = inputStream.read(buffer);
if (count == -1) {
byte[] result = new byte[ByteArrayAndLength.length(list)];
int index = 0;
for (ByteArrayAndLength byteArrayAndLength : list) {
System.arraycopy(byteArrayAndLength.bytes, 0, result, index, byteArrayAndLength.length);
index += byteArrayAndLength.length;
}
assert index == result.length;
return result;
}
list.add(new ByteArrayAndLength(buffer, count));
}
} finally {
inputStream.close();
}
}
static class ByteArrayAndLength {
byte[] bytes;
int length;
public ByteArrayAndLength(byte[] bytes, int length) {
super();
this.bytes = bytes;
this.length = length;
}
static int length(List<ByteArrayAndLength> list) {
int result = 0;
for (ByteArrayAndLength byteArrayAndLength : list) {
result += byteArrayAndLength.length;
}
return result;
}
}
I've taken out some of the exception catching to reduce the size of the code, and Strings.fromHex turns the string back into a byte[]
Maybe you should consider using a javax.crypto package.
Here is an example of how to use Ciphers:
DES encryption
Hope this helps
I would seriously think twice before going this route. The development of the software was halted because standard alternatives exist, and have a look at the mailing list, there's been no significant activity since 2009. In my book that means that the software is abandoned, and abandoned software means you're more or less on your own.
Have a look here on SO, there are several questions and answers that may help you like this one. An at first sight interesting package that could simplify things for you (but still using the standard JCE infrastructure) is jasypt
PHP Function:
$privateKey = "1234567812345678";
$iv = "1234567812345678";
$data = "Test string";
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $privateKey, $data, MCRYPT_MODE_CBC, $iv);
echo(base64_encode($encrypted));
Result: iz1qFlQJfs6Ycp+gcc2z4w==
Java Function
public static String encrypt() throws Exception{
try{
String data = "Test string";
String key = "1234567812345678";
String iv = "1234567812345678";
javax.crypto.spec.SecretKeySpec keyspec = new javax.crypto.spec.SecretKeySpec(key.getBytes(), "AES");
javax.crypto.spec.IvParameterSpec ivspec = new javax.crypto.spec.IvParameterSpec(iv.getBytes());
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(data.getBytes());
return new sun.misc.BASE64Encoder().encode(encrypted);
}catch(Exception e){
return null;
}
}
returns null.
Please note that we are not allowed to change the PHP code. Could somebody please help us get the same results in Java? Many thanks.
You'd have had a better idea of what was going on if you didn't simply swallow up possible Exceptions inside your encrypt() routine. If your function is returning null then clearly an exception happened and you need to know what it was.
In fact, the exception is:
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:854)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:828)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at Encryption.encrypt(Encryption.java:20)
at Encryption.main(Encryption.java:6)
And sure enough, your plaintext is only 11 Java characters long which, in your default encoding, will be 11 bytes.
You need to check what the PHP mcrypt_encrypt function actually does. Since it works, it is clearly using some padding scheme. You need to find out which one it is and use it in your Java code.
Ok -- I looked up the man page for mcrypt_encrypt. It says:
The data that will be encrypted with the given cipher and mode. If the size of the data is not n * blocksize, the data will be padded with \0.
So you need to replicate that in Java. Here's one way:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encryption
{
public static void main(String args[]) throws Exception {
System.out.println(encrypt());
}
public static String encrypt() throws Exception {
try {
String data = "Test string";
String key = "1234567812345678";
String iv = "1234567812345678";
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
// We need to pad with zeros to a multiple of the cipher block size,
// so first figure out what the size of the plaintext needs to be.
byte[] dataBytes = data.getBytes();
int plaintextLength = dataBytes.length;
int remainder = plaintextLength % blockSize;
if (remainder != 0) {
plaintextLength += (blockSize - remainder);
}
// In java, primitive arrays of integer types have all elements
// initialized to zero, so no need to explicitly zero any part of
// the array.
byte[] plaintext = new byte[plaintextLength];
// Copy our actual data into the beginning of the array. The
// rest of the array is implicitly zero-filled, as desired.
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return new sun.misc.BASE64Encoder().encode(encrypted);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
And when I run that I get:
iz1qFlQJfs6Ycp+gcc2z4w==
which is what your PHP program got.
Update (12 June 2016):
As of Java 8, JavaSE finally ships with a documented base64 codec. So instead of
return new sun.misc.BASE64Encoder().encode(encrypted);
you should do something like
return Base64.Encoder.encodeToString(encrypted);
Alternatively, use a 3rd-party library (such as commons-codec) for base64 encoding/decoding rather than using an undocumented internal method.