package java.util.zip; import java.util.Random; /** * This class implements a Traditional Zip Encryption / Decryption * engine according to the ZIP file format specification to encrypt * / decrypt a data after it has been compressed. */ public class TraditionalZipCryption implements ZipCryption { private static long[] crc32Table; private long[] keys; private String password; /** * Encryption header size */ public static int ENCRYPTION_HEADER_SIZE = 12; private static long[] INITIAL_KEY = {305419896L, 591751049L, 878082192L}; private static int CRC_TABLE_SIZE = 256; static { crc32Table = new long[CRC_TABLE_SIZE]; /* * Calculate CRC-32 table * make_crc_table() * https://tools.ietf.org/html/rfc1952#section-8 */ for (int n = 0; n < CRC_TABLE_SIZE; n++) { long c = n; for (int k = 0; k < 8; k++) { c = ((c & 1) == 1) ? 0xedb88320L ^ (c >>> 1) : (c >>> 1); c &= 0xffffffffL; } crc32Table[n] = c; } } /** * Constructor of TraditionalZipCryption. * @param password ZIP password */ public TraditionalZipCryption(String password) { this.password = password; this.keys = new long[ENCRYPTION_HEADER_SIZE]; reset(); } /** * update_crc() * https://tools.ietf.org/html/rfc1952#section-8 */ private long crc32(long crc, int buf) { return (crc32Table[(int)(crc ^ buf) & 0xff] ^ (crc >>> 8)) & 0xffffffffL; } /** * update_keys() * 6.1.5 Initializing the encryption keys * https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT */ private void updateKeys(int c){ keys[0] = crc32(keys[0], c); keys[1] += keys[0] & 0xffL; keys[1] = ((keys[1] * 134775813L) + 1L) & 0xffffffffL; keys[2] = crc32(keys[2], (int)(keys[1] >>> 24)); } /** * decrypt_byte() * 6.1.6 Decrypting the encryption header * https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT * * @return Decrypt byte */ private int decryptByte() { int temp = (int)(keys[2] & 0xffffL) | 2; return ((temp * (temp ^ 1)) >>> 8) & 0xff; } private int encode(int unsignedByteData) { int decByte = decryptByte(); updateKeys(unsignedByteData); return decByte ^ unsignedByteData; } private int decode(int unsignedByteData) { int decByte = (decryptByte() ^ unsignedByteData) & 0xff; updateKeys(decByte); return decByte; } /** * Calculate encription header * * @param e ZIP entry * @return ZIP encryption header */ @Override public byte[] getEncryptionHeader(ZipEntry e) { /* * 6.1.6 Decrypting the encryption header * * After the header is decrypted, the last 1 or 2 bytes in Buffer * should be the high-order word/byte of the CRC for the file being * decrypted, stored in Intel low-byte/high-byte order. Versions of * PKZIP prior to 2.0 used a 2 byte CRC check; a 1 byte CRC check is * used on versions after 2.0. This can be used to test if the password * supplied is correct or not. */ Random rand = new Random(); byte[] encryptionHeader = new byte[ENCRYPTION_HEADER_SIZE]; rand.nextBytes(encryptionHeader); /* This code comes from testkey() at crypt.c in unzip 6.0 */ encryptionHeader[ENCRYPTION_HEADER_SIZE - 1] = (e.crc == -1) ? (byte)((e.xdostime >>> 8) & 0xffL) : (byte)((e.crc >>> 24) & 0xffL); encryptBytes(encryptionHeader); return encryptionHeader; } /** * {@inheritDoc} */ @Override public byte[] encryptBytes(byte[] data, int offset, int length) { for (int idx = offset; idx < length; idx++) { data[idx] = (byte)(encode(Byte.toUnsignedInt(data[idx])) & 0xff); } return data; } /** * {@inheritDoc} */ @Override public byte[] encryptBytes(byte[] data) { return encryptBytes(data, 0, data.length); } /** * {@inheritDoc} */ @Override public void reset() { keys[0] = INITIAL_KEY[0]; keys[1] = INITIAL_KEY[1]; keys[2] = INITIAL_KEY[2]; for (byte b : password.getBytes()) { updateKeys(Byte.toUnsignedInt(b)); } } /** * {@inheritDoc} */ @Override public int getEncryptionHeaderSize() { return ENCRYPTION_HEADER_SIZE; } /** * {@inheritDoc} */ @Override public boolean isValid(ZipEntry e, byte[] encryptionHeader) { /* This code comes from testkey() at crypt.c in unzip 6.0 */ byte checkDigit = encryptionHeader[ENCRYPTION_HEADER_SIZE - 1]; return (e.flag & 8) == 8 ? (checkDigit == (byte)((e.xdostime >>> 8) & 0xffL)) : (checkDigit == (byte)((e.crc >>> 24) & 0xffL)); } /** * {@inheritDoc} */ @Override public byte[] decryptBytes(byte[] data, int offset, int length) { for (int idx = offset; idx < length; idx++) { data[idx] = (byte)(decode(Byte.toUnsignedInt(data[idx])) & 0xff); } return data; } /** * {@inheritDoc} */ @Override public byte[] decryptBytes(byte[] data) { return decryptBytes(data, 0, data.length); } }