uk.ac.ox.webauth.crypto.Des3CbcSha1Kd.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.ox.webauth.crypto.Des3CbcSha1Kd.java

Source

/*
 * Webauth Java - Java implementation of the University of Stanford WebAuth
 * protocol.
 *
 * Copyright (C) 2006 University of Oxford
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package uk.ac.ox.webauth.crypto;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.DecoderException;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import uk.ac.ox.webauth.asn1.APOptions;

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;

/**
 * Class implementing Kerberos encryption type 16, des3-cbc-sha1-kd, as
 * described in section 6.3 of RFC 3961.
 *
 * <p/>$HeadURL$
 * <br/>$LastChangedRevision$
 * <br/>$LastChangedDate$
 * <br/>$LastChangedBy$
 * @author     Mats Henrikson
 * @version    $LastChangedRevision$
 */
public class Des3CbcSha1Kd extends EType {

    private static final IvParameterSpec IV = new IvParameterSpec(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 });
    private static final SecureRandom rand = new SecureRandom();

    private SecretKey key;
    private int keyUsage;

    public Des3CbcSha1Kd(SecretKey key, int keyUsage) {
        this.key = key;
        this.keyUsage = keyUsage;
    }

    @Override
    public ASN1Encodable decrypt(byte[] cipherData) throws IOException, GeneralSecurityException {
        // derive our decryption and hmac keys as per RFC 3961
        // first work out the "well known constant"s for the different keys
        byte[] wkcKe = new byte[5];
        wkcKe[0] = (byte) ((keyUsage >> 24) & 0xFF);
        wkcKe[1] = (byte) ((keyUsage >> 16) & 0xFF);
        wkcKe[2] = (byte) ((keyUsage >> 8) & 0xFF);
        wkcKe[3] = (byte) (keyUsage & 0xFF);
        wkcKe[4] = (byte) 0xAA;
        byte[] wkcKi = (byte[]) wkcKe.clone();
        wkcKi[4] = (byte) 0x55;

        // then make the keys
        // RFC 3961: Derived Key = DK(Base Key, Well-Known Constant)
        SecretKey ke = new SecretKeySpec(dk(key.getEncoded(), wkcKe), "DESede");
        SecretKey ki = new SecretKeySpec(dk(key.getEncoded(), wkcKi), "DESede");

        // set up the HMAC object so we can get the length
        Mac hmacSHA1 = Mac.getInstance("HmacSHA1");
        hmacSHA1.init(ki);
        int hmacLength = hmacSHA1.getMacLength();

        // first split the checksum off the data
        InputStream is = new ByteArrayInputStream(cipherData);
        byte[] data = new byte[cipherData.length - hmacLength];
        if (is.read(data) != data.length) {
            throw new IOException("Couldn't read all the encrypted data.");
        }
        byte[] checksum = new byte[hmacLength];
        if (is.read(checksum) != checksum.length) {
            throw new IOException("Couldn't read all the checksum data.");
        }

        // then decrypt the data
        Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
        cipher.init(DECRYPT_MODE, ke, IV);
        byte[] decrypted = cipher.doFinal(data);

        // check the HMAC
        byte[] newChecksum = hmacSHA1.doFinal(decrypted);
        if (!Arrays.equals(checksum, newChecksum)) {
            throw new GeneralSecurityException("Checksum failure.");
            //System.out.println("Checksum failed.");
        }

        // throw away the confounder and then return an ASN.1 encodable object
        is = new ByteArrayInputStream(decrypted);
        is.skip(cipher.getBlockSize());
        ASN1InputStream ais = new ASN1InputStream(is);
        return (ASN1Encodable) ais.readObject();
    }

    @Override
    public byte[] encrypt(ASN1Encodable o) throws IOException, GeneralSecurityException {
        // derive our decryption and hmac keys as per RFC 3961
        // first work out the "well known constant"s for the different keys
        byte[] wkcKe = new byte[5];
        wkcKe[0] = (byte) ((keyUsage >> 24) & 0xFF);
        wkcKe[1] = (byte) ((keyUsage >> 16) & 0xFF);
        wkcKe[2] = (byte) ((keyUsage >> 8) & 0xFF);
        wkcKe[3] = (byte) (keyUsage & 0xFF);
        wkcKe[4] = (byte) 0xAA;
        byte[] wkcKi = (byte[]) wkcKe.clone();
        wkcKi[4] = (byte) 0x55;

        // then make the keys
        // RFC 3961: Derived Key = DK(Base Key, Well-Known Constant)
        SecretKey ke = new SecretKeySpec(dk(key.getEncoded(), wkcKe), "DESede");
        SecretKey ki = new SecretKeySpec(dk(key.getEncoded(), wkcKi), "DESede");

        // setup the Cipher so we can get the block size
        Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
        cipher.init(ENCRYPT_MODE, ke, IV);
        int blockSize = cipher.getBlockSize();

        // set up the byte array with data
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] confounder = new byte[blockSize];
        synchronized (rand) {
            rand.nextBytes(confounder);
        }
        baos.write(confounder);
        baos.write(o.getDEREncoded());

        // PKCS7 padding
        byte[] pad = new byte[blockSize - (baos.size() % blockSize)];
        Arrays.fill(pad, (byte) pad.length);
        baos.write(pad);
        byte[] data = baos.toByteArray();

        // calculate the checksum
        Mac hmacSHA1 = Mac.getInstance("HmacSHA1");
        hmacSHA1.init(ki);
        byte[] checksum = hmacSHA1.doFinal(data);

        // now encrypt the data
        baos = new ByteArrayOutputStream();
        baos.write(cipher.doFinal(data));
        baos.write(checksum);

        return baos.toByteArray();
    }

    /**
     * From RFC 3961:
     * DK(Key, Constant) = random-to-key(DR(Key, Constant))
     */
    private static byte[] dk(byte[] key, byte[] constant) throws GeneralSecurityException {
        // TODO: currently not checking weak and semi-weak keys as per RFC 3961!!!
        return randomToKey(dr(key, constant));
    }

    /**
     * From RFC 3961:
     *
     * 6.3.1.  Triple DES Key Production (random-to-key, string-to-key)
     *
     * The 168 bits of random key data are converted to a protocol key value
     * as follows.  First, the 168 bits are divided into three groups of 56
     * bits, which are expanded individually into 64 bits as follows:
     *
     * <pre>
     * DES3random-to-key:
     *       1  2  3  4  5  6  7  p
     *       9 10 11 12 13 14 15  p
     *      17 18 19 20 21 22 23  p
     *      25 26 27 28 29 30 31  p
     *      33 34 35 36 37 38 39  p
     *      41 42 43 44 45 46 47  p
     *      49 50 51 52 53 54 55  p
     *      56 48 40 32 24 16  8  p
     * </pre>
     *
     * The "p" bits are parity bits computed over the data bits.  The output
     * of the three expansions, each corrected to avoid "weak" and "semi-
     * weak" keys as in section 6.2, are concatenated to form the protocol
     * key value.
     */
    private static byte[] randomToKey(byte[] rawKey) {
        if (rawKey.length != 21) {
            throw new IllegalArgumentException("The raw key must be 168 bits (21 bytes).");
        }

        // first expand and calculate parity for each of the three groups
        byte[] group1 = expandAndParity(rawKey, 0);
        byte[] group2 = expandAndParity(rawKey, 7);
        byte[] group3 = expandAndParity(rawKey, 14);

        // then put them together into one key and return that
        byte[] key = new byte[24];
        System.arraycopy(group1, 0, key, 0, group1.length);
        System.arraycopy(group2, 0, key, 8, group2.length);
        System.arraycopy(group3, 0, key, 16, group3.length);
        return key;
    }

    private static byte[] expandAndParity(byte[] in, int start) {
        // first work out what the last byte is
        byte[] out = new byte[8];
        System.arraycopy(in, start, out, 0, 7);
        int lastByte = 0;
        for (int last = 0; last < 7; last++) {
            if ((out[last] & 0x01) == 1) {
                lastByte |= (1 << (last + 1));
            }
        }
        out[7] = (byte) lastByte;

        // now work out the parity for each byte
        for (int i = 0; i < out.length; i++) {
            byte current = out[i];
            int bits = 0;
            for (int bit = 1; bit < 8; bit++) {
                current >>= 1;
                if ((current & 0x01) > 0) {
                    bits++;
                }
            }
            // set the parity bit if we get an even number of set bits
            if ((bits % 2) == 0) {
                out[i] |= 0x01;
            } else {
                out[i] &= 0xFE;
            }
        }

        return out;
    }

    /**
     * From RFC 3961:
     * DR(Key, Constant) = k-truncate(E(Key, Constant, initial-cipher-state))
     *
     * Here DR is the random-octet generation function described below, and
     * DK is the key-derivation function produced from it.  In this
     * construction, E(Key, Plaintext, CipherState) is a cipher, Constant is
     * a well-known constant determined by the specific usage of this
     * function, and k-truncate truncates its argument by taking the first k
     * bits.  Here, k is the key generation seed length needed for the
     * encryption system.
     *
     * The output of the DR function is a string of bits; the actual key is
     * produced by applying the cryptosystem's random-to-key operation on
     * this bitstring.
     *
     * If the Constant is smaller than the cipher block size of E, then it
     * must be expanded with n-fold() so it can be encrypted.  If the output
     * of E is shorter than k bits, it is fed back into the encryption as
     * many times as necessary.  The construct is as follows (where |
     * indicates concatentation):
     *
     *    K1 = E(Key, n-fold(Constant), initial-cipher-state)
     *    K2 = E(Key, K1, initial-cipher-state)
     *    K3 = E(Key, K2, initial-cipher-state)
     *    K4 = ...
     *
     *    DR(Key, Constant) = k-truncate(K1 | K2 | K3 | K4 ...)
     */
    private static byte[] dr(byte[] key, byte[] constant) throws GeneralSecurityException {
        // first make a DES3 key
        SecretKeyFactory factory = SecretKeyFactory.getInstance("DESede");
        KeySpec spec = new DESedeKeySpec(key);
        SecretKey secretKey = factory.generateSecret(spec);

        // initialise the cipher to use
        Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
        cipher.init(ENCRYPT_MODE, secretKey, IV);

        // ensure the constant is not smaller than the blocksize
        if (constant.length < cipher.getBlockSize()) {
            constant = nFold(constant, cipher.getBlockSize() * 8);
        }

        // now encrypt until we have at least 21 bytes, the length of a DES3 key
        byte[] input = constant;
        byte[] kn = new byte[0];
        do {
            byte[] newKn = cipher.doFinal(input);
            byte[] oldKn = kn;
            kn = new byte[oldKn.length + newKn.length];
            System.arraycopy(oldKn, 0, kn, 0, oldKn.length);
            System.arraycopy(newKn, 0, kn, oldKn.length, newKn.length);
            input = newKn;
        } while (kn.length < 21);

        // make sure we are returning exactly 21 bytes
        if (kn.length != 21) {
            byte[] newKn = new byte[21];
            System.arraycopy(kn, 0, newKn, 0, 21);
            kn = newKn;
        }

        return kn;
    }

    /**
     * From RFC 3961:
     *
     * We first define a primitive called n-folding, which takes a
     * variable-length input block and produces a fixed-length output
     * sequence.  The intent is to give each input bit approximately
     * equal weight in determining the value of each output bit.  Note
     * that whenever we need to treat a string of octets as a number, the
     * assumed representation is Big-Endian -- Most Significant Byte
     * first.
     *
     * To n-fold a number X, replicate the input value to a length that
     * is the least common multiple of n and the length of X.  Before
     * each repetition, the input is rotated to the right by 13 bit
     * positions.  The successive n-bit chunks are added together using
     * 1's-complement addition (that is, with end-around carry) to yield
     * a n-bit result....
     * @param   in      The incoming bytes.
     * @param   nBitsOut    The number of bits to fold the in bytes into.
     * @return  The in bytes folded into a byte array that is nBytesOut bits wide.
     */
    private static byte[] nFold(byte[] in, int nBitsOut) {
        if ((nBitsOut % 8) != 0) {
            throw new IllegalArgumentException("nBytesOut must be a multiple of 8");
        }

        // Some of this source is adapted from the MIT Kerberos v5 1.4.3 source.

        /* the code below is more readable if I make these bytes instead of bits */
        int nBytesIn = in.length;
        int nBytesOut = nBitsOut / 8;

        /* first compute lcm(n,k) */
        int a = nBytesOut;
        int b = nBytesIn;
        int c;
        while (b != 0) {
            c = b;
            b = a % b;
            a = c;
        }
        int lcm = (nBytesOut * nBytesIn) / a;

        /* now do the real work */
        byte[] workBuffer = new byte[lcm];

        // fill with shifted input
        byte[] shifted = (byte[]) in.clone();
        for (int i = 0; i < (lcm / shifted.length); i++) {
            System.arraycopy(shifted, 0, workBuffer, i * shifted.length, shifted.length);
            byte[] newShifted = new byte[shifted.length];
            for (int sbyteIndex = 0; sbyteIndex < shifted.length; sbyteIndex++) {
                int lowByte = (sbyteIndex - 2 + shifted.length) % shifted.length;
                int highByte = (sbyteIndex - 1 + shifted.length) % shifted.length;
                newShifted[sbyteIndex] = (byte) ((shifted[lowByte] << 3) | ((shifted[highByte] >> 5) & 0x07));
            }
            shifted = newShifted;
        }

        // 1's complement add the shifted blocks together
        byte[] out = new byte[nBytesOut];
        System.arraycopy(workBuffer, 0, out, 0, nBytesOut);
        for (int i = 1; i < (lcm / nBytesOut); i++) {
            int workOffset = i * nBytesOut;
            int carry = 0;
            for (int j = nBytesOut - 1; j >= 0; j--) {
                int result = (((int) out[j]) & 0xFF) + (((int) workBuffer[workOffset + j]) & 0xFF) + (carry & 0xFF);
                out[j] = (byte) result;
                carry = (result >>> 8) & 0x01;
            }
            out[nBytesOut - 1] = (byte) (((int) out[nBytesOut - 1]) + carry);
        }
        return out;
    }

    private static void test_nFold(String data, String input, String correct, int numBits) throws DecoderException {
        Hex hex = new Hex();
        byte[] output = nFold((byte[]) hex.decode(input), numBits);
        if (Arrays.equals((byte[]) hex.decode(correct), output)) {
            System.out.println("PASSED: " + numBits + "-fold of string '" + data + "'.");
        } else {
            System.err.println("FAILED: " + numBits + "-fold of string '" + data + "'.");
        }
    }

    private static void test_DR_DK(String key, String usage, String correct_dr, String correct_dk)
            throws DecoderException, GeneralSecurityException {
        Hex hex = new Hex();
        byte[] input = (byte[]) hex.decode(key);
        byte[] constant = (byte[]) hex.decode(usage);
        byte[] dr = (byte[]) hex.decode(correct_dr);
        byte[] dk = (byte[]) hex.decode(correct_dk);
        System.out.println("Testing DR/DK, usage: " + usage + ", key: " + key);
        byte[] output = dr(input, constant);
        if (Arrays.equals(output, dr)) {
            System.out.println("PASSED: DR");
        } else {
            System.err.println("FAILED: DR");
        }
        output = dk(input, constant);
        if (Arrays.equals(output, dk)) {
            System.out.println("PASSED: DK");
        } else {
            System.err.println("FAILED: DK");
        }
    }

    public static void main(String[] args) throws Exception {
        // test the nFold with test vectors given in RFC 3961

        /*
        64-fold("012345") =
        64-fold(303132333435) = be072631276b1955
        */
        test_nFold("012345", "303132333435", "be072631276b1955", 64);

        /*
        56-fold("password") =
        56-fold(70617373776f7264) = 78a07b6caf85fa
        */
        test_nFold("password", "70617373776f7264", "78a07b6caf85fa", 56);

        /*
        64-fold("Rough Consensus, and Running Code") =
        64-fold(526f75676820436f6e73656e7375732c20616e642052756e6e696e6720436f6465) = bb6ed30870b7f0e0
        */
        test_nFold("Rough Consensus, and Running Code",
                "526f75676820436f6e73656e7375732c20616e642052756e6e696e6720436f6465", "bb6ed30870b7f0e0", 64);

        /*
        168-fold("password") =
        168-fold(70617373776f7264) = 59e4a8ca7c0385c3c37b3f6d2000247cb6e6bd5b3e
        */
        test_nFold("password", "70617373776f7264", "59e4a8ca7c0385c3c37b3f6d2000247cb6e6bd5b3e", 168);

        /*
        192-fold("MASSACHVSETTS INSTITVTE OF TECHNOLOGY")
        192-fold(4d41535341434856534554545320494e53544954565445204f4620544543484e4f4c4f4759) =
           db3b0d8f0b061e603282b308a50841229ad798fab9540c1b
        */
        test_nFold("MASSACHVSETTS INSTITVTE OF TECHNOLOGY",
                "4d41535341434856534554545320494e53544954565445204f4620544543484e4f4c4f4759",
                "db3b0d8f0b061e603282b308a50841229ad798fab9540c1b", 192);

        /*
        168-fold("Q") =
        168-fold(51) = 518a54a2 15a8452a 518a54a2 15a8452a 518a54a2 15
        */
        test_nFold("Q", "51", "518a54a215a8452a518a54a215a8452a518a54a215", 168);

        /*
        168-fold("ba") =
        168-fold(6261) = fb25d531 ae897449 9f52fd92 ea9857c4 ba24cf29 7e
        */
        test_nFold("ba", "6261", "fb25d531ae8974499f52fd92ea9857c4ba24cf297e", 168);

        /*
        key:                 dce06b1f64c857a11c3db57c51899b2cc1791008ce973b92
        usage:               0000000155
        DR:                  935079d14490a75c3093c4a6e8c3b049c71e6ee705
        DK:                  925179d04591a79b5d3192c4a7e9c289b049c71f6ee604cd
        */
        test_DR_DK("dce06b1f64c857a11c3db57c51899b2cc1791008ce973b92", "0000000155",
                "935079d14490a75c3093c4a6e8c3b049c71e6ee705", "925179d04591a79b5d3192c4a7e9c289b049c71f6ee604cd");

        /*
        key:                 5e13d31c70ef765746578531cb51c15bf11ca82c97cee9f2
        usage:               00000001aa
        DR:                  9f58e5a047d894101c469845d67ae3c5249ed812f2
        DK:                  9e58e5a146d9942a101c469845d67a20e3c4259ed913f207
        */
        test_DR_DK("5e13d31c70ef765746578531cb51c15bf11ca82c97cee9f2", "00000001aa",
                "9f58e5a047d894101c469845d67ae3c5249ed812f2", "9e58e5a146d9942a101c469845d67a20e3c4259ed913f207");

        /*
        key:                 98e6fd8a04a4b6859b75a176540b9752bad3ecd610a252bc
        usage:               0000000155
        DR:                  12fff90c773f956d13fc2ca0d0840349dbd39908eb
        DK:                  13fef80d763e94ec6d13fd2ca1d085070249dad39808eabf
        */
        test_DR_DK("98e6fd8a04a4b6859b75a176540b9752bad3ecd610a252bc", "0000000155",
                "12fff90c773f956d13fc2ca0d0840349dbd39908eb", "13fef80d763e94ec6d13fd2ca1d085070249dad39808eabf");

        /*
        key:                 622aec25a2fe2cad7094680b7c64940280084c1a7cec92b5
        usage:               00000001aa
        DR:                  f8debf05b097e7dc0603686aca35d91fd9a5516a70
        DK:                  f8dfbf04b097e6d9dc0702686bcb3489d91fd9a4516b703e
        */
        test_DR_DK("622aec25a2fe2cad7094680b7c64940280084c1a7cec92b5", "00000001aa",
                "f8debf05b097e7dc0603686aca35d91fd9a5516a70", "f8dfbf04b097e6d9dc0702686bcb3489d91fd9a4516b703e");

        /*
        key:                 d3f8298ccb166438dcb9b93ee5a7629286a491f838f802fb
        usage:               6b65726265726f73 ("kerberos")
        DR:                  2270db565d2a3d64cfbfdc5305d4f778a6de42d9da
        DK:                  2370da575d2a3da864cebfdc5204d56df779a7df43d9da43
        */
        test_DR_DK("d3f8298ccb166438dcb9b93ee5a7629286a491f838f802fb", "6b65726265726f73",
                "2270db565d2a3d64cfbfdc5305d4f778a6de42d9da", "2370da575d2a3da864cebfdc5204d56df779a7df43d9da43");

        /*
        key:                 c1081649ada74362e6a1459d01dfd30d67c2234c940704da
        usage:               0000000155
        DR:                  348056ec98fcc517171d2b4d7a9493af482d999175
        DK:                  348057ec98fdc48016161c2a4c7a943e92ae492c989175f7
        */
        test_DR_DK("c1081649ada74362e6a1459d01dfd30d67c2234c940704da", "0000000155",
                "348056ec98fcc517171d2b4d7a9493af482d999175", "348057ec98fdc48016161c2a4c7a943e92ae492c989175f7");

        /*
        key:                 5d154af238f46713155719d55e2f1f790dd661f279a7917c
        usage:               00000001aa
        DR:                  a8818bc367dadacbe9a6c84627fb60c294b01215e5
        DK:                  a8808ac267dada3dcbe9a7c84626fbc761c294b01315e5c1
        */
        test_DR_DK("5d154af238f46713155719d55e2f1f790dd661f279a7917c", "00000001aa",
                "a8818bc367dadacbe9a6c84627fb60c294b01215e5", "a8808ac267dada3dcbe9a7c84626fbc761c294b01315e5c1");

        /*
        key:                 798562e049852f57dc8c343ba17f2ca1d97394efc8adc443
        usage:               0000000155
        DR:                  c813f88b3be2b2f75424ce9175fbc8483b88c8713a
        DK:                  c813f88a3be3b334f75425ce9175fbe3c8493b89c8703b49
        */
        test_DR_DK("798562e049852f57dc8c343ba17f2ca1d97394efc8adc443", "0000000155",
                "c813f88b3be2b2f75424ce9175fbc8483b88c8713a", "c813f88a3be3b334f75425ce9175fbe3c8493b89c8703b49");

        /*
        key:                 26dce334b545292f2feab9a8701a89a4b99eb9942cecd016
        usage:               00000001aa
        DR:                  f58efc6f83f93e55e695fd252cf8fe59f7d5ba37ec
        DK:                  f48ffd6e83f83e7354e694fd252cf83bfe58f7d5ba37ec5d
        */
        test_DR_DK("26dce334b545292f2feab9a8701a89a4b99eb9942cecd016", "00000001aa",
                "f58efc6f83f93e55e695fd252cf8fe59f7d5ba37ec", "f48ffd6e83f83e7354e694fd252cf83bfe58f7d5ba37ec5d");

        SecretKeyFactory factory = SecretKeyFactory.getInstance("DESede");
        KeySpec spec = new DESedeKeySpec(new byte[24]);
        SecretKey secretKey = factory.generateSecret(spec);
        ASN1Encodable apo = new APOptions();
        Des3CbcSha1Kd dcc = new Des3CbcSha1Kd(secretKey, 11);
        byte[] encrypted = dcc.encrypt(apo);
        apo = dcc.decrypt(encrypted);
        System.out.println("Encrypt-decrypt test successful.");
    }
}