Java-Winzipaes在Android上解密10 MB文件的速度很慢

我试图在Samsung S5上使用AES加密从zip文件中解密10 MB的文件,但是它是如此之慢,这真的让我感到惊讶.我对AES很熟悉,所以我不知道它是否消耗大量时间.以下是我的测试结果.有人能告诉我这些结果是否合理吗?

反正有加速AES解密的方法吗?

PS.我使用SpongyCastle来避免类加载器冲突,并且我还修改了winzipaes以使用SpongyCastle.

测试1
设备:三星S5
压缩文件:7za a -tzip -mx = 0 -p1234 -mem = AES256 test.zip 1MB_文件10MB_文件
1MB_文件:1 MB
10MB_文件:10 MB
test.zip:12.5 MB
压缩率:1.00
解密和解压缩:
-> 1MB_文件:3167毫秒
-> 10MB_文件:34137毫秒

测试2
设备:三星S5
压缩文件:7za a -tzip -mx = 1 -p1234 -mem = AES256 test.zip 1MB_文件10MB_文件
1MB_文件:1 MB
10MB_文件:10 MB
test.zip:5.4 MB
压缩率:2.31
解密和解压缩:
-> 1MB_文件:1290毫秒
-> 10MB_文件:15369毫秒

测试3
设备:三星S5
压缩文件:7za a -tzip -mx = 9 -p1234 -mem = AES256 test.zip 1MB_文件10MB_文件
1MB_文件:1 MB
10MB_文件:10 MB
test.zip:5.1 MB
压缩率:2.46
解密和解压缩:
-> 1MB_文件:1202毫秒
-> 10MB_文件:14460毫秒

Winzipaes:https://code.google.com/p/winzipaes/
海绵城堡:http://rtyley.github.io/spongycastle/

================================================== ================================

使用@Maarten Bodewes-Owlstead解决方案

测试2
设备:三星S5
压缩文件:7za a -tzip -mx = 1 -p1234 -mem = AES256 test.zip 1MB_文件10MB_文件
1MB_文件:1 MB
10MB_文件:10 MB
test.zip:5.4 MB
压缩率:2.31
解密和解压缩:
-> 1MB_file:206 ms(原为1290 ms)
-> 10MB_file:1782 ms(是15369 ms)

解决方法:

是的,由于winzipaes的源代码使用了一种相当低效的解密方式,因此有加速的方法:它通过计算IV和初始化密码(用于CTR模式解密)来解密每个块.这可能意味着密钥过于频繁地被初始化.此外,以16个字节的块来处理数据也不是很有效.这主要是由于WinZip执行的AES-CTR使用了一个小字节序计数器,而不是一个大字节序计数器(标准化).

解密似乎还包括在密文上计算HMAC-SHA1,这也会增加大量开销.如果您仅要求存储的文本具有机密性,则可以跳过该步骤,尽管MAC确实具有显着的安全优势,但它提供了加密安全的完整性和真实性.

为了说明我的意思,我创建了一个小的示例代码,至少可以在Java SE计算机上更快地运行.根据Wayne(原始发布者)的说法,这将Android代码的速度提高了大约10倍,在我的Java SE测试中,“仅”我看到的速度是原始代码的3倍左右.

变化:

>创建用于ZIP的特殊小尾数计数器模式
>由于上述原因,简化/优化了解密程序代码
>删除每个文件的双键派生(D’oh!)
>选项不验证MAC(收益相对较小,SHA1相当快)
>使用AESFastEngine,没什么大不了,但是嘿…

很可能可以为加密器创建相同类型的优化.

笔记:

> .zip加密是针对每个存储的文件,因此效率很低,因为密钥的推导也需要针对每个存储的文件进行一次. .zip文件本身的加密将更加有效.
>使用解密器的JCA版本可以提高速度,而Android可以在更高版本中使用OpenSSL代码(尽管它必须执行逐块加密).

/**
 * Adapter for bouncy castle crypto implementation (decryption).
 *
 * @author olaf@merkert.de
 * @author owlstead
 */
public class AESDecrypterOwlstead extends AESCryptoBase implements AESDecrypter {


