AES 加密,解密文件中有多余的垃圾字符
我正在 Android 应用程序中制作调试登录功能.我有一个简单的类,它使用 128 位 AES 加密记录到 .txt 文件.
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.
记录完成后,我用一个简单的 JAVA 程序解密记录的文件.
问题是当我解密加密日志时我得到了一些奇怪的内容,我也得到了加密的内容,但是有一些额外的字符,见下文.
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 应用日志部分:
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 + "
";
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);
}
}
}
输出解密后的日志(用notepad++打开):
T这是有效内容,但您也可以看到额外的 thrash 字符.如果我使用默认的 windows 文本编辑器打开,我也会得到 thrash 字符,但不同.
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.
这是我第一次尝试使用 encrypt -decrypt,我做错了什么?有什么想法吗?
This is my first try with encrypt -decrypt, what m i doing wrong? Any ideas?
推荐答案
AES 是一种仅适用于块的分组密码.您要加密的明文可以是任意长度,因此密码必须始终填充明文以将其填充到块大小的倍数(或者当它已经是块大小的倍数时添加一个完整的块).在此 PKCS#5/PKCS#7 填充中,每个填充字节表示填充字节数.
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.
简单的解决方法是在解密期间迭代 outputBytes
并删除始终在下一行的那些填充字节.一旦您使用多行日志消息或使用语义安全模式(稍后会详细介绍),这将中断.
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.
您当前使用 Cipher.getInstance("AES");
这是 Cipher.getInstance("AES/ECB/PKCS5Padding"); 的非完全限定版本;
.ECB 模式在语义上并不安全.它只是用 AES 和密钥加密每个块(16 个字节).因此,相同的块在密文中将是相同的.这尤其糟糕,因为一些日志消息以相同的开头,攻击者可能能够区分它们.这也是为什么整个文件的解密工作尽管被分块加密的原因.您应该使用随机 IV 的 CBC 模式.
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.
以下是一些示例代码,用于在 CBC 模式下正确使用 AES,并使用流进行随机 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;
}
相关文章