ZIP文件的AES加密不正确的第2个16字节块
问题描述
我正在使用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个字节长的块:abcdefghijklmnop
和qrstuvwxyz
。而且我有使用AES算法加密的正确ZIP文件。
我用WinZip
或WinRar
之类的任何存档器打开该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。有人注意到它了,如何解决?
注意:
- 我的代码加密/解密工作正常。正确压缩和解压缩文件。问题是另一个存档程序(如WinZip或WinRar)无法正确解密此文件。
- 我已尝试在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中找到更多信息>