使用 AES 进行文件加密解密
File Encryption Decryption with AES
public long copyStreamsLong(InputStream in, OutputStream out, long sizeLimit) throws IOException {
long byteCount = 0;
IOException error = null;
long totalBytesRead = 0;
try {
String key = "C4F9EA21977047D6"; // user value (16/24/32 bytes)
// byte[] keyBytes = DatatypeConverter.parseHexBinary(aesKey);
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
System.out.println(secretKey.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] buffer = new byte[CustomLimitedStreamCopier.BYTE_BUFFER_SIZE];
// in.read(buffer);
int bytesRead = -1;
while ((bytesRead = in.read(buffer)) != -1) {
// We are able to abort the copy immediately upon limit violation.
totalBytesRead += bytesRead;
if (sizeLimit > 0 && totalBytesRead > sizeLimit) {
StringBuilder msg = new StringBuilder();
msg.append("Content size violation, limit = ").append(sizeLimit);
throw new ContentLimitViolationException(msg.toString());
}
byte[] obuf = cipher.update(buffer, 0, bytesRead);
if (obuf != null) {
out.write(obuf);
}
byteCount += bytesRead;
}
byte[] obuf = cipher.doFinal();
if (obuf != null) {
out.write(obuf);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
error = e;
CustomLimitedStreamCopier.logger.error("Failed to close input stream: " + this, e);
}
try {
out.close();
} catch (IOException e) {
error = e;
CustomLimitedStreamCopier.logger.error("Failed to close output stream: " + this, e);
}
}
if (error != null)
throw error;
return byteCount;
}
public InputStream getContentInputStream() throws ContentIOException {
ReadableByteChannel channel = getReadableChannel();
InputStream is = Channels.newInputStream(channel);
try {
final String ALGORITHM = "AES";
final String TRANSFORMATION = "AES";
String key = "C4F9EA21977047D6";
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] buffer = ByteStreams.toByteArray(is);
System.out.println("in read" + buffer.length);
is.read(buffer);
byte[] outputBytes = cipher.doFinal(buffer);
is = new ByteArrayInputStream(outputBytes);
}
当我尝试加密输入流时,我将获得增加的字节数组,之后当我在解密时解密该输入流时,它将采用原始字节大小。因此,当我尝试打开该文件时,它会给出过早结束标记的错误,所以我希望加密后的文件大小相同,并且此方法是 java class.
的一部分
这个问题的解决方案是什么?
您使用 TRANSFORMATION = "AES" 初始化密码,这意味着您的 Java 实现将选择默认的 AES 模式,通常是 "AES/ECB/PKCS5Padding”。在 ECB 模式不安全且不应再使用 超过 16 字节的 plaintext/streams 的事实之下,填充将添加额外的字节,直到(多个)块长度为 16 .
所以 运行 我的小程序,您看到在您的实现中,“10”的 inputLen(表示 10 字节长度的 plaintext/stream)将导致 outputSize 为“16”。
为避免这种情况,您确实需要另一种 AES 模式,并且输出与加密端的输入长度相同。您可以为此使用 AES CTR 模式 - 您只需要一个附加参数(初始化向量,16 字节长)。非常重要的是,您 永远不要将同一个 iv 用于超过 1 次加密,因此它应该作为随机值生成。为了解密消息的接收者(“解密者”)需要知道在加密端使用了什么 initvector。
cipher algorithm: AES inputLen 10 outputSize 16
cipher algorithm: AES/CTR/NOPADDING inputLen 10 outputSize 10
编辑(安全警告): 正如总统 James K. Polk 提醒的那样“CTR 模式使得修改攻击者选择的单个字节变得微不足道,因此它需要与身份验证标签结合使用。”。这取决于需要身份验证时将要加密的数据类型(例如,如果您正在加密音乐文件,那么任何修改都将导致“piep”或“krk”,更改财务数据将导致灾难性后果)。
代码:
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class Main {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
System.out.println("");
int inputLen = 10;
final String ALGORITHM = "AES";
// aes ecb mode
final String TRANSFORMATION = "AES";
//final String TRANSFORMATION = "AES/ECB/PKCS5PADDING";
String key = "C4F9EA21977047D6";
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
System.out.format("cipher algorithm: %-20s inputLen %3d outputSize %3d%n", cipher.getAlgorithm(), inputLen, cipher.getOutputSize(inputLen));
// aes ctr mode
String TRANSFORMATION2 = "AES/CTR/NOPADDING";
// you need an unique (random) iv for each encryption
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher2 = Cipher.getInstance(TRANSFORMATION2);
cipher2.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
System.out.format("cipher algorithm: %-20s inputLen %3d outputSize %3d%n", cipher2.getAlgorithm(), inputLen, cipher2.getOutputSize(inputLen));
}
}
可以在此处找到 AES CTR 模式的 运行 实现:
public long copyStreamsLong(InputStream in, OutputStream out, long sizeLimit) throws IOException {
long byteCount = 0;
IOException error = null;
long totalBytesRead = 0;
try {
String key = "C4F9EA21977047D6"; // user value (16/24/32 bytes)
// byte[] keyBytes = DatatypeConverter.parseHexBinary(aesKey);
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
System.out.println(secretKey.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] buffer = new byte[CustomLimitedStreamCopier.BYTE_BUFFER_SIZE];
// in.read(buffer);
int bytesRead = -1;
while ((bytesRead = in.read(buffer)) != -1) {
// We are able to abort the copy immediately upon limit violation.
totalBytesRead += bytesRead;
if (sizeLimit > 0 && totalBytesRead > sizeLimit) {
StringBuilder msg = new StringBuilder();
msg.append("Content size violation, limit = ").append(sizeLimit);
throw new ContentLimitViolationException(msg.toString());
}
byte[] obuf = cipher.update(buffer, 0, bytesRead);
if (obuf != null) {
out.write(obuf);
}
byteCount += bytesRead;
}
byte[] obuf = cipher.doFinal();
if (obuf != null) {
out.write(obuf);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
error = e;
CustomLimitedStreamCopier.logger.error("Failed to close input stream: " + this, e);
}
try {
out.close();
} catch (IOException e) {
error = e;
CustomLimitedStreamCopier.logger.error("Failed to close output stream: " + this, e);
}
}
if (error != null)
throw error;
return byteCount;
}
public InputStream getContentInputStream() throws ContentIOException {
ReadableByteChannel channel = getReadableChannel();
InputStream is = Channels.newInputStream(channel);
try {
final String ALGORITHM = "AES";
final String TRANSFORMATION = "AES";
String key = "C4F9EA21977047D6";
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] buffer = ByteStreams.toByteArray(is);
System.out.println("in read" + buffer.length);
is.read(buffer);
byte[] outputBytes = cipher.doFinal(buffer);
is = new ByteArrayInputStream(outputBytes);
}
当我尝试加密输入流时,我将获得增加的字节数组,之后当我在解密时解密该输入流时,它将采用原始字节大小。因此,当我尝试打开该文件时,它会给出过早结束标记的错误,所以我希望加密后的文件大小相同,并且此方法是 java class.
的一部分这个问题的解决方案是什么?
您使用 TRANSFORMATION = "AES" 初始化密码,这意味着您的 Java 实现将选择默认的 AES 模式,通常是 "AES/ECB/PKCS5Padding”。在 ECB 模式不安全且不应再使用 超过 16 字节的 plaintext/streams 的事实之下,填充将添加额外的字节,直到(多个)块长度为 16 .
所以 运行 我的小程序,您看到在您的实现中,“10”的 inputLen(表示 10 字节长度的 plaintext/stream)将导致 outputSize 为“16”。
为避免这种情况,您确实需要另一种 AES 模式,并且输出与加密端的输入长度相同。您可以为此使用 AES CTR 模式 - 您只需要一个附加参数(初始化向量,16 字节长)。非常重要的是,您 永远不要将同一个 iv 用于超过 1 次加密,因此它应该作为随机值生成。为了解密消息的接收者(“解密者”)需要知道在加密端使用了什么 initvector。
cipher algorithm: AES inputLen 10 outputSize 16
cipher algorithm: AES/CTR/NOPADDING inputLen 10 outputSize 10
编辑(安全警告): 正如总统 James K. Polk 提醒的那样“CTR 模式使得修改攻击者选择的单个字节变得微不足道,因此它需要与身份验证标签结合使用。”。这取决于需要身份验证时将要加密的数据类型(例如,如果您正在加密音乐文件,那么任何修改都将导致“piep”或“krk”,更改财务数据将导致灾难性后果)。
代码:
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class Main {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
System.out.println("");
int inputLen = 10;
final String ALGORITHM = "AES";
// aes ecb mode
final String TRANSFORMATION = "AES";
//final String TRANSFORMATION = "AES/ECB/PKCS5PADDING";
String key = "C4F9EA21977047D6";
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
System.out.format("cipher algorithm: %-20s inputLen %3d outputSize %3d%n", cipher.getAlgorithm(), inputLen, cipher.getOutputSize(inputLen));
// aes ctr mode
String TRANSFORMATION2 = "AES/CTR/NOPADDING";
// you need an unique (random) iv for each encryption
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher2 = Cipher.getInstance(TRANSFORMATION2);
cipher2.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
System.out.format("cipher algorithm: %-20s inputLen %3d outputSize %3d%n", cipher2.getAlgorithm(), inputLen, cipher2.getOutputSize(inputLen));
}
}
可以在此处找到 AES CTR 模式的 运行 实现: