001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.codec.digest; 018 019import java.util.Arrays; 020import java.util.Objects; 021 022/** 023 * Implements the Blake3 algorithm providing a {@linkplain #initHash() hash function} with extensible output (XOF), a 024 * {@linkplain #initKeyedHash(byte[]) keyed hash function} (MAC, PRF), and a 025 * {@linkplain #initKeyDerivationFunction(byte[]) key derivation function} (KDF). Blake3 has a 128-bit security level 026 * and a default output length of 256 bits (32 bytes) which can extended up to 2<sup>64</sup> bytes. 027 * <h2>Hashing</h2> 028 * <p> 029 * Hash mode calculates the same output hash given the same input bytes and can be used as both a message digest and 030 * and extensible output function. 031 * </p> 032 * <pre>{@code 033 * Blake3 hasher = Blake3.initHash(); 034 * hasher.update("Hello, world!".getBytes(StandardCharsets.UTF_8)); 035 * byte[] hash = new byte[32]; 036 * hasher.doFinalize(hash); 037 * }</pre> 038 * <h2>Keyed Hashing</h2> 039 * <p> 040 * Keyed hashes take a 32-byte secret key and calculates a message authentication code on some input bytes. These 041 * also work as pseudo-random functions (PRFs) with extensible output similar to the extensible hash output. Note that 042 * Blake3 keyed hashes have the same performance as plain hashes; the key is used in initialization in place of a 043 * standard initialization vector used for plain hashing. 044 * </p> 045 * <pre>{@code 046 * SecureRandom random = SecureRandom.getInstanceStrong(); 047 * byte[] key = new byte[32]; 048 * random.nextBytes(key); 049 * Blake3 hasher = Blake3.initKeyedHash(key); 050 * hasher.update("Hello, Alice!".getBytes(StandardCharsets.UTF_8)); 051 * byte[] mac = new byte[32]; 052 * hasher.doFinalize(mac); 053 * }</pre> 054 * <h2>Key Derivation</h2> 055 * <p> 056 * A specific hash mode for deriving session keys and other derived keys in a unique key derivation context 057 * identified by some sequence of bytes. These context strings should be unique but do not need to be kept secret. 058 * Additional input data is hashed for key material which can be finalized to derive subkeys. To derive multiple subkeys, 059 * request their combined length in one finalization and split the output. Repeated finalizations start at the beginning 060 * of the same output and do not derive new subkeys. 061 * </p> 062 * <pre>{@code 063 * String context = "org.apache.commons.codec.digest.Blake3Example"; 064 * byte[] sharedSecret = ...; 065 * byte[] senderId = ...; 066 * byte[] recipientId = ...; 067 * Blake3 kdf = Blake3.initKeyDerivationFunction(context.getBytes(StandardCharsets.UTF_8)); 068 * kdf.update(sharedSecret); 069 * kdf.update(senderId); 070 * kdf.update(recipientId); 071 * byte[] keys = kdf.doFinalize(64); 072 * byte[] txKey = Arrays.copyOfRange(keys, 0, 32); 073 * byte[] rxKey = Arrays.copyOfRange(keys, 32, 64); 074 * }</pre> 075 * <p> 076 * Adapted from the ISC-licensed O(1) Cryptography library by Matt Sicker and ported from the reference public domain 077 * implementation by Jack O'Connor. 078 * </p> 079 * 080 * @see <a href="https://github.com/BLAKE3-team/BLAKE3">BLAKE3 hash function</a> 081 * @since 1.16 082 */ 083public final class Blake3 { 084 085 private static final class ChunkState { 086 087 private int[] chainingValue; 088 private final long chunkCounter; 089 private final int flags; 090 091 private final byte[] block = new byte[BLOCK_LEN]; 092 private int blockLength; 093 private int blocksCompressed; 094 095 private ChunkState(final int[] key, final long chunkCounter, final int flags) { 096 chainingValue = key; 097 this.chunkCounter = chunkCounter; 098 this.flags = flags; 099 } 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}