1 package org.bouncycastle.crypto.digests; 2 3 import org.bouncycastle.crypto.DataLengthException; 4 import org.bouncycastle.crypto.Digest; 5 import org.bouncycastle.crypto.Xof; 6 import org.bouncycastle.util.Arrays; 7 import org.bouncycastle.util.Strings; 8 9 /** 10 * ParallelHash - a hash designed to support the efficient hashing of very long strings, by taking advantage 11 * of the parallelism available in modern processors with an optional XOF mode. 12 * <p> 13 * From NIST Special Publication 800-185 - SHA-3 Derived Functions:cSHAKE, KMAC, TupleHash and ParallelHash 14 * </p> 15 */ 16 public class ParallelHash 17 implements Xof, Digest 18 { 19 private static final byte[] N_PARALLEL_HASH = Strings.toByteArray("ParallelHash"); 20 21 private final CSHAKEDigest cshake; 22 private final CSHAKEDigest compressor; 23 private final int bitLength; 24 private final int outputLength; 25 private final int B; 26 private final byte[] buffer; 27 private final byte[] compressorBuffer; 28 29 private boolean firstOutput; 30 private int nCount; 31 private int bufOff; 32 33 /** 34 * Base constructor. 35 * 36 * @param bitLength bit length of the underlying SHAKE function, 128 or 256. 37 * @param S the customization string - available for local use. 38 * @param B the blocksize (in bytes) for hashing. 39 */ ParallelHash(int bitLength, byte[] S, int B)40 public ParallelHash(int bitLength, byte[] S, int B) 41 { 42 this(bitLength, S, B, bitLength * 2); 43 } 44 ParallelHash(int bitLength, byte[] S, int B, int outputSize)45 public ParallelHash(int bitLength, byte[] S, int B, int outputSize) 46 { 47 this.cshake = new CSHAKEDigest(bitLength, N_PARALLEL_HASH, S); 48 this.compressor = new CSHAKEDigest(bitLength, new byte[0], new byte[0]); 49 this.bitLength = bitLength; 50 this.B = B; 51 this.outputLength = (outputSize + 7) / 8; 52 this.buffer = new byte[B]; 53 this.compressorBuffer = new byte[bitLength * 2 / 8]; 54 55 reset(); 56 } 57 ParallelHash(ParallelHash source)58 public ParallelHash(ParallelHash source) 59 { 60 this.cshake = new CSHAKEDigest(source.cshake); 61 this.compressor = new CSHAKEDigest(source.compressor); 62 this.bitLength = source.bitLength; 63 this.B = source.B; 64 this.outputLength = source.outputLength; 65 this.buffer = Arrays.clone(source.buffer); 66 this.compressorBuffer = Arrays.clone(source.compressorBuffer); 67 } 68 getAlgorithmName()69 public String getAlgorithmName() 70 { 71 return "ParallelHash" + cshake.getAlgorithmName().substring(6); 72 } 73 getByteLength()74 public int getByteLength() 75 { 76 return cshake.getByteLength(); 77 } 78 getDigestSize()79 public int getDigestSize() 80 { 81 return outputLength; 82 } 83 update(byte in)84 public void update(byte in) 85 throws IllegalStateException 86 { 87 buffer[bufOff++] = in; 88 if (bufOff == buffer.length) 89 { 90 compress(); 91 } 92 } 93 update(byte[] in, int inOff, int len)94 public void update(byte[] in, int inOff, int len) 95 throws DataLengthException, IllegalStateException 96 { 97 len = Math.max(0, len); 98 99 // 100 // fill the current word 101 // 102 int i = 0; 103 if (bufOff != 0) 104 { 105 while (i < len && bufOff != buffer.length) 106 { 107 buffer[bufOff++] = in[inOff + i++]; 108 } 109 110 if (bufOff == buffer.length) 111 { 112 compress(); 113 } 114 } 115 116 if (i < len) 117 { 118 while (len - i > B) 119 { 120 compress(in, inOff + i, B); 121 i += B; 122 } 123 } 124 125 while (i < len) 126 { 127 update(in[inOff + i++]); 128 } 129 } 130 compress()131 private void compress() 132 { 133 compress(buffer, 0, bufOff); 134 bufOff = 0; 135 } 136 compress(byte[] buf, int offSet, int len)137 private void compress(byte[] buf, int offSet, int len) 138 { 139 compressor.update(buf, offSet, len); 140 compressor.doFinal(compressorBuffer, 0, compressorBuffer.length); 141 142 cshake.update(compressorBuffer, 0, compressorBuffer.length); 143 144 nCount++; 145 } 146 wrapUp(int outputSize)147 private void wrapUp(int outputSize) 148 { 149 if (bufOff != 0) 150 { 151 compress(); 152 } 153 byte[] nOut = XofUtils.rightEncode(nCount); 154 byte[] encOut = XofUtils.rightEncode(outputSize * 8); 155 156 cshake.update(nOut, 0, nOut.length); 157 cshake.update(encOut, 0, encOut.length); 158 159 firstOutput = false; 160 } 161 doFinal(byte[] out, int outOff)162 public int doFinal(byte[] out, int outOff) 163 throws DataLengthException, IllegalStateException 164 { 165 if (firstOutput) 166 { 167 wrapUp(outputLength); 168 } 169 170 int rv = cshake.doFinal(out, outOff, getDigestSize()); 171 172 reset(); 173 174 return rv; 175 } 176 doFinal(byte[] out, int outOff, int outLen)177 public int doFinal(byte[] out, int outOff, int outLen) 178 { 179 if (firstOutput) 180 { 181 wrapUp(outputLength); 182 } 183 184 int rv = cshake.doFinal(out, outOff, outLen); 185 186 reset(); 187 188 return rv; 189 } 190 doOutput(byte[] out, int outOff, int outLen)191 public int doOutput(byte[] out, int outOff, int outLen) 192 { 193 if (firstOutput) 194 { 195 wrapUp(0); 196 } 197 198 return cshake.doOutput(out, outOff, outLen); 199 } 200 reset()201 public void reset() 202 { 203 cshake.reset(); 204 Arrays.clear(buffer); 205 206 byte[] hdr = XofUtils.leftEncode(B); 207 cshake.update(hdr, 0, hdr.length); 208 209 nCount = 0; 210 bufOff = 0; 211 firstOutput = true; 212 } 213 } 214