Aes 解密文件 java 用 base64 数据编码
Aes decrypt file java with base64 data encoding
我参考了 并尝试使用 base64 解码进行文件解密
我的需求是加密时base64编码,解密时base64解码
但我面临以下错误:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at java.base/com.sun.crypto.provider.CipherCore.doFinal(Unknown Source)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(Unknown Source)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(Unknown Source)
at java.base/javax.crypto.Cipher.doFinal(Unknown Source)
at aes.DecryptNew.decryptNew(DecryptNew.java:124)
at aes.DecryptNew.main(DecryptNew.java:32)
我也对如何分块执行解密感到困惑。
请给我建议此代码中的问题。
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Arrays;
import java.util.Base64;
public class DecryptNew {
public static void main(String[] args) {
String plaintextFilename = "D:\\plaintext.txt";
String ciphertextFilename = "D:\\plaintext.txt.crypt";
String decryptedtextFilename = "D:\\plaintextDecrypted.txt";
String password = "testpass";
writeToFile();
String ciphertext = encryptfile(plaintextFilename, password);
System.out.println("ciphertext: " + ciphertext);
decryptNew(ciphertextFilename, password, decryptedtextFilename);
}
static void writeToFile() {
BufferedWriter writer = null;
try
{
writer = new BufferedWriter( new FileWriter("D:\\plaintext.txt"));
byte[] data = Base64.getEncoder().encode("hello\r\nhello".getBytes(StandardCharsets.UTF_8));
writer.write(new String(data));
}
catch ( IOException e)
{
}
finally
{
try
{
if ( writer != null)
writer.close( );
}
catch ( IOException e)
{
}
}
}
public static String encryptfile(String path, String password) {
try {
FileInputStream fis = new FileInputStream(path);
FileOutputStream fos = new FileOutputStream(path.concat(".crypt"));
final byte[] pass = Base64.getEncoder().encode(password.getBytes());
final byte[] salt = (new SecureRandom()).generateSeed(8);
fos.write(Base64.getEncoder().encode("Salted__".getBytes()));
fos.write(salt);
final byte[] passAndSalt = concatenateByteArrays(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = concatenateByteArrays(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("SHA-1");
hash = md.digest(hashData);
keyAndIv = concatenateByteArrays(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
int b;
byte[] d = new byte[8];
while ((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
cos.flush();
cos.close();
fis.close();
System.out.println("encrypt done " + path);
} catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return path;
}
static void decryptNew(String path,String password, String outPath) {
byte[] SALTED_MAGIC = Base64.getEncoder().encode("Salted__".getBytes());
try{
FileInputStream fis = new FileInputStream(path);
FileOutputStream fos = new FileOutputStream(outPath);
final byte[] pass = password.getBytes(StandardCharsets.US_ASCII);
final byte[] inBytes = Files.readAllBytes(Paths.get(path));
final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, SALTED_MAGIC.length);
if (!Arrays.equals(shouldBeMagic, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}
final byte[] salt = Arrays.copyOfRange(inBytes, SALTED_MAGIC.length, SALTED_MAGIC.length + 8);
final byte[] passAndSalt = concatenateByteArrays(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = concatenateByteArrays(hash, passAndSalt);
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-1");
hash = md.digest(hashData);
keyAndIv = concatenateByteArrays(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
String contentDecoded = new String(Base64.getDecoder().decode(clear));
fos.write(contentDecoded.getBytes());
fos.close();
System.out.println("Decrypt is completed");
}catch(Exception e){
e.printStackTrace();
}
}
public static byte[] concatenateByteArrays(byte[] a, byte[] b) {
return ByteBuffer
.allocate(a.length + b.length)
.put(a).put(b)
.array();
}
}
正如我在第一条评论中提到的:加密和解密使用不同的密码编码(加密时使用 Base64,解密时使用 ASCII)。
另外加解密时prefix都是Base64编码的,所以prefix加上salt大于一个block(16字节),因此后面解密时的密文定长失败,假设密文以第二块。
如果在加密和解密时对密码使用相同的编码(例如 ASCII)并且前缀是 ASCII 编码,例如用于加密:
...
byte[] pass = password.getBytes(StandardCharsets.US_ASCII);
byte[] SALTED_MAGIC = "Salted__".getBytes(StandardCharsets.US_ASCII);
byte[] salt = (new SecureRandom()).generateSeed(8);
fos.write(SALTED_MAGIC);
fos.write(salt);
...
和解密:
...
byte[] pass = password.getBytes(StandardCharsets.US_ASCII);
byte[] SALTED_MAGIC = "Salted__".getBytes(StandardCharsets.US_ASCII);
byte[] prefix = fis.readNBytes(8);
byte[] salt = fis.readNBytes(8);
...
然而,当前的加密方法不是 Base64 编码(只有前缀是 Base64 编码,这适得其反,见上文)。
即使是 Base64 编码的明文也不会改变这一点,因为密文本身不是 Base64 编码的。
考虑到您的陈述 我的要求是在加密期间使用 base64 对数据进行编码,在解密期间使用 base64 对数据进行解码 以及使用的 OpenSSL 格式,我假设您想要解密已使用 加密的密文-base64 选项,类似于 OpenSSL,即
openssl enc -aes-256-cbc -base64 -pass pass:testpass -p -in sample.txt -out sample.crypt
在这里,prefix、salt和密文在字节级别上进行连接,然后对结果进行Base64编码。为此,您在问题中发布的实现可以更改如下:
static void decrypt(String path, String password, String outPath) {
try (FileInputStream fis = new FileInputStream(path);
Base64InputStream bis = new Base64InputStream(fis, false, 64, "\r\n".getBytes(StandardCharsets.US_ASCII))) { // from Apache Commons Codec
// Read prefix and salt
byte[] SALTED_MAGIC = "Salted__".getBytes(StandardCharsets.US_ASCII);
byte[] prefix = bis.readNBytes(8);
if (!Arrays.equals(prefix, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}
byte[] salt = bis.readNBytes(8);
// Derive key and IV
byte[] pass = password.getBytes(StandardCharsets.US_ASCII);
byte[] passAndSalt = concatenateByteArrays(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
byte[] hashData = concatenateByteArrays(hash, passAndSalt);
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-1"); // Use digest from encryption
hash = md.digest(hashData);
keyAndIv = concatenateByteArrays(keyAndIv, hash);
}
byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
// Decrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
try (CipherInputStream cis = new CipherInputStream(bis, cipher);
FileOutputStream fos = new FileOutputStream(outPath)) {
int length;
byte[] buffer = new byte[1024];
while ((length = cis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
}
System.out.println("Decrypt is completed");
} catch (Exception e) {
e.printStackTrace();
}
}
正如我在链接 , the processing in chunks can easily be implemented with the class CipherInputStream
. The Base64 decoding can be achieved with the class Base64InputStream
of the Apache Commons Codec 的评论中提到的那样,Michael Fehr 也提到了这一点。这是一种同时进行Base64解码和解密的便捷方式。如果数据不需要进行 Base64 解码(例如,如果在加密期间未使用 -base64 选项),则可以简单地省略 Base64InputStream
class。
前面评论说了,小文件不需要分块处理。仅当文件相对于内存变大时才需要这样做。但是,由于您的加密方法以块的形式处理数据,因此解密也是如此。
请注意,问题中发布的加密与上述 OpenSSL 语句的结果不兼容,即必须根据需要调整加密(类似于解密贴在上面)。
编辑:问题中发布的encryptfile()
方法创建了一个包含Base64编码前缀、原始盐和原始密文的文件。对于密钥派生,应用 Base64 编码的密码。该方法用于加密 Base64 编码的明文。下面的方法是encryptfile()
的对应方法,可以对明文进行解密和Base64解码:
static void decryptfile(String path, String password, String outPath) {
try (FileInputStream fis = new FileInputStream(path)) {
// Read prefix and salt
byte[] SALTED_MAGIC = Base64.getEncoder().encode("Salted__".getBytes());
byte[] prefix = new byte[SALTED_MAGIC.length];
fis.readNBytes(prefix, 0, prefix.length);
if (!Arrays.equals(prefix, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}
byte[] salt = new byte[8];
fis.readNBytes(salt, 0, salt.length);
// Derive key and IV
final byte[] pass = Base64.getEncoder().encode(password.getBytes());
byte[] passAndSalt = concatenateByteArrays(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
byte[] hashData = concatenateByteArrays(hash, passAndSalt);
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-1"); // Use digest from encryption
hash = md.digest(hashData);
keyAndIv = concatenateByteArrays(keyAndIv, hash);
}
byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
// Decrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
try (CipherInputStream cis = new CipherInputStream(fis, cipher);
Base64InputStream bis = new Base64InputStream(cis, false, -1, null); // from Apache Commons Codec
FileOutputStream fos = new FileOutputStream(outPath)) {
int length;
byte[] buffer = new byte[1024];
while ((length = bis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
}
System.out.println("Decrypt is completed");
} catch (Exception e) {
e.printStackTrace();
}
}
decryptfile()
-方法将Base64解码明文写入outPath
中的文件。要获得 Base64 编码的明文,只需从流层次结构中删除 Base64InputStream
实例。
如评论中所述,此方法与 OpenSSL 不兼容,即它无法解密使用上面发布的 OpenSSL 语句生成的密文(有或没有 -base64 选项).要使用 -base64 选项解密由 OpenSSL 生成的密文,请使用 decrypt()
方法。
我参考了
我的需求是加密时base64编码,解密时base64解码
但我面临以下错误:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at java.base/com.sun.crypto.provider.CipherCore.doFinal(Unknown Source)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(Unknown Source)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(Unknown Source)
at java.base/javax.crypto.Cipher.doFinal(Unknown Source)
at aes.DecryptNew.decryptNew(DecryptNew.java:124)
at aes.DecryptNew.main(DecryptNew.java:32)
我也对如何分块执行解密感到困惑。 请给我建议此代码中的问题。
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Arrays;
import java.util.Base64;
public class DecryptNew {
public static void main(String[] args) {
String plaintextFilename = "D:\\plaintext.txt";
String ciphertextFilename = "D:\\plaintext.txt.crypt";
String decryptedtextFilename = "D:\\plaintextDecrypted.txt";
String password = "testpass";
writeToFile();
String ciphertext = encryptfile(plaintextFilename, password);
System.out.println("ciphertext: " + ciphertext);
decryptNew(ciphertextFilename, password, decryptedtextFilename);
}
static void writeToFile() {
BufferedWriter writer = null;
try
{
writer = new BufferedWriter( new FileWriter("D:\\plaintext.txt"));
byte[] data = Base64.getEncoder().encode("hello\r\nhello".getBytes(StandardCharsets.UTF_8));
writer.write(new String(data));
}
catch ( IOException e)
{
}
finally
{
try
{
if ( writer != null)
writer.close( );
}
catch ( IOException e)
{
}
}
}
public static String encryptfile(String path, String password) {
try {
FileInputStream fis = new FileInputStream(path);
FileOutputStream fos = new FileOutputStream(path.concat(".crypt"));
final byte[] pass = Base64.getEncoder().encode(password.getBytes());
final byte[] salt = (new SecureRandom()).generateSeed(8);
fos.write(Base64.getEncoder().encode("Salted__".getBytes()));
fos.write(salt);
final byte[] passAndSalt = concatenateByteArrays(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = concatenateByteArrays(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("SHA-1");
hash = md.digest(hashData);
keyAndIv = concatenateByteArrays(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
int b;
byte[] d = new byte[8];
while ((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
cos.flush();
cos.close();
fis.close();
System.out.println("encrypt done " + path);
} catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return path;
}
static void decryptNew(String path,String password, String outPath) {
byte[] SALTED_MAGIC = Base64.getEncoder().encode("Salted__".getBytes());
try{
FileInputStream fis = new FileInputStream(path);
FileOutputStream fos = new FileOutputStream(outPath);
final byte[] pass = password.getBytes(StandardCharsets.US_ASCII);
final byte[] inBytes = Files.readAllBytes(Paths.get(path));
final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, SALTED_MAGIC.length);
if (!Arrays.equals(shouldBeMagic, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}
final byte[] salt = Arrays.copyOfRange(inBytes, SALTED_MAGIC.length, SALTED_MAGIC.length + 8);
final byte[] passAndSalt = concatenateByteArrays(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = concatenateByteArrays(hash, passAndSalt);
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-1");
hash = md.digest(hashData);
keyAndIv = concatenateByteArrays(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
String contentDecoded = new String(Base64.getDecoder().decode(clear));
fos.write(contentDecoded.getBytes());
fos.close();
System.out.println("Decrypt is completed");
}catch(Exception e){
e.printStackTrace();
}
}
public static byte[] concatenateByteArrays(byte[] a, byte[] b) {
return ByteBuffer
.allocate(a.length + b.length)
.put(a).put(b)
.array();
}
}
正如我在第一条评论中提到的:加密和解密使用不同的密码编码(加密时使用 Base64,解密时使用 ASCII)。
另外加解密时prefix都是Base64编码的,所以prefix加上salt大于一个block(16字节),因此后面解密时的密文定长失败,假设密文以第二块。
如果在加密和解密时对密码使用相同的编码(例如 ASCII)并且前缀是 ASCII 编码,例如用于加密:
...
byte[] pass = password.getBytes(StandardCharsets.US_ASCII);
byte[] SALTED_MAGIC = "Salted__".getBytes(StandardCharsets.US_ASCII);
byte[] salt = (new SecureRandom()).generateSeed(8);
fos.write(SALTED_MAGIC);
fos.write(salt);
...
和解密:
...
byte[] pass = password.getBytes(StandardCharsets.US_ASCII);
byte[] SALTED_MAGIC = "Salted__".getBytes(StandardCharsets.US_ASCII);
byte[] prefix = fis.readNBytes(8);
byte[] salt = fis.readNBytes(8);
...
然而,当前的加密方法不是 Base64 编码(只有前缀是 Base64 编码,这适得其反,见上文)。
即使是 Base64 编码的明文也不会改变这一点,因为密文本身不是 Base64 编码的。
考虑到您的陈述 我的要求是在加密期间使用 base64 对数据进行编码,在解密期间使用 base64 对数据进行解码 以及使用的 OpenSSL 格式,我假设您想要解密已使用 加密的密文-base64 选项,类似于 OpenSSL,即
openssl enc -aes-256-cbc -base64 -pass pass:testpass -p -in sample.txt -out sample.crypt
在这里,prefix、salt和密文在字节级别上进行连接,然后对结果进行Base64编码。为此,您在问题中发布的实现可以更改如下:
static void decrypt(String path, String password, String outPath) {
try (FileInputStream fis = new FileInputStream(path);
Base64InputStream bis = new Base64InputStream(fis, false, 64, "\r\n".getBytes(StandardCharsets.US_ASCII))) { // from Apache Commons Codec
// Read prefix and salt
byte[] SALTED_MAGIC = "Salted__".getBytes(StandardCharsets.US_ASCII);
byte[] prefix = bis.readNBytes(8);
if (!Arrays.equals(prefix, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}
byte[] salt = bis.readNBytes(8);
// Derive key and IV
byte[] pass = password.getBytes(StandardCharsets.US_ASCII);
byte[] passAndSalt = concatenateByteArrays(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
byte[] hashData = concatenateByteArrays(hash, passAndSalt);
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-1"); // Use digest from encryption
hash = md.digest(hashData);
keyAndIv = concatenateByteArrays(keyAndIv, hash);
}
byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
// Decrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
try (CipherInputStream cis = new CipherInputStream(bis, cipher);
FileOutputStream fos = new FileOutputStream(outPath)) {
int length;
byte[] buffer = new byte[1024];
while ((length = cis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
}
System.out.println("Decrypt is completed");
} catch (Exception e) {
e.printStackTrace();
}
}
正如我在链接 CipherInputStream
. The Base64 decoding can be achieved with the class Base64InputStream
of the Apache Commons Codec 的评论中提到的那样,Michael Fehr 也提到了这一点。这是一种同时进行Base64解码和解密的便捷方式。如果数据不需要进行 Base64 解码(例如,如果在加密期间未使用 -base64 选项),则可以简单地省略 Base64InputStream
class。
前面评论说了,小文件不需要分块处理。仅当文件相对于内存变大时才需要这样做。但是,由于您的加密方法以块的形式处理数据,因此解密也是如此。
请注意,问题中发布的加密与上述 OpenSSL 语句的结果不兼容,即必须根据需要调整加密(类似于解密贴在上面)。
编辑:问题中发布的encryptfile()
方法创建了一个包含Base64编码前缀、原始盐和原始密文的文件。对于密钥派生,应用 Base64 编码的密码。该方法用于加密 Base64 编码的明文。下面的方法是encryptfile()
的对应方法,可以对明文进行解密和Base64解码:
static void decryptfile(String path, String password, String outPath) {
try (FileInputStream fis = new FileInputStream(path)) {
// Read prefix and salt
byte[] SALTED_MAGIC = Base64.getEncoder().encode("Salted__".getBytes());
byte[] prefix = new byte[SALTED_MAGIC.length];
fis.readNBytes(prefix, 0, prefix.length);
if (!Arrays.equals(prefix, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}
byte[] salt = new byte[8];
fis.readNBytes(salt, 0, salt.length);
// Derive key and IV
final byte[] pass = Base64.getEncoder().encode(password.getBytes());
byte[] passAndSalt = concatenateByteArrays(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
byte[] hashData = concatenateByteArrays(hash, passAndSalt);
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-1"); // Use digest from encryption
hash = md.digest(hashData);
keyAndIv = concatenateByteArrays(keyAndIv, hash);
}
byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
// Decrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
try (CipherInputStream cis = new CipherInputStream(fis, cipher);
Base64InputStream bis = new Base64InputStream(cis, false, -1, null); // from Apache Commons Codec
FileOutputStream fos = new FileOutputStream(outPath)) {
int length;
byte[] buffer = new byte[1024];
while ((length = bis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
}
System.out.println("Decrypt is completed");
} catch (Exception e) {
e.printStackTrace();
}
}
decryptfile()
-方法将Base64解码明文写入outPath
中的文件。要获得 Base64 编码的明文,只需从流层次结构中删除 Base64InputStream
实例。
如评论中所述,此方法与 OpenSSL 不兼容,即它无法解密使用上面发布的 OpenSSL 语句生成的密文(有或没有 -base64 选项).要使用 -base64 选项解密由 OpenSSL 生成的密文,请使用 decrypt()
方法。