如何用新加密的数据更新部分加密数据?

Posted

技术标签:

【中文标题】如何用新加密的数据更新部分加密数据?【英文标题】:How to update part of the encrypted data with newly encrypted data? 【发布时间】:2017-11-27 13:34:32 【问题描述】:

我需要在生成音频文件时对其进行加密。我在开始时使用虚拟数据加密标头(因为我不知道音频数据的实际大小)并动态加密音频数据。我的计划是用音频文件的实际数据大小更新标题。

但是,当我尝试使用相同的密钥和 IV 用新加密的相同大小的头数据覆盖加密的头数据并尝试稍后解密时,我会生成垃圾数据。

即使我使用相同的密钥和 IV,为什么会发生这种情况?在下面的代码中,我试图模拟我在做什么。生成大小为 64 字节的加密文件,生成大小为 50 字节的解密文件。

没有更新:abcdabcdab0123456789012345678901234567890123456789

带有标题更新:ABCDABCDAB÷‹þ@óMCKLZƒÖ^Ô234567890123456789

预期输出:ABCDABCDAB0123456789012345678901234567890123456789

这是实现已加密数据部分更新的正确方法吗?

protected void Encrypt()


    byte[] numBytes = '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9', '0','1','2','3','4','5','6','7','8','9', '0','1','2','3','4','5','6','7','8','9';
    byte[] smallCase = 'a','b','c','d','a','b','c','d','a','b','c','d','a','b','c','d';
    byte[] capitalCase = 'A','B','C','D','A','B','C','D','A','B','C','D','A','B','C','D';

    try 
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1And8BIT");
        KeySpec spec = new PBEKeySpec("junglebook".toCharArray(), "Salt".getBytes(), 65536, 256);
        SecretKey tmp = null;
        tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        /* Encryption cipher initialization. */
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret);

        AlgorithmParameters params = cipher.getParameters();
        byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();

        Log.d("Encryption" + "iv data :", iv.toString());

        /*Open two Cipher ouput streams to the same encrypted file*/
        FileOutputStream os = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.encrypted");
        CipherOutputStream cos = new CipherOutputStream(os,cipher);

        FileOutputStream os1 = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.encrypted");
        CipherOutputStream cos1 = new CipherOutputStream(os1,cipher);

        int offset = 0;
        Log.d("Encryption", "Writing cipher text to output file");
        //Write 16 bytes header data with smallCase array
        cos.write(smallCase, offset, 16);
        // write 40 bytes actual data
        cos.write(numBytes, offset, 40);

        FileOutputStream ivStream = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/iv.dat");
        if (ivStream != null) 
            Log.d("Encryption", "Writing iv data to output file");
            ivStream.write(iv);
        
        cos.close();

        // Overwrite header data with capitalCase array data
        cos1.write(capitalCase, offset, 16);
        cos1.close();

        ivStream.close();

    catch (Exception e) 
        e.printStackTrace();
    


protected void Decrypt()

    byte[] dBytes = new byte[200];

    try 

        Log.d("Decryption", "Reading iv data ");
        File f1 = new File(sdCard.getAbsolutePath()+"/Notes/iv.dat");
        byte[] newivtext = new byte[(int)f1.length()];
        FileInputStream readivStream = new FileInputStream(sdCard.getAbsolutePath()+"/Notes/iv.dat");
        if(readivStream != null) 
            readivStream.read(newivtext);
        

        // Generate the secret key from same password and salt used in encryption
        SecretKeyFactory dfactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1And8BIT");
        KeySpec dspec = new PBEKeySpec("junglebook".toCharArray(), "Salt".getBytes(), 65536, 256);
        SecretKey dtmp = dfactory.generateSecret(dspec);
        SecretKey dsecret = new SecretKeySpec(dtmp.getEncoded(), "AES");

        // Initialize dcipher
        Cipher dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        dcipher.init(Cipher.DECRYPT_MODE, dsecret, new IvParameterSpec(newivtext));

        FileInputStream inputStream = new FileInputStream(sdCard.getAbsolutePath()+"/Notes/sample.encrypted");
        CipherInputStream cis = new CipherInputStream(inputStream,dcipher);
        FileOutputStream os = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.decrypted");

        int b = cis.read(dBytes);
        while(b != -1) 
            Log.d("Decryption","Bytes decrypted" + b);
            os.write(dBytes, 0, b);
            b = cis.read(dBytes);
        
        cis.close();
        os.close();
     catch (Exception e) 
        e.printStackTrace();
    

【问题讨论】:

您正在使用CBC。如果您查看***页面,您会注意到每个块都依赖于前一个块。如果你更新一个区块,你需要重新加密每个后续区块。 感谢您的快速回复系统发育。什么样的模式适合这种情况?有什么建议么?顺便说一句,我用 NOPADDING 尝试了 GCM 模式。但是,我最终得到了 AEADBadTagException。 也许你可以使用 CTR 模式,然后你只需要更新一个块 @gusto2 感谢您的回复。我用 PKCS5Padding 尝试了 CTR 模式。这次我得到了不同的结果,例如 ABCDABCDAB♠♠♠♠♠♠6789012345678901234567890123456789。额外的 6 个字节似乎是填充字节。我不清楚为什么会生成这些额外的字节。这些填充字节会覆盖实际数据。 【参考方案1】:

我建议你更新几件事:

    你正在打开多个输出流到同一个文件,这很奇怪,运行时不应该允许你这样做。所以 - 如果您想要任何可预测的结果,请仅使用单个输出编写。

    您可以阅读有关操作模式的信息,请参阅CRT 模式不使用填充,并且只允许您更新密文的一部分(假设您未使用经过身份验证的加密)。所以AES/CTR/NoPadding 可以解决你的问题。 (如果操作正确,应该不会有多余的字节)

    您可以使用RandomAccessFile 更新文件的一部分,并根据需要覆盖密文的一部分。

【讨论】:

谢谢 ... AES/CRT/NoPadding 不会产生额外的字节。我现在可以正确解密了。 我想通过覆盖虚拟头在文件开头写入更新的头。所以,我使用了多个输出流。它确实奏效了。但是,可能不是正确的做法。如果我使用单个 CipherOutputStream,我无法在文件开头更新。无法将 CipherOutputStream 与 RandomAccessFile 一起使用。从您的评论中,您希望我使用 doFinal 生成加密字节并使用 RandomAccessFile 覆盖标头。这种理解正确吗? @HithendraNath 确实是这样。用加密的字节数组覆盖文件内容。注意 - 对于文件开头应该没问题。当您想覆盖文件的其他部分时,可能会比较棘手 我明白了。我仍然不清楚为什么带有 NOPADDING 的 GCM 会失败。从提供的模式列表中,我了解到 GCM 是最好用的。使用 CTR 模式会提供足够的安全性吗? CTR 在保密性方面提供了足够的安全性,但没有完整性。 GCM 模式提供内置完整性,因此如果您覆盖密文的一部分,解密失败,使用其他模式(CTR、CBC、OFC、..)您应该自己提供完整性(例如计算密文的 HMAC)跨度>

以上是关于如何用新加密的数据更新部分加密数据?的主要内容,如果未能解决你的问题,请参考以下文章

dwg文件加密如何用

如何用python,对接⼝中的数据进⾏md5加密?

如何用python,对接⼝中的数据进⾏md5加密?

如何用MD5来加密数据表?

C#ASP.NET MD5加密

php rsa加密解密实例