/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.idyl.winzipaes.impl;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Utility methods for handling WinZip AES cryptography using the Java Cryptography Architecture.
*
* @author Matthew Dempsky <mdempsky@google.com>
*/
public class AESUtilsJCA {
public static final int ITERATION_COUNT = 1000;
public static final int BLOCK_SIZE = 16;
private final Cipher cipher;
private final Mac mac;
private final byte[] passwordVerifier;
/* State for implementing AES-CTR. */
private final byte[] iv = new byte[BLOCK_SIZE];
private final byte[] keystream = new byte[BLOCK_SIZE];
private int next = BLOCK_SIZE;
public AESUtilsJCA(String password, int keySize, byte[] salt) {
if (keySize != 128 && keySize != 192 && keySize != 256)
throw new IllegalArgumentException("Illegal keysize: " + keySize);
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
char[] passwordChars = password.toCharArray();
PBEKeySpec keySpec = new PBEKeySpec(passwordChars, salt, ITERATION_COUNT, keySize * 2 + 16);
SecretKey sk = skf.generateSecret(keySpec);
byte[] keyBytes = sk.getEncoded();
cipher = Cipher.getInstance("AES");
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, 0, keySize / 8, "AES");
//int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
//System.out.println( "maxKeyLen=" + maxKeyLen );
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(keyBytes, keySize / 8, keySize / 8, "HmacSHA1"));
passwordVerifier = new byte[2];
System.arraycopy(keyBytes, 2 * (keySize / 8), passwordVerifier, 0, 2);
} catch (NoSuchAlgorithmException e) {
/*
* XXX(mdempsky): Could happen if the user's JRE doesn't support PBKDF2,
* AES, and/or HMAC-SHA1. Throw a better exception?
*/
throw new TypeNotPresentException(e.getMessage(), e);
} catch (InvalidKeyException e) {
e.printStackTrace();
/* Shouldn't happen: our key specs match our algorithms. */
throw new TypeNotPresentException(e.getMessage(), e);
} catch (InvalidKeySpecException e) {
/* Shouldn't happen: our key specs match our algorithms. */
throw new TypeNotPresentException(e.getMessage(), e);
} catch (NoSuchPaddingException e) {
/* Shouldn't happen: we don't specify any padding schemes. */
throw new TypeNotPresentException(e.getMessage(), e);
}
}
public void cryptUpdate(byte[] in, int length) {
try {
/*
* We must implement CTR mode by hand, because WinZip's AES encryption
* scheme is incompatible with Java's AES/CTR/NoPadding.
*/
for (int i = 0; i < length; ++i) {
/*
* If we've exhausted the current keystream block, we need to
* increment the iv and generate another one.
*/
if (next == BLOCK_SIZE) {
for (int j = 0; j < BLOCK_SIZE; ++j)
if (++iv[j] != 0)
break;
cipher.update(iv, 0, BLOCK_SIZE, keystream);
next = 0;
}
in[i] ^= keystream[next++];
}
} catch (ShortBufferException e) {
/* Shouldn't happen: our output buffer is always appropriately sized. */
throw new Error();
}
}
public void authUpdate(byte[] in, int length) {
mac.update(in, 0, length);
}
public byte[] getFinalAuthentifier() {
byte[] auth = new byte[10];
System.arraycopy(mac.doFinal(), 0, auth, 0, 10);
return auth;
}
public byte[] getPasswordVerifier() {
return passwordVerifier;
}
}