Use case 1 (working baseline):
Use case one is straightforward and is implemented / working.
In Java, Write a stream to disk in a single fell swoop.
Wrap output stream with symmetric cipher so that contents on disk are encrypted.
Later, read from disk. Wrap input stream with same symmetric cipher
in a single fell swoop so that contents retrieved from input stream
are plaintext and match original.
Use case 2 (no suitable solution determined):
In Java, Write a stream to disk.
Allow for subsequent bytes ("chunks") to be appended to file.
Wrap output stream with symmetric cipher so that contents on disk are encrypted.
Use same cipher so that all chunks are encrypted in the same manner.
Later, read from disk. Wrap input stream with same symmetric cipher
in a single fell swoop so that contents retrieved from input stream
are plaintext and match original.
Problem statement:
Encrypting and decrypting "abc" does not yield the same result as encrypting and decrypting "a", "b", and "c" separately, and therefore the "chunked" file described in use case 2 will no be successfully decrypted.
// e.g.
decrypt(encrypt("abc")) != decrypt(encrypt("a") + encrypt("b") + encrypt("c"))
The Actual Question:
... so the question is, how might one configure a Java cipher stream that can encrypt one chunk at a time, (a) without having prior knowledge of encrypted chunks, and (b) be decipherable using a single input stream cipher wrapper (without requiring knowledge of indexes where file was appended)...
Unfortunately, in this case you can't have your cake and eat it too.
You must either
write some length bytes at the start of each chunk, or
use an encryption algorithm where decrypt(encrypt("abc")) == decrypt(encrypt("a") + encrypt("b") + encrypt("c")) (aka trivial, and not recommended)
Number 1 is definitely a better choice, and is easier than you might think. Details below.
Number 2, you could use something like a Vigenere cipher, which would allow you to decrypt the whole file in one fell swoop, but would be a compromise in terms of encryption strength.
Details on number 1
The way you would do this is by reserving, for instance, four bytes (a 32-bit integer) at the beginning of each chunk. This integer represents the length of the chunk. To decrypt you would therefore:
Read the first four bytes and convert to integer n.
Read the next n bytes and decrypt.
Read the next four bytes and convert to integer n.
Read the next n bytes, decrypt and append to the first decrypted chunk.
Repeat steps 3 and 4 until end of file is reached.
And obviously this makes the chunk encryption easy because all you have to do is first write how many encrypted bytes you're about to append.
I found a solution close enough to my particular problem (stealing from this post), albeit slightly different from the problem statement (not a single stream).
public static void appendAES(File file, byte[] data, byte[] key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
RandomAccessFile rfile = new RandomAccessFile(file,"rw");
byte[] iv = new byte[16];
byte[] lastBlock = null;
if (rfile.length() % 16L != 0L) {
throw new IllegalArgumentException("Invalid file length (not a multiple of block size)");
} else if (rfile.length() == 16) {
throw new IllegalArgumentException("Invalid file length (need 2 blocks for iv and data)");
} else if (rfile.length() == 0L) {
// new file: start by appending an IV
new SecureRandom().nextBytes(iv);
rfile.write(iv);
// we have our iv, and there's no prior data to reencrypt
} else {
// file length is at least 2 blocks
rfile.seek(rfile.length()-32); // second to last block
rfile.read(iv); // get iv
byte[] lastBlockEnc = new byte[16];
// last block
// it's padded, so we'll decrypt it and
// save it for the beginning of our data
rfile.read(lastBlockEnc);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
lastBlock = cipher.doFinal(lastBlockEnc);
rfile.seek(rfile.length()-16);
// position ourselves to overwrite the last block
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
byte[] out;
if (lastBlock != null) { // lastBlock is null if we're starting a new file
out = cipher.update(lastBlock);
if (out != null) rfile.write(out);
}
out = cipher.doFinal(data);
rfile.write(out);
rfile.close();
}
public static void decryptAES(File file, OutputStream out, byte[] key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
// nothing special here, decrypt as usual
FileInputStream fin = new FileInputStream(file);
byte[] iv = new byte[16];
if (fin.read(iv) < 16) {
throw new IllegalArgumentException("Invalid file length (needs a full block for iv)");
};
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
byte[] buff = new byte[1<<13]; //8kiB
while (true) {
int count = fin.read(buff);
if (count == buff.length) {
out.write(cipher.update(buff));
} else {
out.write(cipher.doFinal(buff,0,count));
break;
}
}
fin.close();
}
public static void main(String[] args) throws Exception {
// prep the new encrypted output file reference
File encryptedFileSpec = File.createTempFile("chunked_aes_encrypted.", ".test");
// prep the new decrypted output file reference
File decryptedFileSpec = File.createTempFile("chunked_aes_decrypted.", ".test");
// generate a key spec
byte[] keySpec = new byte[]{0,12,2,8,4,5,6,7, 8, 9, 10, 11, 12, 13, 14, 15};
// for debug/test purposes only, keep track of what's written
StringBuilder plainTextLog = new StringBuilder();
// perform chunked output
for (int i = 0; i<1000; i++) {
// generate random text of variable length
StringBuilder text = new StringBuilder();
Random rand = new Random();
int n = rand.nextInt(5) + 1;
for (int j = 0; j < n; j++) {
text.append(UUID.randomUUID().toString()); // append random string
}
// record it for later comparison
plainTextLog.append(text.toString());
// write it out
byte[] b = text.toString().getBytes("UTF-8");
appendAES(encryptedFileSpec, b, keySpec);
}
System.out.println("Encrypted " + encryptedFileSpec.getAbsolutePath());
// decrypt
decryptAES(encryptedFileSpec, new FileOutputStream(decryptedFileSpec), keySpec);
System.out.println("Decrypted " + decryptedFileSpec.getAbsolutePath());
// compare expected output to actual
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] expectedDigest = md.digest(plainTextLog.toString().getBytes("UTF-8"));
byte[] expectedBytesEncoded = Base64.getEncoder().encode(expectedDigest);
System.out.println("Expected decrypted content: " + new String(expectedBytesEncoded));
byte[] actualBytes = Files.readAllBytes(Paths.get(decryptedFileSpec.toURI()));
byte[] actualDigest = md.digest(actualBytes);
byte[] actualBytesEncoded = Base64.getEncoder().encode(actualDigest);
System.out.println("> Actual decrypted content: " + new String(actualBytesEncoded));
}
Related
I'm trying to encrypt and decrypt plain text with AES("AES/CBC/PKCS7Padding") with Cipher.
Made secretKey by KeyGenerator and encrypt/decrypt with it.
This works well at first.
However, if plain text is a large string(such as 183244 characters), part of it is decrypted and not all of it.
"part of it" means
If plain text is "abcdefghijklmn", decrypted text comes like "abcdefgh"
(this is an example. actually this happens only with too long characters)
Code
// get key from Android kyestore
SecretKey secretKey = (SecretKey)mKeyStore.getKey(KEY_ALIAS, null);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
IvParameterSpec ivParams = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
//
// Encrypt
//
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
cipherOutputStream.write(plainString.getBytes("UTF-8"));
cipherOutputStream.close();
String encryptedString = Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
//
// Decrypt
//
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivParams);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.decode(encryptedString, Base64.DEFAULT));
CipherInputStream cipherInputStream = new CipherInputStream(byteArrayInputStream, decryptCipher);
outputStream = new ByteArrayOutputStream();
int b;
while ((b = cipherInputStream.read()) != -1) {
outputStream.write(b);
}
outputStream.close();
String decryptedString = outputStream.toString("UTF-8");
//
// results. decrypted string is part of source string and short.
//
int sourceStringLength = plainString.length(); // is 183244;
int decryptedStringLength = decryptedString.length()); // is UNEXPECTED 68553;
Could you please let know the point to fix this?
Edited
I've checked number of bytes on encryption/decryption.
encryption output bytes : "outputStream.toByteArray().length" is 68560
decryption input bytes : "Base64.decode(encryptedString, Base64.DEFAULT).length" is also 68560
I think this means decryption process doesn't lost any part of it and encryption process may truncate part of input plain text.
is this because of restriction of AES algorithm?
Solved
As #zaph commented, not all of the bytes is written to steam.
Replacing
cipherOutputStream.write(plainString.getBytes("UTF-8"));
cipherOutputStream.close();
with
// NOTE: workaround. too bit bytes doesn't writted correctly.
byte[] bytes = plainString.getBytes("UTF-8");
int oneBulkSize = 1024;// temp value for proof of concept. might be bigger one.
int numOfBulk = (bytes.length / oneBulkSize);
for (int i = 0; i < numOfBulk; i++) {
cipherOutputStream.write(bytes, oneBulkSize * i, oneBulkSize);
}
if ((bytes.length % oneBulkSize) != 0) {
cipherOutputStream.write(bytes, oneBulkSize * numOfBulk, bytes.length % oneBulkSize);
}
cipherOutputStream.close();
solved the issue.
AES does not have a data length limitation. The encrypted data should be the same length as the input plus up to an additional 16-bytes for padding.
Add some debugging code in the encryption loop and verify that the entire input is being read. Perhaps the encryption stream needs a loop as does the decryption stream.
Another possibility is that the 183244 is the byte size with UTF-16 encoding but you are reading as UTF-8, most of the UTF-16 characters can be as one UTF-8 byte.
The following is the actual solution code by the OP #nsht, included here for completeness (from a deleted answer by #nsht):
Replacing
cipherOutputStream.write(plainString.getBytes("UTF-8"));
cipherOutputStream.close();
with
// NOTE: workaround. too bit bytes doesn't writted correctly.
byte[] bytes = plainString.getBytes("UTF-8");
int oneBulkSize = 1024;// temp value for proof of concept. might be bigger one.
int numOfBulk = (bytes.length / oneBulkSize);
for (int i = 0; i < numOfBulk; i++) {
cipherOutputStream.write(bytes, oneBulkSize * i, oneBulkSize);
}
if ((bytes.length % oneBulkSize) != 0) {
cipherOutputStream.write(bytes, oneBulkSize * numOfBulk, bytes.length % oneBulkSize);
}
cipherOutputStream.close();
solved the issue.
I'm trying to encrypt a message in Java and decrypt it in Python. Unfortunately i'm just starting with python and am not able to get the decryption working.
That's my Java Code:
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = sr.generateSeed(16);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKey aesKey = keygen.generateKey();
//save byte array in text file to recreate key later
byte[] encodedKey = aesKey.getEncoded();
new File("myPath\\AESKey.txt");
FileOutputStream fos = new FileOutputStream("myPath\\AESKey.txt");
//save AesKey in first 16 bytes and Initial Vector in next 16 bytes
fos.write(encodedKey);
fos.write(iv);
fos.close();
String secretText = "Hello cryptography";
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivSpec);
byte[] encrypted = cipher.doFinal(secretText.getBytes());
BASE64Encoder myEncoder = new BASE64Encoder();
String encodedSecretText = myEncoder.encode(encrypted);
new File("myPath\\encodedSecretText.txt");
FileOutputStream fos2 = new FileOutputStream("myPath\\encodedSecretText.txt");
fos2.write(encodedSecretText.getBytes());
fos2.close();
I was able to decrypt the message with java, but not with python. I hope someone can show me how to do this.i copied the part with padding from another answer and assume that's the problem.
I get the message: ord() expected string of length 1, but int found.
Python:
from Crypto import Random
from Crypto.Cipher import AES
import base64
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
#read bytes of aesKey
file = open("myPath/AESKey.txt","rb")
aesKey = file.read(16)
iv = file.read(16)
file.close()
sec = open("myPath/encodedSecretText.txt")
for line in sec:
encodedSecretText = line.rstrip()
sec.close()
class AESCipher:
def __init__( self, key ):
self.key = key
def encrypt( self, raw ):
raw = pad(raw)
iv = Random.new().read( AES.block_size )
cipher = AES.new( self.key, AES.MODE_CBC, iv )
return base64.b64encode( iv + cipher.encrypt( raw ) )
def decrypt( self, enc ):
enc = base64.b64decode(enc)
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc[16:] ))
aes = AESCipher(aesKey)
print(aes.decrypt(encodedSecretText))
Thanks for any hint.
You're calling ord on an integer. Which is obviously illegal. The whole point of ord is that you give it a Unicode character, as a string, and it gives you back the numerical value of the code point.
So, why do you have a number? Well, I'm not sure what you expected to have, but let's look at what you actually have. If s[-1] is an integer, then s is some kind of sequence of integers. And s is the result of calling cipher.decrypt(). As the documentation for that function says, it returns a byte string. This isn't a specific type, just a description of a type—you can find out what the actual return value is with some basic debugging, maybe like this:
cipher = AES.new(self.key, AES.MODE_CBC, iv )
plaintext = cipher.decrypt(enc[16:])
print(type(plaintext), repr(plaintext))
return unpad(plaintext)
But I'm going to guess that it's a bytes object, which (quoting from http://docs.python.org/3/library/functions.html#bytes) …
is an immutable sequence of integers in the range 0 <= x < 256.
So, s[-1] is an integer in the range [0, 256). Hence the error.
So, what should you be doing instead? Well, why are you trying to call ord? You have a byte. Presumably what you want is a byte. So… just don't call anything there.
Meanwhile, there's at least one other serious error in your code:
for line in sec:
encodedSecretText = line.rstrip()
sec.close()
As pasted, this will raise an IndentationError. And if you indent both the second and third lines, you'll get an error for reading from a closed file. So presumably you want to indent just the second one. In which case, what you're doing is going through all of the lines, stripping the trailing whitespace off each, and then doing nothing with them. At the end of the loop, encodedSecretText holds the last line of encoded text, and all of the other lines are long forgotten and unrecoverable.
If you want to read all of the text into a list of lines, you will want something like this:
encodedSecretText = []
for line in sec:
encodedSecretText.append(line.rstrip())
Or, if you want to read it into one big string, with the newlines removed, you could do the above and then encodedSecretText = b''.join(encodedSecretText), or just do skip the whole loop and do encodedSecretText = sec.read().replace(b'\n', b'').
So I have been working with the Bouncycastle libraries in an attempt to connect with a remote server. This process has been problematic from the get go and now I'm close to getting everything working but some odd things are happening.
When I first started building out the encryption process I was told to use AES 256 with PKCS7Padding. After some nagging I was provided with a c++ example of the server code. It turned out that the IV is 256 bit so I had to use the RijndaelEngine instead. Also in order for this to work correctly I have to use ZeroBytePadding.
Here is my code:
socket = new Socket(remoteIP, port);
outputStream = new PrintWriter(socket.getOutputStream());
inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
byte[] base_64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes("UTF-8");
Security.addProvider(new BouncyCastleProvider());
public String AESEncrypt(String out) throws IOException, DataLengthException, IllegalStateException, InvalidCipherTextException {
byte[] EncKey = key;
byte randKey;
Random randNumber = new Random();
randKey = base_64[randNumber.nextInt(base_64.length)];
EncKey[randKey&0x1f] = randKey;
RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndaelEngine), new ZeroBytePadding());
ParametersWithIV keyParameter = new ParametersWithIV(new KeyParameter(EncKey), iv);
cipher.init(true, keyParameter);
byte[] txt = out.getBytes();
byte[] encoded = new byte[cipher.getOutputSize(txt.length)];
int len = cipher.processBytes(txt, 0, txt.length, encoded, 0);
cipher.doFinal(encoded, len);
char keyChar = (char) randKey;
String encString = new String(Base64.encode(encoded));
encString = encString.substring(0, encString.length()-1) + randKey;
return encString;
}
public void AESDecrypt(String in) throws DataLengthException, IllegalStateException, IOException, InvalidCipherTextException {
byte[] decKey = key;
byte[] msg = in.getBytes();
byte randKey = msg[msg.length-1];
decKey[randKey&0x1f] = randKey;
byte[] trimMsg = new byte[msg.length-1];
System.arraycopy(msg, 0, trimMsg, 0, trimMsg.length);
in = new String(trimMsg);
RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndaelEngine), new ZeroBytePadding());
ParametersWithIV keyParameter = new ParametersWithIV(new KeyParameter(decKey), iv);
cipher.init(false, keyParameter);
byte[] encoded = Base64.decode(in.trim());
byte[] decoded = new byte[cipher.getOutputSize(encoded.length)];
int len = cipher.processBytes(encoded, 0, encoded.length, decoded, 0);
cipher.doFinal(decoded, len);
String decString = new String(decoded);
}
Here is a test function I am using to send and receive messages:
public void serverTest() throws DataLengthException, IllegalStateException, InvalidCipherTextException, IOException {
//out = AESEncrypt(out);
outputStream.write(out + "\n");
outputStream.flush();
String msg = "";
while ((msg = inputStream.readLine()) != null) {
AESDecrypt(msg);
}
}
The key and iv don't change with the exception of the last byte in the key. If I am encrypting I get a random base64 char and change the last byte to that. If its decryption I get the last byte from the message and set the last value of the key to it for decryption.
In the c++ example there was an unencrypted message and two encrypted messages. I could deal with those fine.
Here is the problem, when I send my message to the remote server "encrypted" the app waits for a response until the connection times out but never gets one. If I send the message unencrypted I get either 7 responses which I can successfully decrypt and finally
org.bouncycastle.util.encoders.DecoderException: unable to decode base64 string:
String index out of range: -4 at org.bouncycastle.util.encoders.Base64.decode(Unknown Source)
or my last line before the error will look like this:
?"??n?i???el????s???!_S=??ah????CR??l6??]?{?l??Y?????Gn???+?????9!'??gU&4>??{X????G?.$c=??0?5??GP???_Q5????8??Z\?~???<Kr?????[2\ ???a$?C??z%?W???{?.?????eR?j????~?B"$??"z??W;???<?Yu??Y*???Z?K?e!?????f?;O(?Zw0B??g<???????????,)?L>???A"?????<?????W??#\???f%??j ?EhY/?? ?5R?34r???#?1??I??????M
If I set the encryption/decryption to use PKCS7Padding I get no response when my message is encrypted still but with decryption from the server I get between 2 to 6 responses and then
org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
I am at a loss with this. I don't know what I might be doing wrong so I have come here. I'm hoping the so community can point out my errors and guide me in the right direction.
I have a bit of an update I found my error in the encryption. I wasn't placing the random base64 value at the end of the encrypted string correctly so now I am doing like this.
encString += (char)randKey;
I can get response from the server now. Now the problem is I will some times get one or two readable lines but the rest are all garbage. I asked the individuals who run the server about it and they said in some c# code that they reference the have
return UTF8Encoding.UTF8.GetString(resultArray);
and thats all I have to go off of. I have tried UTF-8 encoding any place where I do getBytes or new String, and I have tried making the BurrferReader stream UTF-8 but it's still garbage.
Have you seedn the BCgit? this has bouncycastle code and examples. I am using the Csharp version in this repository. https://github.com/bcgit/bc-java
All crypto primitive examples are stored here: https://github.com/bcgit/bc-java/tree/master/core/src/test/java/org/bouncycastle/crypto/test
Try this code for testing Aes-CBC
private void testNullCBC()
throws InvalidCipherTextException
{
BufferedBlockCipher b = new BufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
KeyParameter kp = new KeyParameter(Hex.decode("5F060D3716B345C253F6749ABAC10917"));
b.init(true, new ParametersWithIV(kp, new byte[16]));
byte[] out = new byte[b.getOutputSize(tData.length)];
int len = b.processBytes(tData, 0, tData.length, out, 0);
len += b.doFinal(out, len);
if (!areEqual(outCBC1, out))
{
fail("no match on first nullCBC check");
}
b.init(true, new ParametersWithIV(null, Hex.decode("000102030405060708090a0b0c0d0e0f")));
len = b.processBytes(tData, 0, tData.length, out, 0);
len += b.doFinal(out, len);
if (!areEqual(outCBC2, out))
{
fail("no match on second nullCBC check");
}
}
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.