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) {
205 final AEADParameters myAEAD = (AEADParameters) cipherParameters;
206 myInitialAEAD = myAEAD.getAssociatedText();
207 myNonce = myAEAD.getNonce();
208 myKey = myAEAD.getKey();
209 } else if (cipherParameters instanceof ParametersWithIV) {
210 final ParametersWithIV myParms = (ParametersWithIV) cipherParameters;
211 myNonce = myParms.getIV();
212 myKey = (KeyParameter) myParms.getParameters();
213 } else {
214 throw new IllegalArgumentException("invalid parameters passed to GCM_SIV");
215 }
216
217
218 if (myNonce == null || myNonce.length != NONCELEN) {
219 throw new IllegalArgumentException("Invalid nonce");
220 }
221
222
223 if (myKey == null
224 || (myKey.getKey().length != BUFLEN
225 && myKey.getKey().length != (BUFLEN << 1))) {
226 throw new IllegalArgumentException("Invalid key");
227 }
228
229
230 forEncryption = pEncrypt;
231 theInitialAEAD = myInitialAEAD;
232 theNonce = myNonce;
233
234
235 deriveKeys(myKey);
236 resetStreams();
237 }
238
239 @Override
240 public String getAlgorithmName() {
241 return theCipher.getAlgorithmName() + "-GCM-SIV";
242 }
243
244
245
246
247
248
249 private void checkAEADStatus(final int pLen) {
250
251 if ((theFlags & INIT) == 0) {
252 throw new IllegalStateException("Cipher is not initialised");
253 }
254
255
256 if ((theFlags & AEAD_COMPLETE) != 0) {
257 throw new IllegalStateException("AEAD data cannot be processed after ordinary data");
258 }
259
260
261 if (theAEADHasher.getBytesProcessed() + Long.MIN_VALUE
262 > (MAX_DATALEN - pLen) + Long.MIN_VALUE) {
263 throw new IllegalStateException("AEAD byte count exceeded");
264 }
265 }
266
267
268
269
270
271
272 private void checkStatus(final int pLen) {
273
274 if ((theFlags & INIT) == 0) {
275 throw new IllegalStateException("Cipher is not initialised");
276 }
277
278
279 if ((theFlags & AEAD_COMPLETE) == 0) {
280 theAEADHasher.completeHash();
281 theFlags |= AEAD_COMPLETE;
282 }
283
284
285 long dataLimit = MAX_DATALEN;
286 long currBytes = thePlain.size();
287 if (!forEncryption) {
288 dataLimit += BUFLEN;
289 currBytes = theEncData.size();
290 }
291 if (currBytes + Long.MIN_VALUE
292 > (dataLimit - pLen) + Long.MIN_VALUE) {
293 throw new IllegalStateException("byte count exceeded");
294 }
295 }
296
297 @Override
298 public void processAADByte(final byte pByte) {
299
300 checkAEADStatus(1);
301
302
303 theAEADHasher.updateHash(pByte);
304 }
305
306 @Override
307 public void processAADBytes(final byte[] pData,
308 final int pOffset,
309 final int pLen) {
310
311 checkAEADStatus(pLen);
312
313
314 checkBuffer(pData, pOffset, pLen, false);
315
316
317 theAEADHasher.updateHash(pData, pOffset, pLen);
318 }
319
320 @Override
321 public int processByte(final byte pByte,
322 final byte[] pOutput,
323 final int pOutOffset) throws DataLengthException {
324
325 checkStatus(1);
326
327
328 if (forEncryption) {
329 thePlain.write(pByte);
330 theDataHasher.updateHash(pByte);
331 } else {
332 theEncData.write(pByte);
333 }
334
335
336 return 0;
337 }
338
339 @Override
340 public int processBytes(final byte[] pData,
341 final int pOffset,
342 final int pLen,
343 final byte[] pOutput,
344 final int pOutOffset) throws DataLengthException {
345
346 checkStatus(pLen);
347
348
349 checkBuffer(pData, pOffset, pLen, false);
350
351
352 if (forEncryption) {
353 thePlain.write(pData, pOffset, pLen);
354 theDataHasher.updateHash(pData, pOffset, pLen);
355 } else {
356 theEncData.write(pData, pOffset, pLen);
357 }
358
359
360 return 0;
361 }
362
363 @Override
364 public int doFinal(final byte[] pOutput,
365 final int pOffset) throws IllegalStateException, InvalidCipherTextException {
366
367 checkStatus(0);
368
369
370 checkBuffer(pOutput, pOffset, getOutputSize(0), true);
371
372
373 if (forEncryption) {
374
375 final byte[] myTag = calculateTag();
376
377
378 final int myDataLen = BUFLEN + encryptPlain(myTag, pOutput, pOffset);
379
380
381 System.arraycopy(myTag, 0, pOutput, pOffset + thePlain.size(), BUFLEN);
382
383
384 resetStreams();
385 return myDataLen;
386
387
388 } else {
389
390 decryptPlain();
391
392
393 final int myDataLen = thePlain.size();
394 final byte[] mySrc = thePlain.getBuffer();
395 System.arraycopy(mySrc, 0, pOutput, pOffset, myDataLen);
396
397
398 resetStreams();
399 return myDataLen;
400 }
401 }
402
403 @Override
404 public byte[] getMac() {
405 throw new UnsupportedOperationException();
406 }
407
408 @Override
409 public int getUpdateOutputSize(final int pLen) {
410 return 0;
411 }
412
413 @Override
414 public int getOutputSize(final int pLen) {
415 if (forEncryption) {
416 return pLen + thePlain.size() + BUFLEN;
417 }
418 final int myCurr = pLen + theEncData.size();
419 return myCurr > BUFLEN ? myCurr - BUFLEN : 0;
420 }
421
422 @Override
423 public void reset() {
424 resetStreams();
425 }
426
427
428
429
430 private void resetStreams() {
431
432 if (thePlain != null) {
433 thePlain.clearBuffer();
434 }
435
436
437 theAEADHasher.reset();
438 theDataHasher.reset();
439
440
441 thePlain = new GCMSIVCache();
442 theEncData = forEncryption ? null : new GCMSIVCache();
443
444
445 theFlags &= ~AEAD_COMPLETE;
446 Arrays.fill(theGHash, (byte) 0);
447 if (theInitialAEAD != null) {
448 theAEADHasher.updateHash(theInitialAEAD, 0, theInitialAEAD.length);
449 }
450 }
451
452
453
454
455
456
457
458 private static int bufLength(final byte[] pBuffer) {
459 return pBuffer == null ? 0 : pBuffer.length;
460 }
461
462
463
464
465
466
467
468
469
470 private static void checkBuffer(final byte[] pBuffer,
471 final int pOffset,
472 final int pLen,
473 final boolean pOutput) {
474
475 final int myBufLen = bufLength(pBuffer);
476 final int myLast = pOffset + pLen;
477
478
479 final boolean badLen = pLen < 0 || pOffset < 0 || myLast < 0;
480 if (badLen || myLast > myBufLen) {
481 throw pOutput
482 ? new OutputLengthException("Output buffer too short.")
483 : new DataLengthException("Input buffer too short.");
484 }
485 }
486
487
488
489
490
491
492
493
494
495 private int encryptPlain(final byte[] pCounter,
496 final byte[] pTarget,
497 final int pOffset) {
498
499 final byte[] mySrc = thePlain.getBuffer();
500 final byte[] myCounter = Arrays.clone(pCounter);
501 myCounter[BUFLEN - 1] |= MASK;
502 final byte[] myMask = new byte[BUFLEN];
503 int myRemaining = thePlain.size();
504 int myOff = 0;
505
506
507 while (myRemaining > 0) {
508
509 theCipher.processBlock(myCounter, 0, myMask, 0);
510
511
512 final int myLen = Math.min(BUFLEN, myRemaining);
513 xorBlock(myMask, mySrc, myOff, myLen);
514
515
516 System.arraycopy(myMask, 0, pTarget, pOffset + myOff, myLen);
517
518
519 myRemaining -= myLen;
520 myOff += myLen;
521 incrementCounter(myCounter);
522 }
523
524
525 return thePlain.size();
526 }
527
528
529
530
531
532
533 private void decryptPlain() throws InvalidCipherTextException {
534
535 final byte[] mySrc = theEncData.getBuffer();
536 int myRemaining = theEncData.size() - BUFLEN;
537
538
539 if (myRemaining < 0) {
540 throw new InvalidCipherTextException("Data too short");
541 }
542
543
544 final byte[] myExpected = Arrays.copyOfRange(mySrc, myRemaining, myRemaining + BUFLEN);
545 final byte[] myCounter = Arrays.clone(myExpected);
546 myCounter[BUFLEN - 1] |= MASK;
547 final byte[] myMask = new byte[BUFLEN];
548 int myOff = 0;
549
550
551 while (myRemaining > 0) {
552
553 theCipher.processBlock(myCounter, 0, myMask, 0);
554
555
556 final int myLen = Math.min(BUFLEN, myRemaining);
557 xorBlock(myMask, mySrc, myOff, myLen);
558
559
560 thePlain.write(myMask, 0, myLen);
561 theDataHasher.updateHash(myMask, 0, myLen);
562
563
564 myRemaining -= myLen;
565 myOff += myLen;
566 incrementCounter(myCounter);
567 }
568
569
570 final byte[] myTag = calculateTag();
571 if (!Arrays.constantTimeAreEqual(myTag, myExpected)) {
572 reset();
573 throw new InvalidCipherTextException("mac check failed");
574 }
575 }
576
577
578
579
580
581
582 private byte[] calculateTag() {
583
584 theDataHasher.completeHash();
585 final byte[] myPolyVal = completePolyVal();
586
587
588 final byte[] myResult = new byte[BUFLEN];
589
590
591 for (int i = 0; i < NONCELEN; i++) {
592 myPolyVal[i] ^= theNonce[i];
593 }
594
595
596 myPolyVal[BUFLEN - 1] &= (byte) (MASK - 1);
597
598
599 theCipher.processBlock(myPolyVal, 0, myResult, 0);
600 return myResult;
601 }
602
603
604
605
606
607
608 private byte[] completePolyVal() {
609
610 final byte[] myResult = new byte[BUFLEN];
611 gHashLengths();
612 fillReverse(theGHash, 0, BUFLEN, myResult);
613 return myResult;
614 }
615
616
617
618
619 private void gHashLengths() {
620
621 final byte[] myIn = new byte[BUFLEN];
622 Pack.longToBigEndian(Byte.SIZE * theDataHasher.getBytesProcessed(), myIn, 0);
623 Pack.longToBigEndian(Byte.SIZE * theAEADHasher.getBytesProcessed(), myIn, Long.BYTES);
624
625
626 gHASH(myIn);
627 }
628
629
630
631
632
633
634 private void gHASH(final byte[] pNext) {
635 xorBlock(theGHash, pNext);
636 theMultiplier.multiplyH(theGHash);
637 }
638
639
640
641
642
643
644
645
646
647 private static void fillReverse(final byte[] pInput,
648 final int pOffset,
649 final int pLength,
650 final byte[] pOutput) {
651
652 for (int i = 0, j = BUFLEN - 1; i < pLength; i++, j--) {
653
654 pOutput[j] = pInput[pOffset + i];
655 }
656 }
657
658
659
660
661
662
663
664 private static void xorBlock(final byte[] pLeft,
665 final byte[] pRight) {
666
667 for (int i = 0; i < BUFLEN; i++) {
668 pLeft[i] ^= pRight[i];
669 }
670 }
671
672
673
674
675
676
677
678
679
680 private static void xorBlock(final byte[] pLeft,
681 final byte[] pRight,
682 final int pOffset,
683 final int pLength) {
684
685 for (int i = 0; i < pLength; i++) {
686 pLeft[i] ^= pRight[i + pOffset];
687 }
688 }
689
690
691
692
693
694
695 private static void incrementCounter(final byte[] pCounter) {
696
697 for (int i = 0; i < Integer.BYTES; i++) {
698 if (++pCounter[i] != 0) {
699 break;
700 }
701 }
702 }
703
704
705
706
707
708
709 private static void mulX(final byte[] pValue) {
710
711 byte myMask = (byte) 0;
712 for (int i = 0; i < BUFLEN; i++) {
713 final byte myValue = pValue[i];
714 pValue[i] = (byte) (((myValue >> 1) & ~MASK) | myMask);
715 myMask = (myValue & 1) == 0 ? 0 : MASK;
716 }
717
718
719 if (myMask != 0) {
720 pValue[0] ^= ADD;
721 }
722 }
723
724
725
726
727
728
729 private void deriveKeys(final KeyParameter pKey) {
730
731 final byte[] myIn = new byte[BUFLEN];
732 final byte[] myOut = new byte[BUFLEN];
733 final byte[] myResult = new byte[BUFLEN];
734 final byte[] myEncKey = new byte[pKey.getKey().length];
735
736
737 System.arraycopy(theNonce, 0, myIn, BUFLEN - NONCELEN, NONCELEN);
738 theCipher.init(true, pKey);
739
740
741 int myOff = 0;
742 theCipher.processBlock(myIn, 0, myOut, 0);
743 System.arraycopy(myOut, 0, myResult, myOff, HALFBUFLEN);
744 myIn[0]++;
745 myOff += HALFBUFLEN;
746 theCipher.processBlock(myIn, 0, myOut, 0);
747 System.arraycopy(myOut, 0, myResult, myOff, HALFBUFLEN);
748
749
750 myIn[0]++;
751 myOff = 0;
752 theCipher.processBlock(myIn, 0, myOut, 0);
753 System.arraycopy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
754 myIn[0]++;
755 myOff += HALFBUFLEN;
756 theCipher.processBlock(myIn, 0, myOut, 0);
757 System.arraycopy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
758
759
760 if (myEncKey.length == BUFLEN << 1) {
761
762 myIn[0]++;
763 myOff += HALFBUFLEN;
764 theCipher.processBlock(myIn, 0, myOut, 0);
765 System.arraycopy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
766 myIn[0]++;
767 myOff += HALFBUFLEN;
768 theCipher.processBlock(myIn, 0, myOut, 0);
769 System.arraycopy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
770 }
771
772
773 theCipher.init(true, new KeyParameter(myEncKey));
774
775
776 fillReverse(myResult, 0, BUFLEN, myOut);
777 mulX(myOut);
778 theMultiplier.init(myOut);
779 theFlags |= INIT;
780 }
781
782
783
784
785 private static class GCMSIVCache
786 extends ByteArrayOutputStream {
787
788
789
790 private int numHashed;
791
792
793
794
795 GCMSIVCache() {
796 }
797
798
799
800
801
802
803 byte[] getBuffer() {
804 return this.buf;
805 }
806
807
808
809
810 void clearBuffer() {
811 Arrays.fill(getBuffer(), (byte) 0);
812 }
813 }
814
815
816
817
818 private final class GCMSIVHasher {
819
820
821
822 private final byte[] theBuffer = new byte[BUFLEN];
823
824
825
826
827 private final byte[] theByte = new byte[1];
828
829
830
831
832 private int numActive;
833
834
835
836
837 private long numHashed;
838
839
840
841
842
843
844 long getBytesProcessed() {
845 return numHashed;
846 }
847
848
849
850
851 void reset() {
852 numActive = 0;
853 numHashed = 0;
854 }
855
856
857
858
859
860
861 void updateHash(final byte pByte) {
862 theByte[0] = pByte;
863 updateHash(theByte, 0, 1);
864 }
865
866
867
868
869
870
871
872
873 void updateHash(final byte[] pBuffer,
874 final int pOffset,
875 final int pLen) {
876
877 final int mySpace = BUFLEN - numActive;
878 int numProcessed = 0;
879 int myRemaining = pLen;
880 if (numActive > 0
881 && pLen >= mySpace) {
882
883 System.arraycopy(pBuffer, pOffset, theBuffer, numActive, mySpace);
884 fillReverse(theBuffer, 0, BUFLEN, theReverse);
885 gHASH(theReverse);
886
887
888 numProcessed += mySpace;
889 myRemaining -= mySpace;
890 numActive = 0;
891 }
892
893
894 while (myRemaining >= BUFLEN) {
895
896 fillReverse(pBuffer, pOffset + numProcessed, BUFLEN, theReverse);
897 gHASH(theReverse);
898
899
900 numProcessed += BUFLEN;
901 myRemaining -= BUFLEN;
902 }
903
904
905 if (myRemaining > 0) {
906
907 System.arraycopy(pBuffer, pOffset + numProcessed, theBuffer, numActive, myRemaining);
908 numActive += myRemaining;
909 }
910
911
912 numHashed += pLen;
913 }
914
915
916
917
918 void completeHash() {
919
920 if (numActive > 0) {
921
922 Arrays.fill(theReverse, (byte) 0);
923 fillReverse(theBuffer, 0, numActive, theReverse);
924
925
926 gHASH(theReverse);
927 }
928 }
929 }
930 }