View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.codec.digest;
18  
19  import java.util.Arrays;
20  import java.util.Objects;
21  
22  /**
23   * Implements the Blake3 algorithm providing a {@linkplain #initHash() hash function} with extensible output (XOF), a
24   * {@linkplain #initKeyedHash(byte[]) keyed hash function} (MAC, PRF), and a
25   * {@linkplain #initKeyDerivationFunction(byte[]) key derivation function} (KDF). Blake3 has a 128-bit security level
26   * and a default output length of 256 bits (32 bytes) which can extended up to 2<sup>64</sup> bytes.
27   * <h2>Hashing</h2>
28   * <p>
29   * Hash mode calculates the same output hash given the same input bytes and can be used as both a message digest and
30   * and extensible output function.
31   * </p>
32   * <pre>{@code
33   *      Blake3 hasher = Blake3.initHash();
34   *      hasher.update("Hello, world!".getBytes(StandardCharsets.UTF_8));
35   *      byte[] hash = new byte[32];
36   *      hasher.doFinalize(hash);
37   * }</pre>
38   * <h2>Keyed Hashing</h2>
39   * <p>
40   * Keyed hashes take a 32-byte secret key and calculates a message authentication code on some input bytes. These
41   * also work as pseudo-random functions (PRFs) with extensible output similar to the extensible hash output. Note that
42   * Blake3 keyed hashes have the same performance as plain hashes; the key is used in initialization in place of a
43   * standard initialization vector used for plain hashing.
44   * </p>
45   * <pre>{@code
46   *      SecureRandom random = SecureRandom.getInstanceStrong();
47   *      byte[] key = new byte[32];
48   *      random.nextBytes(key);
49   *      Blake3 hasher = Blake3.initKeyedHash(key);
50   *      hasher.update("Hello, Alice!".getBytes(StandardCharsets.UTF_8));
51   *      byte[] mac = new byte[32];
52   *      hasher.doFinalize(mac);
53   * }</pre>
54   * <h2>Key Derivation</h2>
55   * <p>
56   * A specific hash mode for deriving session keys and other derived keys in a unique key derivation context
57   * identified by some sequence of bytes. These context strings should be unique but do not need to be kept secret.
58   * Additional input data is hashed for key material which can be finalized to derive subkeys. To derive multiple subkeys,
59   * request their combined length in one finalization and split the output. Repeated finalizations start at the beginning
60   * of the same output and do not derive new subkeys.
61   * </p>
62   * <pre>{@code
63   *      String context = "org.apache.commons.codec.digest.Blake3Example";
64   *      byte[] sharedSecret = ...;
65   *      byte[] senderId = ...;
66   *      byte[] recipientId = ...;
67   *      Blake3 kdf = Blake3.initKeyDerivationFunction(context.getBytes(StandardCharsets.UTF_8));
68   *      kdf.update(sharedSecret);
69   *      kdf.update(senderId);
70   *      kdf.update(recipientId);
71   *      byte[] keys = kdf.doFinalize(64);
72   *      byte[] txKey = Arrays.copyOfRange(keys, 0, 32);
73   *      byte[] rxKey = Arrays.copyOfRange(keys, 32, 64);
74   * }</pre>
75   * <p>
76   * Adapted from the ISC-licensed O(1) Cryptography library by Matt Sicker and ported from the reference public domain
77   * implementation by Jack O'Connor.
78   * </p>
79   *
80   * @see <a href="https://github.com/BLAKE3-team/BLAKE3">BLAKE3 hash function</a>
81   * @since 1.16
82   */
83  public final class Blake3 {
84  
85      private static final class ChunkState {
86  
87          private int[] chainingValue;
88          private final long chunkCounter;
89          private final int flags;
90  
91          private final byte[] block = new byte[BLOCK_LEN];
92          private int blockLength;
93          private int blocksCompressed;
94  
95          private ChunkState(final int[] key, final long chunkCounter, final int flags) {
96              chainingValue = key;
97              this.chunkCounter = chunkCounter;
98              this.flags = flags;
99          }
100 
101         private int length() {
102             return BLOCK_LEN * blocksCompressed + blockLength;
103         }
104 
105         private Output output() {
106             final int[] blockWords = unpackInts(block, BLOCK_INTS);
107             final int outputFlags = flags | startFlag() | CHUNK_END;
108             return new Output(chainingValue, blockWords, chunkCounter, blockLength, outputFlags);
109         }
110 
111         private int startFlag() {
112             return blocksCompressed == 0 ? CHUNK_START : 0;
113         }
114 
115         private void update(final byte[] input, int offset, int length) {
116             while (length > 0) {
117                 if (blockLength == BLOCK_LEN) {
118                     // If the block buffer is full, compress it and clear it. More
119                     // input is coming, so this compression is not CHUNK_END.
120                     final int[] blockWords = unpackInts(block, BLOCK_INTS);
121                     chainingValue = Arrays.copyOf(
122                             compress(chainingValue, blockWords, BLOCK_LEN, chunkCounter, flags | startFlag()),
123                             CHAINING_VALUE_INTS);
124                     blocksCompressed++;
125                     blockLength = 0;
126                     Arrays.fill(block, (byte) 0);
127                 }
128 
129                 final int want = BLOCK_LEN - blockLength;
130                 final int take = Math.min(want, length);
131                 System.arraycopy(input, offset, block, blockLength, take);
132                 blockLength += take;
133                 offset += take;
134                 length -= take;
135             }
136         }
137     }
138     private static final class EngineState {
139         private final int[] key;
140         private final int flags;
141         // Space for 54 subtree chaining values: 2^54 * CHUNK_LEN = 2^64
142         // No more than 54 entries can ever be added to this stack (after updating 2^64 bytes and not finalizing any)
143         // so we preallocate the stack here. This can be smaller in environments where the data limit is expected to
144         // be much lower.
145         private final int[][] cvStack = new int[54][];
146         private int stackLen;
147         private ChunkState state;
148 
149         private EngineState(final int[] key, final int flags) {
150             this.key = key;
151             this.flags = flags;
152             state = new ChunkState(key, 0, flags);
153         }
154 
155         // Section 5.1.2 of the BLAKE3 spec explains this algorithm in more detail.
156         private void addChunkCV(final int[] firstCV, final long totalChunks) {
157             // This chunk might complete some subtrees. For each completed subtree,
158             // its left child will be the current top entry in the CV stack, and
159             // its right child will be the current value of `newCV`. Pop each left
160             // child off the stack, merge it with `newCV`, and overwrite `newCV`
161             // with the result. After all these merges, push the final value of
162             // `newCV` onto the stack. The number of completed subtrees is given
163             // by the number of trailing 0-bits in the new total number of chunks.
164             int[] newCV = firstCV;
165             long chunkCounter = totalChunks;
166             while ((chunkCounter & 1) == 0) {
167                 newCV = parentChainingValue(popCV(), newCV, key, flags);
168                 chunkCounter >>= 1;
169             }
170             pushCV(newCV);
171         }
172 
173         private void inputData(final byte[] in, int offset, int length) {
174             while (length > 0) {
175                 // If the current chunk is complete, finalize it and reset the
176                 // chunk state. More input is coming, so this chunk is not ROOT.
177                 if (state.length() == CHUNK_LEN) {
178                     final int[] chunkCV = state.output().chainingValue();
179                     final long totalChunks = state.chunkCounter + 1;
180                     addChunkCV(chunkCV, totalChunks);
181                     state = new ChunkState(key, totalChunks, flags);
182                 }
183 
184                 // Compress input bytes into the current chunk state.
185                 final int want = CHUNK_LEN - state.length();
186                 final int take = Math.min(want, length);
187                 state.update(in, offset, take);
188                 offset += take;
189                 length -= take;
190             }
191         }
192 
193         private void outputHash(final byte[] out, final int offset, final int length) {
194             // Starting with the Output from the current chunk, compute all the
195             // parent chaining values along the right edge of the tree, until we
196             // have the root Output.
197             Output output = state.output();
198             int parentNodesRemaining = stackLen;
199             while (parentNodesRemaining-- > 0) {
200                 final int[] parentCV = cvStack[parentNodesRemaining];
201                 output = parentOutput(parentCV, output.chainingValue(), key, flags);
202             }
203             output.rootOutputBytes(out, offset, length);
204         }
205 
206         private int[] popCV() {
207             return cvStack[--stackLen];
208         }
209 
210         private void pushCV(final int[] cv) {
211             cvStack[stackLen++] = cv;
212         }
213 
214         private void reset() {
215             stackLen = 0;
216             Arrays.fill(cvStack, null);
217             state = new ChunkState(key, 0, flags);
218         }
219     }
220 
221     /**
222      * Represents the state just prior to either producing an eight word chaining value or any number of output bytes
223      * when the ROOT flag is set.
224      */
225     private static final class Output {
226 
227         private final int[] inputChainingValue;
228         private final int[] blockWords;
229         private final long counter;
230         private final int blockLength;
231         private final int flags;
232 
233         private Output(final int[] inputChainingValue, final int[] blockWords, final long counter, final int blockLength, final int flags) {
234             this.inputChainingValue = inputChainingValue;
235             this.blockWords = blockWords;
236             this.counter = counter;
237             this.blockLength = blockLength;
238             this.flags = flags;
239         }
240 
241         private int[] chainingValue() {
242             return Arrays.copyOf(compress(inputChainingValue, blockWords, blockLength, counter, flags), CHAINING_VALUE_INTS);
243         }
244 
245         private void rootOutputBytes(final byte[] out, int offset, int length) {
246             int outputBlockCounter = 0;
247             while (length > 0) {
248                 int chunkLength = Math.min(OUT_LEN * 2, length);
249                 length -= chunkLength;
250                 final int[] words = compress(inputChainingValue, blockWords, blockLength, outputBlockCounter++, flags | ROOT);
251                 int wordCounter = 0;
252                 while (chunkLength > 0) {
253                     final int wordLength = Math.min(Integer.BYTES, chunkLength);
254                     packInt(words[wordCounter++], out, offset, wordLength);
255                     offset += wordLength;
256                     chunkLength -= wordLength;
257                 }
258             }
259         }
260     }
261 
262     private static final int BLOCK_LEN = 64;
263     private static final int BLOCK_INTS = BLOCK_LEN / Integer.BYTES;
264     private static final int KEY_LEN = 32;
265     private static final int KEY_INTS = KEY_LEN / Integer.BYTES;
266     private static final int OUT_LEN = 32;
267     private static final int CHUNK_LEN = 1024;
268     private static final int CHAINING_VALUE_INTS = 8;
269 
270     /**
271      * Standard hash key used for plain hashes; same initialization vector as Blake2s.
272      */
273     private static final int[] IV = { 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 };
274 
275     // domain flags
276     private static final int CHUNK_START = 1;
277     private static final int CHUNK_END = 1 << 1;
278     private static final int PARENT = 1 << 2;
279     private static final int ROOT = 1 << 3;
280     private static final int KEYED_HASH = 1 << 4;
281     private static final int DERIVE_KEY_CONTEXT = 1 << 5;
282     private static final int DERIVE_KEY_MATERIAL = 1 << 6;
283 
284     /**
285      * Pre-permuted for all 7 rounds; the second row (2,6,3,...) indicates the base permutation.
286      */
287     // @formatter:off
288     private static final byte[][] MSG_SCHEDULE = {
289             { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
290             { 2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8 },
291             { 3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1 },
292             { 10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6 },
293             { 12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4 },
294             { 9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7 },
295             { 11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13 }
296     };
297     // @formatter:on
298 
299     private static void checkBufferArgs(final byte[] buffer, final int offset, final int length) {
300         Objects.requireNonNull(buffer, "buffer");
301         if (offset < 0) {
302             throw new IndexOutOfBoundsException("Offset must be non-negative");
303         }
304         if (length < 0) {
305             throw new IndexOutOfBoundsException("Length must be non-negative");
306         }
307         final int bufferLength = buffer.length;
308         if (offset > bufferLength - length) {
309             throw new IndexOutOfBoundsException("Offset " + offset + " and length " + length + " out of bounds with buffer length " + bufferLength);
310         }
311     }
312 
313     private static int[] compress(final int[] chainingValue, final int[] blockWords, final int blockLength, final long counter, final int flags) {
314         final int[] state = Arrays.copyOf(chainingValue, BLOCK_INTS);
315         System.arraycopy(IV, 0, state, 8, 4);
316         state[12] = (int) counter;
317         state[13] = (int) (counter >> Integer.SIZE);
318         state[14] = blockLength;
319         state[15] = flags;
320         for (int i = 0; i < 7; i++) {
321             final byte[] schedule = MSG_SCHEDULE[i];
322             round(state, blockWords, schedule);
323         }
324         for (int i = 0; i < state.length / 2; i++) {
325             state[i] ^= state[i + 8];
326             state[i + 8] ^= chainingValue[i];
327         }
328         return state;
329     }
330 
331     /**
332      * The mixing function, G, which mixes either a column or a diagonal.
333      */
334     private static void g(final int[] state, final int a, final int b, final int c, final int d, final int mx, final int my) {
335         state[a] += state[b] + mx;
336         state[d] = Integer.rotateRight(state[d] ^ state[a], 16);
337         state[c] += state[d];
338         state[b] = Integer.rotateRight(state[b] ^ state[c], 12);
339         state[a] += state[b] + my;
340         state[d] = Integer.rotateRight(state[d] ^ state[a], 8);
341         state[c] += state[d];
342         state[b] = Integer.rotateRight(state[b] ^ state[c], 7);
343     }
344 
345     /**
346      * Calculates the Blake3 hash of the provided data.
347      *
348      * @param data source array to absorb data from.
349      * @return 32-byte hash squeezed from the provided data.
350      * @throws NullPointerException Thrown if data is null.
351      */
352     public static byte[] hash(final byte[] data) {
353         return initHash().update(data).doFinalize(OUT_LEN);
354     }
355 
356     /**
357      * Constructs a fresh Blake3 hash function. The instance returned functions as an arbitrary length message digest.
358      *
359      * @return fresh Blake3 instance in hashed mode.
360      */
361     public static Blake3 initHash() {
362         return new Blake3(IV, 0);
363     }
364 
365     /**
366      * Constructs a fresh Blake3 key derivation function using the provided key derivation context byte string.
367      * The instance returned functions as a key-derivation function which can further absorb additional context data
368      * before squeezing derived key data.
369      *
370      * @param kdfContext A globally unique key-derivation context byte string to separate key derivation contexts from each other.
371      * @return fresh Blake3 instance in key derivation mode.
372      * @throws NullPointerException Thrown if kdfContext is null.
373      */
374     public static Blake3 initKeyDerivationFunction(final byte[] kdfContext) {
375         Objects.requireNonNull(kdfContext, "kdfContext");
376         final EngineState kdf = new EngineState(IV, DERIVE_KEY_CONTEXT);
377         kdf.inputData(kdfContext, 0, kdfContext.length);
378         final byte[] key = new byte[KEY_LEN];
379         kdf.outputHash(key, 0, key.length);
380         return new Blake3(unpackInts(key, KEY_INTS), DERIVE_KEY_MATERIAL);
381     }
382 
383     /**
384      * Constructs a fresh Blake3 keyed hash function. The instance returned functions as a pseudorandom function (PRF) or as a
385      * message authentication code (MAC).
386      *
387      * @param key 32-byte secret key.
388      * @return fresh Blake3 instance in keyed mode using the provided key.
389      * @throws NullPointerException     Thrown if key is null.
390      * @throws IllegalArgumentException Thrown if key is not 32 bytes.
391      */
392     public static Blake3 initKeyedHash(final byte[] key) {
393         Objects.requireNonNull(key, "key");
394         if (key.length != KEY_LEN) {
395             throw new IllegalArgumentException("Blake3 keys must be 32 bytes");
396         }
397         return new Blake3(unpackInts(key, KEY_INTS), KEYED_HASH);
398     }
399 
400     /**
401      * Calculates the Blake3 keyed hash (MAC) of the provided data.
402      *
403      * @param key  32-byte secret key.
404      * @param data source array to absorb data from.
405      * @return 32-byte mac squeezed from the provided data.
406      * @throws NullPointerException Thrown if key or data are null.
407      */
408     public static byte[] keyedHash(final byte[] key, final byte[] data) {
409         return initKeyedHash(key).update(data).doFinalize(OUT_LEN);
410     }
411 
412     private static void packInt(final int value, final byte[] dst, final int off, final int len) {
413         for (int i = 0; i < len; i++) {
414             dst[off + i] = (byte) (value >>> i * Byte.SIZE);
415         }
416     }
417 
418     private static int[] parentChainingValue(final int[] leftChildCV, final int[] rightChildCV, final int[] key, final int flags) {
419         return parentOutput(leftChildCV, rightChildCV, key, flags).chainingValue();
420     }
421 
422     private static Output parentOutput(final int[] leftChildCV, final int[] rightChildCV, final int[] key, final int flags) {
423         final int[] blockWords = Arrays.copyOf(leftChildCV, BLOCK_INTS);
424         System.arraycopy(rightChildCV, 0, blockWords, 8, CHAINING_VALUE_INTS);
425         return new Output(key.clone(), blockWords, 0, BLOCK_LEN, flags | PARENT);
426     }
427 
428     private static void round(final int[] state, final int[] msg, final byte[] schedule) {
429         // Mix the columns.
430         g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]);
431         g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]);
432         g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]);
433         g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]);
434 
435         // Mix the diagonals.
436         g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]);
437         g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]);
438         g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]);
439         g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);
440     }
441 
442     private static int unpackInt(final byte[] buf, final int off) {
443         return buf[off] & 0xFF | (buf[off + 1] & 0xFF) << 8 | (buf[off + 2] & 0xFF) << 16 | (buf[off + 3] & 0xFF) << 24;
444     }
445 
446     private static int[] unpackInts(final byte[] buf, final int nrInts) {
447         final int[] values = new int[nrInts];
448         for (int i = 0, off = 0; i < nrInts; i++, off += Integer.BYTES) {
449             values[i] = unpackInt(buf, off);
450         }
451         return values;
452     }
453 
454     private final EngineState engineState;
455 
456     private Blake3(final int[] key, final int flags) {
457         engineState = new EngineState(key, flags);
458     }
459 
460     /**
461      * Finalizes hash output into the provided array.
462      *
463      * <p>
464      * This method does not change the hash state. Each invocation starts at the beginning of the output for the bytes supplied to {@code update()}.
465      * Repeated invocations without additional input produce the same output prefix. Additional calls to {@code update()} append input to the existing hash
466      * state, even after finalization.
467      * </p>
468      *
469      * @param out destination array to finalize bytes into.
470      * @return {@code this} instance.
471      * @throws NullPointerException Thrown if out is null.
472      */
473     public Blake3 doFinalize(final byte[] out) {
474         return doFinalize(out, 0, out.length);
475     }
476 
477     /**
478      * Finalizes hash output into a region of the provided array.
479      *
480      * <p>
481      * This method does not change the hash state. Each invocation starts at the beginning of the output for the bytes supplied to {@code update()}.
482      * Repeated invocations without additional input produce the same output prefix. The offset selects the destination array position, not a position in
483      * the hash output. Additional calls to {@code update()} append input to the existing hash state, even after finalization.
484      * </p>
485      *
486      * @param out    destination array to finalize bytes into.
487      * @param offset where in the array to begin writing bytes to.
488      * @param length number of bytes to finalize.
489      * @return {@code this} instance.
490      * @throws NullPointerException      Thrown if out is null.
491      * @throws IndexOutOfBoundsException Thrown if offset or length are negative or if offset + length is greater than the
492      *                                   length of the provided array.
493      */
494     public Blake3 doFinalize(final byte[] out, final int offset, final int length) {
495         checkBufferArgs(out, offset, length);
496         engineState.outputHash(out, offset, length);
497         return this;
498     }
499 
500     /**
501      * Finalizes hash output into a new array.
502      *
503      * <p>
504      * This method does not change the hash state. Each invocation starts at the beginning of the output for the bytes supplied to {@code update()}.
505      * Repeated invocations without additional input produce the same output prefix. Additional calls to {@code update()} append input to the existing hash
506      * state, even after finalization.
507      * </p>
508      *
509      * @param nrBytes number of bytes to finalize.
510      * @return requested number of finalized bytes.
511      * @throws IllegalArgumentException Thrown if nrBytes is negative.
512      */
513     public byte[] doFinalize(final int nrBytes) {
514         if (nrBytes < 0) {
515             throw new IllegalArgumentException("Requested bytes must be non-negative");
516         }
517         final byte[] hash = new byte[nrBytes];
518         doFinalize(hash);
519         return hash;
520     }
521 
522     /**
523      * Resets this instance back to its initial state when it was first constructed.
524      *
525      * @return {@code this} instance.
526      */
527     public Blake3 reset() {
528         engineState.reset();
529         return this;
530     }
531 
532     /**
533      * Updates this hash state using the provided bytes.
534      *
535      * <p>
536      * Input is appended to the existing hash state, including after finalization. Call {@link #reset()} first to start a new message.
537      * </p>
538      *
539      * @param in source array to update data from.
540      * @return {@code this} instance.
541      * @throws NullPointerException Thrown if in is null.
542      */
543     public Blake3 update(final byte[] in) {
544         return update(in, 0, in.length);
545     }
546 
547     /**
548      * Updates this hash state using the provided bytes at an offset.
549      *
550      * <p>
551      * Input is appended to the existing hash state, including after finalization. Call {@link #reset()} first to start a new message.
552      * </p>
553      *
554      * @param in     source array to update data from.
555      * @param offset where in the array to begin reading bytes.
556      * @param length number of bytes to update.
557      * @return {@code this} instance.
558      * @throws NullPointerException      Thrown if in is null.
559      * @throws IndexOutOfBoundsException Thrown if offset or length are negative or if offset + length is greater than the
560      *                                   length of the provided array.
561      */
562     public Blake3 update(final byte[] in, final int offset, final int length) {
563         checkBufferArgs(in, offset, length);
564         engineState.inputData(in, offset, length);
565         return this;
566     }
567 
568 }