    private final boolean verify;

    public AESDecrypterOwlstead(boolean verify) {
        this.verify = verify;
    }

    // TODO consider keySize (but: we probably need to adapt the key size for the zip file as well)
    public void init( String pwStr, int keySize, byte[] salt, byte[] pwVerification ) throws ZipException {
        byte[] pwBytes = pwStr.getBytes();

        super.saltBytes = salt;

        PBEParametersGenerator generator = new PKCS5S2ParametersGenerator();
        generator.init( pwBytes, salt, ITERATION_COUNT );

        cipherParameters = generator.generateDerivedParameters(KEY_SIZE_BIT*2 + 16);
        byte[] keyBytes = ((KeyParameter)cipherParameters).getKey();

        this.cryptoKeyBytes = new byte[ KEY_SIZE_BYTE ];
        System.arraycopy( keyBytes, 0, cryptoKeyBytes, 0, KEY_SIZE_BYTE );

        this.authenticationCodeBytes = new byte[ KEY_SIZE_BYTE ];
        System.arraycopy( keyBytes, KEY_SIZE_BYTE, authenticationCodeBytes, 0, KEY_SIZE_BYTE );

        // based on SALT + PASSWORD (password is probably correct)
        this.pwVerificationBytes = new byte[ 2 ];
        System.arraycopy( keyBytes, KEY_SIZE_BYTE*2, this.pwVerificationBytes, 0, 2 );

        if( !ByteArrayHelper.isEqual( this.pwVerificationBytes, pwVerification ) ) {
            throw new ZipException("wrong password - " + ByteArrayHelper.toString(this.pwVerificationBytes) + "/ " + ByteArrayHelper.toString(pwVerification));
        }

        cipherParameters = new KeyParameter(cryptoKeyBytes);

        // checksum added to the end of the encrypted data, update on each encryption call

        if (this.verify) {
            this.mac = new HMac( new SHA1Digest() );
            this.mac.init( new KeyParameter(authenticationCodeBytes) );
        }

        this.aesCipher = new SICZIPBlockCipher(new AESFastEngine());
        this.blockSize = aesCipher.getBlockSize();

        // incremented on each 16 byte block and used as encryption NONCE (ivBytes)

        // warning: non-CTR; little endian IV and starting with 1 instead of 0

        nonce = 1;

        byte[] ivBytes = ByteArrayHelper.toByteArray( nonce, 16 );

        ParametersWithIV ivParams = new ParametersWithIV(cipherParameters, ivBytes);
        aesCipher.init( false, ivParams );
    }

    // --------------------------------------------------------------------------

    protected CipherParameters cipherParameters;

    protected SICZIPBlockCipher aesCipher;

    protected HMac mac;


    @Override
    public void decrypt(byte[] in, int length) {
        if (verify) {
            mac.update(in, 0, length);
        }
        aesCipher.processBytes(in, 0, length, in, 0);
    }

    public byte[] getFinalAuthentication() {
        if (!verify) {
            return null;
        }
        byte[] macBytes = new byte[ mac.getMacSize() ];
        mac.doFinal( macBytes, 0 );
        byte[] macBytes10 = new byte[10];
        System.arraycopy( macBytes, 0, macBytes10, 0, 10 );
        return macBytes10;
    }
}

当然,您还需要引用的SICZIPCipher:

/**
 * Implements the Segmented Integer Counter (SIC) mode on top of a simple
 * block cipher. This mode is also known as CTR mode. This CTR mode
 * was altered to comply with the ZIP little endian counter and
 * different starting point.
 */
