alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (CipherBox.java)

This example Java source code file (CipherBox.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

aead_cipher, badpaddingexception, block_cipher, cipher, cipherbox, crypto, gcmparameterspec, hexdumpencoder, illegalblocksizeexception, invalid, ivparameterspec, jce, nio, nosuchalgorithmexception, runtimeexception, security, shortbufferexception, util

The CipherBox.java Java example source code

/*
 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package sun.security.ssl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Arrays;

import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.GCMParameterSpec;

import java.nio.*;

import sun.security.ssl.CipherSuite.*;
import static sun.security.ssl.CipherSuite.*;
import static sun.security.ssl.CipherSuite.CipherType.*;

import sun.misc.HexDumpEncoder;


/**
 * This class handles bulk data enciphering/deciphering for each SSLv3
 * message.  This provides data confidentiality.  Stream ciphers (such
 * as RC4) don't need to do padding; block ciphers (e.g. DES) need it.
 *
 * Individual instances are obtained by calling the static method
 * newCipherBox(), which should only be invoked by BulkCipher.newCipher().
 *
 * In RFC 2246, with bock ciphers in CBC mode, the Initialization
 * Vector (IV) for the first record is generated with the other keys
 * and secrets when the security parameters are set.  The IV for
 * subsequent records is the last ciphertext block from the previous
 * record.
 *
 * In RFC 4346, the implicit Initialization Vector (IV) is replaced
 * with an explicit IV to protect against CBC attacks.  RFC 4346
 * recommends two algorithms used to generated the per-record IV.
 * The implementation uses the algorithm (2)(b), as described at
 * section 6.2.3.2 of RFC 4346.
 *
 * The usage of IV in CBC block cipher can be illustrated in
 * the following diagrams.
 *
 *   (random)
 *        R         P1                    IV        C1
 *        |          |                     |         |
 *  SIV---+    |-----+    |-...            |-----    |------
 *        |    |     |    |                |    |    |     |
 *     +----+  |  +----+  |             +----+  |  +----+  |
 *     | Ek |  |  + Ek +  |             | Dk |  |  | Dk |  |
 *     +----+  |  +----+  |             +----+  |  +----+  |
 *        |    |     |    |                |    |    |     |
 *        |----|     |----|           SIV--+    |----|     |-...
 *        |          |                     |       |
 *       IV         C1                     R      P1
 *                                     (discard)
 *
 *       CBC Encryption                    CBC Decryption
 *
 * NOTE that any ciphering involved in key exchange (e.g. with RSA) is
 * handled separately.
 *
 * @author David Brownell
 * @author Andreas Sterbenz
 */
final class CipherBox {

    // A CipherBox that implements the identity operation
    final static CipherBox NULL = new CipherBox();

    /* Class and subclass dynamic debugging support */
    private static final Debug debug = Debug.getInstance("ssl");

    // the protocol version this cipher conforms to
    private final ProtocolVersion protocolVersion;

    // cipher object
    private final Cipher cipher;

    /**
     * secure random
     */
    private SecureRandom random;

    /**
     * fixed IV, the implicit nonce of AEAD cipher suite, only apply to
     * AEAD cipher suites
     */
    private final byte[] fixedIv;

    /**
     * the key, reserved only for AEAD cipher initialization
     */
    private final Key key;

    /**
     * the operation mode, reserved for AEAD cipher initialization
     */
    private final int mode;

    /**
     * the authentication tag size, only apply to AEAD cipher suites
     */
    private final int tagSize;

    /**
     * the record IV length, only apply to AEAD cipher suites
     */
    private final int recordIvSize;

    /**
     * cipher type
     */
    private final CipherType cipherType;

    /**
     * Fixed masks of various block size, as the initial decryption IVs
     * for TLS 1.1 or later.
     *
     * For performance, we do not use random IVs. As the initial decryption
     * IVs will be discarded by TLS decryption processes, so the fixed masks
     * do not hurt cryptographic strength.
     */
    private static Hashtable<Integer, IvParameterSpec> masks;

    /**
     * NULL cipherbox. Identity operation, no encryption.
     */
    private CipherBox() {
        this.protocolVersion = ProtocolVersion.DEFAULT;
        this.cipher = null;
        this.cipherType = STREAM_CIPHER;
        this.fixedIv = new byte[0];
        this.key = null;
        this.mode = Cipher.ENCRYPT_MODE;    // choose at random
        this.random = null;
        this.tagSize = 0;
        this.recordIvSize = 0;
    }

    /**
     * Construct a new CipherBox using the cipher transformation.
     *
     * @exception NoSuchAlgorithmException if no appropriate JCE Cipher
     * implementation could be found.
     */
    private CipherBox(ProtocolVersion protocolVersion, BulkCipher bulkCipher,
            SecretKey key, IvParameterSpec iv, SecureRandom random,
            boolean encrypt) throws NoSuchAlgorithmException {
        try {
            this.protocolVersion = protocolVersion;
            this.cipher = JsseJce.getCipher(bulkCipher.transformation);
            this.mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;

            if (random == null) {
                random = JsseJce.getSecureRandom();
            }
            this.random = random;
            this.cipherType = bulkCipher.cipherType;

            /*
             * RFC 4346 recommends two algorithms used to generated the
             * per-record IV. The implementation uses the algorithm (2)(b),
             * as described at section 6.2.3.2 of RFC 4346.
             *
             * As we don't care about the initial IV value for TLS 1.1 or
             * later, so if the "iv" parameter is null, we use the default
             * value generated by Cipher.init() for encryption, and a fixed
             * mask for decryption.
             */
            if (iv == null && bulkCipher.ivSize != 0 &&
                    mode == Cipher.DECRYPT_MODE &&
                    protocolVersion.v >= ProtocolVersion.TLS11.v) {
                iv = getFixedMask(bulkCipher.ivSize);
            }

            if (cipherType == AEAD_CIPHER) {
                // AEAD must completely initialize the cipher for each packet,
                // and so we save initialization parameters for packet
                // processing time.

                // Set the tag size for AEAD cipher
                tagSize = bulkCipher.tagSize;

                // Reserve the key for AEAD cipher initialization
                this.key = key;

                fixedIv = iv.getIV();
                if (fixedIv == null ||
                        fixedIv.length != bulkCipher.fixedIvSize) {
                    throw new RuntimeException("Improper fixed IV for AEAD");
                }

                // Set the record IV length for AEAD cipher
                recordIvSize = bulkCipher.ivSize - bulkCipher.fixedIvSize;

                // DON'T initialize the cipher for AEAD!
            } else {
                // CBC only requires one initialization during its lifetime
                // (future packets/IVs set the proper CBC state), so we can
                // initialize now.

                // Zeroize the variables that only apply to AEAD cipher
                this.tagSize = 0;
                this.fixedIv = new byte[0];
                this.recordIvSize = 0;
                this.key = null;

                // Initialize the cipher
                cipher.init(mode, key, iv, random);
            }
        } catch (NoSuchAlgorithmException e) {
            throw e;
        } catch (Exception e) {
            throw new NoSuchAlgorithmException
                    ("Could not create cipher " + bulkCipher, e);
        } catch (ExceptionInInitializerError e) {
            throw new NoSuchAlgorithmException
                    ("Could not create cipher " + bulkCipher, e);
        }
    }

    /*
     * Factory method to obtain a new CipherBox object.
     */
    static CipherBox newCipherBox(ProtocolVersion version, BulkCipher cipher,
            SecretKey key, IvParameterSpec iv, SecureRandom random,
            boolean encrypt) throws NoSuchAlgorithmException {
        if (cipher.allowed == false) {
            throw new NoSuchAlgorithmException("Unsupported cipher " + cipher);
        }

        if (cipher == B_NULL) {
            return NULL;
        } else {
            return new CipherBox(version, cipher, key, iv, random, encrypt);
        }
    }

    /*
     * Get a fixed mask, as the initial decryption IVs for TLS 1.1 or later.
     */
    private static IvParameterSpec getFixedMask(int ivSize) {
        if (masks == null) {
            masks = new Hashtable<Integer, IvParameterSpec>(5);
        }

        IvParameterSpec iv = masks.get(ivSize);
        if (iv == null) {
            iv = new IvParameterSpec(new byte[ivSize]);
            masks.put(ivSize, iv);
        }

        return iv;
    }

    /*
     * Encrypts a block of data, returning the size of the
     * resulting block.
     */
    int encrypt(byte[] buf, int offset, int len) {
        if (cipher == null) {
            return len;
        }

        try {
            int blockSize = cipher.getBlockSize();
            if (cipherType == BLOCK_CIPHER) {
                len = addPadding(buf, offset, len, blockSize);
            }

            if (debug != null && Debug.isOn("plaintext")) {
                try {
                    HexDumpEncoder hd = new HexDumpEncoder();

                    System.out.println(
                        "Padded plaintext before ENCRYPTION:  len = "
                        + len);
                    hd.encodeBuffer(
                        new ByteArrayInputStream(buf, offset, len),
                        System.out);
                } catch (IOException e) { }
            }


            if (cipherType == AEAD_CIPHER) {
                try {
                    return cipher.doFinal(buf, offset, len, buf, offset);
                } catch (IllegalBlockSizeException | BadPaddingException ibe) {
                    // unlikely to happen
                    throw new RuntimeException(
                        "Cipher error in AEAD mode in JCE provider " +
                        cipher.getProvider().getName(), ibe);
                }
            } else {
                int newLen = cipher.update(buf, offset, len, buf, offset);
                if (newLen != len) {
                    // catch BouncyCastle buffering error
                    throw new RuntimeException("Cipher buffering error " +
                        "in JCE provider " + cipher.getProvider().getName());
                }
                return newLen;
            }
        } catch (ShortBufferException e) {
            // unlikely to happen, we should have enough buffer space here
            throw new ArrayIndexOutOfBoundsException(e.toString());
        }
    }

    /*
     * Encrypts a ByteBuffer block of data, returning the size of the
     * resulting block.
     *
     * The byte buffers position and limit initially define the amount
     * to encrypt.  On return, the position and limit are
     * set to last position padded/encrypted.  The limit may have changed
     * because of the added padding bytes.
     */
    int encrypt(ByteBuffer bb, int outLimit) {

        int len = bb.remaining();

        if (cipher == null) {
            bb.position(bb.limit());
            return len;
        }

        int pos = bb.position();

        int blockSize = cipher.getBlockSize();
        if (cipherType == BLOCK_CIPHER) {
            // addPadding adjusts pos/limit
            len = addPadding(bb, blockSize);
            bb.position(pos);
        }

        if (debug != null && Debug.isOn("plaintext")) {
            try {
                HexDumpEncoder hd = new HexDumpEncoder();

                System.out.println(
                    "Padded plaintext before ENCRYPTION:  len = "
                    + len);
                hd.encodeBuffer(bb.duplicate(), System.out);

            } catch (IOException e) { }
        }

        /*
         * Encrypt "in-place".  This does not add its own padding.
         */
        ByteBuffer dup = bb.duplicate();
        if (cipherType == AEAD_CIPHER) {
            try {
                int outputSize = cipher.getOutputSize(dup.remaining());
                if (outputSize > bb.remaining()) {
                    // need to expand the limit of the output buffer for
                    // the authentication tag.
                    //
                    // DON'T worry about the buffer's capacity, we have
                    // reserved space for the authentication tag.
                    if (outLimit < pos + outputSize) {
                        // unlikely to happen
                        throw new ShortBufferException(
                                    "need more space in output buffer");
                    }
                    bb.limit(pos + outputSize);
                }
                int newLen = cipher.doFinal(dup, bb);
                if (newLen != outputSize) {
                    throw new RuntimeException(
                            "Cipher buffering error in JCE provider " +
                            cipher.getProvider().getName());
                }
                return newLen;
            } catch (IllegalBlockSizeException |
                           BadPaddingException | ShortBufferException ibse) {
                // unlikely to happen
                throw new RuntimeException(
                        "Cipher error in AEAD mode in JCE provider " +
                        cipher.getProvider().getName(), ibse);
            }
        } else {
            int newLen;
            try {
                newLen = cipher.update(dup, bb);
            } catch (ShortBufferException sbe) {
                // unlikely to happen
                throw new RuntimeException("Cipher buffering error " +
                    "in JCE provider " + cipher.getProvider().getName());
            }

            if (bb.position() != dup.position()) {
                throw new RuntimeException("bytebuffer padding error");
            }

            if (newLen != len) {
                // catch BouncyCastle buffering error
                throw new RuntimeException("Cipher buffering error " +
                    "in JCE provider " + cipher.getProvider().getName());
            }
            return newLen;
        }
    }


    /*
     * Decrypts a block of data, returning the size of the
     * resulting block if padding was required.
     *
     * For SSLv3 and TLSv1.0, with block ciphers in CBC mode the
     * Initialization Vector (IV) for the first record is generated by
     * the handshake protocol, the IV for subsequent records is the
     * last ciphertext block from the previous record.
     *
     * From TLSv1.1, the implicit IV is replaced with an explicit IV to
     * protect against CBC attacks.
     *
     * Differentiating between bad_record_mac and decryption_failed alerts
     * may permit certain attacks against CBC mode. It is preferable to
     * uniformly use the bad_record_mac alert to hide the specific type of
     * the error.
     */
    int decrypt(byte[] buf, int offset, int len,
            int tagLen) throws BadPaddingException {
        if (cipher == null) {
            return len;
        }

        try {
            int newLen;
            if (cipherType == AEAD_CIPHER) {
                try {
                    newLen = cipher.doFinal(buf, offset, len, buf, offset);
                } catch (IllegalBlockSizeException ibse) {
                    // unlikely to happen
                    throw new RuntimeException(
                        "Cipher error in AEAD mode in JCE provider " +
                        cipher.getProvider().getName(), ibse);
                }
            } else {
                newLen = cipher.update(buf, offset, len, buf, offset);
                if (newLen != len) {
                    // catch BouncyCastle buffering error
                    throw new RuntimeException("Cipher buffering error " +
                        "in JCE provider " + cipher.getProvider().getName());
                }
            }
            if (debug != null && Debug.isOn("plaintext")) {
                try {
                    HexDumpEncoder hd = new HexDumpEncoder();

                    System.out.println(
                        "Padded plaintext after DECRYPTION:  len = "
                        + newLen);
                    hd.encodeBuffer(
                        new ByteArrayInputStream(buf, offset, newLen),
                        System.out);
                } catch (IOException e) { }
            }

            if (cipherType == BLOCK_CIPHER) {
                int blockSize = cipher.getBlockSize();
                newLen = removePadding(
                    buf, offset, newLen, tagLen, blockSize, protocolVersion);

                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
                    if (newLen < blockSize) {
                        throw new BadPaddingException("invalid explicit IV");
                    }
                }
            }
            return newLen;
        } catch (ShortBufferException e) {
            // unlikely to happen, we should have enough buffer space here
            throw new ArrayIndexOutOfBoundsException(e.toString());
        }
    }


    /*
     * Decrypts a block of data, returning the size of the
     * resulting block if padding was required.  position and limit
     * point to the end of the decrypted/depadded data.  The initial
     * limit and new limit may be different, given we may
     * have stripped off some padding bytes.
     *
     *  @see decrypt(byte[], int, int)
     */
    int decrypt(ByteBuffer bb, int tagLen) throws BadPaddingException {

        int len = bb.remaining();

        if (cipher == null) {
            bb.position(bb.limit());
            return len;
        }

        try {
            /*
             * Decrypt "in-place".
             */
            int pos = bb.position();
            ByteBuffer dup = bb.duplicate();
            int newLen;
            if (cipherType == AEAD_CIPHER) {
                try {
                    newLen = cipher.doFinal(dup, bb);
                } catch (IllegalBlockSizeException ibse) {
                    // unlikely to happen
                    throw new RuntimeException(
                        "Cipher error in AEAD mode \"" + ibse.getMessage() +
                        " \"in JCE provider " + cipher.getProvider().getName());
                }
            } else {
                newLen = cipher.update(dup, bb);
                if (newLen != len) {
                    // catch BouncyCastle buffering error
                    throw new RuntimeException("Cipher buffering error " +
                        "in JCE provider " + cipher.getProvider().getName());
                }
            }

            // reset the limit to the end of the decryted data
            bb.limit(pos + newLen);

            if (debug != null && Debug.isOn("plaintext")) {
                try {
                    HexDumpEncoder hd = new HexDumpEncoder();

                    System.out.println(
                        "Padded plaintext after DECRYPTION:  len = "
                        + newLen);

                    hd.encodeBuffer(
                        (ByteBuffer)bb.duplicate().position(pos), System.out);
                } catch (IOException e) { }
            }

            /*
             * Remove the block padding.
             */
            if (cipherType == BLOCK_CIPHER) {
                int blockSize = cipher.getBlockSize();
                bb.position(pos);
                newLen = removePadding(bb, tagLen, blockSize, protocolVersion);

                // check the explicit IV of TLS v1.1 or later
                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
                    if (newLen < blockSize) {
                        throw new BadPaddingException("invalid explicit IV");
                    }

                    // reset the position to the end of the decrypted data
                    bb.position(bb.limit());
                }
            }
            return newLen;
        } catch (ShortBufferException e) {
            // unlikely to happen, we should have enough buffer space here
            throw new ArrayIndexOutOfBoundsException(e.toString());
        }
    }

    private static int addPadding(byte[] buf, int offset, int len,
            int blockSize) {
        int     newlen = len + 1;
        byte    pad;
        int     i;

        if ((newlen % blockSize) != 0) {
            newlen += blockSize - 1;
            newlen -= newlen % blockSize;
        }
        pad = (byte) (newlen - len);

        if (buf.length < (newlen + offset)) {
            throw new IllegalArgumentException("no space to pad buffer");
        }

        /*
         * TLS version of the padding works for both SSLv3 and TLSv1
         */
        for (i = 0, offset += len; i < pad; i++) {
            buf [offset++] = (byte) (pad - 1);
        }
        return newlen;
    }

    /*
     * Apply the padding to the buffer.
     *
     * Limit is advanced to the new buffer length.
     * Position is equal to limit.
     */
    private static int addPadding(ByteBuffer bb, int blockSize) {

        int     len = bb.remaining();
        int     offset = bb.position();

        int     newlen = len + 1;
        byte    pad;
        int     i;

        if ((newlen % blockSize) != 0) {
            newlen += blockSize - 1;
            newlen -= newlen % blockSize;
        }
        pad = (byte) (newlen - len);

        /*
         * Update the limit to what will be padded.
         */
        bb.limit(newlen + offset);

        /*
         * TLS version of the padding works for both SSLv3 and TLSv1
         */
        for (i = 0, offset += len; i < pad; i++) {
            bb.put(offset++, (byte) (pad - 1));
        }

        bb.position(offset);
        bb.limit(offset);

        return newlen;
    }

    /*
     * A constant-time check of the padding.
     *
     * NOTE that we are checking both the padding and the padLen bytes here.
     *
     * The caller MUST ensure that the len parameter is a positive number.
     */
    private static int[] checkPadding(
            byte[] buf, int offset, int len, byte pad) {

        if (len <= 0) {
            throw new RuntimeException("padding len must be positive");
        }

        // An array of hits is used to prevent Hotspot optimization for
        // the purpose of a constant-time check.
        int[] results = {0, 0};    // {missed #, matched #}
        for (int i = 0; i <= 256;) {
            for (int j = 0; j < len && i <= 256; j++, i++) {     // j <= i
                if (buf[offset + j] != pad) {
                    results[0]++;       // mismatched padding data
                } else {
                    results[1]++;       // matched padding data
                }
            }
        }

        return results;
    }

    /*
     * A constant-time check of the padding.
     *
     * NOTE that we are checking both the padding and the padLen bytes here.
     *
     * The caller MUST ensure that the bb parameter has remaining.
     */
    private static int[] checkPadding(ByteBuffer bb, byte pad) {

        if (!bb.hasRemaining()) {
            throw new RuntimeException("hasRemaining() must be positive");
        }

        // An array of hits is used to prevent Hotspot optimization for
        // the purpose of a constant-time check.
        int[] results = {0, 0};    // {missed #, matched #}
        bb.mark();
        for (int i = 0; i <= 256; bb.reset()) {
            for (; bb.hasRemaining() && i <= 256; i++) {
                if (bb.get() != pad) {
                    results[0]++;       // mismatched padding data
                } else {
                    results[1]++;       // matched padding data
                }
            }
        }

        return results;
    }

    /*
     * Typical TLS padding format for a 64 bit block cipher is as follows:
     *   xx xx xx xx xx xx xx 00
     *   xx xx xx xx xx xx 01 01
     *   ...
     *   xx 06 06 06 06 06 06 06
     *   07 07 07 07 07 07 07 07
     * TLS also allows any amount of padding from 1 and 256 bytes as long
     * as it makes the data a multiple of the block size
     */
    private static int removePadding(byte[] buf, int offset, int len,
            int tagLen, int blockSize,
            ProtocolVersion protocolVersion) throws BadPaddingException {

        // last byte is length byte (i.e. actual padding length - 1)
        int padOffset = offset + len - 1;
        int padLen = buf[padOffset] & 0xFF;

        int newLen = len - (padLen + 1);
        if ((newLen - tagLen) < 0) {
            // If the buffer is not long enough to contain the padding plus
            // a MAC tag, do a dummy constant-time padding check.
            //
            // Note that it is a dummy check, so we won't care about what is
            // the actual padding data.
            checkPadding(buf, offset, len, (byte)(padLen & 0xFF));

            throw new BadPaddingException("Invalid Padding length: " + padLen);
        }

        // The padding data should be filled with the padding length value.
        int[] results = checkPadding(buf, offset + newLen,
                        padLen + 1, (byte)(padLen & 0xFF));
        if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
            if (results[0] != 0) {          // padding data has invalid bytes
                throw new BadPaddingException("Invalid TLS padding data");
            }
        } else { // SSLv3
            // SSLv3 requires 0 <= length byte < block size
            // some implementations do 1 <= length byte <= block size,
            // so accept that as well
            // v3 does not require any particular value for the other bytes
            if (padLen > blockSize) {
                throw new BadPaddingException("Invalid SSLv3 padding");
            }
        }
        return newLen;
    }

    /*
     * Position/limit is equal the removed padding.
     */
    private static int removePadding(ByteBuffer bb,
            int tagLen, int blockSize,
            ProtocolVersion protocolVersion) throws BadPaddingException {

        int len = bb.remaining();
        int offset = bb.position();

        // last byte is length byte (i.e. actual padding length - 1)
        int padOffset = offset + len - 1;
        int padLen = bb.get(padOffset) & 0xFF;

        int newLen = len - (padLen + 1);
        if ((newLen - tagLen) < 0) {
            // If the buffer is not long enough to contain the padding plus
            // a MAC tag, do a dummy constant-time padding check.
            //
            // Note that it is a dummy check, so we won't care about what is
            // the actual padding data.
            checkPadding(bb.duplicate(), (byte)(padLen & 0xFF));

            throw new BadPaddingException("Invalid Padding length: " + padLen);
        }

        // The padding data should be filled with the padding length value.
        int[] results = checkPadding(
                (ByteBuffer)bb.duplicate().position(offset + newLen),
                (byte)(padLen & 0xFF));
        if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
            if (results[0] != 0) {          // padding data has invalid bytes
                throw new BadPaddingException("Invalid TLS padding data");
            }
        } else { // SSLv3
            // SSLv3 requires 0 <= length byte < block size
            // some implementations do 1 <= length byte <= block size,
            // so accept that as well
            // v3 does not require any particular value for the other bytes
            if (padLen > blockSize) {
                throw new BadPaddingException("Invalid SSLv3 padding");
            }
        }

        /*
         * Reset buffer limit to remove padding.
         */
        bb.position(offset + newLen);
        bb.limit(offset + newLen);

        return newLen;
    }

    /*
     * Dispose of any intermediate state in the underlying cipher.
     * For PKCS11 ciphers, this will release any attached sessions, and
     * thus make finalization faster.
     */
    void dispose() {
        try {
            if (cipher != null) {
                // ignore return value.
                cipher.doFinal();
            }
        } catch (Exception e) {
            // swallow all types of exceptions.
        }
    }

    /*
     * Does the cipher use CBC mode?
     *
     * @return true if the cipher use CBC mode, false otherwise.
     */
    boolean isCBCMode() {
        return cipherType == BLOCK_CIPHER;
    }

    /*
     * Does the cipher use AEAD mode?
     *
     * @return true if the cipher use AEAD mode, false otherwise.
     */
    boolean isAEADMode() {
        return cipherType == AEAD_CIPHER;
    }

    /*
     * Is the cipher null?
     *
     * @return true if the cipher is null, false otherwise.
     */
    boolean isNullCipher() {
        return cipher == null;
    }

    /*
     * Gets the explicit nonce/IV size of the cipher.
     *
     * The returned value is the SecurityParameters.record_iv_length in
     * RFC 4346/5246.  It is the size of explicit IV for CBC mode, and the
     * size of explicit nonce for AEAD mode.
     *
     * @return the explicit nonce size of the cipher.
     */
    int getExplicitNonceSize() {
        switch (cipherType) {
            case BLOCK_CIPHER:
                // For block ciphers, the explicit IV length is of length
                // SecurityParameters.record_iv_length, which is equal to
                // the SecurityParameters.block_size.
                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
                    return cipher.getBlockSize();
                }
                break;
            case AEAD_CIPHER:
                return recordIvSize;
                        // It is also the length of sequence number, which is
                        // used as the nonce_explicit for AEAD cipher suites.
        }

        return 0;
    }

    /*
     * Applies the explicit nonce/IV to this cipher. This method is used to
     * decrypt an SSL/TLS input record.
     *
     * The returned value is the SecurityParameters.record_iv_length in
     * RFC 4346/5246.  It is the size of explicit IV for CBC mode, and the
     * size of explicit nonce for AEAD mode.
     *
     * @param  authenticator the authenticator to get the additional
     *         authentication data
     * @param  contentType the content type of the input record
     * @param  bb the byte buffer to get the explicit nonce from
     *
     * @return the explicit nonce size of the cipher.
     */
    int applyExplicitNonce(Authenticator authenticator, byte contentType,
            ByteBuffer bb) throws BadPaddingException {
        switch (cipherType) {
            case BLOCK_CIPHER:
                // sanity check length of the ciphertext
                int tagLen = (authenticator instanceof MAC) ?
                                    ((MAC)authenticator).MAClen() : 0;
                if (tagLen != 0) {
                    if (!sanityCheck(tagLen, bb.remaining())) {
                        throw new BadPaddingException(
                                "ciphertext sanity check failed");
                    }
                }

                // For block ciphers, the explicit IV length is of length
                // SecurityParameters.record_iv_length, which is equal to
                // the SecurityParameters.block_size.
                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
                    return cipher.getBlockSize();
                }
                break;
            case AEAD_CIPHER:
                if (bb.remaining() < (recordIvSize + tagSize)) {
                    throw new BadPaddingException(
                                        "invalid AEAD cipher fragment");
                }

                // initialize the AEAD cipher for the unique IV
                byte[] iv = Arrays.copyOf(fixedIv,
                                    fixedIv.length + recordIvSize);
                bb.get(iv, fixedIv.length, recordIvSize);
                bb.position(bb.position() - recordIvSize);
                GCMParameterSpec spec = new GCMParameterSpec(tagSize * 8, iv);
                try {
                    cipher.init(mode, key, spec, random);
                } catch (InvalidKeyException |
                            InvalidAlgorithmParameterException ikae) {
                    // unlikely to happen
                    throw new RuntimeException(
                                "invalid key or spec in GCM mode", ikae);
                }

                // update the additional authentication data
                byte[] aad = authenticator.acquireAuthenticationBytes(
                        contentType, bb.remaining() - recordIvSize - tagSize);
                cipher.updateAAD(aad);

                return recordIvSize;
                        // It is also the length of sequence number, which is
                        // used as the nonce_explicit for AEAD cipher suites.
        }

       return 0;
    }

    /*
     * Applies the explicit nonce/IV to this cipher. This method is used to
     * decrypt an SSL/TLS input record.
     *
     * The returned value is the SecurityParameters.record_iv_length in
     * RFC 4346/5246.  It is the size of explicit IV for CBC mode, and the
     * size of explicit nonce for AEAD mode.
     *
     * @param  authenticator the authenticator to get the additional
     *         authentication data
     * @param  contentType the content type of the input record
     * @param  buf the byte array to get the explicit nonce from
     * @param  offset the offset of the byte buffer
     * @param  cipheredLength the ciphered fragment length of the output
     *         record, it is the TLSCiphertext.length in RFC 4346/5246.
     *
     * @return the explicit nonce size of the cipher.
     */
    int applyExplicitNonce(Authenticator authenticator,
            byte contentType, byte[] buf, int offset,
            int cipheredLength) throws BadPaddingException {

        ByteBuffer bb = ByteBuffer.wrap(buf, offset, cipheredLength);

        return applyExplicitNonce(authenticator, contentType, bb);
    }

    /*
     * Creates the explicit nonce/IV to this cipher. This method is used to
     * encrypt an SSL/TLS output record.
     *
     * The size of the returned array is the SecurityParameters.record_iv_length
     * in RFC 4346/5246.  It is the size of explicit IV for CBC mode, and the
     * size of explicit nonce for AEAD mode.
     *
     * @param  authenticator the authenticator to get the additional
     *         authentication data
     * @param  contentType the content type of the input record
     * @param  fragmentLength the fragment length of the output record, it is
     *         the TLSCompressed.length in RFC 4346/5246.
     *
     * @return the explicit nonce of the cipher.
     */
    byte[] createExplicitNonce(Authenticator authenticator,
            byte contentType, int fragmentLength) {

        byte[] nonce = new byte[0];
        switch (cipherType) {
            case BLOCK_CIPHER:
                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
                    // For block ciphers, the explicit IV length is of length
                    // SecurityParameters.record_iv_length, which is equal to
                    // the SecurityParameters.block_size.
                    //
                    // Generate a random number as the explicit IV parameter.
                    nonce = new byte[cipher.getBlockSize()];
                    random.nextBytes(nonce);
                }
                break;
            case AEAD_CIPHER:
                // To be unique and aware of overflow-wrap, sequence number
                // is used as the nonce_explicit of AEAD cipher suites.
                nonce = authenticator.sequenceNumber();

                // initialize the AEAD cipher for the unique IV
                byte[] iv = Arrays.copyOf(fixedIv,
                                            fixedIv.length + nonce.length);
                System.arraycopy(nonce, 0, iv, fixedIv.length, nonce.length);
                GCMParameterSpec spec = new GCMParameterSpec(tagSize * 8, iv);
                try {
                    cipher.init(mode, key, spec, random);
                } catch (InvalidKeyException |
                            InvalidAlgorithmParameterException ikae) {
                    // unlikely to happen
                    throw new RuntimeException(
                                "invalid key or spec in GCM mode", ikae);
                }

                // update the additional authentication data
                byte[] aad = authenticator.acquireAuthenticationBytes(
                                                contentType, fragmentLength);
                cipher.updateAAD(aad);
                break;
        }

        return nonce;
    }

    /*
     * Is this cipher available?
     *
     * This method can only be called by CipherSuite.BulkCipher.isAvailable()
     * to test the availability of a cipher suites.  Please DON'T use it in
     * other places, otherwise, the behavior may be unexpected because we may
     * initialize AEAD cipher improperly in the method.
     */
    Boolean isAvailable() {
        // We won't know whether a cipher for a particular key size is
        // available until the cipher is successfully initialized.
        //
        // We do not initialize AEAD cipher in the constructor.  Need to
        // initialize the cipher to ensure that the AEAD mode for a
        // particular key size is supported.
        if (cipherType == AEAD_CIPHER) {
            try {
                Authenticator authenticator =
                    new Authenticator(protocolVersion);
                byte[] nonce = authenticator.sequenceNumber();
                byte[] iv = Arrays.copyOf(fixedIv,
                                            fixedIv.length + nonce.length);
                System.arraycopy(nonce, 0, iv, fixedIv.length, nonce.length);
                GCMParameterSpec spec = new GCMParameterSpec(tagSize * 8, iv);

                cipher.init(mode, key, spec, random);
            } catch (Exception e) {
                return Boolean.FALSE;
            }
        }   // Otherwise, we have initialized the cipher in the constructor.

        return Boolean.TRUE;
    }

    /**
     * Sanity check the length of a fragment before decryption.
     *
     * In CBC mode, check that the fragment length is one or multiple times
     * of the block size of the cipher suite, and is at least one (one is the
     * smallest size of padding in CBC mode) bigger than the tag size of the
     * MAC algorithm except the explicit IV size for TLS 1.1 or later.
     *
     * In non-CBC mode, check that the fragment length is not less than the
     * tag size of the MAC algorithm.
     *
     * @return true if the length of a fragment matches above requirements
     */
    private boolean sanityCheck(int tagLen, int fragmentLen) {
        if (!isCBCMode()) {
            return fragmentLen >= tagLen;
        }

        int blockSize = cipher.getBlockSize();
        if ((fragmentLen % blockSize) == 0) {
            int minimal = tagLen + 1;
            minimal = (minimal >= blockSize) ? minimal : blockSize;
            if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
                minimal += blockSize;   // plus the size of the explicit IV
            }

            return (fragmentLen >= minimal);
        }

        return false;
    }

}

Other Java examples (source code examples)

Here is a short list of links related to this Java CipherBox.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.