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

de.schlichtherle.truezip.zip.WinZipAesEntryOutputStream Maven / Gradle / Ivy

Go to download

The file system driver family for ZIP and related archive file types. Add the JAR artifact of this module to the run time class path to make its file system drivers available for service location in the client API modules.

There is a newer version: 7.7.9
Show newest version
/*
 * Copyright (C) 2005-2015 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package de.schlichtherle.truezip.zip;

import de.schlichtherle.truezip.crypto.BufferedPartialBlockCipher;
import de.schlichtherle.truezip.crypto.CipherOutputStream;
import de.schlichtherle.truezip.crypto.param.KeyStrength;
import de.schlichtherle.truezip.io.LEDataOutputStream;
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;

import javax.annotation.concurrent.NotThreadSafe;
import java.io.IOException;
import java.security.SecureRandom;

/**
 * Encrypts ZIP entry contents according the WinZip AES specification.
 * 
 * @since   TrueZIP 7.3
 * @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     RawZipOutputStream$WinZipAesOutputMethod
 * @author  Christian Schlichtherle
 */
@NotThreadSafe
final class WinZipAesEntryOutputStream extends CipherOutputStream {

    /**
     * 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;

    private final SecureRandom shaker = new SecureRandom();

    private final WinZipAesEntryParameters param;

    /** 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 LEDataOutputStream dos;

    WinZipAesEntryOutputStream(
            final LEDataOutputStream out,
            final WinZipAesEntryParameters param)
    throws IOException {
        super(out, new BufferedPartialBlockCipher(new WinZipAesCipher()));
        assert null != out;
        assert null != param;
        this.param = param;

        // 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];
        shaker.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);
        paranoidWipe(passwd); // 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.
        cipher.init(true, aesCtrParam);

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

        // Reinit chain of output streams as Encrypt-then-MAC.
        dos = (LEDataOutputStream) this.delegate;
        mos = new MacOutputStream(mac);
        delegate = new TeeOutputStream(dos, mos);

        // Write header.
        dos.write(salt);
        writePasswordVerifier(keyParam);
    }

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

    /** Wipe the given array. */
    private void paranoidWipe(final byte[] passwd) {
        shaker.nextBytes(passwd);
    }

    @Override
    protected void finish() throws IOException {
        // Flush partial block to out, if any.
        super.finish();

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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy