Key Management

Abstract

This article shows how to set passwords for RAES encrypted ZIP files 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 RAES encrypted ZIP files are managed by an instance of a sub-class of the abstract archive driver implementation class ZipRaesDriver. For custom encrypted application file formats, this should be an instance of the class SafeZipRaesDriver

Whenever this archive driver class reads or writes a RAES encrypted ZIP file, it uses an instance of the interface KeyManagerProvider in order to obtain an instance of the interface KeyManager for the interface AesCipherParameters 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 RAES encrypted ZIP files 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 ZIP.RAES Files By Implementing A Custom Driver

This option bypasses the key manager by subclassing SafeZipRaesDriver and overriding the relevant methods for dealing with RAES parameters.

/**
 * Returns a new archive detector which uses the given password for all
 * RAES encrypted ZIP files 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.
 * @return A new archive detector which uses the given password for all
 *         RAES encrypted ZIP files with the given list of suffixes.
 */
public static TArchiveDetector newArchiveDetector1(
        FsDriverProvider delegate,
        String suffixes,
        char[] password) {
    return new TArchiveDetector(delegate,
            suffixes, new CustomZipRaesDriver(password));
}

private static final class CustomZipRaesDriver extends SafeZipRaesDriver {
    final RaesParameters param;
    
    CustomZipRaesDriver(char[] password) {
        super(IOPoolLocator.SINGLETON, KeyManagerLocator.SINGLETON);
        param = new CustomRaesParameters(password);
    }
    
    @Override
    protected RaesParameters raesParameters(FsModel model) {
        // 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;
    }
} // CustomZipRaesDriver

private static final class CustomRaesParameters
implements Type0RaesParameters {
    final char[] password;
    
    CustomRaesParameters(final char[] password) {
        this.password = password.clone();
    }
    
    @Override
    public char[] getWritePassword()
    throws RaesKeyException {
        return password.clone();
    }
    
    @Override
    public char[] getReadPassword(boolean invalid)
    throws RaesKeyException {
        if (invalid)
            throw new RaesKeyException("Invalid password!");
        return password.clone();
    }
    
    @Override
    public KeyStrength getKeyStrength()
    throws RaesKeyException {
        return KeyStrength.BITS_128;
    }
    
    @Override
    public void setKeyStrength(KeyStrength keyStrength)
    throws RaesKeyException {
        // We have been using only 128 bits to create archive entries.
        assert KeyStrength.BITS_128 == keyStrength;
    }
} // CustomRaesParameters

Setting Passwords For All ZIP.RAES Files 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
 * RAES encrypted ZIP files 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.
 * @return A new archive detector which uses the given password for all
 *         RAES encrypted ZIP files with the given list of suffixes.
 */
public static TArchiveDetector newArchiveDetector2(
        FsDriverProvider delegate,
        String suffixes,
        char[] password) {
    return new TArchiveDetector(delegate,
                suffixes,
                new SafeZipRaesDriver(
                    IOPoolLocator.SINGLETON,
                    new PromptingKeyManagerService(
                        new CustomView(password))));
}

private static final class CustomView
implements PromptingKeyProvider.View<AesCipherParameters> {
    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 AesCipherParameters newKey() {
        AesCipherParameters param = new AesCipherParameters();
        param.setPassword(password);
        param.setKeyStrength(KeyStrength.BITS_128);
        return param;
    }
    
    @Override
    public void promptWriteKey(Controller<AesCipherParameters> 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<AesCipherParameters> 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