1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.gordianknot.impl.ext.modes;
18
19 import org.bouncycastle.crypto.BlockCipher;
20 import org.bouncycastle.crypto.CipherParameters;
21 import org.bouncycastle.crypto.DataLengthException;
22 import org.bouncycastle.crypto.InvalidCipherTextException;
23 import org.bouncycastle.crypto.OutputLengthException;
24 import org.bouncycastle.crypto.engines.AESEngine;
25 import org.bouncycastle.crypto.modes.AEADBlockCipher;
26 import org.bouncycastle.crypto.modes.gcm.GCMMultiplier;
27 import org.bouncycastle.crypto.modes.gcm.Tables4kGCMMultiplier;
28 import org.bouncycastle.crypto.params.AEADParameters;
29 import org.bouncycastle.crypto.params.KeyParameter;
30 import org.bouncycastle.crypto.params.ParametersWithIV;
31 import org.bouncycastle.util.Arrays;
32 import org.bouncycastle.util.Pack;
33
34 import java.io.ByteArrayOutputStream;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class GordianGCMSIVBlockCipher
50 implements AEADBlockCipher {
51
52
53
54 private static final int BUFLEN = 16;
55
56
57
58
59 private static final int HALFBUFLEN = BUFLEN >> 1;
60
61
62
63
64 private static final int NONCELEN = 12;
65
66
67
68
69
70 private static final int MAX_DATALEN = Integer.MAX_VALUE - 8 - BUFLEN;
71
72
73
74
75 private static final byte MASK = (byte) 0b10000000;
76
77
78
79
80 private static final byte ADD = (byte) 0b11100001;
81
82
83
84
85 private static final int INIT = 1;
86
87
88
89
90 private static final int AEAD_COMPLETE = 2;
91
92
93
94
95 private final BlockCipher theCipher;
96
97
98
99
100 private final GCMMultiplier theMultiplier;
101
102
103
104
105 private final byte[] theGHash = new byte[BUFLEN];
106
107
108
109
110 private final byte[] theReverse = new byte[BUFLEN];
111
112
113
114
115 private final GCMSIVHasher theAEADHasher;
116
117
118
119
120 private final GCMSIVHasher theDataHasher;
121
122
123
124
125 private GCMSIVCache thePlain;
126
127
128
129
130 private GCMSIVCache theEncData;
131
132
133
134
135 private boolean forEncryption;
136
137
138
139
140 private byte[] theInitialAEAD;
141
142
143
144
145 private byte[] theNonce;
146
147
148
149
150 private int theFlags;
151
152
153
154
155 public GordianGCMSIVBlockCipher() {
156 this(AESEngine.newInstance());
157 }
158
159
160
161
162
163
164 public GordianGCMSIVBlockCipher(final BlockCipher pCipher) {
165 this(pCipher, new Tables4kGCMMultiplier());
166 }
167
168
169
170
171
172
173
174 public GordianGCMSIVBlockCipher(final BlockCipher pCipher,
175 final GCMMultiplier pMultiplier) {
176
177 if (pCipher.getBlockSize() != BUFLEN) {
178 throw new IllegalArgumentException("Cipher required with a block size of " + BUFLEN + ".");
179 }
180
181
182 theCipher = pCipher;
183 theMultiplier = pMultiplier;
184
185
186 theAEADHasher = new GCMSIVHasher();
187 theDataHasher = new GCMSIVHasher();
188 }
189
190 @Override
191 public BlockCipher getUnderlyingCipher() {
192 return theCipher;
193 }
194
195 @Override
196 public void init(final boolean pEncrypt,
197 final CipherParameters cipherParameters) throws IllegalArgumentException {
198
199 byte[] myInitialAEAD = null;
200 final byte[] myNonce;
201 final KeyParameter myKey;
202
203
204 if (cipherParameters instanceof AEADParameters myAEAD) {
205 myInitialAEAD = myAEAD.getAssociatedText();
206 myNonce = myAEAD.getNonce();
207 myKey = myAEAD.getKey();
208 } else if (cipherParameters instanceof ParametersWithIV myParms) {
209 myNonce = myParms.getIV();
210 myKey = (KeyParameter) myParms.getParameters();
211 } else {
212 throw new IllegalArgumentException("invalid parameters passed to GCM_SIV");
213 }
214
215
216 if (myNonce == null || myNonce.length != NONCELEN) {
217 throw new IllegalArgumentException("Invalid nonce");
218 }
219
220
221 if (myKey == null
222 || (myKey.getKey().length != BUFLEN
223 && myKey.getKey().length != (BUFLEN << 1))) {
224 throw new IllegalArgumentException("Invalid key");
225 }
226
227
228 forEncryption = pEncrypt;
229 theInitialAEAD = myInitialAEAD;
230 theNonce = myNonce;
231
232
233 deriveKeys(myKey);
234 resetStreams();
235 }
236
237 @Override
238 public String getAlgorithmName() {
239 return theCipher.getAlgorithmName() + "-GCM-SIV";
240 }
241
242
243
244
245
246
247 private void checkAEADStatus(final int pLen) {
248
249 if ((theFlags & INIT) == 0) {
250 throw new IllegalStateException("Cipher is not initialised");
251 }
252
253
254 if ((theFlags & AEAD_COMPLETE) != 0) {
255 throw new IllegalStateException("AEAD data cannot be processed after ordinary data");
256 }
257
258
259 if (theAEADHasher.getBytesProcessed() + Long.MIN_VALUE
260 > (MAX_DATALEN - pLen) + Long.MIN_VALUE) {
261 throw new IllegalStateException("AEAD byte count exceeded");
262 }
263 }
264
265
266
267
268
269
270 private void checkStatus(final int pLen) {
271
272 if ((theFlags & INIT) == 0) {
273 throw new IllegalStateException("Cipher is not initialised");
274 }
275
276
277 if ((theFlags & AEAD_COMPLETE) == 0) {
278 theAEADHasher.completeHash();
279 theFlags |= AEAD_COMPLETE;
280 }
281
282
283 long dataLimit = MAX_DATALEN;
284 long currBytes = thePlain.size();
285 if (!forEncryption) {
286 dataLimit += BUFLEN;
287 currBytes = theEncData.size();
288 }
289 if (currBytes + Long.MIN_VALUE
290 > (dataLimit - pLen) + Long.MIN_VALUE) {
291 throw new IllegalStateException("byte count exceeded");
292 }
293 }
294
295 @Override
296 public void processAADByte(final byte pByte) {
297
298 checkAEADStatus(1);
299
300
301 theAEADHasher.updateHash(pByte);
302 }
303
304 @Override
305 public void processAADBytes(final byte[] pData,
306 final int pOffset,
307 final int pLen) {
308
309 checkAEADStatus(pLen);
310
311
312 checkBuffer(pData, pOffset, pLen, false);
313
314
315 theAEADHasher.updateHash(pData, pOffset, pLen);
316 }
317
318 @Override
319 public int processByte(final byte pByte,
320 final byte[] pOutput,
321 final int pOutOffset) throws DataLengthException {
322
323 checkStatus(1);
324
325
326 if (forEncryption) {
327 thePlain.write(pByte);
328 theDataHasher.updateHash(pByte);
329 } else {
330 theEncData.write(pByte);
331 }
332
333
334 return 0;
335 }
336
337 @Override
338 public int processBytes(final byte[] pData,
339 final int pOffset,
340 final int pLen,
341 final byte[] pOutput,
342 final int pOutOffset) throws DataLengthException {
343
344 checkStatus(pLen);
345
346
347 checkBuffer(pData, pOffset, pLen, false);
348
349
350 if (forEncryption) {
351 thePlain.write(pData, pOffset, pLen);
352 theDataHasher.updateHash(pData, pOffset, pLen);
353 } else {
354 theEncData.write(pData, pOffset, pLen);
355 }
356
357
358 return 0;
359 }
360
361 @Override
362 public int doFinal(final byte[] pOutput,
363 final int pOffset) throws IllegalStateException, InvalidCipherTextException {
364
365 checkStatus(0);
366
367
368 checkBuffer(pOutput, pOffset, getOutputSize(0), true);
369
370
371 if (forEncryption) {
372
373 final byte[] myTag = calculateTag();
374
375
376 final int myDataLen = BUFLEN + encryptPlain(myTag, pOutput, pOffset);
377
378
379 System.arraycopy(myTag, 0, pOutput, pOffset + thePlain.size(), BUFLEN);
380
381
382 resetStreams();
383 return myDataLen;
384
385
386 } else {
387
388 decryptPlain();
389
390
391 final int myDataLen = thePlain.size();
392 final byte[] mySrc = thePlain.getBuffer();
393 System.arraycopy(mySrc, 0, pOutput, pOffset, myDataLen);
394
395
396 resetStreams();
397 return myDataLen;
398 }
399 }
400
401 @Override
402 public byte[] getMac() {
403 throw new UnsupportedOperationException();
404 }
405
406 @Override
407 public int getUpdateOutputSize(final int pLen) {
408 return 0;
409 }
410
411 @Override
412 public int getOutputSize(final int pLen) {
413 if (forEncryption) {
414 return pLen + thePlain.size() + BUFLEN;
415 }
416 final int myCurr = pLen + theEncData.size();
417 return myCurr > BUFLEN ? myCurr - BUFLEN : 0;
418 }
419
420 @Override
421 public void reset() {
422 resetStreams();
423 }
424
425
426
427
428 private void resetStreams() {
429
430 if (thePlain != null) {
431 thePlain.clearBuffer();
432 }
433
434
435 theAEADHasher.reset();
436 theDataHasher.reset();
437
438
439 thePlain = new GCMSIVCache();
440 theEncData = forEncryption ? null : new GCMSIVCache();
441
442
443 theFlags &= ~AEAD_COMPLETE;
444 Arrays.fill(theGHash, (byte) 0);
445 if (theInitialAEAD != null) {
446 theAEADHasher.updateHash(theInitialAEAD, 0, theInitialAEAD.length);
447 }
448 }
449
450
451
452
453
454
455
456 private static int bufLength(final byte[] pBuffer) {
457 return pBuffer == null ? 0 : pBuffer.length;
458 }
459
460
461
462
463
464
465
466
467
468 private static void checkBuffer(final byte[] pBuffer,
469 final int pOffset,
470 final int pLen,
471 final boolean pOutput) {
472
473 final int myBufLen = bufLength(pBuffer);
474 final int myLast = pOffset + pLen;
475
476
477 final boolean badLen = pLen < 0 || pOffset < 0 || myLast < 0;
478 if (badLen || myLast > myBufLen) {
479 throw pOutput
480 ? new OutputLengthException("Output buffer too short.")
481 : new DataLengthException("Input buffer too short.");
482 }
483 }
484
485
486
487
488
489
490
491
492
493 private int encryptPlain(final byte[] pCounter,
494 final byte[] pTarget,
495 final int pOffset) {
496
497 final byte[] mySrc = thePlain.getBuffer();
498 final byte[] myCounter = Arrays.clone(pCounter);
499 myCounter[BUFLEN - 1] |= MASK;
500 final byte[] myMask = new byte[BUFLEN];
501 int myRemaining = thePlain.size();
502 int myOff = 0;
503
504
505 while (myRemaining > 0) {
506
507 theCipher.processBlock(myCounter, 0, myMask, 0);
508
509
510 final int myLen = Math.min(BUFLEN, myRemaining);
511 xorBlock(myMask, mySrc, myOff, myLen);
512
513
514 System.arraycopy(myMask, 0, pTarget, pOffset + myOff, myLen);
515
516
517 myRemaining -= myLen;
518 myOff += myLen;
519 incrementCounter(myCounter);
520 }
521
522
523 return thePlain.size();
524 }
525
526
527
528
529
530
531 private void decryptPlain() throws InvalidCipherTextException {
532
533 final byte[] mySrc = theEncData.getBuffer();
534 int myRemaining = theEncData.size() - BUFLEN;
535
536
537 if (myRemaining < 0) {
538 throw new InvalidCipherTextException("Data too short");
539 }
540
541
542 final byte[] myExpected = Arrays.copyOfRange(mySrc, myRemaining, myRemaining + BUFLEN);
543 final byte[] myCounter = Arrays.clone(myExpected);
544 myCounter[BUFLEN - 1] |= MASK;
545 final byte[] myMask = new byte[BUFLEN];
546 int myOff = 0;
547
548
549 while (myRemaining > 0) {
550
551 theCipher.processBlock(myCounter, 0, myMask, 0);
552
553
554 final int myLen = Math.min(BUFLEN, myRemaining);
555 xorBlock(myMask, mySrc, myOff, myLen);
556
557
558 thePlain.write(myMask, 0, myLen);
559 theDataHasher.updateHash(myMask, 0, myLen);
560
561
562 myRemaining -= myLen;
563 myOff += myLen;
564 incrementCounter(myCounter);
565 }
566
567
568 final byte[] myTag = calculateTag();
569 if (!Arrays.constantTimeAreEqual(myTag, myExpected)) {
570 reset();
571 throw new InvalidCipherTextException("mac check failed");
572 }
573 }
574
575
576
577
578
579
580 private byte[] calculateTag() {
581
582 theDataHasher.completeHash();
583 final byte[] myPolyVal = completePolyVal();
584
585
586 final byte[] myResult = new byte[BUFLEN];
587
588
589 for (int i = 0; i < NONCELEN; i++) {
590 myPolyVal[i] ^= theNonce[i];
591 }
592
593
594 myPolyVal[BUFLEN - 1] &= (byte) (MASK - 1);
595
596
597 theCipher.processBlock(myPolyVal, 0, myResult, 0);
598 return myResult;
599 }
600
601
602
603
604
605
606 private byte[] completePolyVal() {
607
608 final byte[] myResult = new byte[BUFLEN];
609 gHashLengths();
610 fillReverse(theGHash, 0, BUFLEN, myResult);
611 return myResult;
612 }
613
614
615
616
617 private void gHashLengths() {
618
619 final byte[] myIn = new byte[BUFLEN];
620 Pack.longToBigEndian(Byte.SIZE * theDataHasher.getBytesProcessed(), myIn, 0);
621 Pack.longToBigEndian(Byte.SIZE * theAEADHasher.getBytesProcessed(), myIn, Long.BYTES);
622
623
624 gHASH(myIn);
625 }
626
627
628
629
630
631
632 private void gHASH(final byte[] pNext) {
633 xorBlock(theGHash, pNext);
634 theMultiplier.multiplyH(theGHash);
635 }
636
637
638
639
640
641
642
643
644
645 private static void fillReverse(final byte[] pInput,
646 final int pOffset,
647 final int pLength,
648 final byte[] pOutput) {
649
650 for (int i = 0, j = BUFLEN - 1; i < pLength; i++, j--) {
651
652 pOutput[j] = pInput[pOffset + i];
653 }
654 }
655
656
657
658
659
660
661
662 private static void xorBlock(final byte[] pLeft,
663 final byte[] pRight) {
664
665 for (int i = 0; i < BUFLEN; i++) {
666 pLeft[i] ^= pRight[i];
667 }
668 }
669
670
671
672
673
674
675
676
677
678 private static void xorBlock(final byte[] pLeft,
679 final byte[] pRight,
680 final int pOffset,
681 final int pLength) {
682
683 for (int i = 0; i < pLength; i++) {
684 pLeft[i] ^= pRight[i + pOffset];
685 }
686 }
687
688
689
690
691
692
693 private static void incrementCounter(final byte[] pCounter) {
694
695 for (int i = 0; i < Integer.BYTES; i++) {
696 if (++pCounter[i] != 0) {
697 break;
698 }
699 }
700 }
701
702
703
704
705
706
707 private static void mulX(final byte[] pValue) {
708
709 byte myMask = (byte) 0;
710 for (int i = 0; i < BUFLEN; i++) {
711 final byte myValue = pValue[i];
712 pValue[i] = (byte) (((myValue >> 1) & ~MASK) | myMask);
713 myMask = (myValue & 1) == 0 ? 0 : MASK;
714 }
715
716
717 if (myMask != 0) {
718 pValue[0] ^= ADD;
719 }
720 }
721
722
723
724
725
726
727 private void deriveKeys(final KeyParameter pKey) {
728
729 final byte[] myIn = new byte[BUFLEN];
730 final byte[] myOut = new byte[BUFLEN];
731 final byte[] myResult = new byte[BUFLEN];
732 final byte[] myEncKey = new byte[pKey.getKey().length];
733
734
735 System.arraycopy(theNonce, 0, myIn, BUFLEN - NONCELEN, NONCELEN);
736 theCipher.init(true, pKey);
737
738
739 int myOff = 0;
740 theCipher.processBlock(myIn, 0, myOut, 0);
741 System.arraycopy(myOut, 0, myResult, myOff, HALFBUFLEN);
742 myIn[0]++;
743 myOff += HALFBUFLEN;
744 theCipher.processBlock(myIn, 0, myOut, 0);
745 System.arraycopy(myOut, 0, myResult, myOff, HALFBUFLEN);
746
747
748 myIn[0]++;
749 myOff = 0;
750 theCipher.processBlock(myIn, 0, myOut, 0);
751 System.arraycopy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
752 myIn[0]++;
753 myOff += HALFBUFLEN;
754 theCipher.processBlock(myIn, 0, myOut, 0);
755 System.arraycopy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
756
757
758 if (myEncKey.length == BUFLEN << 1) {
759
760 myIn[0]++;
761 myOff += HALFBUFLEN;
762 theCipher.processBlock(myIn, 0, myOut, 0);
763 System.arraycopy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
764 myIn[0]++;
765 myOff += HALFBUFLEN;
766 theCipher.processBlock(myIn, 0, myOut, 0);
767 System.arraycopy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
768 }
769
770
771 theCipher.init(true, new KeyParameter(myEncKey));
772
773
774 fillReverse(myResult, 0, BUFLEN, myOut);
775 mulX(myOut);
776 theMultiplier.init(myOut);
777 theFlags |= INIT;
778 }
779
780
781
782
783 private static class GCMSIVCache
784 extends ByteArrayOutputStream {
785
786
787
788 private int numHashed;
789
790
791
792
793 GCMSIVCache() {
794 }
795
796
797
798
799
800
801 byte[] getBuffer() {
802 return this.buf;
803 }
804
805
806
807
808 void clearBuffer() {
809 Arrays.fill(getBuffer(), (byte) 0);
810 }
811 }
812
813
814
815
816 private final class GCMSIVHasher {
817
818
819
820 private final byte[] theBuffer = new byte[BUFLEN];
821
822
823
824
825 private final byte[] theByte = new byte[1];
826
827
828
829
830 private int numActive;
831
832
833
834
835 private long numHashed;
836
837
838
839
840
841
842 long getBytesProcessed() {
843 return numHashed;
844 }
845
846
847
848
849 void reset() {
850 numActive = 0;
851 numHashed = 0;
852 }
853
854
855
856
857
858
859 void updateHash(final byte pByte) {
860 theByte[0] = pByte;
861 updateHash(theByte, 0, 1);
862 }
863
864
865
866
867
868
869
870
871 void updateHash(final byte[] pBuffer,
872 final int pOffset,
873 final int pLen) {
874
875 final int mySpace = BUFLEN - numActive;
876 int numProcessed = 0;
877 int myRemaining = pLen;
878 if (numActive > 0
879 && pLen >= mySpace) {
880
881 System.arraycopy(pBuffer, pOffset, theBuffer, numActive, mySpace);
882 fillReverse(theBuffer, 0, BUFLEN, theReverse);
883 gHASH(theReverse);
884
885
886 numProcessed += mySpace;
887 myRemaining -= mySpace;
888 numActive = 0;
889 }
890
891
892 while (myRemaining >= BUFLEN) {
893
894 fillReverse(pBuffer, pOffset + numProcessed, BUFLEN, theReverse);
895 gHASH(theReverse);
896
897
898 numProcessed += BUFLEN;
899 myRemaining -= BUFLEN;
900 }
901
902
903 if (myRemaining > 0) {
904
905 System.arraycopy(pBuffer, pOffset + numProcessed, theBuffer, numActive, myRemaining);
906 numActive += myRemaining;
907 }
908
909
910 numHashed += pLen;
911 }
912
913
914
915
916 void completeHash() {
917
918 if (numActive > 0) {
919
920 Arrays.fill(theReverse, (byte) 0);
921 fillReverse(theBuffer, 0, numActive, theReverse);
922
923
924 gHASH(theReverse);
925 }
926 }
927 }
928 }