View Javadoc
1   /*
2    * GordianKnot: Security Suite
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://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, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.gordianknot.impl.ext.digests;
18  
19  import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianBlake2Parameters;
20  import org.bouncycastle.crypto.ExtendedDigest;
21  import org.bouncycastle.crypto.Xof;
22  import org.bouncycastle.util.Arrays;
23  import org.bouncycastle.util.Memoable;
24  
25  /**
26   * Blake2X implementation.
27   * <p>This implementation supports the following elements of Blake2Parameters
28   * <ul>
29   *     <li>Key
30   *     <li>Nonce
31   *     <li>Personalisation
32   *     <li>Max Output length
33   * </ul>
34   * <p>If OutputLen is set to zero, then Blake2X defaults to the underlying digestLength.
35   */
36  public class GordianBlake2Xof
37          implements ExtendedDigest, Memoable, Xof {
38      /**
39       * The underlying Blake instance.
40       */
41      private final GordianBlake2Base theUnderlying;
42  
43      /**
44       * The combining Blake instance.
45       */
46      private final GordianBlake2Base theComposite;
47  
48      /**
49       * The single byte buffer.
50       */
51      private final byte[] singleByte = new byte[1];
52  
53      /**
54       * The XofLen.
55       */
56      private long theXofLen;
57  
58      /**
59       * The Root hash.
60       */
61      private byte[] theRoot;
62  
63      /**
64       * The XofRemaining.
65       */
66      private long theXofRemaining;
67  
68      /**
69       * The current hash.
70       */
71      private byte[] theCurrent;
72  
73      /**
74       * The data index within the current hash.
75       */
76      private int theHashIndex;
77  
78      /**
79       * The Xof NodeIndex.
80       */
81      private int theNodeIndex;
82  
83      /**
84       * Constructor.
85       *
86       * @param pDigest the underlying digest.
87       */
88      public GordianBlake2Xof(final GordianBlake2Base pDigest) {
89          /* Create the two digests */
90          theUnderlying = pDigest;
91          theComposite = (GordianBlake2Base) theUnderlying.copy();
92  
93          /* Configure the composite */
94          theComposite.setTreeConfig(0, 0, theUnderlying.getDigestSize());
95          theComposite.setInnerLength(theUnderlying.getDigestSize());
96          theComposite.setXofLen(0);
97  
98          /* Clear outputting flag */
99          theXofRemaining = -1L;
100         theXofLen = -1L;
101     }
102 
103     /**
104      * Constructor.
105      *
106      * @param pSource the source digest.
107      */
108     public GordianBlake2Xof(final GordianBlake2Xof pSource) {
109         /* Create hashes */
110         theUnderlying = pSource.theUnderlying instanceof GordianBlake2bDigest ? new GordianBlake2bDigest() : new GordianBlake2sDigest();
111         theComposite = (GordianBlake2Base) theUnderlying.copy();
112 
113         /* Initialise from source */
114         reset(pSource);
115     }
116 
117     /**
118      * Initialise.
119      *
120      * @param pParams the parameters.
121      */
122     public void init(final GordianBlake2Parameters pParams) {
123         /* Pass selective parameters to the underlying hash */
124         theUnderlying.setKey(pParams.getKey());
125         theUnderlying.setSalt(pParams.getSalt());
126         theUnderlying.setPersonalisation(pParams.getPersonalisation());
127         theUnderlying.setXofLen(pParams.getMaxOutputLength());
128         theUnderlying.reset();
129 
130         /* Pass selective parameters to the underlying hash */
131         theComposite.setSalt(pParams.getSalt());
132         theComposite.setPersonalisation(pParams.getPersonalisation());
133         theComposite.setXofLen(pParams.getMaxOutputLength());
134         theComposite.reset();
135 
136         /* Adjust XofLength */
137         theXofLen = theUnderlying.getXofLen();
138         theXofRemaining = -1L;
139         theRoot = null;
140         theCurrent = null;
141     }
142 
143     @Override
144     public String getAlgorithmName() {
145         final String myBase = theUnderlying instanceof GordianBlake2bDigest ? "Blake2Xb" : "Blake2Xs";
146         if (theXofLen == -1) {
147             return myBase;
148         }
149         final long myLen = theXofLen == 0 ? theUnderlying.getDigestSize() : theXofLen;
150         return myBase + "-" + (myLen * Byte.SIZE);
151     }
152 
153     @Override
154     public int getDigestSize() {
155         return (theXofLen == 0 || theXofLen == -1) ? theUnderlying.getDigestSize() : (int) theXofLen;
156     }
157 
158     @Override
159     public int getByteLength() {
160         return theUnderlying.getByteLength();
161     }
162 
163     @Override
164     public void update(final byte b) {
165         singleByte[0] = b;
166         update(singleByte, 0, 1);
167     }
168 
169     @Override
170     public void update(final byte[] pMessage,
171                        final int pOffset,
172                        final int pLen) {
173         if (theXofRemaining != -1) {
174             throw new IllegalStateException("Already outputting");
175         }
176         theUnderlying.update(pMessage, pOffset, pLen);
177     }
178 
179     @Override
180     public int doFinal(final byte[] pOut,
181                        final int pOutOffset) {
182         return doFinal(pOut, pOutOffset, getDigestSize());
183     }
184 
185     @Override
186     public int doFinal(final byte[] pOut,
187                        final int pOutOffset,
188                        final int pOutLen) {
189         /* Build the required output */
190         final int length = doOutput(pOut, pOutOffset, pOutLen);
191 
192         /* reset the underlying digest and return the length */
193         reset();
194         return length;
195     }
196 
197     @Override
198     public int doOutput(final byte[] pOut,
199                         final int pOutOffset,
200                         final int pOutLen) {
201         /* If we have not created the root hash yet */
202         if (theRoot == null) {
203             /* Calculate the underlying hash */
204             theRoot = new byte[theUnderlying.getDigestSize()];
205             theUnderlying.doFinal(theRoot, 0);
206             theNodeIndex = 0;
207 
208             /* If we have a null Xof */
209             if (theXofLen == 0) {
210                 /* Calculate the number of bytes available */
211                 theXofRemaining = theUnderlying.getDigestSize();
212                 theCurrent = theRoot;
213 
214                 /* Else we are handling a normal Xof */
215             } else {
216                 /* Calculate the number of bytes available */
217                 theXofRemaining = theXofLen == -1
218                         ? (1L << Integer.SIZE) * theUnderlying.getDigestSize()
219                         : theXofLen;
220 
221                 /* Allocate a new current hash buffer */
222                 theCurrent = new byte[theUnderlying.getDigestSize()];
223                 theHashIndex = theCurrent.length;
224             }
225         }
226 
227         /* Reject if there is insufficient Xof remaining */
228         if (pOutLen < 0
229                 || (theXofRemaining > 0 && pOutLen > theXofRemaining)) {
230             throw new IllegalArgumentException("Insufficient bytes remaining");
231         }
232 
233         /* If we have some remaining data in the current hash */
234         int dataLeft = pOutLen;
235         int outPos = pOutOffset;
236         if (theHashIndex < theCurrent.length) {
237             /* Copy data from current hash */
238             final int dataToCopy = Math.min(dataLeft, theCurrent.length - theHashIndex);
239             System.arraycopy(theCurrent, theHashIndex, pOut, outPos, dataToCopy);
240 
241             /* Adjust counters */
242             theXofRemaining -= dataToCopy;
243             theHashIndex += dataToCopy;
244             outPos += dataToCopy;
245             dataLeft -= dataToCopy;
246         }
247 
248         /* Loop until we have completed the request */
249         while (dataLeft > 0) {
250             /* Calculate the next hash */
251             obtainNextHash();
252 
253             /* Copy data from current hash */
254             final int dataToCopy = Math.min(dataLeft, theCurrent.length);
255             System.arraycopy(theCurrent, 0, pOut, outPos, dataToCopy);
256 
257             /* Adjust counters */
258             theXofRemaining -= dataToCopy;
259             theHashIndex += dataToCopy;
260             outPos += dataToCopy;
261             dataLeft -= dataToCopy;
262         }
263 
264         /* Return the number of bytes transferred */
265         return pOutLen;
266     }
267 
268     @Override
269     public void reset() {
270         theUnderlying.reset();
271         theComposite.reset();
272         theRoot = null;
273         theCurrent = null;
274         theXofRemaining = -1L;
275         theHashIndex = 0;
276         theNodeIndex = 0;
277     }
278 
279     @Override
280     public void reset(final Memoable pSource) {
281         /* Access source */
282         final GordianBlake2Xof mySource = (GordianBlake2Xof) pSource;
283 
284         /* Reset digests */
285         theUnderlying.reset(mySource.theUnderlying);
286         theComposite.reset(mySource.theComposite);
287 
288         /* Clone hashes */
289         theRoot = Arrays.clone(mySource.theRoot);
290         theCurrent = Arrays.clone(mySource.theCurrent);
291 
292         /* Copy state */
293         theXofLen = mySource.theXofLen;
294         theXofRemaining = mySource.theXofRemaining;
295         theHashIndex = mySource.theHashIndex;
296         theNodeIndex = mySource.theNodeIndex;
297     }
298 
299     @Override
300     public GordianBlake2Xof copy() {
301         return new GordianBlake2Xof(this);
302     }
303 
304     /**
305      * Obtain the next hash.
306      */
307     private void obtainNextHash() {
308         /* Set the digestLength */
309         int digestLen = theUnderlying.getDigestSize();
310         if (theXofRemaining < digestLen) {
311             digestLen = (int) theXofRemaining;
312         }
313         theComposite.setDigestLength(digestLen);
314 
315         /* Calculate the hash */
316         theComposite.setNodePosition(theNodeIndex++, 0);
317         theComposite.update(theRoot, 0, theRoot.length);
318         theComposite.doFinal(theCurrent, 0);
319         theHashIndex = 0;
320     }
321 }