使用流对文件进行加密和解密,使用附加的初始化向量 Java。

huangapple go评论79阅读模式
英文:

Encrypt and decrypt a file with streams, using an appended initalisation vector java

问题

我已经编写了一个Java程序,该程序会要求用户指定一个随机的密钥(以Base64编码) ,指定要加密的明文文件以及输出文件的名称(加密后的文本文件)。

然后可以通过相反的过程来解密(密钥 + 输入的加密文件 + 输出解密后的文件名)。

这与OpenSSL提供的行为非常相似。

我的Java代码使用了AES/CBC/PKCS5PADDING模式。

我已经将随机生成的初始化向量(16字节IV)附加到密文之前,使用了文件输出流。然后,我认为密码输出流会在IV之后写入密文。

我可以确认从加密和解密方法中生成和提取的IV是相同的(都可以使用Base64编码进行打印,并且会匹配)。

然而,问题在于尝试解密加密文本时。解密后的文本能够显示加密文本的第二部分(与原始明文的第二部分匹配)。

示例:

原始文本:my secret motto: i am awesome!

密文:<lots of encrypted characters>

解密后的文本:<a few encrypted characters> i am awesome!

然而,第一部分似乎被覆盖了,或者可能收集了一些奇怪的剩余加密文本。这让我相信在使用流加密/解密IV时存在一些问题。

相关执行的代码是当布尔值 fileStoreIV 为真时。否则的代码是当用户提供密钥和IV作为输入时。

因此 fout.write(initVector);encryptedData.read(fileIV); 是主要的代码部分。

加密方法:

private static void encrypt(byte[] key, byte[] initVector, String inputFile, String outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IOException {
    // 加密初始化
    Cipher cipher = Cipher.getInstance(CIPHER);
    if(fileStoreIV) {
        SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    }
    else {
        IvParameterSpec iv = new IvParameterSpec(initVector);
        SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
    }
    
    // 文件错误检查
    // ...(省略部分代码)
    
    try (InputStream fin = FileEncryptor.class.getResourceAsStream(loadFile.getName());
            OutputStream fout = Files.newOutputStream(saveFile);
            CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher) {
    }) {
        final byte[] bytes = new byte[1024];
        for(int length=fin.read(bytes); length!=-1; length = fin.read(bytes)){
            if(fileStoreIV) {
                fout.write(initVector);
                fileStoreIV = false;
            }
            cipherOut.write(bytes, 0, length);
        }
    } catch (IOException e) {
        // ...(省略部分代码)
    }
    System.out.println("SUCCESS! Encryption finished, saved at specified location");
}

解密方法:

private static void decrypt(String inputKEY, String inputIV, String inputFile, String outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidKeyException, InvalidAlgorithmParameterException {
    // 解密初始化
    Cipher cipher = Cipher.getInstance(CIPHER);
    if(!fileStoreIV) {
        IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(inputIV));
        SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
    }
    
    // 文件错误检查
    // ...(省略部分代码)
    
    InputStream encryptedData = Files.newInputStream(loadFilePath);
    
    if(fileStoreIV) {
        {
            byte[] fileIV = new byte[16];
            encryptedData.read(fileIV);
            System.out.println(Base64.getEncoder().encodeToString(fileIV));
            SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(fileIV));
            fileStoreIV = false;
        }
    }
    
    try (CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher);	
         OutputStream decryptedOut = Files.newOutputStream(saveFile)){
        final byte[] bytes = new byte[1024];
        for(int length=decryptStream.read(bytes); length!=-1; length = decryptStream.read(bytes)){
            decryptedOut.write(bytes, 0, length);
        }
    } catch (IOException e) {
        // ...(省略部分代码)
    }
    
    System.out.println("SUCESS! Decryption finished, saved at specified location");
}

附注:当我在明文文件中添加足够的空格之前和之内时,我设法移动足够的文本,以便解密文件显示为:<a few encrypted characters> my secret motto: i am awesome!

英文:

I have coded up a Java program which asks the user to specify a random secret key (in base 64 encoding), to specify the plaintext file to encrypt, and the name of the output file (the encrypted text file.)

Then the reverse to decrypt (secret key + input encrypted file + name of output decrypted file).

This follows the very similar behavior to what OpenSSL offers.

My java code uses the mode AES/CBC/PKCS5PADDING.

I have appended my randomly generated initialization vector (16 byte IV), to my ciphertext beforehand using the file output stream. Then I believe the cipher output stream writes the ciphertext after the IV.

I can confirm that the generated and fetched IV from both the encrypt and decrypt method is the same (both can be printed out with base64 encoding, and will match).

The problem, however, lies with trying to decrypt the encrypted text. The decrypted text is able to show the second half of the encrypted text (which matches the second half of the original plain text).

Example:

Plain text: my secret motto: i am awesome!

Cipher text: <lots of encrypted characters>

Decrypted text: <a few encrypted characters> i am awesome!

The first half though seems to be overwritten or perhaps collected some weird leftover encrypted text. This leads me to believe that something is not quite right with how I am encrypting/decrypting the IV using streams.

The relevant executed code is when the boolean fileStoreIV is true. The else code is for when the user provides both the key and IV as inputs.

