AES encryption, got extra trash characters in decrypted file - java
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.
Related
BadPaddingException with Cipher class
I'm coding a program that generates a simmetrical AES key, encodes it using an assimetrical public key, writes that encoded AES key in a plain text file, and then it recovers the encoded AES key and decodes it. This is the function that encodes the AES key and returns it as byte[]: private static byte[] EncodeKey(String valueToEncode, Key myKey) { byte[] encodedKey = null; try { //Cipher Cipher c = Cipher.getInstance("RSA", "SunJCE"); c.init(Cipher.ENCRYPT_MODE, myKey); //Encoding encodedKey = c.doFinal(valueToEncode.getBytes()); } catch (Exception ex) { System.out.println(ex.toString()); } //try return encodedKey; } //CifrarCadenaRSA Then I write this byte array in a txt file with this another function: private static void WriteTextFile(String path, byte[] valueToWrite) { try { //Check if file exists File f = new File(path); if (f.exists()) { f.delete(); } //if f.createNewFile(); //FileOutputStream FileOutputStream fos = new FileOutputStream(f); //Writing fos.write(valueToWrite); } catch (Exception ex) { System.out.println(ex.toString()); } //try } //EscribirArchivoTexto And finally, I load the encoded data and try to decode it: private static Key DecodeKeyFromFile(String path, Key keyToDecode) { //Clave descifrada Key decodedKey = null; try { //File File f = new File(ruta); //Reading text file byte[] encodedKey = Files.readAllBytes(f.toPath()); //Cipher for decoding Cipher c = Cipher.getInstance("RSA", "SunJCE"); c.init(Cipher.DECRYPT_MODE, clave); //Decoding: THIS IS THROWING BadPaddingException byte[] keyBytes = c.doFinal(encodedKey); decodedKey = (Key) new SecretKeySpec(claveBytes, 0, claveBytes.length, "AES"); } catch (Exception ex) { System.out.println(ex.toString()); } //try return decodedKey; } //CargarClaveCifrada As you can see, I use the same algorithm and padding for both encoding and decoding. I also tried using "RSA/ECB/PKCS1Padding" but nothing changes. So why is decoding throwing a BadPaddingException? The exception I get: javax.crypto.BadPaddingException: Decryption error And the trace: javax.crypto.BadPaddingException: Decryption error at sun.security.rsa.RSAPadding.unpadV15(Unknown Source) at sun.security.rsa.RSAPadding.unpad(Unknown Source) at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:356) at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389) at javax.crypto.Cipher.doFinal(Cipher.java:2165) at cifradoHibrido.CifradoHibrido.DecodeKeyFromFile(CifradoHibrido.java:197) at cifradoHibrido.CifradoHibrido.DecodeMessage(CifradoHibrido.java:174) at cifradoHibrido.CifradoHibrido.main(CifradoHibrido.java:98)
Android exception java.io.IOException: last block incomplete
I am working on decrypting a binary file encrypted in C# using Rijndael encryption method. The file is copied to an android device. The decryption logic works fine when run in a java based desktop test program. But it throws java.io.IOException: last block incomplete when run in android. I am using the code below. public static void Decrypt(String fileIn, String fileOut, byte[] key, byte[] IV, long offset) { // First we are going to open the file streams FileInputStream fsIn; try { fsIn = new FileInputStream(fileIn);,, FileOutputStream fsOut = new FileOutputStream(fileOut); // create cipher object Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(IV)); // create the encryption stream CipherInputStream cis = new CipherInputStream(fsIn, cipher); // set a buffer and keep writing to the stream int bufferLen = KiloByte; byte[] buffer = new byte[bufferLen]; int bytesRead = 0; // read a chunk of data from the input file while ( (bytesRead = cis.read(buffer, 0, bufferLen)) != -1) { // write to file fsOut.write(buffer, 0, bytesRead); } fsOut.flush(); // close streams fsOut.close(); cis.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } The key is generated by using the function public static byte[] GetKey(String password, byte[] IV, int length) throws NoSuchAlgorithmException, InvalidKeySpecException { // Length is kept 16 to make it compatible with all platforms SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); KeySpec ks = new PBEKeySpec(password.toCharArray(), IV, 1000, length*8); SecretKey s = f.generateSecret(ks); Key k = new SecretKeySpec(s.getEncoded(),"AES"); return k.getEncoded(); } I have gone through many posts on internet related to the topic. Based on that, I have made sure that I use byte array rather String. But still getting this issue.
Java AES PKCS7 - last block incomplete in decryption
I have an Python application that creates a few files encrypted with AES/PKCS7. I have to read those files with a Java service. But my code is throwing an exception: "javax.crypto.IllegalBlockSizeException: last block incomplete in decryption" Here's my decryption code: public String Decrypt(String strText) { try { // Text to decrypt byte[] test = strText.getBytes(); //bytKey is the same key as Python app SecretKeySpec objKey = new SecretKeySpec(bytKey, "AES"); Cipher objCipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); objCipher.init(Cipher.DECRYPT_MODE, objKey); // Here I got an exception >> byte[] bytValue = objCipher.doFinal(test); return new String(bytValue); } catch (Exception exc) { exc.printStackTrace(); } return ""; } If I decode the encrypted text before step into doFinal I get another exception: "javax.crypto.BadPaddingException: pad block corrupted" public String Decrypt(String strText) { try { BASE64Decoder decoder = new BASE64Decoder(); byte[] test = decoder.decodeBuffer(strText); SecretKeySpec objKey = new SecretKeySpec(bytKey, "AES"); Cipher objCipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); objCipher.init(Cipher.DECRYPT_MODE, objKey); byte[] bytValue = objCipher.doFinal(test); return new String(bytValue); } catch (Exception exc) { exc.printStackTrace(); } return ""; } I'm not an expert on encrypt/decrypt and I guess it's quite simple to solve. Any ideas to solve this issue? Thanks in advance!
Java - Missing final characters when encrypting using blowfish
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()
Encryption message in java
i am a project about using java's bouncycastle to do the encryption. However, when I encrypt the message, it throws an exception for me. javax.crypto.IllegalBlockSizeException: data not block size aligned I am using Blowfish/ECB/NoPadding, and the message is an xml. public static void main(String args[]){ String message = "<abc>ABCDEFG</abc>"; String key = "key"; byte[] b = encrypt(message.getBytes(), key.getBytes()); } public byte[] encrypt(byte encrypt[], byte en_key[]) { try { SecretKeySpec key = new SecretKeySpec(en_key, "Blowfish"); Cipher cipher = Cipher.getInstance("Blowfish/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, en_key); return cipher.doFinal(encrypt); } catch (Exception e) { e.printStackTrace(); return null; } } Could anyone can help me? Thank you
You are using NoPadding and the size of your input data must not match the block size of the cipher, so an IllegalBlockSizeException is being thrown. If you use NoPadding you need to make sure that your input is a multiple of 8 bytes. Specify a padding scheme. Change to Blowfish/CBC/PKCS5Padding and it should work. Padding it manually with null bytes: Create a new array with a bigger size that is a multiple of 8 and then copy the old array into it. public static byte[] encrypt(byte encrypt[], byte en_key[]) { if(encrypt.length % 8 != 0){ //not a multiple of 8 //create a new array with a size which is a multiple of 8 byte[] padded = new byte[encrypt.length + 8 - (encrypt.length % 8)]; //copy the old array into it System.arraycopy(encrypt, 0, padded, 0, encrypt.length); encrypt = padded; } try { SecretKeySpec key = new SecretKeySpec(en_key, "Blowfish"); Cipher cipher = Cipher.getInstance("Blowfish/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, key); return cipher.doFinal(encrypt); } catch (Exception e) { e.printStackTrace(); return null; } }
public static void main(String args[]){ String message = "<abc>ABCDEFG</abc>"; String key = "key"; byte[] b = encrypt(message.getBytes(), key.getBytes()); } public byte[] encrypt(byte encrypt[], byte en_key[]) { try { SecretKeySpec key = new SecretKeySpec(en_key, "Blowfish"); Cipher cipher = Cipher.getInstance("Blowfish/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, en_key); return cipher.doFinal(encrypt); } catch (Exception e) { e.printStackTrace(); return null; } }