Key Management

Abstract

This article shows how to set passwords for WinZip AES encrypted ZIP entries programmatically. Use whatever approach fits your needs best when you want to set the password programmatically instead of prompting the user for a key by means of the default Swing or Console based user interfaces.

Introduction

At runtime, all WinZip AES encrypted ZIP entries are managed by an instance of a sub-class of the abstract archive driver implementation class ZipDriver. For custom encrypted application file formats, this should be an instance of the class JarDriver

Whenever this archive driver class reads or writes a WinZip AES encrypted ZIP entry, it uses an instance of the interface KeyManagerProvider in order to obtain an instance of the interface KeyManager for the interface AesPbeParameters and finally obtain an instance of the interface KeyProvider for the prospective archive file.

Because dependency injection is used all over the place in order to resolve the implementation classes of the interfaces KeyManagerProvider, KeyManager and KeyProvider, this architecture provides several ways to set passwords programmatically. In order to set a common password for all WinZip AES encrypted ZIP entries you need a custom TArchiveDetector which you can either inject into any TFile constructor or install as the default archive detector by calling

TConfig.get().setArchiveDetector(detector);

Setting Passwords For All WinZip AES Entries By Implementing A Custom Driver

This option bypasses the key manager by subclassing JarDriver and overriding the relevant methods for dealing with WinZip AES parameters.

/**
 * Returns a new archive detector which uses the given password for all
 * WinZip AES encrypted ZIP entries with the given list of suffixes.
 * <p>
 * When used for encryption, the AES key strength will be set to 128 bits.
 * <p>
 * A protective copy of the given password char array is made.
 * It's recommended to overwrite the parameter array with any non-password
 * data after calling this method.
 *
 * @param  delegate the file system driver provider to decorate.
 * @param  suffixes A list of file name suffixes which shall identify
 *         prospective archive files.
 *         This must not be {@code null} and must not be empty.
 * @param  password the password byte array to be copied for internal use.
 *         The bytes should be limited to seven bits only, see
 *         {@link WinZipAesParameters}.
 * @return A new archive detector which uses the given password for all
 *         WinZip AES encrypted ZIP entries with the given list of suffixes.
 */
public static TArchiveDetector newArchiveDetector1(
        FsDriverProvider delegate,
        String suffixes,
        byte[] password) {
    return new TArchiveDetector(delegate,
            suffixes, new CustomZipDriver1(password));
}

private static final class CustomZipDriver1 extends ZipDriver {
    final ZipCryptoParameters param;
    
    CustomZipDriver1(byte[] password) {
        super(IOPoolLocator.SINGLETON);
        param = new CustomWinZipAesParameters(password);
    }
    
    @Override
    protected ZipCryptoParameters zipCryptoParameters(
            FsModel model,
            Charset charset) {
        // If you need the URI of the particular archive file, then call
        // model.getMountPoint().toUri().
        // If you need a more user friendly form of this URI, then call
        // model.getMountPoint().toHierarchicalUri().
        
        // Let's not use the key manager but instead our custom parameters.
        return param;
    }
            
    @Override
    public <M extends FsModel> FsController<M> decorate(
            FsController<M> controller) {
        // This is a minor improvement: The default implementation decorates
        // the default file system controller chain with a package private
        // file system controller which uses the key manager to keep track
        // of the encryption parameters.
        // Because we are not using the key manager, we don't need this
        // special purpose file system controller and can simply return the
        // given file system controller chain instead.
        return controller;
    }
    
    @Override
    protected boolean process(
            ZipDriverEntry input,
            ZipDriverEntry output) {
        // Because we are using the same encryption key for all entries
        // of our custom archive file format we do NOT need to process the
        // entries according to the following pipeline when copying them:
        // decrypt(inputKey) -> inflate() -> deflate() -> encrypt(outputKey)
        
        // This reduces the processing pipeline to a simple copy operation
        // and is a DRASTIC performance improvement, e.g. when compacting
        // an archive file.
        return false;
        
        // This is the default implementation - try to see the difference.
        //return input.isEncrypted() || output.isEncrypted();
    }
} // CustomZipDriver1

