/* * 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(); 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. */ @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 * * @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 * * @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. */ 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"); } }