Therefore fout.write(initVector); and encryptedData.read(fileIV); are the main bits of code.

Encryption method:

private static void encrypt(byte[] key, byte[] initVector, String inputFile, String outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IOException {
//Initalisation for encryption
Cipher cipher = Cipher.getInstance(CIPHER);
if(fileStoreIV) {
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
}
else {
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
}
//File error checking
File loadFile = new File(inputFile);
Path saveFile = Paths.get(outputFile);
Path loadFilePath = Paths.get(inputFile);
if (!Files.exists(loadFilePath)){
System.out.println("The inputFile you specified does not exist");
return;
}
Path parentDir = saveFile.getParent();
if (parentDir != null && !Files.exists(parentDir)) {
System.out.println("The outputFile directory/s you specified does not exist");
return;
}
System.out.println("Secret key is " + Base64.getEncoder().encodeToString(key));
System.out.println("IV is " + Base64.getEncoder().encodeToString(initVector));
//Special file reading and writing with 'Cipher Stream'
try (InputStream fin = FileEncryptor.class.getResourceAsStream(loadFile.getName());
OutputStream fout = Files.newOutputStream(saveFile);
CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher) {
}) {
final byte[] bytes = new byte[1024];
for(int length=fin.read(bytes); length!=-1; length = fin.read(bytes)){
if(fileStoreIV) {
fout.write(initVector);
fileStoreIV = false;
}
cipherOut.write(bytes, 0, length);
}
} catch (IOException e) {
System.out.println("Something went wrong with reading and writing these files!");
System.out.println("Please check you have the latest version of this program");
System.out.println("Contact your IT admin to make sure you have sufficient privileges");
}
System.out.println("SUCCESS! Encryption finished, saved at specified location");

Decryption method:

private static void decrypt(String inputKEY, String inputIV, String inputFile, String outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidKeyException, InvalidAlgorithmParameterException {
//Initalisation for decryption
Cipher cipher = Cipher.getInstance(CIPHER);
if(!fileStoreIV) {
IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(inputIV));
SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
}
Path saveFile = Paths.get(outputFile);
Path loadFilePath = Paths.get(inputFile);
if (!Files.exists(loadFilePath)){
System.out.println("The inputFile you specified does not exist");
return;
}
Path parentDir = saveFile.getParent();
if (parentDir != null && !Files.exists(parentDir)) {
System.out.println("The outputFile directory/s you specified does not exist");
return;
}
InputStream encryptedData = Files.newInputStream(loadFilePath);
if(fileStoreIV) {
{
byte[] fileIV = new byte[16];
encryptedData.read(fileIV);
System.out.println(Base64.getEncoder().encodeToString(fileIV));
SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(fileIV));
fileStoreIV = false;
}
}
try (CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher);	
OutputStream decryptedOut = Files.newOutputStream(saveFile)){
final byte[] bytes = new byte[1024];
for(int length=decryptStream.read(bytes); length!=-1; length = decryptStream.read(bytes)){
decryptedOut.write(bytes, 0, length);
}
} catch (IOException e) {
System.out.println("Something went wrong with reading and writing these files!");
System.out.println("Please check you have the latest version of this program");
System.out.println("Contact your IT admin to make sure you have sufficient privileges");
}
System.out.println("SUCESS! Decryption finished, saved at specified location");

Additional note: when I add enough whitespaces before and inside the plain text file. I manage to shift enough of the text over, to get the decrypted file to display: <a few encrypted characters> my secret motto: i am awesome!.

答案1

得分: 4

以下是你提供的代码部分的翻译:

// 主要部分:解密未成功运行的原因在于 Encrypt 方法中的这部分代码:

if (fileStoreIV) {
    SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
} else {
    IvParameterSpec iv = new IvParameterSpec(initVector);
    SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
}

// 当 `fileStoreIV == true` 时,你没有为 `cipher.init` 函数指定 IV。由于 IV 是错误的,
// 解密后的明文的第一个块(AES 的前 16 个字节)被有效随机的垃圾数据替换;
// 根据 @dave_thompson_085 的评论,这可能是明文长度的一半、全部或很小一部分。

// 我已修改了你的代码,因为它由于其他一些错误而无法直接运行,但我懒得纠正它们,而是为你提供了一个完整可运行的示例代码。

// 以下是程序的输出,我的明文文件包含文本 `The quick brown fox jumps over the lazy dog`:

Secret key is S2guVMqVk8goYy3QsgBSMmjLLCyvoknprTGoFsxMZEo=
IV is VFIYWeCT6ixg/lwk9bBQ9g==
成功加密已完成保存在指定位置
成功解密已完成保存在指定位置
文件 decryptedtext.txt 的内容
The quick brown fox jumps over the lazy dog

// 代码:

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class FileEncryptorSo {
    // ...(其余代码未翻译,保持原样)
}

请注意,我只翻译了你提供的代码部分,其他内容均未包含在内。如果你需要更多翻译或有其他问题,请随时提问。

英文:

The main part why decryption is not running successfully is this part of your code in Encrypt-method:

if(fileStoreIV) {
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
} else {
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
}