private static final class CustomWinZipAesParameters
implements WinZipAesParameters {
    final byte[] password;
    
    CustomWinZipAesParameters(final byte[] password) {
        this.password = password.clone();
    }
    
    @Override
    public byte[] getWritePassword(String name)
    throws ZipKeyException {
        return password.clone();
    }
    
    @Override
    public byte[] getReadPassword(String name, boolean invalid)
    throws ZipKeyException {
        if (invalid)
            throw new ZipKeyException(name + " (invalid password)");
        return password.clone();
    }
    
    @Override
    public AesKeyStrength getKeyStrength(String arg0)
    throws ZipKeyException {
        return AesKeyStrength.BITS_128;
    }
    
    @Override
    public void setKeyStrength(String name, AesKeyStrength keyStrength)
    throws ZipKeyException {
        // We have been using only 128 bits to create archive entries.
        assert AesKeyStrength.BITS_128 == keyStrength;
    }
} // CustomWinZipAesParameters

Setting Passwords For All WinZip AES Entries By Implementing A Custom View

Another option is to customize the key manager by instantiating the class PromptingKeyManagerService which applies the Model-View-Controller pattern to manage its key providers. Here I simply substitute the default view class with a custom implementation.

/**
 * Returns a new archive detector which uses the given password for all
 * WinZip AES encrypted ZIP entries with the given list of suffixes.
 * <p>
 * When used for encryption, the AES key strength will be set to 128 bits.
 * <p>
 * A protective copy of the given password char array is made.
 * It's recommended to overwrite the parameter array with any non-password
 * data after calling this method.
 *
 * @param  delegate the file system driver provider to decorate.
 * @param  suffixes A list of file name suffixes which shall identify
 *         prospective archive files.
 *         This must not be {@code null} and must not be empty.
 * @param  password the password char array to be copied for internal use.
 *         The characters should be limited to US-ASCII, see
 *         {@link WinZipAesParameters}.
 * @return A new archive detector which uses the given password for all
 *         WinZip AES encrypted ZIP entries with the given list of suffixes.
 */
public static TArchiveDetector newArchiveDetector2(
        FsDriverProvider delegate,
        String suffixes,
        char[] password) {
    return new TArchiveDetector(delegate,
                suffixes, new CustomZipDriver2(password));
}

private static final class CustomZipDriver2 extends ZipDriver {
    final KeyManagerProvider provider;
    
    CustomZipDriver2(char[] password) {
        super(IOPoolLocator.SINGLETON);
        this.provider = new PromptingKeyManagerService(
                new CustomView(password));
    }
    
    @Override
    protected KeyManagerProvider getKeyManagerProvider() {
        return provider;
    }
} // CustomZipDriver2

private static final class CustomView
implements PromptingKeyProvider.View<AesPbeParameters> {
    final char[] password;
    
    CustomView(char[] password) {
        this.password = password.clone();
    }
    
    /**
     * You need to create a new key because the key manager may eventually
     * reset it when the archive file gets moved or deleted.
     */
    private AesPbeParameters newKey() {
        AesPbeParameters param = new AesPbeParameters();
        param.setPassword(password);
        param.setKeyStrength(AesKeyStrength.BITS_128);
        return param;
    }
    
    @Override
    public void promptWriteKey(Controller<AesPbeParameters> controller)
    throws UnknownKeyException {
        // You might as well call controller.getResource() here in order to
        // programmatically set the parameters for individual resource URIs.
        // Note that this would typically return the hierarchical URI of
        // the archive file unless ZipDriver.mountPointUri(FsModel) would
        // have been overridden.
        controller.setKey(newKey());
    }
    
    @Override
    public void promptReadKey(  Controller<AesPbeParameters> controller,
                                boolean invalid)
    throws UnknownKeyException {
        // You might as well call controller.getResource() here in order to
        // programmatically set the parameters for individual resource URIs.
        // Note that this would typically return the hierarchical URI of
        // the archive file unless ZipDriver.mountPointUri(FsModel) would
        // have been overridden.
        controller.setKey(newKey());
    }
} // CustomView