ZIP文件的AES加密不正确的第2个16字节块

来源:爱站网时间:2021-09-16编辑:网友分享
我正在使用Java的Cipher实现来实现Zip AES加密。这是我的加密代码:公共最终类AesEncoder实现Encoder {private final Cipher cipher; ...

问题描述


我正在使用Java的Cipher实现进行实现Zip AES加密。这是我的加密代码:

public final class AesEncoder implements Encoder {

    private final Cipher cipher;
    private final Mac mac;
    private final byte[] salt;
    private final byte[] derivedPasswordVerifier;

    // AesStrength is an Enum with AES strength constants like salt or mac length
    public static AesEncoder create(AesStrength strength, char[] password) throws Exception {
        KeySpec spec = new PBEKeySpec(password, salt, 1000, strength.getSize());
        SecretKey secretKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec);
        byte[] iv = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secretKey.getEncoded(), "AES"), new IvParameterSpec(iv));

        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(new SecretKeySpec(macKey, "HmacSHA1"));
        return new AesEncoder(cipher, mac, salt, derivedPasswordVerifier);
    }

    private static byte[] generateSalt(AesStrength strength) {
        SecureRandom random = new SecureRandom();
        byte[] buf = new byte[strength.getSaltLength()];
        random.nextBytes(buf);
        return buf;
    }

    @Override
    public void encrypt(byte[] buf, int offs, int len) {
        try {
            // buf is INPUT DATA WITH LENGTH 16 bytes (alwasy, because AES requires it)
            cipher.update(buf, offs, len, buf);
            mac.update(buf, offs, len);
        } catch(Exception e) {
            throw new Zip4jException(e);
        }
    }

    // ...

}

而且我想加密以下文本:

abcdefghijklmnopqrstuvwxyz

[没有详细说明,我两次调用了encrypt()方法,两次调用的都是16个字节长的块:abcdefghijklmnopqrstuvwxyz。而且我有使用AES算法加密的正确ZIP文件。

我用WinZipWinRar之类的任何存档器打开该ZIP文件,然后打开加密文件。结果,我得到以下文本:

abcdefghijklmnopÄÝB`CÙ〜Wi¯

如您所见,第一个块已正确加密,但第二个块已正确加密-没有。

我已经研究了这个问题。我找到了一个名为zip4j的可行解决方案,并且发现了两个区别:

First:此库具有自定义AES实现AESEngine;我使用jdk实现;

Second:从第一个字节开始增加Initialization Vector

  • 第一块 iv = {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  • 第二块 iv = {2,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

但是jdk中使用的com.sun.crypto.provider.CounterMode,从头开始增加向量:

  • 第一块 iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
  • 第二块 iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};

P.S。如果我设置初始矢量,例如iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }

这是来自CounterMode的代码:

private static void increment(byte[] b) {
    int n = b.length - 1;
    while ((n >= 0) && (++b[n] == 0)) {
        n--;
    }
}

[The Question。有人注意到它了,如何解决?

注意:

  1. 我的代码加密/解密工作正常。正确压缩和解压缩文件。问题是另一个存档程序(如WinZip或WinRar)无法正确解密此文件。
  2. 我已尝试在jdk中检查整个实现,并在对第二个块进行加密之前修改了Initial Vector以使其完全为{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }。结果-第二个区块是正确加密

解决方法:


我已经解决了这个问题。问题是Java的AES实现与ZIP标准有些不同。

我已经使用Java的zip4jvm类在Cypher库中实现了AES加密:

class AesEncoder {
    public static AesEncoder create(AesStrength strength, char[] password) {
        try {
            byte[] salt = strength.generateSalt();
            byte[] key = AesEngine.createKey(password, salt, strength);

            Cipher cipher = AesEngine.createCipher(strength.createSecretKeyForCipher(key));
            Mac mac = AesEngine.createMac(strength.createSecretKeyForMac(key));
            byte[] passwordChecksum = strength.createPasswordChecksum(key);

            return new AesEncoder(cipher, mac, salt, passwordChecksum);
        } catch(Exception e) {
            throw new Zip4jvmException(e);
        }
    }
}

@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
enum AesStrength {
    S128(128),
    S192(192),
    S256(256);

    private final int size;

    public final int saltLength() {
        return size / 16;
    }

    private int macLength() {
        return size / 8;
    }

    private int keyLength() {
        return size / 8;
    }

    public SecretKeySpec createSecretKeyForCipher(byte[] key) {
        return new SecretKeySpec(key, 0, keyLength(), "AES");
    }

    public SecretKeySpec createSecretKeyForMac(byte[] key) {
        return new SecretKeySpec(key, keyLength(), macLength(), "HmacSHA1");
    }

    public byte[] createPasswordChecksum(byte[] key) {
        final int offs = keyLength() + macLength();
        return new byte[] { key[offs], key[offs + 1] };
    }

    public byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] buf = new byte[saltLength()];
        random.nextBytes(buf);
        return buf;
    }

}

class AesEngine {
    private static final int ITERATION_COUNT = 1000;

    public static byte[] createKey(char[] password, byte[] salt, AesStrength strength) throws NoSuchAlgorithmException, InvalidKeySpecException {
        int keyLength = strength.getSize() * 2 + 16;
        KeySpec keySpec = new PBEKeySpec(password, salt, ITERATION_COUNT, keyLength);
        return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(keySpec).getEncoded();
    }

    public static Cipher createCipher(SecretKeySpec secretKeySpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
        Cipher cipher = Cipher.getInstance("AES");
        // use custom AES implementation, so no worry for DECRYPT_MODE
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        return cipher;
    }

    public static Mac createMac(SecretKeySpec secretKeySpec) throws NoSuchAlgorithmException, InvalidKeyException {
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(secretKeySpec);
        return mac;
    }

}

您可以在AesEncoder.java中找到更多信息>

上一篇:如何从同一类的另一个对象中调用变量

下一篇:使用Asynctask在Android中显示ProgressBar

您可能感兴趣的文章

相关阅读

热门软件源码

最新软件源码下载