Loading…

Kryptel/Java

Key Callback Example

Introduction

The high-level components – Kryptel storage handlers and Silver Key handlers – call IKeyCallback's functions to get the key material required for encryption or decryption. IKeyCallback is implemented by the client application, which is responsible for obtaining the password or the key from the user or from some kind of key storage.

As mentioned in the IKeyCallback description, the interface consists of two independent part, serving requests from new and legacy components.

New-style Callbacks

New Kryptel 8 or Silver Key 5 introduced a new concept of Unified Key Block. The new components do not perform any key processing; they treat the key block as a black box, leaving the job of key handling to the client-provided IKeyCallback.

This approach has several important advantages. It allows using of complex keys and makes possible secure communication with the Password Manager. Surprisingly, key callback function remains simple enough (with the help of a rather complex KeyBlock class, but anyway).

final class KeyData {
    public byte flags;
    public byte[] baseKey;
    public byte[] keyRecord;
}

KeyData EncryptionKeyCallback(Object arg,
                              String prompt,
                              KeyBlock.ComponentDescriptor desc) throws Exception;

KeyData DecryptionKeyCallback(Object arg,
                              String prompt,
                              byte[] keyRecord,
                              KeyBlock.ComponentDescriptor desc) throws Exception;

Parameters

arg
This argument is provided by the client; it usually points to some context-specific data. For instance, it is the keyArg argument of IEncryptedFileStorage.Create and IEncryptedFileStorage.Open. The storage does not use that argument, it just passes it as is to the key callback.
prompt
A string to be shown in a password dialog, usually the name of the file to be encrypted or decrypted.
keyRecord
A byte array containing the Unified Key Block read from the encrypted file.
desc
A descriptor containing information about the cipher and the hash function that are to be used for actual key production.

These functions return a KeyData structure; the most important member of which is baseKey – a desc.hashSize-long array containing the key. It is called base key because it is a base for derivative keys: the actual encryption key, the HMAC key, and the initialization vector.

Example New-style Callback

First, let us define some functions that will obtain raw key material, i.e. text passwords and binary keys. In theory these functions are to interface with the user, but we won't bother a busy human with silly requests.

private String AskPassword() {
    // Don't ask the user, we know better
    return "qwerty123";
}

private byte[] GetBinaryKey() {
    // No one will guess we are using a key this simple
    byte[] bk = new byte [Constants.BINARY_KEY_SIZE];
    for (int i = 0; i < bk.length; i++) bk[i] = (byte)(i & 0xFF);
    return bk;
}

Note that these functions are not a part of the IKeyCallback interface; they just make the example code clearer.

EncryptionKeyCallback

We want to encrypt the data with a composite key consisting of a password and a binary key.

public KeyData EncryptionKeyCallback(Object arg,
                                     String prompt,
                                     ComponentDescriptor desc) throws Exception {
    KeyBlock kb = new KeyBlock(desc);

    KeyBlock.CompositeKey key = kb.new CompositeKey(KeyBlock.PASSWORD_DEFAULT_FLAGS);
    key.AddSubkey(kb.new Password(AskPassword(), (byte)0));
    key.AddSubkey(kb.new BinaryKey(GetBinaryKey(), (byte)0));

    kb.AddKey(key);

    KeyData keydata =  new KeyData();
    keydata.flags = kb.GetFlags();
    keydata.baseKey = kb.GetBaseKey();
    keydata.keyRecord = kb.GetKeyBlock();

    kb.CleanUp();  // Not necessary but recommended
    return keydata;
}

Note that we specify zero flags mask for the component keys. That's because the flags of the individual components of a composite key are ignored; we could specify any value.

DecryptionKeyCallback

Decryption callback should parse the provided key block and ask the user for the required key material.

public KeyData DecryptionKeyCallback(Object arg,
                                     String prompt,
                                     byte[] keyRecord,
                                     ComponentDescriptor desc) throws Exception {
    KeyBlock kb = new KeyBlock(desc);
    kb.LoadKeyBlock(keyRecord);

    byte expected = kb.Expected();
    do {
        if ((expected & KEY_MATERIAL_PASSWORD) != 0)
            expected = kb.MatchPassword(AskPassword());
        else if ((expected & KEY_MATERIAL_BINARY_KEY) != 0)
            expected = kb.MatchBinaryKey(GetBinaryKey());
        else
            throw new Exception("Unsupported key material.");

        if ((expected & KEY_MATERIAL_FAILED) != 0)
                throw new Exception("Wrong password or key.");
    } while (expected != KEY_MATERIAL_MATCHED);

    KeyData keydata =  new KeyData();
    keydata.flags = kb.GetFlags();
    keydata.baseKey = kb.GetBaseKey();
    keydata.keyRecord = keyRecord;

    kb.CleanUp();  // Not necessary but recommended
    return keydata;
}

The logic of this function is pretty simple – it loads the key block, then requests keys from the user in the loop until it gets a complete encryption key.

Legacy Callback

A typical legacy callback function just throws a some kind of Not supported exception. However if you need to read files created by old versions, or if you need to create files readable by old versions, then you will have to write a real function.

public KeyRecord Callback(Object arg,
                          String prompt,
                          int allowed,
                          UUID expected) throws Exception {
    KeyRecord kr = new KeyRecord();
    kr.keyMaterial = KeyIdent.IDENT_PASSWORD;
    kr.password = AskPassword();
    return kr;
}