英文:
Best way to decrypt a file on the fly
问题
我有一个加密的CSV文件,每一行都是加密的,如下所示:
- 加密的行1<新行字符>
- 加密的行2<新行字符>
- 加密的行3<新行字符>
加密密钥:3B047F64FC0DC09ECFA7C59C2C84FBB703F074474459B4A8690A9229297B77F6
示例文件:
FE8C7D902C44957624DBA215F4DE1DF08E5B44A29E57176C222D1B040201ED520C920063A8F83A623EC7590F96DEB9E714DF52E826E1219915936765D86C19FEC8B7974023711A9706458A203BADEA36A44530A262B470D0983716A5A533472A
03269E5BC0BC1BF9411E44A0AE7E9490AE27F1E42770020B342BCB1171F1D757AFDAC75F7FA8F6C753CA9DA5AA831FD4878C761ECEBBE261BE0D67B12F15DBFB03311C72138120FB174C56AD4676AF19
F8E7A3201B9DDFAA11A3017DC3756706E832DA95A387C7889FE37DB586A20102793E70A7378A54AFFEF1E1239F56BD1589A7B9348847D1BE7A78759C93BA6A534F83A0C5FC59BCFABAB3E47510C354D4
CF813CADE9778381CF68E613E6E9D86E22F0C413A76A4B6C429FB9EDA20C7F2582B293D35FD2206C6E7AEB96464DA0EF22005D019274E3AC32A5861D7C068EFFA5395D86DEB48C4A31E928A7B9720708
我有一个以java.io.Reader为输入的函数。我需要解密这个文件,然后即时提供解密后文件内容的java.io.Reader。
我的代码:
String path = "path-to-file";
File file = new File(path);
String encryptionKey = "encryptionKey";
SecretKeySpec secKeySpec = new SecretKeySpec(Hex.decode(encryptionKey), "AES");
byte[] ivBytes = new byte[16];
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secKeySpec, ivParameterSpec);
Reader reader = new InputStreamReader(new FileInputStream(file), UTF_8);
BufferedReader rd = new BufferedReader(reader);
String line = "";
while ((line = rd.readLine()) != null) {
System.out.println(new String(cipher.doFinal(Hex.decode(line))));
}
我需要以最有效的方式将HEX解码和解密后的字符串包装成java.io.Reader。(文件大小可能非常大)。
我有一个使用java.io.Reader读取其字符以执行一些业务逻辑的函数。
我不想解密并写入文件,然后重新读取文件。我需要即时或以安全的方式执行此操作。
最佳方法是什么?
英文:
I have an encrypted CSV file that is encrypted line by line. i.e:
- encrypted_row1<new_line_char>
- encrypted_row2<new_line_char>
- encrypted_row3<new_line_char>
Encryption key: 3B047F64FC0DC09ECFA7C59C2C84FBB703F074474459B4A8690A9229297B77F6
Example File:
> FE8C7D902C44957624DBA215F4DE1DF08E5B44A29E57176C222D1B040201ED520C920063A8F83A623EC7590F96DEB9E714DF52E826E1219915936765D86C19FEC8B7974023711A9706458A203BADEA36A44530A262B470D0983716A5A533472A
03269E5BC0BC1BF9411E44A0AE7E9490AE27F1E42770020B342BCB1171F1D757AFDAC75F7FA8F6C753CA9DA5AA831FD4878C761ECEBBE261BE0D67B12F15DBFB03311C72138120FB174C56AD4676AF19
F8E7A3201B9DDFAA11A3017DC3756706E832DA95A387C7889FE37DB586A20102793E70A7378A54AFFEF1E1239F56BD1589A7B9348847D1BE7A78759C93BA6A534F83A0C5FC59BCFABAB3E47510C354D4
CF813CADE9778381CF68E613E6E9D86E22F0C413A76A4B6C429FB9EDA20C7F2582B293D35FD2206C6E7AEB96464DA0EF22005D019274E3AC32A5861D7C068EFFA5395D86DEB48C4A31E928A7B9720708
I have a function that takes java.io.Reader as input. I need to decrypt this file then provide a java.io.Reader of the decrypted file content on the fly.
My code:
String path = "path-to-file";
File file = new File(path);
String encryptionKey = "encryptionKey";
SecretKeySpec secKeySpec = new SecretKeySpec(Hex.decode(encryptionKey), "AES");
byte[] ivBytes = new byte[16];
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secKeySpec, ivParameterSpec);
Reader reader = new InputStreamReader(new FileInputStream(file), UTF_8);
BufferedReader rd = new BufferedReader(reader);
String line = "";
while ((line = rd.readLine()) != null) {
System.out.println(new String(cipher.doFinal(Hex.decode(line))));
}
I need to wrap the HEX decoded and decrypted string in a java.io.Reader in the most efficient way possible. ( File sizes can be very large ).
I have a function that use java.io.Reader to read its chars to do some business logic on it.
I don't want to decrypt and write the file then re-read it. I need to do this on the fly or in a secure way
What can be the best way to do this?
答案1
得分: 2
这可以通过CipherInputStream/CipherOutputStream类来实现。
然而,这里有一个重要的警告:某些加密模式中包含身份验证,如果身份验证是您的加密模式的一部分,
那么在安全地进行即时解密是不可能的。
关键是:如果攻击者向您发送一条消息,并且您开始基于解密数据运行代码,但您不知道数据是否真实,那么这些数据很可能是恶意的。因此,要安全地处理加密数据,您必须在处理接收到的数据之前解密和验证身份验证。
英文:
This can be achieved with the CipherInputStream/CipherOutputStream classes.
However, there is an important caveat here: Some encryption modes have authentication in them, and if authentication is part of your encryption mode,
then it is impossible to decrypt on the fly securely.
The point is the following: If an attacker sends you a message, and you start running code based on data you decrypt, but you don't know the data is authentic, the data may very well be malicious. Therefore, to act securely on encrypted data, you must decrypt and validate the authentication before acting on the data received.
答案2
得分: 1
你的用例是一种混合了标准流/读取器实现中不完全可用工具的情况。
它的特殊之处在于,你从一个读取器过程(加密文件的行)开始,然后每行都必须回退到基于字节的处理过程(Cipher 操作),并且你希望输出具有字符语义(读取器),以便传递给 CSV 解析器。这个字符/字节/字符的部分并不简单。
我将使用的过程是创建另一个读取器,我称之为 LineByLineProcessingReader
,它允许逐行消耗输入,然后处理它,然后将每行的输出作为读取器可用。
对于这个目的,实际的处理过程并不重要。你的处理实现可能是十六进制解码,然后解密,然后转换回字符串,但它也可能是其他任何处理。
棘手的部分在于使过程符合读取器 API。
在符合读取器 API 时,你可以选择扩展 FilterReader
或直接扩展 Reader
本身。通常使用的是过滤器版本。
我选择不扩展过滤器版本,因为总体上,我的处理过程不会暴露原始文件的内容。由于过滤器的实现始终回退到原始读取器的实现,这种遮罩意味着需要重新实现所有内容,这会带来很多工作。
另一方面,如果我直接覆盖 Reader
,我只需要正确实现一个方法,因为实际上 Reader
需要表达的只是 read(buf, o, c)
方法,其他方法都是在其上实现的。
我选择实现的策略类似于在某个数据源上创建自己的 Iterator
实现。我预取了输入文件的一行,并将其作为 Reader
在内部变量中提供。
完成这些后,我只需要确保每次前一个读取完全后,都会预取这个读取器变量(例如当前行),但不是在前一个读取器之前。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class LineByLineProcessingReader extends Reader {
// 省略了类的其余部分...
@Override
public void close() throws IOException {
input.close();
closedOrFinished = true;
}
}
还需要在 processRawLine
方法中插入你的解密过程。
一个快速的类测试(你可能需要进一步检查):
import junit.framework.TestCase;
import org.junit.Assert;
// 省略了导入语句...
public class LineByLineProcessingReaderTest extends TestCase {
// 省略了测试方法...
}
英文:
You use case is a mixture of tools that are not quite available in the standard stream/reader implementations.
What makes is special is that you start with a reader process (lines of the encrypted files), then each line has to fall back to a bytes based process (Cipher operations), and you want the output to have character semantics (reader) to pass on to a CSV parser. This char/byte/char part is not trivial.
The process I would use is to create another reader, I call it the LineByLineProcessingReader
which allows to consume the input line by line, then process it, then make the output of each line available as a Reader.
The process really does not matter for this purpose. Your process implementation would be to Hex decode, then decypher, then convert back to string, but it might very well be just anything.
The tricky part is all in making the process conform to the Reader API.
When conforming to the Reader API, you have a choice, of extending FilterReader
or Reader
itself. The usual being the filter version.
I choose not to extend the filter version, because on the whole, my process is not to ever expose the original file's contents. As the filter's implementation always fallback to the orignal reader's, this maksing would imply re-implementing everything, which incurs a lot of work.
On the other hand, if I override Reader
directly, I only have one method to get right, because everything Reader
really has to express is the read(buf, o, c)
method, all other are implemented on top of it.
The strategy I chose to implement it, is akin to creating you own Iterator
implementation over some source of data. I prefetch a line of the input file, and make it available as a Reader
, in an internal variable.
That being done, I only need to make sure that this reader variable (e.g. current line) is always prefetched each time the previous has been fully read, just not before.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class LineByLineProcessingReader extends Reader {
/** What to use as a line break. Becuase bufferedReader does not report the line breaks, we insert them manually using this */
final String lineBreak;
/** The original input being read */
final BufferedReader input;
/** A reader for the current processed line. */
private StringReader currentReader;
private boolean closedOrFinished = false;
/**
* Creates a reader that will ingest an input line by line,
* then process it (default implementation is a no-op),
* then recreate a reader for it, until there is no more line.
*
* @param in a Reader object providing the underlying stream.
* @throws NullPointerException if <code>in</code> is <code>null</code>
*/
protected LineByLineProcessingReader(BufferedReader in, String lineBreak) {
this.input = in;
this.lineBreak = lineBreak;
}
public int read(char[] cbuf, int off, int len) throws IOException {
ensureNextLine();
// Check end of input
if (currentReader == null) {
return -1;
}
int read = currentReader.read(cbuf, off, len);
// Edge case : if current reader was at its end
if (read < 0) {
currentReader = null;
// Recurse to go fetch next line.
return read(cbuf, off, len);
}
// General case, we have our result.
// We may have read less than was asked (in length), but it's contractually OK.
return read;
}
/**
* Advances the underlying input to the next line, and makes it available
* for reading inside the {@link #currentReader}
*/
private void ensureNextLine() throws IOException {
// Do not try to read if closed or already finished
if (closedOrFinished) {
return;
}
// Check if there is still data to be read
if (currentReader != null) {
return;
}
String nextLine = input.readLine();
if (nextLine == null) {
// Nothing was left to read, we are bailing out.
currentReader = null;
closedOrFinished = true;
return;
}
// We have a new line, process it and publish it as a reader
String processedLine = processRawLine(nextLine);
currentReader = new StringReader(processedLine+lineBreak);
}
/**
* Performs a process of the raw line read from the underlying source.
* @param rawLine the raw line read
* @return a processed line
*/
protected String processRawLine(String rawLine) {
return rawLine;
}
@Override
public void close() throws IOException {
input.close();
closedOrFinished = true;
}
}
What would be left to do is plug your decryption process inside the processLine
method.
A very quick test for the class (you might want to check it further).
import junit.framework.TestCase;
import org.junit.Assert;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LineByLineProcessingReaderTest extends TestCase {
public void testRead() throws IOException {
String input = "a\nb";
// Reading a char one by one
try (Reader r = new LineByLineProcessingReader(new BufferedReader(new StringReader(input)), "\n")) {
String oneByOne = readExcatlyCharByChar(r, 3);
Assert.assertEquals(input, oneByOne);
}
// Reading lines
List<String> lines = readAllLines(
new LineByLineProcessingReader(
new BufferedReader(new StringReader(input)),
"\n"
)
);
Assert.assertEquals(Arrays.asList("a", "b"), lines);
String[] moreComplexInput = new String[] {"Two households, both alike in dignity",
"In fair Verona, where we lay our scene",
"From ancient grudge break to new mutiny",
"Where civil blood makes civil hands unclean." +
"From forth the fatal loins of these two foes" +
"A pair of star-cross'd lovers take their life;" +
"Whose misadventured piteous overthrows" +
"Do with their death bury their parents' strife." +
"The fearful passage of their death-mark'd love",
"And the continuance of their parents' rage",
"Which, but their children's end, nought could remove",
"Is now the two hours' traffic of our stage;" +
"The which if you with patient ears attend",
"What here shall miss, our toil shall strive to mend."};
lines = readAllLines(new LineByLineProcessingReader(
new BufferedReader(new StringReader(String.join("\n", moreComplexInput))), "\n") {
@Override
protected String processRawLine(String rawLine) {
return rawLine.toUpperCase();
}
});
Assert.assertEquals(Arrays.stream(moreComplexInput).map(String::toUpperCase).collect(Collectors.toList()), lines);
}
private String readExcatlyCharByChar(Reader reader,int numberOfReads) throws IOException {
int nbRead = 0;
try (StringWriter output = new StringWriter()) {
while (nbRead < numberOfReads) {
int read = reader.read();
if (read < 0) {
throw new IOException("Expected " + numberOfReads + " but were only " + nbRead + " available");
}
output.write(read);
nbRead++;
}
return output.toString();
}
}
private List<String> readAllLines(Reader reader) throws IOException {
try (BufferedReader b = new BufferedReader(reader)) {
return b.lines().collect(Collectors.toList());
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论