AES加密,在解密文件中获得额外的垃圾字符

我在Android应用程序中进行调试登录function。 我有一个简单的类, 使用128位AES加密记录到.txt文件。

记录完成后, 我用一个简单的JAVA程序解密记录的文件。

问题是,当我解密加密日志时我得到了一些奇怪的内容,我也得到了加密内容,但是还有一些额外的字符,见下文。

Android应用日志记录部分:

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(); } } } 

这是解密器JAVA程序:

 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); } } } 

输出解密日志(用记事本++打开):

在此处输入图像描述

这里是有效内容,但您也可以看到额外的捶打字符。 如果我打开默认的Windows文本编辑器,我也会得到捶打特征,但不同的。

这是我第一次尝试使用encrypt -decrypt, mi做错了什么? 有任何想法吗?

AES是块密码,仅适用于块。 您要加密的明文可以是任意长度,因此密码必须始终填充明文以将其填充到块大小的倍数(或者当它已经是块大小的倍数时添加完整块)。 在此PKCS#5 / PKCS#7填充中,每个填充字节表示填充字节的数量。

简单的解决方法是在解密期间迭代outputBytes并删除总是在下一行的填充字节。 一旦您使用多行日志消息或使用语义安全模式(稍后将详细介绍),这将中断。

更好的解决方法是在消息之前写入每条日志消息的字节数,读取该消息并仅解密那么多字节。 这也可能更容易实现文件流。

您目前使用Cipher.getInstance("AES"); 这是Cipher.getInstance("AES/ECB/PKCS5Padding");的非完全限定版本Cipher.getInstance("AES/ECB/PKCS5Padding"); 。 ECB模式在语义上不安全。 它只使用AES和密钥加密每个块(16个字节)。 所以相同的块在密文中是相同的。 这特别糟糕,因为一些日志消息启动相同,攻击者可能能够区分它们。 这也是为什么尽管以块为单位加密整个文件的解密仍然有效的原因。 您应该使用随机IV的CBC模式。

下面是一些示例代码,用于在CBC模式下正确使用AES,并使用随机IV流:

 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 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 decryptLog(InputStream is) throws Exception{ ArrayList logs = new ArrayList(); 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; } 

您已使用不同的加密上下文加密每条日志消息。 当您在密码对象上调用doFinal方法时,明文将被填充到16的倍数。实际上,您的日志文件是许多小加密消息的序列。 但是,在解密时,您忽略这些消息边界并将文件视为单个加密消息。 结果是填充字符没有被正确剥离。 你看到的’垃圾’字符可能是这些填充字节。 您需要重新设计日志文件格式,以保留消息边界,以便解密器可以发现它们或完全消除它们。

另外,不要在Java加密中使用默认值:它们不可移植。 例如, Cipher.getInstance()采用alg/mode/paddingforms的字符串。 始终指定所有三个。 我注意到你也使用默认的无参数String.getBytes()方法。 总是指定一个Charset,几乎总是“UTF8”是最好的选择。