/*
 * Copyright (c) 2016, 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.provider;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Arrays;
import java.util.Locale;

public class CtrDrbg extends AbstractDrbg {

    private static final int AES_LIMIT;

    static {
        try {
            AES_LIMIT = Cipher.getMaxAllowedKeyLength("AES");
        } catch (Exception e) {
            // should not happen
            throw new AssertionError("Cannot detect AES", e);
        }
    }

    private Cipher cipher;

    private String cipherAlg;
    private String keyAlg;

    private int ctrLen;
    private int blockLen;
    private int keyLen;
    private int seedLen;

    private byte[] v;
    private byte[] k;

    public CtrDrbg(SecureRandomParameters params) {
        mechName = "CTR_DRBG";
        configure(params);
    }

    private static int alg2strength(String algorithm) {
        switch (algorithm.toUpperCase(Locale.ROOT)) {
            case "AES-128":
                return 128;
            case "AES-192":
                return 192;
            case "AES-256":
                return 256;
            default:
                throw new IllegalArgumentException(algorithm +
                        " not supported in CTR_DBRG");
        }
    }

    @Override
    protected void chooseAlgorithmAndStrength() {
        if (requestedAlgorithm != null) {
            algorithm = requestedAlgorithm.toUpperCase(Locale.ROOT);
            int supportedStrength = alg2strength(algorithm);
            if (requestedInstantiationSecurityStrength >= 0) {
                int tryStrength = getStandardStrength(
                        requestedInstantiationSecurityStrength);
                if (tryStrength > supportedStrength) {
                    throw new IllegalArgumentException(algorithm +
                            " does not support strength " +
                            requestedInstantiationSecurityStrength);
                }
                this.securityStrength = tryStrength;
            } else {
                this.securityStrength = (DEFAULT_STRENGTH > supportedStrength) ?
                        supportedStrength : DEFAULT_STRENGTH;
            }
        } else {
            int tryStrength = (requestedInstantiationSecurityStrength < 0) ?
                    DEFAULT_STRENGTH : requestedInstantiationSecurityStrength;
            tryStrength = getStandardStrength(tryStrength);
            // Default algorithm, use AES-128 if AES-256 is not available.
            // Remember to sync with "securerandom.drbg.config" in java.security
            if (tryStrength <= 128 && AES_LIMIT < 256) {
                algorithm = "AES-128";
            } else if (AES_LIMIT >= 256) {
                algorithm = "AES-256";
            } else {
                throw new IllegalArgumentException("unsupported strength " +
                        requestedInstantiationSecurityStrength);
            }
            this.securityStrength = tryStrength;
        }
        switch (algorithm.toUpperCase(Locale.ROOT)) {
            case "AES-128":
            case "AES-192":
            case "AES-256":
                this.keyAlg = "AES";
                this.cipherAlg = "AES/ECB/NoPadding";
                switch (algorithm) {
                    case "AES-128":
                        this.keyLen = 128 / 8;
                        break;
                    case "AES-192":
                        this.keyLen = 192 / 8;
                        if (AES_LIMIT < 192) {
                            throw new IllegalArgumentException(algorithm +
                                " not available (because policy) in CTR_DBRG");
                        }
                        break;
                    case "AES-256":
                        this.keyLen = 256 / 8;
                        if (AES_LIMIT < 256) {
                            throw new IllegalArgumentException(algorithm +
                                " not available (because policy) in CTR_DBRG");
                        }
                        break;
                    default:
                        throw new IllegalArgumentException(algorithm +
                            " not supported in CTR_DBRG");
                }
                this.blockLen = 128 / 8;
                break;
            default:
                throw new IllegalArgumentException(algorithm +
                        " not supported in CTR_DBRG");
        }
        this.seedLen = this.blockLen + this.keyLen;
        this.ctrLen = this.blockLen;    // TODO
        if (usedf) {
            this.minLength = this.securityStrength / 8;
        } else {
            this.minLength = this.maxLength =
                    this.maxPersonalizationStringLength =
                            this.maxAdditionalInputLength = seedLen;
        }
    }

    
This call, used by the constructors, instantiates the digest.
/** * This call, used by the constructors, instantiates the digest. */
@Override protected void initEngine() { try { /* * Use the local SunJCE implementation to avoid native * performance overhead. */ cipher = Cipher.getInstance(cipherAlg, "SunJCE"); } catch (NoSuchProviderException | NoSuchAlgorithmException | NoSuchPaddingException e) { // Fallback to any available. try { cipher = Cipher.getInstance(cipherAlg); } catch (NoSuchAlgorithmException | NoSuchPaddingException exc) { throw new InternalError( "internal error: " + cipherAlg + " not available.", exc); } } } private void status() { if (debug != null) { debug.println(this, "Key = " + hex(k)); debug.println(this, "V = " + hex(v)); debug.println(this, "reseed counter = " + reseedCounter); } } // 800-90Ar1 10.2.1.2. CTR_DRBG_Update private void update(byte[] input) { if (input.length != seedLen) { // Should not happen throw new IllegalArgumentException("input length not seedLen: " + input.length); } try { int m = (seedLen + blockLen - 1) / blockLen; byte[] temp = new byte[m * blockLen]; // Step 1. temp = Null. // Step 2. Loop for (int i = 0; i < m; i++) { // Step 2.1. Increment addOne(v, ctrLen); // Step 2.2. Block_Encrypt cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k, keyAlg)); // Step 2.3. Encrypt into right position, no need to cat cipher.doFinal(v, 0, blockLen, temp, i * blockLen); } // Step 3. Truncate temp = Arrays.copyOf(temp, seedLen); // Step 4: Add for (int i = 0; i < seedLen; i++) { temp[i] ^= input[i]; } // Step 5: leftmost k = Arrays.copyOf(temp, keyLen); // Step 6: rightmost v = Arrays.copyOfRange(temp, seedLen - blockLen, seedLen); // Step 7. Return } catch (GeneralSecurityException e) { throw new InternalError(e); } } @Override protected void instantiateAlgorithm(byte[] ei) { if (debug != null) { debug.println(this, "instantiate"); } byte[] more; if (usedf) { // 800-90Ar1 10.2.1.3.2 Step 1-2. cat bytes if (personalizationString == null) { more = nonce; } else { if (nonce.length + personalizationString.length < 0) { // Length must be represented as a 32 bit integer in df() throw new IllegalArgumentException( "nonce plus personalization string is too long"); } more = Arrays.copyOf( nonce, nonce.length + personalizationString.length); System.arraycopy(personalizationString, 0, more, nonce.length, personalizationString.length); } } else { // 800-90Ar1 10.2.1.3.1 // Step 1-2, no need to expand personalizationString, we only XOR // with shorter length more = personalizationString; } reseedAlgorithm(ei, more); }
Block_cipher_df in 10.3.2
Params:
  • input – the input string
Returns:the output block (always of seedLen)
/** * Block_cipher_df in 10.3.2 * * @param input the input string * @return the output block (always of seedLen) */
private byte[] df(byte[] input) { // 800-90Ar1 10.3.2 // 2. L = len (input_string)/8 int l = input.length; // 3. N = number_of_bits_to_return/8 int n = seedLen; // 4. S = L || N || input_string || 0x80 byte[] ln = new byte[8]; ln[0] = (byte)(l >> 24); ln[1] = (byte)(l >> 16); ln[2] = (byte)(l >> 8); ln[3] = (byte)(l); ln[4] = (byte)(n >> 24); ln[5] = (byte)(n >> 16); ln[6] = (byte)(n >> 8); ln[7] = (byte)(n); // 5. Zero padding of S // Not necessary, see bcc // 8. K = leftmost (0x00010203...1D1E1F, keylen). byte[] k = new byte[keyLen]; for (int i = 0; i < k.length; i++) { k[i] = (byte)i; } // 6. temp = the Null String byte[] temp = new byte[seedLen]; // 7. i = 0 for (int i = 0; i * blockLen < temp.length; i++) { // 9.1 IV = i || 0^(outlen - len (i)). outLen is blockLen byte[] iv = new byte[blockLen]; iv[0] = (byte)(i >> 24); iv[1] = (byte)(i >> 16); iv[2] = (byte)(i >> 8); iv[3] = (byte)(i); int tailLen = temp.length - blockLen*i; if (tailLen > blockLen) { tailLen = blockLen; } // 9.2 temp = temp || BCC (K, (IV || S)). System.arraycopy(bcc(k, iv, ln, input, new byte[]{(byte)0x80}), 0, temp, blockLen*i, tailLen); } // 10. K = leftmost(temp, keylen) k = Arrays.copyOf(temp, keyLen); // 11. x = select(temp, keylen+1, keylen+outlen) byte[] x = Arrays.copyOfRange(temp, keyLen, temp.length); // 12. temp = the Null string // No need to clean up, temp will be overwritten for (int i = 0; i * blockLen < seedLen; i++) { try { cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k, keyAlg)); int tailLen = temp.length - blockLen*i; // 14. requested_bits = leftmost(temp, nuumber_of_bits_to_return) if (tailLen > blockLen) { tailLen = blockLen; } x = cipher.doFinal(x); System.arraycopy(x, 0, temp, blockLen * i, tailLen); } catch (GeneralSecurityException e) { throw new InternalError(e); } } // 15. Return return temp; }
Block_Encrypt in 10.3.3
Params:
  • k – the key
  • data – after concatenated, the data to be operated upon. This is a series of byte[], each with an arbitrary length. Note that the full length is not necessarily a multiple of outlen. XOR with zero is no-op.
Returns:the result
/** * Block_Encrypt in 10.3.3 * * @param k the key * @param data after concatenated, the data to be operated upon. This is * a series of byte[], each with an arbitrary length. Note * that the full length is not necessarily a multiple of * outlen. XOR with zero is no-op. * @return the result */
private byte[] bcc(byte[] k, byte[]... data) { byte[] chain = new byte[blockLen]; int n1 = 0; // index in data int n2 = 0; // index in data[n1] // pack blockLen of bytes into chain from data[][], again and again while (n1 < data.length) { int j; out: for (j = 0; j < blockLen; j++) { while (n2 >= data[n1].length) { n1++; if (n1 >= data.length) { break out; } n2 = 0; } chain[j] ^= data[n1][n2]; n2++; } if (j == 0) { // all data happens to be consumed in the last loop break; } try { cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k, keyAlg)); chain = cipher.doFinal(chain); } catch (GeneralSecurityException e) { throw new InternalError(e); } } return chain; } @Override protected synchronized void reseedAlgorithm( byte[] ei, byte[] additionalInput) { if (usedf) { // 800-90Ar1 10.2.1.3.2 Instantiate. // 800-90Ar1 10.2.1.4.2 Reseed. // Step 1: cat bytes if (additionalInput != null) { if (ei.length + additionalInput.length < 0) { // Length must be represented as a 32 bit integer in df() throw new IllegalArgumentException( "entropy plus additional input is too long"); } byte[] temp = Arrays.copyOf( ei, ei.length + additionalInput.length); System.arraycopy(additionalInput, 0, temp, ei.length, additionalInput.length); ei = temp; } // Step 2. df (seed_material, seedlen). ei = df(ei); } else { // 800-90Ar1 10.2.1.3.1 Instantiate // 800-90Ar1 10.2.1.4.1 Reseed // Step 1-2. Needless // Step 3. seed_material = entropy_input XOR more if (additionalInput != null) { // additionalInput.length <= seedLen for (int i = 0; i < additionalInput.length; i++) { ei[i] ^= additionalInput[i]; } } } if (v == null) { // 800-90Ar1 10.2.1.3.2 Instantiate. Step 3-4 // 800-90Ar1 10.2.1.3.1 Instantiate. Step 4-5 k = new byte[keyLen]; v = new byte[blockLen]; } //status(); // 800-90Ar1 10.2.1.3.1 Instantiate. Step 6 // 800-90Ar1 10.2.1.3.2 Instantiate. Step 5 // 800-90Ar1 10.2.1.4.1 Reseed. Step 4 // 800-90Ar1 10.2.1.4.2 Reseed. Step 3 update(ei); // 800-90Ar1 10.2.1.3.1 Instantiate. Step 7 // 800-90Ar1 10.2.1.3.2 Instantiate. Step 6 // 800-90Ar1 10.2.1.4.1 Reseed. Step 5 // 800-90Ar1 10.2.1.4.2 Reseed. Step 4 reseedCounter = 1; //status(); // Whatever step. Return }
Add one to data, only touch the last len bytes.
/** * Add one to data, only touch the last len bytes. */
private static void addOne(byte[] data, int len) { for (int i = 0; i < len; i++) { data[data.length - 1 - i]++; if (data[data.length - 1 - i] != 0) { break; } } } @Override public synchronized void generateAlgorithm( byte[] result, byte[] additionalInput) { if (debug != null) { debug.println(this, "generateAlgorithm"); } // 800-90Ar1 10.2.1.5.1 Generate // 800-90Ar1 10.2.1.5.2 Generate // Step 1: Check reseed_counter. Will not fail. Already checked in // AbstractDrbg#engineNextBytes. if (additionalInput != null) { if (usedf) { // 10.2.1.5.2 Step 2.1 additionalInput = df(additionalInput); } else { // 10.2.1.5.1 Step 2.1-2.2 additionalInput = Arrays.copyOf(additionalInput, seedLen); } // 10.2.1.5.1 Step 2.3 // 10.2.1.5.2 Step 2.2 update(additionalInput); } else { // 10.2.1.5.1 Step 2 Else // 10.2.1.5.2 Step 2 Else additionalInput = new byte[seedLen]; } // Step 3. temp = Null int pos = 0; int len = result.length; // Step 4. Loop while (len > 0) { // Step 4.1. Increment addOne(v, ctrLen); try { // Step 4.2. Encrypt cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k, keyAlg)); byte[] out = cipher.doFinal(v); // Step 4.3 and 5. Cat bytes and leftmost System.arraycopy(out, 0, result, pos, (len > blockLen) ? blockLen : len); } catch (GeneralSecurityException e) { throw new InternalError(e); } len -= blockLen; if (len <= 0) { // shortcut, so that pos needn't be updated break; } pos += blockLen; } // Step 6. Update update(additionalInput); // Step 7. reseed_counter++ reseedCounter++; //status(); // Step 8. Return } @Override public String toString() { return super.toString() + "," + (usedf ? "use_df" : "no_df"); } }