When fileStoreIV == true you don't specify the IV for the cipher.init function. And because the IV was wrong, the first block (16 bytes for AES) of decrypted plaintext was replaced by effectively random garbage; this might be half, all, or a tiny fraction of the plaintext depending on its length (as per comment of @dave_thompson_085).

I modified your code as it does not run out of the box due to some other errors but I'm too lazy to correct them, instead you find a full running example code below.

Here is the output of the program, my plaintext-file contains the text The quick brown fox jumps over the lazy dog:

Secret key is S2guVMqVk8goYy3QsgBSMmjLLCyvoknprTGoFsxMZEo=
IV is VFIYWeCT6ixg/lwk9bBQ9g==
SUCCESS! Encryption finished, saved at specified location
SUCESS! Decryption finished, saved at specified location
Content of file decryptedtext.txt
The quick brown fox jumps over the lazy dog

code:

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class FileEncryptorSo {
static String ALGORITHM = "AES";
static String CIPHER = "AES/CBC/PKCS5PADDING";
static boolean fileStoreIV = true;
public static void main(String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
System.out.println("");
String plaintextFilename = "plaintext.txt";
String ciphertextFilename = "ciphertext.enc";
String decryptedFilename = "decryptedtext.txt";
// random aes 256 key
byte[] key = new byte[32];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(key);
// random iv
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
encrypt(key, iv, plaintextFilename, ciphertextFilename);
decrypt(Base64.getEncoder().encodeToString(key), Base64.getEncoder().encodeToString(iv), ciphertextFilename, decryptedFilename);
printTextfile(decryptedFilename);
}
private static void encrypt(byte[] key, byte[] initVector, String inputFile, String outputFile)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
//Initalisation for encryption
Cipher cipher = Cipher.getInstance(CIPHER);
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
/* ### leave out this part !
if (fileStoreIV) {
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
} else {
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
}
*/
//File error checking
// ### not used File loadFile = new File(inputFile);
Path saveFile = Paths.get(outputFile);
Path loadFilePath = Paths.get(inputFile);
if (!Files.exists(loadFilePath)) {
System.out.println("The inputFile you specified does not exist");
return;
}
Path parentDir = saveFile.getParent();
if (parentDir != null && !Files.exists(parentDir)) {
System.out.println("The outputFile directory/s you specified does not exist");
return;
}
System.out.println("Secret key is " + Base64.getEncoder().encodeToString(key));
System.out.println("IV is " + Base64.getEncoder().encodeToString(initVector));
try (FileInputStream in = new FileInputStream(inputFile);
FileOutputStream out = new FileOutputStream(outputFile);
CipherOutputStream encryptedOutputStream = new CipherOutputStream(out, cipher);) {
if (fileStoreIV) {
out.write(initVector);
// ### leave out this line fileStoreIV = false;
}
byte[] buffer = new byte[1024];
int nread;
while ((nread = in.read(buffer)) > 0) {
encryptedOutputStream.write(buffer, 0, nread);
}
encryptedOutputStream.flush();
} catch (IOException e) {
System.out.println("Something went wrong with reading and writing these files!");
System.out.println("Please check you have the latest version of this program");
System.out.println("Contact your IT admin to make sure you have sufficient privileges");
}
System.out.println("SUCCESS! Encryption finished, saved at specified location");
}
private static void decrypt(String inputKEY, String inputIV, String inputFile, String outputFile)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
//Initalisation for decryption
Cipher cipher = Cipher.getInstance(CIPHER);
if (!fileStoreIV) {
IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(inputIV));
SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
}
Path saveFile = Paths.get(outputFile);
Path loadFilePath = Paths.get(inputFile);
if (!Files.exists(loadFilePath)) {
System.out.println("The inputFile you specified does not exist");
return;
}
Path parentDir = saveFile.getParent();
if (parentDir != null && !Files.exists(parentDir)) {
System.out.println("The outputFile directory/s you specified does not exist");
return;
}
//byte[] fileIV = new byte[16];
try (FileInputStream in = new FileInputStream(inputFile);
CipherInputStream cipherInputStream = new CipherInputStream(in, cipher);
FileOutputStream out = new FileOutputStream(outputFile))
{
byte[] buffer = new byte[1024];
if (fileStoreIV) {
byte[] fileIV = new byte[16];
in.read(fileIV);
SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(fileIV));
// ### leave out his line fileStoreIV = false;
}
int nread;
while ((nread = cipherInputStream.read(buffer)) > 0) {
out.write(buffer, 0, nread);
}
out.flush();
} catch (IOException e) {
System.out.println("Something went wrong with reading and writing these files!");
System.out.println("Please check you have the latest version of this program");
System.out.println("Contact your IT admin to make sure you have sufficient privileges");
}
System.out.println("SUCESS! Decryption finished, saved at specified location");
}
private static void printTextfile (String filename) throws IOException {
File file = new File(filename);
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int) file.length()];
fis.read(data);
fis.close();
String str = new String(data, "UTF-8");
System.out.println("Content of file " + filename + "\n" + str);
}
}

huangapple
  • 本文由 发表于 2020年8月30日 12:32:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/63653977.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定