All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.truevfs.comp.zip.WinZipAesOutputStream Maven / Gradle / Ivy

There is a newer version: 0.14.0
Show newest version
/*
 * Copyright (C) 2005-2015 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package net.java.truevfs.comp.zip;

import java.io.IOException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.annotation.concurrent.NotThreadSafe;
import net.java.truecommons.io.DecoratingOutputStream;
import net.java.truecommons.io.LittleEndianOutputStream;
import net.java.truecommons.key.spec.KeyStrength;
import net.java.truevfs.comp.zip.crypto.BufferedPartialBlockCipher;
import net.java.truevfs.comp.zip.crypto.CipherOutputStream;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.io.MacOutputStream;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.io.TeeOutputStream;

/**
 * Encrypts ZIP entry contents according the WinZip AES specification.
 *
 * @see     AES Encryption Information: Encryption Specification AE-1 and AE-2 (WinZip Computing, S.L.)
 * @see     AES Coding Tips for Developers (WinZip Computing, S.L.)
 * @see     A Password Based File Encyption Utility (Dr. Gladman)
 * @see     RFC 2898: PKCS #5: Password-Based Cryptography Specification Version 2.0 (IETF et al.)
 * @see     AbstractZipOutputStream$WinZipAesOutputMethod
 * @author  Christian Schlichtherle
 */
@NotThreadSafe
final class WinZipAesOutputStream extends DecoratingOutputStream {

    /**
     * The iteration count for the derived keys of the cipher, KLAC and MAC.
     */
    static final int ITERATION_COUNT = 1000;

    /**
     * The block size of the Advanced Encryption Specification (AES) Algorithm
     * in bits ({@value #AES_BLOCK_SIZE_BITS}).
     */
    static final int AES_BLOCK_SIZE_BITS = 128;

    static final int PWD_VERIFIER_BITS = 16;

    /** The message authentication code (MAC) output stream. */
    private final MacOutputStream mos;

    /**
     * The low level data output stream.
     * Used for writing the header and footer.
     **/
    private final LittleEndianOutputStream leos;

    WinZipAesOutputStream(
            final WinZipAesEntryParameters param,
            final LittleEndianOutputStream leos)
    throws IOException {
        assert null != param;
        try {
            assert null != leos;

            // Init key strength.
            final KeyStrength keyStrength = param.getKeyStrength();
            final int keyStrengthBits = keyStrength.getBits();
            final int keyStrengthBytes = keyStrength.getBytes();

            // Shake the salt.
            final byte[] salt = new byte[keyStrengthBytes / 2];
            new SecureRandom().nextBytes(salt);

            // Init password.
            final byte[] passwd = param.getWritePassword();

            // Derive cipher and MAC parameters.
            final PBEParametersGenerator gen = new PKCS5S2ParametersGenerator();
            gen.init(passwd, salt, ITERATION_COUNT);
            // Here comes the strange part about WinZip AES encryption:
            // Its unorthodox use of the Password-Based Key Derivation
            // Function 2 (PBKDF2) of PKCS #5 V2.0 alias RFC 2898.
            // Yes, the password verifier is only a 16 bit value.
            // So we must use the MAC for password verification, too.
            assert AES_BLOCK_SIZE_BITS <= keyStrengthBits;
            final KeyParameter keyParam =
                    (KeyParameter) gen.generateDerivedParameters(
                        2 * keyStrengthBits + PWD_VERIFIER_BITS);
            Arrays.fill(passwd, (byte) 0); // must not wipe before generator use!

            // Can you believe they "forgot" the nonce in the CTR mode IV?! :-(
            final byte[] ctrIv = new byte[AES_BLOCK_SIZE_BITS / 8];
            final ParametersWithIV aesCtrParam = new ParametersWithIV(
                    new KeyParameter(keyParam.getKey(), 0, keyStrengthBytes),
                    ctrIv); // yes, the IV is an array of zero bytes!
            final KeyParameter sha1HMacParam = new KeyParameter(
                    keyParam.getKey(),
                    keyStrengthBytes,
                    keyStrengthBytes);

            // Init cipher and stream.
            final BufferedBlockCipher
                    cipher = new BufferedPartialBlockCipher(new WinZipAesCipher());
            cipher.init(true, aesCtrParam);

            // Init MAC.
            final Mac mac = new HMac(new SHA1Digest());
            mac.init(sha1HMacParam);

            // Init chain of output streams as Encrypt-then-MAC.
            this.leos = leos;
            mos = new MacOutputStream(mac);
            out = new CipherOutputStream(cipher,
                    new TeeOutputStream(leos, mos));

            // Write header.
            leos.write(salt);
            writePasswordVerifier(param, keyParam);
        } catch (final Throwable ex) {
            try {
                leos.close();
            } catch (final Throwable ex2) {
                ex.addSuppressed(ex2);
            }
            throw ex;
        }
    }

    private void writePasswordVerifier(WinZipAesEntryParameters param, KeyParameter keyParam)
    throws IOException {
        leos.write(
                keyParam.getKey(),
                2 * param.getKeyStrength().getBytes(),
                PWD_VERIFIER_BITS / 8);
    }

    void finish() throws IOException {
        // Flush partial block to out, if any.
        ((CipherOutputStream) out).finish();

        // Compute and write the first half of the MAC to the footer.
        final byte[] buf = mos.getMac();
        leos.write(buf, 0, buf.length / 2);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy