In my project a textfile is chosen and become encrypted. The encrypted text is saved seperatly as well as the key. Now I try to create a program which is decrypting the file when the right keyfile is available. I think the decrypting program needs to look pretty like the encrypting program just in DECRYPT_MODE. When I read in the key I don't know how to do the next step at it to decrypt the textfile. Maybe anyone can help me how I use the key from .txt file and use it to decrypt the encoded file.
The encrypting program:
public class encrypt {
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException {
//Key is created and saved in File
KeyGenerator keygenerator = KeyGenerator.getInstance("AES");
SecretKey myDesKey = keygenerator.generateKey();
String encodedKey = Base64.getEncoder().encodeToString(myDesKey.getEncoded());
Path keypath = Paths.get("C:/xxx/key.txt");
Path keyfile = Files.createFile(keypath);
Files.write(keyfile, encodedKey.getBytes(), StandardOpenOption.WRITE);
Cipher desalgCipher;
desalgCipher = Cipher.getInstance("AES");
desalgCipher.init(Cipher.ENCRYPT_MODE, myDesKey);
Path target = Paths.get("C:/xxx/encrypted.txt");
Path file = Files.createFile(target);
Path path = Paths.get("test.txt");
try(InputStream is = Files.newInputStream(path);
CipherInputStream cipherIS = new CipherInputStream(is, desalgCipher);
BufferedReader reader = new BufferedReader(new InputStreamReader(cipherIS));){
String line;
while((line = reader.readLine()) != null){
System.out.println(line);
Files.write(file, line.getBytes(), StandardOpenOption.WRITE);
}
}
}
}
Decrypt: read in the key and decrypt it
public class decrypt {
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException {
try {
File fileDir = new File("C:/Users/JT/Desktop/key.txt");
BufferedReader in = new BufferedReader(
new InputStreamReader(new FileInputStream(fileDir), "UTF-8"));
String str;
while ((str = in.readLine()) != null) {
System.out.println(str);
}
in.close();
}
catch (UnsupportedEncodingException e)
{
System.out.println(e.getMessage());
}
catch (IOException e)
{
System.out.println(e.getMessage());
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
byte[] decodedKey = Base64.getDecoder().decode(sb.toString());
SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
SecretKeySpec key = new SecretKeySpec(sb.toString().getBytes(), "Base64");
Cipher desalgCipher;
desalgCipher = Cipher.getInstance("AES");
desalgCipher.init(Cipher.DECRYPT_MODE, key);
Path path = Paths.get("encrypted.txt"); // path to your file
try(InputStream is = Files.newInputStream(path); // get an IS on your file
CipherInputStream cipherIS = new CipherInputStream(is, desalgCipher); // wraps stream using cipher
BufferedReader reader = new BufferedReader(new InputStreamReader(cipherIS));){ // init reader.
String line;
while((line = reader.readLine()) != null){
System.out.println(line);
}
}
}
}
Your application is not being programmed the right way. Currently you try to encrypt by wrapping the input stream with a CipherInputStream instance. Then this instance again is wrapped with a BufferedReader instance.
So what you are doing is to first convert the bytes of the input file - probably text - into ciphertext. This ciphertext can contain any byte value. Then you try to read those bytes in line-by-line using the default character set and line endings. Obviously after encryption even the notion of lines doesn't exist anymore, so you'll loose data in that final step.
Then you convert back to bytes, which you then (somehow) try to decrypt. This will obviously fail as you lost data during the readLine statement.
What you should do is to read in the file using bytes. You can then write to a CipherOutputStream. If the file with the ciphertext needs to be actual text you can use a Base64 stream which the new java.util.Base64 nicely provides.
Only once you programmed the encryption correctly you can try and reverse the process. As long as data is lost obviously the decryption will fail (with an error or garbage output, depending on the mode and your luck).
If you're unlucky you will end up with code that works 99% of the time. So good luck and heed the comments: don't try and perform encryption without understanding what you're doing. It will end with tears - or a smashed keyboard.
Related
In my java program I would like to read a .txt file in and encode it afterwards. I know how to read a File in and tried to learn how to encode an array. The problem I have is that I don't know how to combine it, it doesn't work the way I tried it.
Here's the part I can read in my text file with:
public class ReadFile {
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("test.txt");
BufferedReader br = new BufferedReader(fr);
String zeile = "";
do
{
zeile = br.readLine();
System.out.println(zeile);
}
while (zeile != null);
br.close();
}
}
In this part I can encrypt and decrypt bytes:
public class Crypt {
public static void main(String[] args) {
try{
KeyGenerator keygenerator = KeyGenerator.getInstance("DES");
SecretKey myDesKey = keygenerator.generateKey();
Cipher desalgCipher;
desalgCipher = Cipher.getInstance("DES");
byte[] text = "test".getBytes("UTF8");
desalgCipher.init(Cipher.ENCRYPT_MODE, myDesKey);
byte[] textEncrypted = desalgCipher.doFinal(text);
String s = new String(textEncrypted);
System.out.println(s);
desalgCipher.init(Cipher.DECRYPT_MODE, myDesKey);
byte[] textDecrypted = desalgCipher.doFinal(textEncrypted);
s = new String(textDecrypted);
System.out.println(s);
}
catch(Exception e)
{
System.out.println("Error");
}
}
}
I thought to read the text file in and put it in a string to encode it, but I think it is way too complex. Is there another way to connect them, or is another way for encoding required?
I strongly advise you to use Streams ( see https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html & https://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html) rather than directly using a FileReader.
Encryption happens at a lower level (on bytes) than what you're trying to do.
Java ciphers offer the convenient CipherInputStream (and CipherOutputStream ) to encrypt byte streams on the fly. It's much cheaper and more scalable than trying to dump a whole file in a single byte[] (moreso because you're decoding and re-encoding the file content).
If you want an example of use, please look at the following snippet :
public static void encrypt(Path inputFile, OutputStream output) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException {
// init cipher
KeyGenerator keygenerator = KeyGenerator.getInstance("DES");
SecretKey myDesKey = keygenerator.generateKey();
Cipher desalgCipher;
desalgCipher = Cipher.getInstance("DES");
desalgCipher.init(Cipher.ENCRYPT_MODE, myDesKey);
try(InputStream is = Files.newInputStream(inputFile); // get an IS on your file
CipherInputStream cipherIS = new CipherInputStream(is, desalgCipher)){ // wraps input Stream with cipher
copyStreams(cipherIS, output); // copyStream is let to the implementer's choice.
}
}
And I'll let you figure out how to decrypt.
EDIT :
A common way to communicate encrypted bytes without fear for encoding issues is to encode the raw bytes with base 64.
You can wrap the outputStream with Base64.getEncoder().wrap(os)
FileReader/FileWriter are the wrong (old utility) classes, as they use the current platform encoding, and a file encrypted on one computer (Greek Windows) would not be decryptable on another computer (Linux server).
Text in java, String, is in Unicode. One cannot (should not) throw arbitrary bytes into a String.
So the following cannot be done
new String(textEncrypted); // Uses the default platform encoding
new String(textEncrypted, "UTF-8"); // Probably the bytes are not valid UTF-8
So do:
Path path = Paths.get("text.txt");
byte[] content = Files.readAllBytes(path);
content = encrypt(content);
Files.write(path, content);
Can you take an inputStream from a png file encrypt and send out as an encrypted stream and save it as a png picture file?
After encryption I want to decrypt the file and view it, but am not able to see the photo from the decrypted stream.
When I save an OutputStream I am not able to view the file, encrypted or decrypted. I'm not throwing any exceptions I am just not able to see the photo after decrypting an encrypted version.
My attempt do decrypt the file looks like this.
public void testDecryptionOfPhoto() throws Exception{
File file = new File(getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "encryptedTest.png");
InputStream inputStream = new FileInputStream(file);
InputStream decryptedPhoto = decryption.decryptInputStream(inputStream);
File file2 = new File(getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "photo.png");
OutputStream outputStream = new FileOutputStream(file2);
IOUtils.copy(decryptedPhoto,outputStream);
outputStream.close();
Encryption
public InputStream encryptInputStream(InputStream inputStream) throws Exception{
KeyCipher keyCiper = new KeyCipher();
String streamContent = CharStreams.toString(new InputStreamReader(inputStream, "UTF-8"));
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(ENCRYPT_MODE, keyCipher.getSecretSpecKey(), keyCipher.getIvParameterSpec());
InputStream encryptedStream = new ByteArrayInputStream(encodeToString(cipher.doFinal(streamContent.getBytes("UTF-8")), DEFAULT).getBytes());
return encryptedStream;
}
Decryption
public InputStream decryptInputStream(InputStream inputStream) throws Exception{
KeyCipher keyCipher = new keyCipher();
String streamContents = CharStreams.toString(new InputStreamReader(inputStream, "UTF-8"));
byte[] encrypted = Base64.decode(streamContents, DEFAULT);
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.DECRYPT_MODE, keyCipher.getSecretSpecKey(), keyCipher.getIvParameterSpec());
byte[] decryptedBytes = cipher.doFinal(encrypted);
InputStream decryptedStream = new ByteArrayInputStream(decryptedBytes);
return decryptedStream;
You cannot use char or toString() to handle encrypted streams. And also not utf-8 in any way. Its not text that you are streaming. Just stream the encrypted bytes and receive them as bytes.
Im making a debug loggin function in an android app.
I have a simple class which is logging to .txt file using 128 bit AES encryption.
After the logging is done, i decrypt the logged file with a simple JAVA program.
The problem is when i decrypt the encrypted log i got some weird content in it, i also got the encrypted content, but there are some extra characters, see below.
Android app logging part:
public class FileLogger {
//file and folder name
public static String LOG_FILE_NAME = "my_log.txt";
public static String LOG_FOLDER_NAME = "my_log_folder";
static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS");
//My secret key, 16 bytes = 128 bit
static byte[] key = {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6};
//Appends to a log file, using encryption
public static void appendToLog(Context context, Object msg) {
String msgStr;
String timestamp = "t:" + formatter.format(new java.util.Date());
msgStr = msg + "|" + timestamp + "\n";
File sdcard = Environment.getExternalStorageDirectory();
File dir = new File(sdcard.getAbsolutePath() + "/" + LOG_FOLDER_NAME);
if (!dir.exists()) {
dir.mkdir();
}
File encryptedFile = new File(dir, LOG_FILE_NAME);
try {
//Encryption using my key above defined
Key secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] outputBytes = cipher.doFinal(msgStr.getBytes());
//Writing to the file using append mode
FileOutputStream outputStream = new FileOutputStream(encryptedFile, true);
outputStream.write(outputBytes);
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
}
And this is the decrypter JAVA program:
public class Main {
//output file name after decryption
private static String decryptedFileName;
//input encrypted file
private static String fileSource;
//a prefix tag for output file name
private static String outputFilePrefix = "decrypted_";
//My key for decryption, its the same as in the encrypter program.
static byte[] key = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };
//Decrypting function
public static void decrypt(byte[] key, File inputFile, File outputFile) throws Exception {
try {
Key secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
FileInputStream inputStream = new FileInputStream(inputFile);
byte[] inputBytes = new byte[(int) inputFile.length()];
inputStream.read(inputBytes);
byte[] outputBytes = cipher.doFinal(inputBytes);
FileOutputStream outputStream = new FileOutputStream(outputFile, true);
outputStream.write(outputBytes);
inputStream.close();
outputStream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
//first argument is the intput file source
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Add log file name as a parameter.");
} else {
fileSource = args[0];
try {
File sourceFile = new File(fileSource);
if (sourceFile.exists()) {
//Decrption
decryptedFileName = outputFilePrefix + sourceFile.getName();
File decryptedFile = new File(decryptedFileName);
decrypt(key, sourceFile, decryptedFile);
} else {
System.out.println("Log file not found: " + fileSource);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Decryption done, output file: " + decryptedFileName);
}
}
}
Output decrypted log (Opened with notepad++):
There is the valid content, but you also can see the extra thrash characters. If I open with the default windows text editor i also got thrash charaters, but different ones.
This is my first try with encrypt -decrypt, what m i doing wrong?
Any ideas?
AES is a block cipher which only works on blocks. The plaintext that you want to encrypt can be of any length, so the cipher must always pad the plaintext to fill it up to a multiple of the block size (or add a complete block when it already is a multiple of the block size). In this PKCS#5/PKCS#7 padding each padding byte denotes the number of padded bytes.
The easy fix would be to iterate over outputBytes during decryption and remove those padding bytes which are always on the next line. This will break as soon as you use multiline log messages or use a semantically secure mode (more on that later).
The better fix would be to write the number of bytes for each log message before the message, read that and decrypt only that many bytes. This also probably easier to implement with file streams.
You currently use Cipher.getInstance("AES"); which is a non-fully qualified version of Cipher.getInstance("AES/ECB/PKCS5Padding");. ECB mode is not semantically secure. It simply encrypts each block (16 bytes) with AES and the key. So blocks that are the same will be the same in ciphertext. This is particularly bad, because some log messages start the same and an attacker might be able to distinguish them. This is also the reason why the decryption of the whole file worked despite being encrypted in chunks. You should use CBC mode with a random IV.
Here is some sample code for proper use of AES in CBC mode with a random IV using streams:
private static SecretKey key = generateAESkey();
private static String cipherString = "AES/CBC/PKCS5Padding";
public static void main(String[] args) throws Exception {
ByteArrayOutputStream log = new ByteArrayOutputStream();
appendToLog("Test1", log);
appendToLog("Test2 is longer", log);
appendToLog("Test3 is multiple of block size!", log);
appendToLog("Test4 is shorter.", log);
byte[] encLog = log.toByteArray();
List<String> logs = decryptLog(new ByteArrayInputStream(encLog));
for(String logLine : logs) {
System.out.println(logLine);
}
}
private static SecretKey generateAESkey() {
try {
return KeyGenerator.getInstance("AES").generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private static byte[] generateIV() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
return iv;
}
public static void appendToLog(String s, OutputStream os) throws Exception {
Cipher cipher = Cipher.getInstance(cipherString);
byte[] iv = generateIV();
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] data = cipher.doFinal(s.getBytes("UTF-8"));
os.write(data.length);
os.write(iv);
os.write(data);
}
public static List<String> decryptLog(InputStream is) throws Exception{
ArrayList<String> logs = new ArrayList<String>();
while(is.available() > 0) {
int len = is.read();
byte[] encLogLine = new byte[len];
byte[] iv = new byte[16];
is.read(iv);
is.read(encLogLine);
Cipher cipher = Cipher.getInstance(cipherString);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] data = cipher.doFinal(encLogLine);
logs.add(new String(data, "UTF-8"));
}
return logs;
}
You've encrypted each log message with a distinct encryption context. When you call the doFinal method on the cipher object the plaintext is padded out to a multiple of 16. Effectively, your log file is sequence of many small encrypted messages. However on decryption you are ignoring these message boundaries and treating the file as a single encrypted message. The result is that the padding characters are not being properly stripped. What you are seeing as 'trash' characters are likely these padding bytes. You will need to redesign your logfile format, either to preserve the message boundaries so the decryptor can discover them or to eliminate them altogether.
Also, don't use defaults in Java cryptography: they're not portable. For example, Cipher.getInstance() takes a string of the form alg/mode/padding. Always specify all three. I notice you also use the default no-args String.getBytes() method. Always specify a Charset, and almost always "UTF8" is the best choice.
I'm trying to encrypt/decrypt text from a file but i am receiving the following error:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
I'm using AES 128 bit with AES/ECB/PKCS5Padding. Any idea why I am getting this error?
Here is my code:
public class AES_Encryption {
public static void main(String[] args) throws Exception {
String str = new Scanner(new File("src//plainText.txt")).useDelimiter("\\Z").next();
FileWriter fstream = new FileWriter("src//cipherText.txt");
BufferedWriter out = new BufferedWriter(fstream);
FileWriter fstream2 = new FileWriter("src//decrpytedText.txt");
BufferedWriter out2 = new BufferedWriter(fstream2);
System.out.println("" + str);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
Key key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = cipher.doFinal(str.getBytes());
String ct = new String(cipherText);
System.out.println( new String(cipherText, "UTF8") );
out.append(ct);
out.close();
String cipherT = new Scanner(new File("src//cipherText.txt")).useDelimiter("\\Z").next();
cipher.init(Cipher.DECRYPT_MODE, key);
//byte[] decVal = Base64.decode(cipherT.getBytes());
byte[] newPlainText = cipher.doFinal(cipherT.getBytes());
String dt = new String(newPlainText, "UTF8");
out2.append(dt);
out2.close();
}
}
Your error is treating the ciphertext as a string:
String ct = new String(cipherText); // <--- Noooo!
There will be values in your byte array that cannot be expressed as characters in the default charset.
Always treat your ciphertext as a byte array, even when reading or writing to files.
One get the IllegalBlockSizeException in following case as mentioned in Cipher API documentaion:
IllegalBlockSizeException - If this cipher is a block cipher, no padding has been requested (only in encryption mode), and the total input length of the data
processed by this cipher is not a multiple of block size
In Your case you are Encrypting the String correctly , But while decryption you are treating the cipherText as String and then
you are putting cipherT.getBytes() byte array in doFinal method of Cipher. The byte array conversion of String is not same as reading byte array from the file in binary mode.
The functionality and limitation of String.toBytes() as mentioned in String API documentaion is as follows:
Encodes this String into a sequence of bytes using the platform's default charset, storing the result into a new byte array. The
behavior of this method when this string cannot be encoded in the
default charset is unspecified. The CharsetEncoder class should be
used when more control over the encoding process is required.
What I suggest for you is to read the cipherText.txt File in binary mode and then put the byte array you got after reading the file in doFinal method of Cipher . I have modified your code in following way:
public class AES_Encryption {
public static void main(String[] args) throws Exception {
String str = new Scanner(new File("plainText.txt")).useDelimiter("\\t").next();
FileOutputStream fstream = new FileOutputStream("cipherText.txt");
BufferedOutputStream out = new BufferedOutputStream(fstream);
FileOutputStream fstream2 = new FileOutputStream("decrpytedText.txt");
BufferedOutputStream out2 = new BufferedOutputStream(fstream2);
System.out.println("INPUT String:\n" + str);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
Key key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = cipher.doFinal(str.getBytes());
System.out.println("ENCRYPTED String:\n"+new String(cipherText, "UTF8") );
out.write(cipherText);
out.flush();
out.close();
//String cipherT = new Scanner(new File("cipherText.txt")).nextLine();
BufferedInputStream bfin = new BufferedInputStream(new FileInputStream(new File("cipherText.txt")));//To read the file in Binary Mode.
cipher.init(Cipher.DECRYPT_MODE, key);
int BUFFERSIZE = 1024;
byte[] readBytes = new byte[BUFFERSIZE];
byte[] data = null;
int totalRead = -1;
while( (totalRead = bfin.read(readBytes))!=-1)
{
byte[] temp = new byte[(data == null ? totalRead : data.length)];
System.arraycopy((data==null ? readBytes : data),0,temp,0, temp.length);
data = new byte[(data == null ? 0 : data.length) + totalRead];
System.arraycopy(temp, 0, data, 0, temp.length);
System.arraycopy(readBytes, 0, data, data.length - temp.length, totalRead);
}
if (data!=null)
{
byte[] newPlainText = cipher.doFinal(data);
out2.write(newPlainText);
out2.flush();
System.out.println("DECRYPTED String:\n"+new String(newPlainText,"UTF8"));
}
else
{
System.out.println("No Data Found");
}
//String dt = new String(newPlainText, "UTF8");
out2.close();
}
}
I hope this would help you in resolving the exception you getting ...
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()