Android解密:结果文件中缺少最后一个块(16个字节)

时间:2013-10-30 07:10:13

标签: java android ios encryption aes

我正在研究解密算法来解密我从服务器接收的电子书阅读器应用程序的PDF。 有相同的代码在iOS上运行完美,我现在正试图让代码在Android上运行。

只是一些细节,解密是AES并以ECB模式运行。加密密钥是一个十六进制字符串数组,我将其转换为字节数组(通过将每两个字符转换为一个字节,例如:“FF”变为255,等等。)

我所经历的非常有趣。我正在比较iOS和Android解密后的结果文件,我注意到这一点,Android解密文件比iOS解密文件短16个字节,特别是最后。所有其他字节都是相同的(我将在下面的一些例子中发布)。

这种差异导致我的电子书阅读器拒绝打开PDF,同时成功打开iOS书籍。

这是我的解密代码:

private void performDecryption(DocumentModel document)
    {                               
        byte[] keyBytes = generateByteArray(document.getEncryptionKey());


        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");

        File encryptedDocument = new File(getBookFolderDocumentName(document, document.getFileSuffix()));
        File decryptedDocument = new File(BOOK_FOLDER + document.getGeneratedAssetName() + "_decrypted" + "." + document.getFileSuffix());

        decryptedDocument.mkdirs();
        if (decryptedDocument.exists())
            decryptedDocument.delete();


        Cipher cipher = null;    

        try
        {

            cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec);         
        } 
        catch (NoSuchAlgorithmException noSuchAlgorithmEx)
        {
            Log.e("Decryption", "NoSuchAlgorithmException: " + noSuchAlgorithmEx.getMessage());
        }
        catch (NoSuchPaddingException noSuchPaddingEx)
        {
            Log.e("Decryption", "NoSuchPaddingException: " + noSuchPaddingEx.getMessage());
        }
        catch (InvalidKeyException invalidKeyEx)
        {
            Log.e("Decryption", "InvalidKeyException: " + invalidKeyEx.getMessage());
        } 

        FileInputStream encryptedFileStream = null;
        FileOutputStream decryptedFileStream = null;


        try
        {

            encryptedFileStream = new FileInputStream(encryptedDocument);
            decryptedFileStream = new FileOutputStream(decryptedDocument);



            long totalFileSize = encryptedDocument.length();
            long totalDecrypted = 0;
            int lastPercentage = -1;
            int currentPercentage = 0;

            byte[] encryptedBuffer = new byte[4096];
            byte[] decryptedBuffer = new byte[4096];
            int encryptedLength = 0;
            int decryptedLength = 0;

            while((encryptedLength = encryptedFileStream.read(encryptedBuffer)) > 0)
            {   
                while (encryptedLength % 16 != 0) // the code never lands in this loop
                {                   
                    encryptedBuffer[encryptedLength] = 0;
                    encryptedLength++;
                }

                decryptedLength = cipher.update(encryptedBuffer, 0, encryptedLength, decryptedBuffer);


                while (decryptedLength % 16 != 0) // the code never lands in this loop
                {
                    decryptedBuffer[decryptedLength] = 0;
                    decryptedLength++;
                }

                decryptedFileStream.write(decryptedBuffer, 0, decryptedLength);


                totalDecrypted += encryptedLength;

                currentPercentage = (int)(((float)totalDecrypted / (float)totalFileSize) * 100f);

                if (currentPercentage != lastPercentage)
                {
                    lastPercentage = currentPercentage;
                    Log.i("Decryption", "Decrypting... " + currentPercentage + "%");
                }
            }




            Log.i("Decryption", "Finished decrypting!");
        }
        catch (FileNotFoundException fileNotFoundEx)
        {
            Log.e("Decryption", "FileNotFoundException: " + fileNotFoundEx.getMessage());
        }
        catch (IOException ioEx)
        {
            Log.e("Decryption", "IOException: " + ioEx.getMessage());
        } 
        catch (ShortBufferException e) 
        {       
            e.printStackTrace();
        }
        finally
        {

        }

        try 
        {                   
            encryptedFileStream.close();
            decryptedFileStream.close();
            cipherOutputStream.close();         
        } 
        catch (IOException e1) 
        {

        }


        document.setDecryptedFilePath(decryptedDocument.getAbsolutePath());



        Log.i("Decryption", "Finished!");
    }

以下是pdf文件中的一些示例(我使用十六进制读取器获取这些结果):

第1册(iOS):

0D 0A 3C 3C 2F 53 69 7A 65 20 35 38 31 3E 3E 0D 
0A 73 74 61 72 74 78 72 65 66 0D 0A 31 31 36 0D 
0A 25 25 45 4F 46 0D 0A 08 08 08 08 08 08 08 08 <-- this block is missing in android. 

第1册(Android):

0D 0A 3C 3C 2F 53 69 7A 65 20 35 38 31 3E 3E 0D 
0A 73 74 61 72 74 78 72 65 66 0D 0A 31 31 36 0D

第2册(iOS):

65 6E 64 6F 62 6A 0D 73 74 61 72 74 78 72 65 66 
0D 0A 34 30 36 32 35 33 36 0D 0A 25 25 45 4F 46 
0D 0A 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E <-- this block is missing in android.

第2册(Android):

65 6E 64 6F 62 6A 0D 73 74 61 72 74 78 72 65 66 
0D 0A 34 30 36 32 35 33 36 0D 0A 25 25 45 4F 46

我注意到的是,最后的字节末尾有几个相同的字节,等于它们出现的次数。在第1册iOS中,在最后一个块中,字节08恰好出现了8次。在第2册iOS中,在最后一个块中,字节0e(14)恰好出现了14次,等等。

除此之外,我不确定发生了什么模式,所以我不确定如何解决这个问题。

我已尝试使用以下不同的填充类型:

ZeroBytePadding, NoPadding, PKCS5Padding, PKCS7Padding 

非常感谢任何想法。

2 个答案:

答案 0 :(得分:4)

好的,我发现了问题......它与填充类型或类似的东西没有任何关系。显然,使用Cipher.update()将工作正常,直到最后的数据块。 update()方法将省略它。

这意味着,在完成解密过程时,必须调用doFinal(),否则最终字节将不会被写入。

这是我在执行解密的大型while循环后立即添加的代码:

byte[] finalBytes = cipher.doFinal();

byte[] finalBytesPadded = new byte[16];

for (int i = 0; i < finalBytes.length; i++)
{
    finalBytesPadded[i] = finalBytes[i];
}

for (int i = finalBytes.length; i < 16; i++)
{
    finalBytesPadded[i] = (byte)(16 - finalBytes.length);
}

decryptedFileStream.write(finalBytesPadded);

我可能无法使代码更好,但确实如此。问题解决了:))

答案 1 :(得分:0)

经过一些快速的谷歌搜索,似乎你应该使用

cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

而不是

cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding");

看起来正在使用PKCS#7填充。 http://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7

Android似乎没有内置PKCS#7,但this guy表示PKCS#7填充与PKCS#5填充兼容。