public class SICZIPBlockCipher
    extends StreamBlockCipher
    implements SkippingStreamCipher
{
    private final BlockCipher     cipher;
    private final int             blockSize;

    private byte[]          IV;
    private byte[]          counter;
    private byte[]          counterOut;
    private int             byteCount;

    /**
     * Basic constructor.
     *
     * @param c the block cipher to be used.
     */
    public SICZIPBlockCipher(BlockCipher c)
    {
        super(c);

        this.cipher = c;
        this.blockSize = cipher.getBlockSize();
        this.IV = new byte[blockSize];
        this.counter = new byte[blockSize];
        this.counterOut = new byte[blockSize];
        this.byteCount = 0;
    }

    public void init(
        boolean             forEncryption, //ignored by this CTR mode
        CipherParameters    params)
        throws IllegalArgumentException
    {
        if (params instanceof ParametersWithIV)
        {
            ParametersWithIV ivParam = (ParametersWithIV)params;
            byte[] iv = ivParam.getIV();
            System.arraycopy(iv, 0, IV, 0, IV.length);

            // if null it's an IV changed only.
            if (ivParam.getParameters() != null)
            {
                cipher.init(true, ivParam.getParameters());
            }

            reset();
        }
        else
        {
            throw new IllegalArgumentException("SICZIP mode requires ParametersWithIV");
        }
    }

    public String getAlgorithmName()
    {
        return cipher.getAlgorithmName() + "/SICZIP";
    }

    public int getBlockSize()
    {
        return cipher.getBlockSize();
    }

    public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
          throws DataLengthException, IllegalStateException
    {
        processBytes(in, inOff, blockSize, out, outOff);

        return blockSize;
    }

    protected byte calculateByte(byte in)
          throws DataLengthException, IllegalStateException
    {
        if (byteCount == 0)
        {
            cipher.processBlock(counter, 0, counterOut, 0);

            return (byte)(counterOut[byteCount++] ^ in);
        }

        byte rv = (byte)(counterOut[byteCount++] ^ in);

        if (byteCount == counter.length)
        {
            byteCount = 0;

            incrementCounter();
        }

        return rv;
    }

    private void incrementCounter()
    {
        // increment counter by 1.
        for (int i = 0; i < counter.length && ++counter[i] == 0; i++)
        {
            ; // do nothing - pre-increment and test for 0 in counter does the job.
        }
    }

    private void decrementCounter()
    {
        // TODO test - owlstead too lazy to test

        if (counter[counter.length - 1] == 0)
        {
            boolean nonZero = false;

            for (int i = 0; i < counter.length; i++)
            {
                if (counter[i] != 0)
                {
                    nonZero = true;
                }
            }

            if (!nonZero)
            {
                throw new IllegalStateException("attempt to reduce counter past zero.");
            }
        }

        // decrement counter by 1.
        for (int i = 0; i < counter.length && --counter[i] == -1; i++)
        {
            ;
        }
    }

    private void adjustCounter(long n)
    {
        if (n >= 0)
        {
            long numBlocks = (n + byteCount) / blockSize;

            for (long i = 0; i != numBlocks; i++)
            {
                incrementCounter();
            }

            byteCount = (int)((n + byteCount) - (blockSize * numBlocks));
        }
        else
        {
            long numBlocks = (-n - byteCount) / blockSize;

            for (long i = 0; i != numBlocks; i++)
            {
                decrementCounter();
            }

            int gap = (int)(byteCount + n + (blockSize * numBlocks));

            if (gap >= 0)
            {
                byteCount = 0;
            }
            else
            {
                decrementCounter();
                byteCount =  blockSize + gap;
            }
        }
    }

    public void reset()
    {
        System.arraycopy(IV, 0, counter, 0, counter.length);
        cipher.reset();
        this.byteCount = 0;
    }

    public long skip(long numberOfBytes)
    {
        adjustCounter(numberOfBytes);

        cipher.processBlock(counter, 0, counterOut, 0);

        return numberOfBytes;
    }

    public long seekTo(long position)
    {
        reset();

        return skip(position);
    }

    public long getPosition()
    {
        byte[] res = new byte[IV.length];

        System.arraycopy(counter, 0, res, 0, res.length);

        for (int i = 0; i < res.length; i++)
        {
            int v = (res[i] - IV[i]);

            if (v < 0)
            {
               res[i + 1]--;
               v += 256;
            }

            res[i] = (byte)v;
        }

        // TODO still broken - owlstead too lazy to fix for zip
        return Pack.bigEndianToLong(res, res.length - 8) * blockSize + byteCount;
    }
}
上一篇:JavaScript中用于javax.crypto的备用代码


下一篇:C到PHP的翻译,解密功能