Java 使用AES算法对大型文件进行加密和解密

Java 使用AES算法对大型文件进行加密和解密,java,image,pdf,encryption,aes,Java,Image,Pdf,Encryption,Aes,我正在加密和解密大型文件,如视频、mp3和pdf。我使用下面的算法。我曾尝试在解密后下载pdf文件,但渲染需要一分钟以上的时间,即使下载的文件只有100kb 下面是我的代码,我怎样才能更快地解决错误并呈现或下载解密文件 public OutputStream encrypt(OutputStream stream) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,

我正在加密和解密大型文件,如视频、mp3和pdf。我使用下面的算法。我曾尝试在解密后下载pdf文件,但渲染需要一分钟以上的时间,即使下载的文件只有100kb

下面是我的代码,我怎样才能更快地解决错误并呈现或下载解密文件

public OutputStream encrypt(OutputStream stream) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
    OutputStream fos = stream;
    SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(), "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE,sks);
    final  CipherOutputStream cos = new CipherOutputStream(fos, cipher);
    return cos;
}

public InputStream decrypt(InputStream stream) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
    InputStream fis = stream;
    SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(), "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, sks);
    final CipherInputStream cis = new CipherInputStream(fis, cipher);
    return cis;
    
}
我得到这个错误“是由:javax.crypto.IllegalBlockSizeException引起的:当使用填充密码解密时,输入长度必须是16的倍数
在java.base/com.sun.crypto.provider.CipherCore.prepareInputBuffer(CipherCore.java:1005)“

此代码存在多个问题

  • “MyDifficultPassw”.getBytes()
  • 这是个坏主意
    getBytes()
    通过使用“平台默认编码”对字符进行解码,将字符转换为字节。虽然在未来的一些java中,可能只是被硬锁定到UTF-8,但它现在不是这样工作的,这意味着您的实际密码(如密钥字节)取决于您运行它的硬件,而您显然不希望这样。尝试
    .getBytes(StandardCharsets.UTF_8)
    取而代之,现在无论在什么硬件上运行它,它都是一致的

  • SecretKeySpec sks=newsecretkeyspec(“MyDifficultPassw.getBytes(),“AES”)
  • 除了字符集编码的问题之外,这里还有一个更大的问题。加密比你想象的要复杂得多。有3个特定的AES密码(其中“AES”是所有3个密码的组名)。3个AES密码中的每一个都需要不同大小的密码

    换句话说,你不能只接受一个密码,把它转换成字节,然后一天就结束了。具体地说,您需要128位、192位或256位(以字节为单位,除以8:16、24或32字节),其他什么都不行。您的示例通常(取决于编码!)会产生16个字节,这意味着这可以工作并得到AES128,但这不是通常的方法。通常的方法是使用密码作为“种子”来创建16、24或32个字节。这允许用户输入任意长度的密码,而不是强制输入“一个密码,如果通过UTF-8转换为字节,最终正好是16、24或32字节”

    byte[] salt = new byte[] {10, 99, 88, 20, 8, 20, 77, 1};
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHMacSHA256");
    KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
    SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    
    这将使用用户提供的密码(
    password
    ),并使用PBKDF2算法将其转换为256位密钥,这是一个好主意,而SO答案不是解释原因的合适场所-可以在web上搜索此信息。“salt”是一种很难识别密码的东西:它需要事先很难猜到,但不需要特别保密。您可以在源文件中为它硬编码8个随机选择的数字;你也需要它来解密

  • Cipher.getInstance(“AES”)
  • 用于流式传输数据的密码比这复杂得多!至少有3个因素:

    A.算法。好吧,你明白了:AES

    B.填充。这些密码是分组密码。他们在区块上作业。AES(所有变体)一次操作16个字节(注意:编辑:这用于说明它操作16/24/32个字节;正如注释中指出的,这是不正确的)它不能在较少或较多的情况下运行。那么,如果您的文件大小不能很好地划分为16,该怎么办?如果你有一个45字节的文件呢?填充算法定义了接下来要做的事情。你需要一个<代码>PKCS5P添加
    是常见的选择

    C.链接模式。这是一个更复杂的野兽:加密算法的核心是一个数学公式,它将一个数字(输入数字)转换为另一个数字(输出数字),该公式的特性是,如果我给你输出数字,你除了尝试每一个可能的数字外,无法找出输入数字是什么。然而,这确实意味着每次相同的输入号码加密到相同的输出号码。这使得观察加密数据的人仍然能够发现重复的数据,即使他们不知道重复的是什么。这是一个巨大的问题:例如,一个徽标的图片将很容易识别。请参阅本页上的示例,了解这一点的重要性。例如,为了解决这个问题,你可以将上一个块“混合”到下一个块中,这样就解决了问题。NIST推荐的模式为GCM。请注意,GCM或多或少有内置的填充(我过于简化了),因此我们可以使用
    NoPadding
    。另一种流行但问题稍大的链接模式是CBC,它确实需要填充:

    那么,让我们来解决这个问题:
    Cipher.getInstance(“AES/GCM/NoPadding”)
    ,或
    Cipher.getInstance(“AES/CBC/PKCS5Padding”)

    对解密代码应用相同的转换,您就解决了问题。。。有点

    如何解决错误并更快地呈现或下载解密文件

    public OutputStream encrypt(OutputStream stream) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        OutputStream fos = stream;
        SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE,sks);
        final  CipherOutputStream cos = new CipherOutputStream(fos, cipher);
        return cos;
    }
    
    public InputStream decrypt(InputStream stream) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        InputStream fis = stream;
        SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, sks);
        final CipherInputStream cis = new CipherInputStream(fis, cipher);
        return cis;
        
    }
    
    这听起来像是你把缓冲搞砸了。这与粘贴的代码没有任何关系,但这是交给此方法的输入/输出流的问题。许多流需要大量写入/读取,否则速度相当慢。通过将它们包装在
    BufferedXStream
    中来修复此问题

    坏的:

    好:


    请注意,一般来说,如果你真的不知道自己在做什么,最终结果经受住黑客攻击的几率很低。没有一个好的解决方案可以解决这个问题——阅读安全知识,检查代码,尝试使用更多现成的东西,而不是手工操作。但是,这些都不是保证。

    此代码存在多个问题

  • “MyDifficultPassw”.getBytes()
  • 这是个坏主意
    getBytes()
    通过使用“平台默认编码”对字符进行解码,将字符转换为字节。虽然在未来的一些java中,可能只是被硬锁定到UTF-8,但它现在不是这样工作的,这意味着您的实际密码(如
    try (FileOutputStream fos = new BufferedOutputStream(FileOutputStream("encrypted.txt"))) {
        CipherOutputStream cos = encrypt(fos);
         // write data
    }