• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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