1

我目前正在研究一种解密算法来解密我从服务器接收的用于电子书阅读器应用程序的 PDF。在 iOS 上运行的等效代码可以完美运行,我现在正试图让代码在 Android 上运行。

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

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

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

这是我的解密代码:

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(安卓):

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 册(安卓):

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

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

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

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

ZeroBytePadding, NoPadding, PKCS5Padding, PKCS7Padding 

任何想法将不胜感激。

4

2 回答 2

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);

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

于 2013-10-30T08:25:23.947 回答
0

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

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

代替

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

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

Android 似乎没有内置 PKCS #7,但是这个家伙说 PKCS #7 填充与 PKCS #5 填充兼容。

于 2013-10-30T07:54:31.483 回答