1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.security.cryptauth.lib.securemessage; 16 17 import com.google.security.annotations.SuppressInsecureCipherModeCheckerReviewed; 18 import java.io.UnsupportedEncodingException; 19 import java.security.InvalidAlgorithmParameterException; 20 import java.security.InvalidKeyException; 21 import java.security.Key; 22 import java.security.MessageDigest; 23 import java.security.NoSuchAlgorithmException; 24 import java.security.PrivateKey; 25 import java.security.PublicKey; 26 import java.security.SecureRandom; 27 import java.security.Signature; 28 import java.security.SignatureException; 29 import javax.annotation.Nullable; 30 import javax.crypto.BadPaddingException; 31 import javax.crypto.Cipher; 32 import javax.crypto.IllegalBlockSizeException; 33 import javax.crypto.Mac; 34 import javax.crypto.NoSuchPaddingException; 35 import javax.crypto.SecretKey; 36 import javax.crypto.spec.IvParameterSpec; 37 import javax.crypto.spec.SecretKeySpec; 38 39 /** 40 * Encapsulates the cryptographic operations used by the {@code SecureMessage*} classes. 41 */ 42 public class CryptoOps { 43 CryptoOps()44 private CryptoOps() {} // Do not instantiate 45 46 /** 47 * Enum of supported signature types, with additional mappings to indicate the name of the 48 * underlying JCA algorithm used to create the signature. 49 * @see <a href= 50 * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html"> 51 * Java Cryptography Architecture, Standard Algorithm Name Documentation</a> 52 */ 53 public enum SigType { 54 HMAC_SHA256(SecureMessageProto.SigScheme.HMAC_SHA256, "HmacSHA256", false), 55 ECDSA_P256_SHA256(SecureMessageProto.SigScheme.ECDSA_P256_SHA256, "SHA256withECDSA", true), 56 RSA2048_SHA256(SecureMessageProto.SigScheme.RSA2048_SHA256, "SHA256withRSA", true); 57 getSigScheme()58 public SecureMessageProto.SigScheme getSigScheme() { 59 return sigScheme; 60 } 61 getJcaName()62 public String getJcaName() { 63 return jcaName; 64 } 65 isPublicKeyScheme()66 public boolean isPublicKeyScheme() { 67 return publicKeyScheme; 68 } 69 valueOf(SecureMessageProto.SigScheme sigScheme)70 public static SigType valueOf(SecureMessageProto.SigScheme sigScheme) { 71 for (SigType value : values()) { 72 if (value.sigScheme.equals(sigScheme)) { 73 return value; 74 } 75 } 76 throw new IllegalArgumentException("Unsupported SigType: " + sigScheme); 77 } 78 79 private final SecureMessageProto.SigScheme sigScheme; 80 private final String jcaName; 81 private final boolean publicKeyScheme; 82 SigType(SecureMessageProto.SigScheme sigType, String jcaName, boolean publicKeyScheme)83 SigType(SecureMessageProto.SigScheme sigType, String jcaName, boolean publicKeyScheme) { 84 this.sigScheme = sigType; 85 this.jcaName = jcaName; 86 this.publicKeyScheme = publicKeyScheme; 87 } 88 } 89 90 /** 91 * Enum of supported encryption types, with additional mappings to indicate the name of the 92 * underlying JCA algorithm used to perform the encryption. 93 * @see <a href= 94 * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html"> 95 * Java Cryptography Architecture, Standard Algorithm Name Documentation</a> 96 */ 97 public enum EncType { 98 NONE(SecureMessageProto.EncScheme.NONE, "InvalidDoNotUseForJCA"), 99 AES_256_CBC(SecureMessageProto.EncScheme.AES_256_CBC, "AES/CBC/PKCS5Padding"); 100 getEncScheme()101 public SecureMessageProto.EncScheme getEncScheme() { 102 return encScheme; 103 } 104 getJcaName()105 public String getJcaName() { 106 return jcaName; 107 } 108 valueOf(SecureMessageProto.EncScheme encScheme)109 public static EncType valueOf(SecureMessageProto.EncScheme encScheme) { 110 for (EncType value : values()) { 111 if (value.encScheme.equals(encScheme)) { 112 return value; 113 } 114 } 115 throw new IllegalArgumentException("Unsupported EncType: " + encScheme); 116 } 117 118 private final SecureMessageProto.EncScheme encScheme; 119 private final String jcaName; 120 EncType(SecureMessageProto.EncScheme encScheme, String jcaName)121 EncType(SecureMessageProto.EncScheme encScheme, String jcaName) { 122 this.encScheme = encScheme; 123 this.jcaName = jcaName; 124 } 125 } 126 127 /** 128 * Truncated hash output length, in bytes. 129 */ 130 static final int DIGEST_LENGTH = 20; 131 /** 132 * A salt value specific to this library, generated as SHA-256("SecureMessage") 133 */ 134 private static final byte[] SALT = sha256("SecureMessage"); 135 136 /** 137 * Signs {@code data} using the algorithm specified by {@code sigType} with {@code signingKey}. 138 * 139 * @param rng is required for public key signature schemes 140 * @return raw signature 141 * @throws InvalidKeyException if {@code signingKey} is incompatible with {@code sigType} 142 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType} 143 */ sign( SigType sigType, Key signingKey, @Nullable SecureRandom rng, byte[] data)144 static byte[] sign( 145 SigType sigType, Key signingKey, @Nullable SecureRandom rng, byte[] data) 146 throws InvalidKeyException, NoSuchAlgorithmException { 147 if ((signingKey == null) || (data == null)) { 148 throw new NullPointerException(); 149 } 150 if (sigType.isPublicKeyScheme()) { 151 if (rng == null) { 152 throw new NullPointerException(); 153 } 154 if (!(signingKey instanceof PrivateKey)) { 155 throw new InvalidKeyException("Expected a PrivateKey"); 156 } 157 Signature sigScheme = Signature.getInstance(sigType.getJcaName()); 158 sigScheme.initSign((PrivateKey) signingKey, rng); 159 try { 160 // We include a fixed magic value (salt) in the signature so that if the signing key is 161 // reused in another context we can't be confused -- provided that the other user of the 162 // signing key only signs statements that do not begin with this salt. 163 sigScheme.update(SALT); 164 sigScheme.update(data); 165 return sigScheme.sign(); 166 } catch (SignatureException e) { 167 throw new IllegalStateException(e); // Consistent with failures in Mac.doFinal 168 } 169 } else { 170 Mac macScheme = Mac.getInstance(sigType.getJcaName()); 171 // Note that an AES-256 SecretKey should work with most Mac schemes 172 SecretKey derivedKey = deriveAes256KeyFor(getSecretKey(signingKey), getPurpose(sigType)); 173 macScheme.init(derivedKey); 174 return macScheme.doFinal(data); 175 } 176 } 177 178 /** 179 * Verifies the {@code signature} on {@code data} using the algorithm specified by 180 * {@code sigType} with {@code verificationKey}. 181 * 182 * @return true iff the signature is verified 183 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType} 184 * @throws InvalidKeyException if {@code verificationKey} is incompatible with {@code sigType} 185 * @throws SignatureException 186 */ verify(Key verificationKey, SigType sigType, byte[] signature, byte[] data)187 static boolean verify(Key verificationKey, SigType sigType, byte[] signature, byte[] data) 188 throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { 189 if ((verificationKey == null) || (signature == null) || (data == null)) { 190 throw new NullPointerException(); 191 } 192 if (sigType.isPublicKeyScheme()) { 193 if (!(verificationKey instanceof PublicKey)) { 194 throw new InvalidKeyException("Expected a PublicKey"); 195 } 196 Signature sigScheme = Signature.getInstance(sigType.getJcaName()); 197 sigScheme.initVerify((PublicKey) verificationKey); 198 sigScheme.update(SALT); // See the comments in sign() for more on this 199 sigScheme.update(data); 200 return sigScheme.verify(signature); 201 } else { 202 Mac macScheme = Mac.getInstance(sigType.getJcaName()); 203 SecretKey derivedKey = 204 deriveAes256KeyFor(getSecretKey(verificationKey), getPurpose(sigType)); 205 macScheme.init(derivedKey); 206 return constantTimeArrayEquals(signature, macScheme.doFinal(data)); 207 } 208 } 209 210 /** 211 * Generate a random IV appropriate for use with the algorithm specified in {@code encType}. 212 * 213 * @return a freshly generated IV (a random byte sequence of appropriate length) 214 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} 215 */ 216 @SuppressInsecureCipherModeCheckerReviewed 217 // See b/26525455 for security review. generateIv(EncType encType, SecureRandom rng)218 static byte[] generateIv(EncType encType, SecureRandom rng) throws NoSuchAlgorithmException { 219 if (rng == null) { 220 throw new NullPointerException(); 221 } 222 try { 223 Cipher encrypter = Cipher.getInstance(encType.getJcaName()); 224 byte[] iv = new byte[encrypter.getBlockSize()]; 225 rng.nextBytes(iv); 226 return iv; 227 } catch (NoSuchPaddingException e) { 228 throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException 229 } 230 } 231 232 /** 233 * Encrypts {@code plaintext} using the algorithm specified in {@code encType}, with the specified 234 * {@code iv} and {@code encryptionKey}. 235 * 236 * @param rng source of randomness to be used with the specified cipher, if necessary 237 * @return encrypted data 238 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} 239 * @throws InvalidKeyException if {@code encryptionKey} is incompatible with {@code encType} 240 */ 241 @SuppressInsecureCipherModeCheckerReviewed 242 // See b/26525455 for security review. encrypt( Key encryptionKey, EncType encType, @Nullable SecureRandom rng, byte[] iv, byte[] plaintext)243 static byte[] encrypt( 244 Key encryptionKey, EncType encType, @Nullable SecureRandom rng, byte[] iv, byte[] plaintext) 245 throws NoSuchAlgorithmException, InvalidKeyException { 246 if ((encryptionKey == null) || (iv == null) || (plaintext == null)) { 247 throw new NullPointerException(); 248 } 249 if (encType == EncType.NONE) { 250 throw new NoSuchAlgorithmException("Cannot use NONE type here"); 251 } 252 try { 253 Cipher encrypter = Cipher.getInstance(encType.getJcaName()); 254 SecretKey derivedKey = 255 deriveAes256KeyFor(getSecretKey(encryptionKey), getPurpose(encType)); 256 encrypter.init(Cipher.ENCRYPT_MODE, derivedKey, new IvParameterSpec(iv), rng); 257 return encrypter.doFinal(plaintext); 258 } catch (InvalidAlgorithmParameterException e) { 259 throw new AssertionError(e); // Should never happen 260 } catch (IllegalBlockSizeException e) { 261 throw new AssertionError(e); // Should never happen 262 } catch (BadPaddingException e) { 263 throw new AssertionError(e); // Should never happen 264 } catch (NoSuchPaddingException e) { 265 throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException 266 } 267 } 268 269 /** 270 * Decrypts {@code ciphertext} using the algorithm specified in {@code encType}, with the 271 * specified {@code iv} and {@code decryptionKey}. 272 * 273 * @return the plaintext (decrypted) data 274 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} 275 * @throws InvalidKeyException if {@code decryptionKey} is incompatible with {@code encType} 276 * @throws InvalidAlgorithmParameterException if {@code encType} exceeds legal cryptographic 277 * strength limits in this jurisdiction 278 * @throws IllegalBlockSizeException if {@code ciphertext} contains an illegal block 279 * @throws BadPaddingException if {@code ciphertext} contains an illegal padding 280 */ 281 @SuppressInsecureCipherModeCheckerReviewed 282 // See b/26525455 for security review decrypt(Key decryptionKey, EncType encType, byte[] iv, byte[] ciphertext)283 static byte[] decrypt(Key decryptionKey, EncType encType, byte[] iv, byte[] ciphertext) 284 throws NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, 285 IllegalBlockSizeException, BadPaddingException { 286 if ((decryptionKey == null) || (iv == null) || (ciphertext == null)) { 287 throw new NullPointerException(); 288 } 289 if (encType == EncType.NONE) { 290 throw new NoSuchAlgorithmException("Cannot use NONE type here"); 291 } 292 try { 293 Cipher decrypter = Cipher.getInstance(encType.getJcaName()); 294 SecretKey derivedKey = 295 deriveAes256KeyFor(getSecretKey(decryptionKey), getPurpose(encType)); 296 decrypter.init(Cipher.DECRYPT_MODE, derivedKey, new IvParameterSpec(iv)); 297 return decrypter.doFinal(ciphertext); 298 } catch (NoSuchPaddingException e) { 299 throw new AssertionError(e); // Should never happen 300 } 301 } 302 303 /** 304 * Computes a collision-resistant hash of {@link #DIGEST_LENGTH} bytes 305 * (using a truncated SHA-256 output). 306 */ digest(byte[] data)307 static byte[] digest(byte[] data) throws NoSuchAlgorithmException { 308 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); 309 byte[] truncatedHash = new byte[DIGEST_LENGTH]; 310 System.arraycopy(sha256.digest(data), 0, truncatedHash, 0, DIGEST_LENGTH); 311 return truncatedHash; 312 } 313 314 /** 315 * Returns {@code true} if the two arrays are equal to one another. 316 * When the two arrays differ in length, trivially returns {@code false}. 317 * When the two arrays are equal in length, does a constant-time comparison 318 * of the two, i.e. does not abort the comparison when the first differing 319 * element is found. 320 * 321 * <p>NOTE: This is a copy of {@code java/com/google/math/crypto/ConstantTime#arrayEquals}. 322 * 323 * @param a An array to compare 324 * @param b Another array to compare 325 * @return {@code true} if these arrays are both null or if they have equal 326 * length and equal bytes in all elements 327 */ constantTimeArrayEquals(@ullable byte[] a, @Nullable byte[] b)328 static boolean constantTimeArrayEquals(@Nullable byte[] a, @Nullable byte[] b) { 329 if (a == null || b == null) { 330 return (a == b); 331 } 332 if (a.length != b.length) { 333 return false; 334 } 335 byte result = 0; 336 for (int i = 0; i < b.length; i++) { 337 result = (byte) (result | a[i] ^ b[i]); 338 } 339 return (result == 0); 340 } 341 342 // @VisibleForTesting getPurpose(SigType sigType)343 static String getPurpose(SigType sigType) { 344 return "SIG:" + sigType.getSigScheme().getNumber(); 345 } 346 347 // @VisibleForTesting getPurpose(EncType encType)348 static String getPurpose(EncType encType) { 349 return "ENC:" + encType.getEncScheme().getNumber(); 350 } 351 getSecretKey(Key key)352 private static SecretKey getSecretKey(Key key) throws InvalidKeyException { 353 if (!(key instanceof SecretKey)) { 354 throw new InvalidKeyException("Expected a SecretKey"); 355 } 356 return (SecretKey) key; 357 } 358 359 /** 360 * @return the UTF-8 encoding of the given string 361 * @throws RuntimeException if the UTF-8 charset is not present. 362 */ utf8StringToBytes(String input)363 public static byte[] utf8StringToBytes(String input) { 364 try { 365 return input.getBytes("UTF-8"); 366 } catch (UnsupportedEncodingException e) { 367 throw new RuntimeException(e); // Shouldn't happen, UTF-8 is universal 368 } 369 } 370 371 /** 372 * @return SHA-256(UTF-8 encoded input) 373 */ sha256(String input)374 public static byte[] sha256(String input) { 375 MessageDigest sha256; 376 try { 377 sha256 = MessageDigest.getInstance("SHA-256"); 378 return sha256.digest(utf8StringToBytes(input)); 379 } catch (NoSuchAlgorithmException e) { 380 throw new RuntimeException("No security provider initialized yet?", e); 381 } 382 } 383 384 /** 385 * A key derivation function specific to this library, which accepts a {@code masterKey} and an 386 * arbitrary {@code purpose} describing the intended application of the derived sub-key, 387 * and produces a derived AES-256 key safe to use as if it were independent of any other 388 * derived key which used a different {@code purpose}. 389 * 390 * @param masterKey any key suitable for use with HmacSHA256 391 * @param purpose a UTF-8 encoded string describing the intended purpose of derived key 392 * @return a derived SecretKey suitable for use with AES-256 393 * @throws InvalidKeyException if the encoded form of {@code masterKey} cannot be accessed 394 */ deriveAes256KeyFor(SecretKey masterKey, String purpose)395 static SecretKey deriveAes256KeyFor(SecretKey masterKey, String purpose) 396 throws NoSuchAlgorithmException, InvalidKeyException { 397 return new SecretKeySpec(hkdf(masterKey, SALT, utf8StringToBytes(purpose)), "AES"); 398 } 399 400 /** 401 * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length. 402 * 403 * Please make sure to select a salt that is fixed and unique for your codebase, and use the 404 * {@code info} parameter to specify any additional bits that should influence the derived key. 405 * 406 * @param inputKeyMaterial master key from which to derive sub-keys 407 * @param salt a (public) randomly generated 256-bit input that can be re-used 408 * @param info arbitrary information that is bound to the derived key (i.e., used in its creation) 409 * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info) 410 * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed 411 */ hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info)412 public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info) 413 throws NoSuchAlgorithmException, InvalidKeyException { 414 return hkdf(inputKeyMaterial, salt, info, /* length= */ 32); 415 } 416 417 /** 418 * Implements HKDF (RFC 5869) with the SHA-256 hash. 419 * 420 * <p>Please make sure to select a salt that is fixed and unique for your codebase, and use the 421 * {@code info} parameter to specify any additional bits that should influence the derived key. 422 * 423 * @param inputKeyMaterial master key from which to derive sub-keys 424 * @param salt a (public) randomly generated 256-bit input that can be re-used 425 * @param info arbitrary information that is bound to the derived key (i.e., used in its creation) 426 * @param length length of returned key material 427 * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info) 428 * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed 429 */ hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info, int length)430 public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info, int length) 431 throws NoSuchAlgorithmException, InvalidKeyException { 432 if ((inputKeyMaterial == null) || (salt == null) || (info == null)) { 433 throw new NullPointerException(); 434 } 435 if (length < 0) { 436 throw new IllegalArgumentException("Length must be positive"); 437 } 438 return hkdfSha256Expand(hkdfSha256Extract(inputKeyMaterial, salt), info, length); 439 } 440 441 /** 442 * @return the concatenation of {@code a} and {@code b}, treating {@code null} as the empty array. 443 */ concat(@ullable byte[] a, @Nullable byte[] b)444 static byte[] concat(@Nullable byte[] a, @Nullable byte[] b) { 445 if ((a == null) && (b == null)) { 446 return new byte[] { }; 447 } 448 if (a == null) { 449 return b; 450 } 451 if (b == null) { 452 return a; 453 } 454 byte[] result = new byte[a.length + b.length]; 455 System.arraycopy(a, 0, result, 0, a.length); 456 System.arraycopy(b, 0, result, a.length, b.length); 457 return result; 458 } 459 460 /** 461 * Since {@code Arrays.copyOfRange(...)} is not available on older Android platforms, 462 * a custom method for computing a subarray is provided here. 463 * 464 * @return the substring of {@code in} from {@code beginIndex} (inclusive) 465 * up to {@code endIndex} (exclusive) 466 */ subarray(byte[] in, int beginIndex, int endIndex)467 static byte[] subarray(byte[] in, int beginIndex, int endIndex) { 468 if (in == null) { 469 throw new NullPointerException(); 470 } 471 int length = endIndex - beginIndex; 472 if ((length < 0) 473 || (beginIndex < 0) 474 || (endIndex < 0) 475 || (beginIndex >= in.length) 476 || (endIndex > in.length)) { 477 throw new IndexOutOfBoundsException(); 478 } 479 byte[] result = new byte[length]; 480 if (length > 0) { 481 System.arraycopy(in, beginIndex, result, 0, length); 482 } 483 return result; 484 } 485 486 /** 487 * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is used 488 * to pre-process the inputKeyMaterial and mix it with the salt, producing output suitable for use 489 * with HKDF expansion function (which produces the actual derived key). 490 * 491 * @see #hkdfSha256Expand(byte[], byte[], int) 492 * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC) 493 * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed 494 * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable 495 */ hkdfSha256Extract(SecretKey inputKeyMaterial, byte[] salt)496 private static byte[] hkdfSha256Extract(SecretKey inputKeyMaterial, byte[] salt) 497 throws NoSuchAlgorithmException, InvalidKeyException { 498 Mac macScheme = Mac.getInstance("HmacSHA256"); 499 try { 500 macScheme.init(new SecretKeySpec(salt, "AES")); 501 } catch (InvalidKeyException e) { 502 throw new AssertionError(e); // This should never happen 503 } 504 // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should be 505 // consistent across implementations. 506 byte[] encodedKeyMaterial = inputKeyMaterial.getEncoded(); 507 if (encodedKeyMaterial == null) { 508 throw new InvalidKeyException("Cannot get encoded form of SecretKey"); 509 } 510 return macScheme.doFinal(encodedKeyMaterial); 511 } 512 513 /** 514 * HKDF (RFC 5869) expansion function, using the SHA-256 hash function. 515 * 516 * @param pseudoRandomKey should be generated by {@link #hkdfSha256Extract(SecretKey, byte[])} 517 * @param info arbitrary information the derived key should be bound to 518 * @param length length of the output key material in bytes 519 * @return raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01) 520 * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable 521 */ hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info, int length)522 private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info, int length) 523 throws NoSuchAlgorithmException { 524 Mac macScheme = Mac.getInstance("HmacSHA256"); 525 try { 526 macScheme.init(new SecretKeySpec(pseudoRandomKey, "AES")); 527 } catch (InvalidKeyException e) { 528 throw new AssertionError(e); // This should never happen 529 } 530 531 // Number of blocks N = ceil(hash length / output length). 532 int blocks = length / 32; 533 if (length % 32 > 0) { 534 blocks += 1; 535 } 536 537 // The counter used to generate the blocks according to the RFC is only one byte long, 538 // which puts a limit on the number of blocks possible. 539 if (blocks > 0xFF) { 540 throw new IllegalArgumentException("Maximum HKDF output length exceeded."); 541 } 542 543 byte[] outputBlock = new byte[32]; 544 byte[] counter = new byte[1]; 545 byte[] output = new byte[32 * blocks]; 546 for (int i = 0; i < blocks; ++i) { 547 macScheme.reset(); 548 if (i > 0) { 549 // Previous block 550 macScheme.update(outputBlock); 551 } 552 // Arbitrary info 553 macScheme.update(info); 554 // Counter 555 counter[0] = (byte) (i + 1); 556 outputBlock = macScheme.doFinal(counter); 557 558 System.arraycopy(outputBlock, 0, output, 32 * i, 32); 559 } 560 561 return subarray(output, 0, length); 562 } 563 564 } 565