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