如何附加到AES加密文件 [英] How to append to AES encrypted file
问题描述
我正在编写某种会生成加密日志文件的记录器.不幸的是,密码学不是我的强项.现在,我可以向文件写入几条消息,然后关闭文件.然后我可以打开它,附加一些消息,再次关闭,解密后,我在文件中间看到填充字节.有什么方法可以处理加密文件,而不必每次我想添加一些消息时都将其解密?
I'm writing some kind of logger that produces encrypted log-file. Unfortunately, cryptography is not my strong side. Now I can write to file several messages and then close file. Then i can open it, append some messages, close again and after decryption I see padding bytes in the middle of file. Is there any way to work with encrypted file without having to decrypt it every time I want to append some messages?
编辑:更多细节.当前实现使用CipherOutputStream.据我了解,没有办法寻求使用它. 如果我将控制输出数据大小可被块大小整除,是否可以使用"NoPadding"选项?
EDIT: little more details. Current implementation utlizes CipherOutputStream. As i understand there's no way to seek using it. Can i use 'NoPadding' option if i will control that output data size is divisible by block size?
推荐答案
If you're using AES in CBC mode, you can use the second to last block as the IV to decrypt the last block, which may be only partially full, then again to encrypt the plaintext of the last block followed by the new plaintext.
这是概念证明:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AppendAES {
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 {
byte[] key = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
for (int i = 0; i<1000; i++) {
appendAES(new File("log.aes"),"All work and no play makes Jack a dull boy. ".getBytes("UTF-8"),key);
}
decryptAES(new File("log.aes"), new FileOutputStream("plain.txt"), key);
}
}
我想指出的是,输出与一次加密所有内容所产生的输出没有什么不同.这不是不是的自定义加密形式,它是标准的AES/CBC/PKCS5Padding.唯一特定于实现的细节是,在空白文件的情况下,我已经在开始数据之前编写了iv.
I'd like to point out that the output is no different than what would be produced by encrypting all in one run. This is not a custom form of encryption --- it's standard AES/CBC/PKCS5Padding. The only implementation-specific detail is that, in the case of a blank file, I've written the iv before beginning the data.
使用CipherOutputStream
改进了(针对我的口味)解决方案:
Improved (for my taste) solution using CipherOutputStream
:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AppendAES {
public static CipherOutputStream appendAES(File file, SecretKeySpec key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return appendAES(file, key, null);
}
public static CipherOutputStream appendAES(File file, SecretKeySpec key, SecureRandom sr) 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
if (sr == null) sr = new SecureRandom();
sr.nextBytes(iv);
rfile.write(iv);
} else {
// file length is at least 2 blocks
rfile.seek(rfile.length()-32);
rfile.read(iv);
byte[] lastBlockEnc = new byte[16];
rfile.read(lastBlockEnc);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
lastBlock = cipher.doFinal(lastBlockEnc);
rfile.seek(rfile.length()-16);
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] out;
if (lastBlock != null) {
out = cipher.update(lastBlock);
if (out != null) rfile.write(out);
}
CipherOutputStream cos = new CipherOutputStream(new FileOutputStream(rfile.getFD()),cipher);
return cos;
}
public static CipherInputStream decryptAES(File file, SecretKeySpec key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
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, key, new IvParameterSpec(iv));
CipherInputStream cis = new CipherInputStream(fin,cipher);
return cis;
}
public static void main(String[] args) throws Exception {
byte[] keyBytes = new byte[]{
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
};
SecretKeySpec key = new SecretKeySpec(keyBytes,"AES");
for (int i = 0; i<100; i++) {
CipherOutputStream cos = appendAES(new File("log.aes"),key);
cos.write("All work and no play ".getBytes("UTF-8"));
cos.write("makes Jack a dull boy. \n".getBytes("UTF-8"));
cos.close();
}
CipherInputStream cis = decryptAES(new File("log.aes"), key);
BufferedReader bread = new BufferedReader(new InputStreamReader(cis,"UTF-8"));
System.out.println(bread.readLine());
cis.close();
}
}
这篇关于如何附加到AES加密文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!