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.util.Arrays;
22  import org.bouncycastle.util.Memoable;
23  
24  /**
25   * Blake2 Base class.
26   */
27  @SuppressWarnings("checkstyle:MagicNumber")
28  public abstract class GordianBlake2Base
29          implements ExtendedDigest, Memoable {
30      /**
31       * Number of Words.
32       */
33      static final int NUMWORDS = 8;
34  
35      /**
36       * Maximum Byte value.
37       */
38      private static final int MAXBYTE = 0xFF;
39  
40      /**
41       * Message word permutations.
42       */
43      private static final byte[][] SIGMA = {
44              {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
45              {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
46              {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
47              {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
48              {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
49              {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
50              {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
51              {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
52              {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
53              {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
54      };
55  
56      /**
57       * The number of Rounds.
58       */
59      private final int theRounds;
60  
61      /**
62       * The maximum xofLen.
63       */
64      private final long theMaxXofLen;
65  
66      /**
67       * The buffer.
68       */
69      private final byte[] theBuffer;
70  
71      /**
72       * Position of last inserted byte.
73       */
74      private int thePos;
75  
76      /**
77       * The digestLength.
78       */
79      private short theDigestLen;
80  
81      /**
82       * The key.
83       */
84      private byte[] theKey;
85  
86      /**
87       * The salt.
88       */
89      private byte[] theSalt;
90  
91      /**
92       * The personalisation.
93       */
94      private byte[] thePersonal;
95  
96      /**
97       * The fanOut.
98       */
99      private short theFanOut;
100 
101     /**
102      * The maxDepth.
103      */
104     private short theMaxDepth;
105 
106     /**
107      * The leafLength.
108      */
109     private int theLeafLen;
110 
111     /**
112      * The nodeOffSet.
113      */
114     private int theNodeOffset;
115 
116     /**
117      * The nodeDepth.
118      */
119     private short theNodeDepth;
120 
121     /**
122      * The xofLength.
123      */
124     private int theXofLen;
125 
126     /**
127      * The innerLength.
128      */
129     private short theInnerLen;
130 
131     /**
132      * Is this the final block?
133      */
134     private boolean isLastBlock;
135 
136     /**
137      * Is this the last node at this depth?
138      */
139     private boolean isLastNode;
140 
141     /**
142      * Constructor.
143      *
144      * @param pRounds   the number of rounds.
145      * @param pBlockLen the blockLength
146      */
147     GordianBlake2Base(final int pRounds,
148                       final int pBlockLen) {
149         /* Store parameters */
150         theRounds = pRounds;
151         theBuffer = new byte[pBlockLen];
152         theFanOut = 1;
153         theMaxDepth = 1;
154 
155         /* Determine maxXofLen */
156         theMaxXofLen = isBlake2b() ? 0xFFFFFFFEL : 0xFFFEL;
157     }
158 
159     /**
160      * Constructor.
161      *
162      * @param pSource the source
163      */
164     GordianBlake2Base(final GordianBlake2Base pSource) {
165         /* Store parameters */
166         theRounds = pSource.theRounds;
167         theBuffer = new byte[pSource.theBuffer.length];
168 
169         /* Determine maxXofLen */
170         theMaxXofLen = pSource.theMaxXofLen;
171     }
172 
173     /**
174      * Is the digest a blake2b digest?
175      *
176      * @return true/false
177      */
178     public abstract boolean isBlake2b();
179 
180     /**
181      * Set the digestLength.
182      *
183      * @param pLength the digestLength.
184      */
185     void setDigestLength(final int pLength) {
186         if (pLength < 0 || pLength > theBuffer.length << 2) {
187             throw new IllegalArgumentException("DigestLength out of range");
188         }
189         theDigestLen = (short) pLength;
190     }
191 
192     @Override
193     public int getDigestSize() {
194         return theDigestLen;
195     }
196 
197     /**
198      * Initialise.
199      *
200      * @param pParams the parameters.
201      */
202     public void init(final GordianBlake2Parameters pParams) {
203         /* Store parameters */
204         setKey(pParams.getKey());
205         setSalt(pParams.getSalt());
206         setPersonalisation(pParams.getPersonalisation());
207         setXofLen(pParams.getMaxOutputLength());
208         setTreeConfig(pParams.getTreeFanOut(), pParams.getTreeMaxDepth(), pParams.getTreeLeafLen());
209 
210         /* Reset the cipher */
211         reset();
212     }
213 
214     /**
215      * Set the key.
216      *
217      * @param pKey the key.
218      */
219     void setKey(final byte[] pKey) {
220         if (pKey == null || pKey.length == 0) {
221             clearKey();
222             theKey = null;
223         } else {
224             if (pKey.length > theBuffer.length >> 1) {
225                 throw new IllegalArgumentException("Key too long");
226             }
227             clearKey();
228             theKey = Arrays.copyOf(pKey, pKey.length);
229         }
230     }
231 
232     /**
233      * Clear the key.
234      */
235     private void clearKey() {
236         if (theKey != null) {
237             Arrays.fill(theKey, (byte) 0);
238         }
239     }
240 
241     /**
242      * Obtain the keyLength.
243      *
244      * @return the keyLength
245      */
246     int getKeyLen() {
247         return theKey == null ? 0 : theKey.length;
248     }
249 
250     /**
251      * Set the salt.
252      *
253      * @param pSalt the salt.
254      */
255     void setSalt(final byte[] pSalt) {
256         if (pSalt == null || pSalt.length == 0) {
257             theSalt = null;
258         } else {
259             if (pSalt.length != theBuffer.length >> 3) {
260                 throw new IllegalArgumentException("Salt incorrect length");
261             }
262             theSalt = Arrays.copyOf(pSalt, pSalt.length);
263         }
264     }
265 
266     /**
267      * Obtain the salt.
268      *
269      * @return the salt
270      */
271     byte[] getSalt() {
272         return theSalt;
273     }
274 
275     /**
276      * Set the personalisation.
277      *
278      * @param pPersonal the personalisation.
279      */
280     void setPersonalisation(final byte[] pPersonal) {
281         if (pPersonal == null || pPersonal.length == 0) {
282             thePersonal = null;
283         } else {
284             if (pPersonal.length != theBuffer.length >> 3) {
285                 throw new IllegalArgumentException("Personalisation incorrect length");
286             }
287             thePersonal = Arrays.copyOf(pPersonal, pPersonal.length);
288         }
289     }
290 
291     /**
292      * Obtain the personalisation.
293      *
294      * @return the personalisation
295      */
296     byte[] getPersonal() {
297         return thePersonal;
298     }
299 
300     /**
301      * Set the xofLen.
302      *
303      * @param pXofLen the xofLength.
304      */
305     void setXofLen(final long pXofLen) {
306         if (pXofLen < -1 || pXofLen > theMaxXofLen) {
307             throw new IllegalArgumentException("XofLength out of range");
308         }
309         theXofLen = (int) pXofLen;
310     }
311 
312     /**
313      * Obtain the xofLength.
314      *
315      * @return the xofLength
316      */
317     int getXofLen() {
318         return theXofLen;
319     }
320 
321     /**
322      * Set the treeConfig.
323      *
324      * @param pFanOut   the fanOut.
325      * @param pMaxDepth the maxDepth.
326      * @param pLeafLen  the leafLength.
327      */
328     void setTreeConfig(final int pFanOut,
329                        final int pMaxDepth,
330                        final int pLeafLen) {
331         /* Check that fanOut value makes sense */
332         if (pFanOut < 0 || pFanOut > MAXBYTE) {
333             throw new IllegalArgumentException("FanOut out of range");
334         }
335         final boolean seqMode = pFanOut == 1;
336 
337         /* Check that maxDepth value makes sense */
338         final boolean xofMode = pMaxDepth == 0;
339         if (pMaxDepth < 0 || pMaxDepth > MAXBYTE) {
340             throw new IllegalArgumentException("MaxDepth out of range");
341         }
342         if (seqMode != (pMaxDepth == 1)) {
343             throw new IllegalArgumentException("Inconsistent treeConfig for sequentialMode");
344         }
345 
346         /* Check that leaf value makes sense */
347         if (pLeafLen < 0) {
348             throw new IllegalArgumentException("LeafLength out of range");
349         }
350         if (seqMode != (pLeafLen == 0)) {
351             throw new IllegalArgumentException("Inconsistent treeConfig for LeafLen and fanOut");
352         }
353         if (xofMode && pFanOut != 0) {
354             throw new IllegalArgumentException("Inconsistent treeConfig for xofMode");
355         }
356 
357         /* Record the values */
358         theFanOut = (short) pFanOut;
359         theMaxDepth = (short) pMaxDepth;
360         theLeafLen = pLeafLen;
361     }
362 
363     /**
364      * Obtain the fanout.
365      *
366      * @return the fanout
367      */
368     short getFanOut() {
369         return theFanOut;
370     }
371 
372     /**
373      * Obtain the maxDepth.
374      *
375      * @return the maxDepth
376      */
377     short getMaxDepth() {
378         return theMaxDepth;
379     }
380 
381     /**
382      * Obtain the leafLength.
383      *
384      * @return the leafLength
385      */
386     int getLeafLen() {
387         return theLeafLen;
388     }
389 
390     /**
391      * Set the nodePosition.
392      *
393      * @param pOffset the offset.
394      * @param pDepth  the depth.
395      */
396     void setNodePosition(final int pOffset,
397                          final int pDepth) {
398         if (pOffset < 0) {
399             throw new IllegalArgumentException("NodeOffset out of range");
400         }
401         theNodeOffset = pOffset;
402         if (pDepth < 0 || pDepth > MAXBYTE) {
403             throw new IllegalArgumentException("NodeDepth out of range");
404         }
405         theNodeDepth = (byte) pDepth;
406         reset();
407     }
408 
409     /**
410      * Obtain the nodeOffset.
411      *
412      * @return the nodeOffset
413      */
414     int getNodeOffset() {
415         return theNodeOffset;
416     }
417 
418     /**
419      * Obtain the nodeDepth.
420      *
421      * @return the nodeDepth
422      */
423     short getNodeDepth() {
424         return theNodeDepth;
425     }
426 
427     /**
428      * is this the last node?
429      *
430      * @return true/false
431      */
432     boolean isLastBlock() {
433         return isLastBlock;
434     }
435 
436     /**
437      * Set the lastNode indicator.
438      */
439     void setLastNode() {
440         isLastNode = true;
441     }
442 
443     /**
444      * is this the last node?
445      *
446      * @return true/false
447      */
448     boolean isLastNode() {
449         return isLastNode;
450     }
451 
452     /**
453      * Set the innerLength.
454      *
455      * @param pInnerLen the innerLength.
456      */
457     void setInnerLength(final int pInnerLen) {
458         if (pInnerLen < 0 || pInnerLen > MAXBYTE) {
459             throw new IllegalArgumentException("InnerLength out of range");
460         }
461         theInnerLen = (short) pInnerLen;
462     }
463 
464     /**
465      * Obtain the innerLength.
466      *
467      * @return the innerLength
468      */
469     short getInnerLen() {
470         return theInnerLen;
471     }
472 
473     @Override
474     public void update(final byte b) {
475         /* If the buffer is full */
476         final int blockLen = theBuffer.length;
477         final int remainingLength = blockLen - thePos;
478         if (remainingLength == 0) {
479             /* Process the buffer */
480             adjustCounter(blockLen);
481             compressF(theBuffer, 0);
482 
483             /* Reset the buffer */
484             Arrays.fill(theBuffer, (byte) 0);
485             thePos = 0;
486         }
487 
488         /* Store the byte */
489         theBuffer[thePos] = b;
490         thePos++;
491     }
492 
493     @Override
494     public void update(final byte[] pMessage,
495                        final int pOffset,
496                        final int pLen) {
497         /* Ignore null operation */
498         if (pMessage == null || pLen == 0) {
499             return;
500         }
501 
502         /* Process any bytes currently in the buffer */
503         final int blockLen = theBuffer.length;
504         int remainingLen = 0; // left bytes of buffer
505         if (thePos != 0) {
506             /* Calculate space remaining in the buffer */
507             remainingLen = blockLen - thePos;
508 
509             /* If there is sufficient space in the buffer */
510             if (remainingLen >= pLen) {
511                 /* Copy date into byffer and return */
512                 System.arraycopy(pMessage, pOffset, theBuffer, thePos, pLen);
513                 thePos += pLen;
514                 return;
515             }
516 
517             /* Fill the buffer */
518             System.arraycopy(pMessage, pOffset, theBuffer, thePos, remainingLen);
519 
520             /* Adjust bytes count */
521             adjustCounter(blockLen);
522 
523             /* Process the buffer */
524             compressF(theBuffer, 0);
525 
526             /* Reset the buffer */
527             thePos = 0;
528             Arrays.fill(theBuffer, (byte) 0);
529         }
530 
531         /* process all blocks except the last one */
532         int messagePos;
533         final int blockWiseLastPos = pOffset + pLen - blockLen;
534         for (messagePos = pOffset + remainingLen; messagePos < blockWiseLastPos; messagePos += blockLen) {
535             /* Adjust bytes count */
536             adjustCounter(blockLen);
537 
538             /* Process the buffer */
539             compressF(pMessage, messagePos);
540         }
541 
542         /* Fill the buffer with the remaining bytes of the message */
543         final int len = pLen - messagePos;
544         System.arraycopy(pMessage, messagePos, theBuffer, 0, pOffset + len);
545         thePos += pOffset + len;
546     }
547 
548     @Override
549     public int doFinal(final byte[] pOut,
550                        final int pOutOffset) {
551         /* Adjust flags and counter */
552         isLastBlock = true;
553         completeCounter(thePos);
554 
555         /* Process the buffer */
556         compressF(theBuffer, 0);
557         Arrays.fill(theBuffer, (byte) 0);
558 
559         /* Output the digest */
560         outputDigest(pOut, pOutOffset);
561 
562         /* Reset the state */
563         reset();
564 
565         /* Return the digest length */
566         return theDigestLen;
567     }
568 
569     @Override
570     public void reset() {
571         /* Reset flags */
572         isLastBlock = false;
573         isLastNode = false;
574 
575         /* Reset the data Buffer */
576         thePos = 0;
577         Arrays.fill(theBuffer, (byte) 0);
578 
579         /* Activate */
580         activateH();
581     }
582 
583     /**
584      * Copy state from source.
585      *
586      * @param pSource the source
587      */
588     void reset(final GordianBlake2Base pSource) {
589         /* Copy config */
590         theDigestLen = pSource.theDigestLen;
591         theInnerLen = pSource.theInnerLen;
592         theLeafLen = pSource.theLeafLen;
593         theXofLen = pSource.theXofLen;
594         theFanOut = pSource.theFanOut;
595         theMaxDepth = pSource.theMaxDepth;
596         theNodeDepth = pSource.theNodeDepth;
597         theNodeOffset = pSource.theNodeOffset;
598 
599         /* Copy flags */
600         isLastNode = pSource.isLastNode;
601 
602         /* Clone arrays */
603         theKey = Arrays.clone(pSource.theKey);
604         theSalt = Arrays.clone(pSource.theSalt);
605         thePersonal = Arrays.clone(pSource.thePersonal);
606 
607         /* Copy buffer */
608         System.arraycopy(pSource.theBuffer, 0, theBuffer, 0, theBuffer.length);
609         thePos = pSource.thePos;
610     }
611 
612     /**
613      * Adjust Counter.
614      *
615      * @param pCount bytes processed
616      */
617     abstract void adjustCounter(int pCount);
618 
619     /**
620      * Complete Counter.
621      *
622      * @param pCount bytes processed
623      */
624     abstract void completeCounter(int pCount);
625 
626     /**
627      * Output the digest.
628      *
629      * @param pOut       the output buffer
630      * @param pOutOffset the offset in the output buffer
631      */
632     abstract void outputDigest(byte[] pOut,
633                                int pOutOffset);
634 
635     /**
636      * Init the keyBlock.
637      */
638     void initKeyBlock() {
639         /* If we have a key */
640         if (theKey != null) {
641             /* Initialise the first data block */
642             System.arraycopy(theKey, 0, theBuffer, 0, theKey.length);
643             thePos = theBuffer.length;
644         }
645     }
646 
647     /**
648      * ActivateH.
649      */
650     abstract void activateH();
651 
652     /**
653      * Obtain the Sigma for the round.
654      *
655      * @param pRound the round
656      * @return the Sigma
657      */
658     private static byte[] getSigmaForRound(final int pRound) {
659         return SIGMA[pRound % SIGMA.length];
660     }
661 
662     /**
663      * Compress a message.
664      *
665      * @param pMessage the message buffer
666      * @param pMsgPos  the position within the message buffer
667      */
668     private void compressF(final byte[] pMessage,
669                            final int pMsgPos) {
670         /* Initialise the buffers */
671         initV();
672         initM(pMessage, pMsgPos);
673 
674         /* Loop through the rounds */
675         for (int round = 0; round < theRounds; round++) {
676             /* Obtain the relevant SIGMA */
677             final byte[] sigma = getSigmaForRound(round);
678 
679             /* Apply to columns of V */
680             mixG(sigma[0], sigma[1], 0, 4, 8, 12);
681             mixG(sigma[2], sigma[3], 1, 5, 9, 13);
682             mixG(sigma[4], sigma[5], 2, 6, 10, 14);
683             mixG(sigma[6], sigma[7], 3, 7, 11, 15);
684 
685             /* Apply to diagonals of V */
686             mixG(sigma[8], sigma[9], 0, 5, 10, 15);
687             mixG(sigma[10], sigma[11], 1, 6, 11, 12);
688             mixG(sigma[12], sigma[13], 2, 7, 8, 13);
689             mixG(sigma[14], sigma[15], 3, 4, 9, 14);
690         }
691 
692         /* Adjust H */
693         adjustH();
694     }
695 
696     /**
697      * Initialise V.
698      */
699     abstract void initV();
700 
701     /**
702      * Initialise M.
703      *
704      * @param pMessage the message buffer
705      * @param pMsgPos  the position in the message buffer
706      */
707     abstract void initM(byte[] pMessage,
708                         int pMsgPos);
709 
710     /**
711      * Mix function.
712      *
713      * @param msgIdx1 the first msgIndex
714      * @param msgIdx2 the second msgIndex
715      * @param posA    position A
716      * @param posB    position B
717      * @param posC    position C
718      * @param posD    position D
719      */
720     abstract void mixG(int msgIdx1,
721                        int msgIdx2,
722                        int posA,
723                        int posB,
724                        int posC,
725                        int posD);
726 
727     /**
728      * Adjust H.
729      */
730     abstract void adjustH();
731 }