mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
531 lines
18 KiB
Java
531 lines
18 KiB
Java
/*
|
|
* 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");
|
|
}
|
|
}
|