0

用例 1(工作基线):

用例一很简单,并且已实现/工作。

  1. 在 Java 中,一举将流写入磁盘。
  2. 使用对称密码包装输出流,以便加密磁盘上的内容。
  3. 稍后,从磁盘读取。一举用相同的对称密码包装输入流,以便从输入流中检索到的内容是明文并匹配原始内容。

用例 2(未确定合适的解决方案):

  1. 在 Java 中,将流写入磁盘。
  2. 允许将后续字节(“块”)附加到文件中。
  3. 使用对称密码包装输出流,以便加密磁盘上的内容。
  4. 使用相同的密码,以便所有块都以相同的方式加密。
  5. 稍后,从磁盘读取。一举用相同的对称密码包装输入流,以便从输入流中检索到的内容是明文并匹配原始内容。

问题陈述:

加密和解密“abc”与分别加密和解密“a”、“b”和“c”产生的结果不同,因此用例2中描述的“分块”文件将无法成功解密。

// e.g.
decrypt(encrypt("abc")) != decrypt(encrypt("a") + encrypt("b") + encrypt("c"))

实际问题:

...所以问题是,如何配置一个可以一次加密一个块的 Java 密码流,(a) 无需事先了解加密块,以及 (b) 可以使用单个输入流密码包装器进行解密 (无需了解附加文件的索引)...

4

2 回答 2

0

不幸的是,在这种情况下,你不能吃蛋糕也不能吃。

你必须要么

  1. 在每个块的开头写入一些长度字节,或者
  2. 使用加密算法decrypt(encrypt("abc")) == decrypt(encrypt("a") + encrypt("b") + encrypt("c"))(又名微不足道,不推荐)

1 号绝对是更好的选择,而且比您想象的要容易。详情如下。

第二,你可以使用像Vigenere cipher这样的东西,它可以让你一举解密整个文件,但在加密强度方面会有所妥协。

1号详细信息

例如,您可以通过在每个块的开头保留四个字节(一个 32 位整数)来执行此操作。该整数表示块的长度。因此,要解密,您将:

  1. 读取前四个字节并转换为整数n
  2. 读取下一个n字节并解密。
  3. 读取接下来的四个字节并转换为整数n
  4. 读取下一个n字节,解密并附加到第一个解密的块。
  5. 重复步骤 3 和 4,直到到达文件末尾。

显然,这使得块加密变得容易,因为您所要做的就是首先写入您将要附加的加密字节数。

于 2015-01-31T00:04:33.050 回答
0

我找到了一个足够接近我的特定问题的解决方案(从这篇文章中窃取),尽管与问题陈述略有不同(不是单个流)。

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 {

    // prep the new encrypted output file reference
    File encryptedFileSpec = File.createTempFile("chunked_aes_encrypted.", ".test");

    // prep the new decrypted output file reference
    File decryptedFileSpec = File.createTempFile("chunked_aes_decrypted.", ".test");

    // generate a key spec 
    byte[] keySpec = new byte[]{0,12,2,8,4,5,6,7, 8, 9, 10, 11, 12, 13, 14, 15};

    // for debug/test purposes only, keep track of what's written 
    StringBuilder plainTextLog = new StringBuilder();

    // perform chunked output
    for (int i = 0; i<1000; i++) {

        // generate random text of variable length
        StringBuilder text = new StringBuilder();
        Random rand = new Random();
        int  n = rand.nextInt(5) + 1;
        for (int j = 0; j < n; j++) {
            text.append(UUID.randomUUID().toString()); // append random string
        }

        // record it for later comparison
        plainTextLog.append(text.toString());

        // write it out
        byte[] b = text.toString().getBytes("UTF-8");
        appendAES(encryptedFileSpec, b, keySpec);
    }

    System.out.println("Encrypted " + encryptedFileSpec.getAbsolutePath());

    // decrypt
    decryptAES(encryptedFileSpec, new FileOutputStream(decryptedFileSpec), keySpec);
    System.out.println("Decrypted " + decryptedFileSpec.getAbsolutePath());

    // compare expected output to actual
    MessageDigest md = MessageDigest.getInstance("MD5");
    byte[] expectedDigest = md.digest(plainTextLog.toString().getBytes("UTF-8"));

    byte[] expectedBytesEncoded = Base64.getEncoder().encode(expectedDigest);
    System.out.println("Expected decrypted content: " + new String(expectedBytesEncoded));

    byte[] actualBytes = Files.readAllBytes(Paths.get(decryptedFileSpec.toURI()));
    byte[] actualDigest = md.digest(actualBytes);
    byte[] actualBytesEncoded = Base64.getEncoder().encode(actualDigest);
    System.out.println("> Actual decrypted content: " + new String(actualBytesEncoded));


}
于 2015-02-04T06:30:41.713 回答