I'm trying to encrypt/decrypt some files, which I will be reading/writing using FileIn/OutputStreams piped through CipherIn/OutputStreams. Fairly simple in concept, and I've gotten this to work using raw byte arrays and Cipher.doFinal. So I know my encryption parameters (bit size, iv size, etc.) are correct. (Or at least functional?)
I'm able to write data through a CipherOutputStream just fine. However, when I try to read that data back through a CipherInputStream, it hangs indefinitely.
The only related problem I've found remains unanswered, and is potentially fundamentally different from my problem, as my problem will always have all the data available on disk, as opposed to the related problem's reliance on Sockets.
I've tried a number of solutions, the most obvious one being changing the buffer size (data = new byte[4096];). I've tried a number of values, including the size of the plaintext and the size of the encrypted data. None of these values work. The only solution I've found is avoiding using a CipherInputStream altogether, and instead relying on Cipher.doFinal and Cipher.update.
Am I missing something? It would be very nice to be able to use a CipherInputStream, rather than having to reinvent the wheel by using Cipher.update.
SSCCE:
private static final String AES_ALG = "aes_256/gcm/nopadding";
private static final int GCM_TAG_SIZE = 128;
private static void doEncryptionTest() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, FileNotFoundException, IOException
{
File f = new File("encrypted_random_data.dat");
// 12-byte long iv
byte[] iv = new byte[] {0x27, 0x51, 0x34, 0x14, -0x65, 0x4d, -0x67, 0x35, -0x63, 0x11, -0x02, -0x05};
// 256-bit long key
byte[] keyBytes = new byte[] {0x55, -0x7f, -0x17, -0x29, -0x68, 0x25, 0x29, 0x5f, -0x27, -0x2d, -0x4d, 0x1b,
0x25, 0x74, 0x57, 0x35, -0x23, -0x1b, 0x12, 0x7c, 0x1, -0xf, -0x60, -0x42, 0x1c, 0x61, 0x3e, -0x5,
-0x13, 0x31, -0x48, -0x6e};
SecretKey key = new SecretKeySpec(keyBytes, "AES");
OutputStream os = encryptStream(key, iv, f);
System.out.println("generating random data...");
// 24MB of random data
byte[] data = new byte[25165824];
new Random().nextBytes(data);
System.out.println("encrypting and writing data...");
os.write(data);
os.close();
InputStream is = decryptStream(key, iv, f);
System.out.println("reading and decrypting data...");
// read the data in 4096 byte packets
int n;
data = new byte[4096];
while ((n = is.read(data)) > 0)
{
System.out.println("read " + n + " bytes.");
}
is.close();
}
private static OutputStream encryptStream(SecretKey key, byte[] iv, File f) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, FileNotFoundException
{
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
Cipher enc = Cipher.getInstance(AES_ALG);
enc.init(Cipher.ENCRYPT_MODE, key, spec);
OutputStream os = new CipherOutputStream(new FileOutputStream(f), enc);
return os;
}
private static InputStream decryptStream(SecretKey key, byte[] iv, File f) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, FileNotFoundException
{
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
Cipher dec = Cipher.getInstance(AES_ALG);
dec.init(Cipher.DECRYPT_MODE, key, spec);
InputStream is = new CipherInputStream(new FileInputStream(f), dec);
return is;
}
It doesn't hang, it's just very slow. The CipherInputStream has a fixed input buffer of size 512, meaning it invokes the Cipher#update(byte[], int, int) method with at most 512 bytes at a time. Decrypting manually with bigger buffer sizes makes it a lot faster.
The reason is that calling update 50 000 times with 512 bytes takes a lot longer than calling it, say, 400 times with 65 kilobytes. I'm not sure why exactly, but there seems to be a constant overhead that you have to pay for every call to update, regardless of the amount of data you pass it.
Additionally be aware that you cannot use AES GCM to decrypt large files. By design, Sun's implementation of the cipher buffers the whole ciphertext in memory before decrypting it. You'd have to split the plaintext into small enough chunks and encrypt each chunk individually.
See also https://crypto.stackexchange.com/questions/20333/encryption-of-big-files-in-java-with-aes-gcm and How come putting the GCM authentication tag at the end of a cipher stream require internal buffering during decryption?.
Related
I'm trying to 3DES encrypt a string and store it in a properties file using this example. The problem I'm having is I do not want to write the contents of encrypt() and decrypt() to a file directly from the methods. I want to store it in a string for use later.
Below are the methods I'm using.
As you can see this uses CipherOutputStream and CipherInputStream. How would I read the result of both encrypt() and decrypt() into a String instead of writing it out to file?
public static void encrypt(SecretKey key, InputStream in, OutputStream out)
throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, IOException {
// Create and initialize the encryption engine
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.ENCRYPT_MODE, key);
// Create a special output stream to do the work for us
CipherOutputStream cos = new CipherOutputStream(out, cipher);
// Read from the input and write to the encrypting output stream
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
}
cos.close();
// For extra security, don't leave any plaintext hanging around memory.
java.util.Arrays.fill(buffer, (byte) 0);
}
/**
* Use the specified TripleDES key to decrypt bytes ready from the input
* stream and write them to the output stream. This method uses uses Cipher
* directly to show how it can be done without CipherInputStream and
* CipherOutputStream.
*/
public static void decrypt(SecretKey key, InputStream in, OutputStream out)
throws NoSuchAlgorithmException, InvalidKeyException, IOException,
IllegalBlockSizeException, NoSuchPaddingException,
BadPaddingException {
// Create and initialize the decryption engine
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.DECRYPT_MODE, key);
// Read bytes, decrypt, and write them out.
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(cipher.update(buffer, 0, bytesRead));
}
// Write out the final bunch of decrypted bytes
out.write(cipher.doFinal());
out.flush();
}
Simple: instead of passing a file output stream to these methods - pass a different kind of stream, for example a ByteArrayOutputStream.
Then you can extract the encrypted data as string from that stream.
And to ensure that the final result is reasonable encoded, you should actually use some kind of Base64OutputStream which in turn writes to that ByteArrayOutputStream.
I want to encrypt and then decrypt file use AES. I have read many topics about error "Given final block not properly padded". But i don't find solution for me.
Sorry about specify the language of my code, i don't know write language java
Here is my code :
Variables
// IV, secret, salt in the same time
private byte[] salt = { 'h', 'u', 'n', 'g', 'd', 'h', '9', '4' };
public byte[] iv;
public SecretKey secret;
createSecretKey
public void createSecretKey(String password){
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), "AES");
}
method Encrypt
public void encrypt(String inputFile){
FileInputStream fis = new FileInputStream(inputFile);
// Save file: inputFile.enc
FileOutputStream fos = new FileOutputStream(inputFile + ".enc");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// Gen Initialization Vector
iv = (byte[]) ((IvParameterSpec) params
.getParameterSpec(IvParameterSpec.class)).getIV();
// read from file (plaint text) -----> save with .enc
int readByte;
byte[] buffer = new byte[1024];
while ((readByte = fis.read(buffer)) != -1) {
fos.write(cipher.doFinal(buffer), 0, readByte);
}
fis.close();
fos.flush();
fos.close();
}
method Decrypt
public void decrypt(String inputFile){
FileInputStream fis = new FileInputStream(inputFile);
// Save file: filename.dec
FileOutputStream fos = new FileOutputStream(inputFile.substring(0,
inputFile.length() - 4) + ".dec");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
// Read from file encrypted ---> .dec
int readByte;
byte[] buffer = new byte[1024];
while ((readByte = fis.read(buffer)) != -1) {
fos.write(cipher.doFinal(buffer), 0, readByte);
}
fos.flush();
fos.close();
fis.close();
}
Update
Solution: edit size of buffer is multiples of 16. Use CipherInput/ Output for read/ write file.
Tks Artjom B.
AES is a block cipher and as such only works on blocks of 16 bytes. A mode of operation such as CBC enables you to chain multiple blocks together. A padding such as PKCS#5 padding enables you to encrypt arbitrary length plaintext by filling the plaintext up to the next multiple of the block size.
The problem is that you're encrypting every 1024 bytes separately. Since 1024 divides the block size, the padding adds a full block before encryption. The ciphertext chunks are therefore 1040 bytes long. Then during decryption, you're only reading 1024 missing the padding. Java tries to decrypt it and then tries to remove the padding. If the padding is malformed (because it's not there), then the exception is thrown.
Easy fix
Simply increase your buffer for decryption to 1040 bytes.
Proper fix
Don't encrypt it in separate chunks, but either use Cipher#update(byte[], int, int) instead of Cipher.doFinal to update the ciphertext for every buffer you read or use a CipherInputStream.
Other security considerations:
You're missing a random IV. Without it, it may be possible for an attacker to see that you encrypted the same plaintext under the same key only by observing the ciphertexts.
You're missing ciphertext authentication. Without it, you can't reliably detect (malicious) changes in the ciphertexts and may open your system to attacks such as padding oracle attack. Either use an authenticated mode like GCM or run your created ciphertext through HMAC to create an authentication tag and write it to the end. Then you can verify the tag during/before decryption.
You are under the false assumption that the length of the encrypted data equals the length of the plain data, but the encrypted AES data is always a multiple of the AES block size (16 bytes) and can have an additional full padding block.
The most efficient way of dealing with stream encryption would be to use JCE's CipherOutputStream and CipherInputStream (http://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherInputStream.html). These classes do all the work for you.
Also, make sure you always save the newly generated IV in your encryption method to be able to use it for the decryption.
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));
}
I have an app that encrypts the pixels of an image and then decrypts them.
The encryption code is:
File file = new File(fullPath, "uoc" +format +".png");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteBuffer bb = ByteBuffer.allocate(image.getByteCount());
image.copyPixelsToBuffer(bb);
byte[] b = bb.array();
image.copyPixelsFromBuffer(ByteBuffer.wrap(encrypt_image(b, password)));
image.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] b2 = baos.toByteArray();
bos.write(b2);
bos.flush();
bos.close();
So right now I can open the file and I see noise, that's just what I want.
Once it works I need to decrypt the content of the image with no success :(
My code is similar than the one to encrypt:
ByteBuffer bb = ByteBuffer.allocate(image_file.getByteCount());
image_file.copyPixelsToBuffer(bb);
byte[] b = bb.array();
// Decrypt
decryptedData = Security.decrypt(key,b);
image_file.copyPixelsFromBuffer(ByteBuffer.wrap(decryptedData));
The problems is in Security.decrypt(key,b) because it throws pad bloc corrupted exception.
My encryption algorythm is:
public static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
and decryption:
public static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
And I have checked that the key is the same in both cases.
Anyone has an idea about how to solve that issue?
EDIT:
I have tried the next code but it throws exception as well:
ByteBuffer bb = ByteBuffer.allocate(image.getByteCount());
image.copyPixelsToBuffer(bb);
byte[] b = bb.array();
image.copyPixelsFromBuffer(ByteBuffer.wrap(Security.encrypt(Security.getRaw(password),b)));
//****
ByteBuffer bb2 = ByteBuffer.allocate(image.getByteCount());
image.copyPixelsToBuffer(bb2);
byte[] b_aux = bb.array();
// Decrypt
image.copyPixelsFromBuffer(ByteBuffer.wrap(Security.decrypt(Security.getRaw(password),b_aux)));
//*****
image.compress(Bitmap.CompressFormat.PNG, 100, baos);
Right now I am totally lost and my final project depends on this algorithm.
Anyone can help me??
The problem is that the encrypted bytes no longer fit the image bytes, the array size is going to be lost, and so on. Can't you just write the bytes immediately to a file?
It appears that you are PNG compressing the encrypted content. You need to do this in the reverse order. Perform any compression (such as PNG), then encrypt the result.
On the client side, decrypt the content, then decompress.
i think it should look something like:
image.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] encryptedPNG = Security.encrypt(Security.getRaw(password), boas.toByteArray());
//Decrypt
byte[] decryptedPNG = Security.decrypt(Security.getRaw(password), encryptedPNG);
image2.decompress(Bitmap.CompressFormat.PNG, new ByteArrayInputStream(decryptedPNG))
"Is there any algorythm to uncompress a bitmap so I could get the correct pixels?"
---I would very much like to know the answer to this one .I couldn't find a decompress method .currently Im storing the bitmap.compressed version(as a blob field in the database) and would like to continue doing so(i'm aware of the option of storing the whole image before calling compress, dont want to shift to that as yet). I badly need the full image for full image display, the compressed one is perfect for thumbnails and storage, any thoughts welcome.
I am using some java code that encrypts the contents of a text file using Blowfish. When I convert the encrypted file back (i.e. decrypt it) the string is missing a character from the end. Any ideas why? I am very new to Java and have been fiddling with this for hours with no luck.
The file war_and_peace.txt just contains the string "This is some text". decrypted.txt contains "This is some tex" (with no t on the end). Here is the java code:
public static void encrypt(String key, InputStream is, OutputStream os) throws Throwable {
encryptOrDecrypt(key, Cipher.ENCRYPT_MODE, is, os);
}
public static void decrypt(String key, InputStream is, OutputStream os) throws Throwable {
encryptOrDecrypt(key, Cipher.DECRYPT_MODE, is, os);
}
private static byte[] getBytes(String toGet)
{
try
{
byte[] retVal = new byte[toGet.length()];
for (int i = 0; i < toGet.length(); i++)
{
char anychar = toGet.charAt(i);
retVal[i] = (byte)anychar;
}
return retVal;
}catch(Exception e)
{
String errorMsg = "ERROR: getBytes :" + e;
return null;
}
}
public static void encryptOrDecrypt(String key, int mode, InputStream is, OutputStream os) throws Throwable {
String iv = "12345678";
byte[] IVBytes = getBytes(iv);
IvParameterSpec IV = new IvParameterSpec(IVBytes);
byte[] KeyData = key.getBytes();
SecretKeySpec blowKey = new SecretKeySpec(KeyData, "Blowfish");
//Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/NoPadding");
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(Cipher.ENCRYPT_MODE, blowKey, IV);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
} else if (mode == Cipher.DECRYPT_MODE) {
cipher.init(Cipher.DECRYPT_MODE, blowKey, IV);
CipherOutputStream cos = new CipherOutputStream(os, cipher);
doCopy(is, cos);
}
}
public static void doCopy(InputStream is, OutputStream os) throws IOException {
byte[] bytes = new byte[4096];
//byte[] bytes = new byte[64];
int numBytes;
while ((numBytes = is.read(bytes)) != -1) {
os.write(bytes, 0, numBytes);
}
os.flush();
os.close();
is.close();
}
public static void main(String[] args) {
//Encrypt the reports
try {
String key = "squirrel123";
FileInputStream fis = new FileInputStream("war_and_peace.txt");
FileOutputStream fos = new FileOutputStream("encrypted.txt");
encrypt(key, fis, fos);
FileInputStream fis2 = new FileInputStream("encrypted.txt");
FileOutputStream fos2 = new FileOutputStream("decrypted.txt");
decrypt(key, fis2, fos2);
} catch (Throwable e) {
e.printStackTrace();
}
}
`
There is a couple of things not optimal here.
But let's first solve your problem. The reason why the last portion of your input is somehow missing is the padding you specify: none! Without specifying a padding, the Cipher can just operate on full-length blocks (8 bytes for Blowfish). Excess input that is less than a block long will be silently discarded, and there's your missing text. In detail: "This is some text" is 17 bytes long, so two full blocks will be decrypted, and the final 17th byte, "t", will be discarded.
Always use a padding in combination with symmetric block ciphers, PKCS5Padding is fine.
Next, when operating with Cipher, you don't need to implement your own getBytes() - there's String#getBytes already doing the job for you. Just be sure to operate on the same character encoding when getting the bytes and when reconstructing a String from bytes later on, it's a common source of errors.
You should have a look at the JCE docs, they will help you avoiding some of the common mistakes.
For example, using String keys directly is a no-go for symmetric cryptography, they do not contain enough entropy, which would make it easier to brute-force such a key. The JCE gives you theKeyGenerator class and you should always use it unless you know exactly what you are doing. It generates a securely random key of the appropriate size for you, but in addition, and that is something people tend to forget, it will also ensure that it doesn't create a weak key. For example, there are known weak keys for Blowfish that should be avoided in practical use.
Finally, you shouldn't use a deterministic IV when doing CBC encryption. There are some recent attacks that make it possible to exploit this, resulting in total recovery of the message, and that's obviously not cool. The IV should always be chosen at random (using a SecureRandom) in order to make it unpredictable. Cipher does this for you by default, you can simply obtain the used IV after encryption with Cipher#getIV.
On another note, less security-relevant: you should close streams in a finally block to ensure they're closed at all cost - otherwise you will be left with an open file handle in case of an exception.
Here's an updated version of your code that takes all these aspects into account (had to use Strings instead of files in main, but you can simply replace it with what you had there):
private static final String ALGORITHM = "Blowfish/CBC/PKCS5Padding";
/* now returns the IV that was used */
private static byte[] encrypt(SecretKey key,
InputStream is,
OutputStream os) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
return cipher.getIV();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static void decrypt(SecretKey key,
byte[] iv,
InputStream is,
OutputStream os)
{
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static void doCopy(InputStream is, OutputStream os)
throws IOException {
try {
byte[] bytes = new byte[4096];
int numBytes;
while ((numBytes = is.read(bytes)) != -1) {
os.write(bytes, 0, numBytes);
}
} finally {
is.close();
os.close();
}
}
public static void main(String[] args) {
try {
String plain = "I am very secret. Help!";
KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish");
SecretKey key = keyGen.generateKey();
byte[] iv;
InputStream in = new ByteArrayInputStream(plain.getBytes("UTF-8"));
ByteArrayOutputStream out = new ByteArrayOutputStream();
iv = encrypt(key, in, out);
in = new ByteArrayInputStream(out.toByteArray());
out = new ByteArrayOutputStream();
decrypt(key, iv, in, out);
String result = new String(out.toByteArray(), "UTF-8");
System.out.println(result);
System.out.println(plain.equals(result)); // => true
} catch (Exception e) {
e.printStackTrace();
}
}
You have your CipherInputStream and CipherOutputStream mixed up. To encrypt, you read from a plain inputstream and write to a CipherOutputStream. To decrypt ... you get the idea.
EDIT:
What is happening is that you have specified NOPADDING and you are attempting to encrypt using a CipherInputStream. The first 16 bytes form two valid complete blocks and so are encrypted correctly. Then there is only 1 byte left over, and when the CipherInputStream class receives the end-of-file indication it performs a Cipher.doFinal() on the cipher object and receives an IllegalBlockSizeException. This exception is swallowed, and read returns -1 indicating end-of-file. If however you use PKCS5PADDING everything should work.
EDIT 2:
emboss is correct in that the real issue is simply that it is tricky and error-prone to use the CipherStream classes with the NOPADDING option. In fact, these classes explicitly state that they silently swallow every Security exception thrown by the underlying Cipher instance, so they are perhaps not a good choice for beginners.
Keys are binary, and String is not a container for binary data. Use a byte[].
When I had this problem I had to call doFinal on the cipher:
http://docs.oracle.com/javase/1.4.2/docs/api/javax/crypto/Cipher.html#doFinal()