1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package io.github.tonywasher.joceanus.gordianknot.impl.ext.digests;
19
20 import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianSkeinParameters;
21 import org.bouncycastle.crypto.OutputLengthException;
22 import org.bouncycastle.crypto.engines.ThreefishEngine;
23 import org.bouncycastle.crypto.macs.SkeinMac;
24 import org.bouncycastle.crypto.params.SkeinParameters;
25 import org.bouncycastle.util.Arrays;
26 import org.bouncycastle.util.Integers;
27 import org.bouncycastle.util.Memoable;
28 import org.bouncycastle.util.Pack;
29
30 import java.util.Enumeration;
31 import java.util.HashMap;
32 import java.util.Hashtable;
33 import java.util.Map;
34 import java.util.Vector;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 @SuppressWarnings("checkstyle:MagicNumber")
69 public class GordianSkeinBase
70 implements Memoable {
71
72
73
74 public static final int SKEIN_256 = ThreefishEngine.BLOCKSIZE_256;
75
76
77
78 public static final int SKEIN_512 = ThreefishEngine.BLOCKSIZE_512;
79
80
81
82
83 public static final int SKEIN_1024 = ThreefishEngine.BLOCKSIZE_1024;
84
85
86
87
88 static class Configuration {
89
90
91
92 private byte[] bytes = new byte[32];
93
94
95
96
97
98
99 Configuration(final long outputSizeBits) {
100
101 bytes[0] = (byte) 'S';
102 bytes[1] = (byte) 'H';
103 bytes[2] = (byte) 'A';
104 bytes[3] = (byte) '3';
105
106
107 bytes[4] = 1;
108 bytes[5] = 0;
109
110
111 Pack.longToLittleEndian(outputSizeBits, bytes, 8);
112 }
113
114
115
116
117
118
119 public byte[] getBytes() {
120 return bytes;
121 }
122 }
123
124
125
126
127 public static class Parameter {
128
129
130
131 private final int type;
132
133
134
135
136 private final byte[] value;
137
138
139
140
141
142
143
144 public Parameter(final int pType, final byte[] pValue) {
145 this.type = pType;
146 this.value = pValue;
147 }
148
149
150
151
152
153
154 public int getType() {
155 return type;
156 }
157
158
159
160
161
162
163 public byte[] getValue() {
164 return value;
165 }
166 }
167
168
169
170
171 private static final int PARAM_TYPE_KEY = 0;
172
173
174
175
176 private static final int PARAM_TYPE_CONFIG = 4;
177
178
179
180
181 private static final int PARAM_TYPE_MESSAGE = 48;
182
183
184
185
186 private static final int PARAM_TYPE_OUTPUT = 63;
187
188
189
190
191
192 private static final Map<Integer, long[]> INITIAL_STATES = new HashMap<>();
193
194 static {
195
196 final long[] skein256o128 = {
197 0xe1111906964d7260L,
198 0x883daaa77c8d811cL,
199 0x10080df491960f7aL,
200 0xccf7dde5b45bc1c2L
201 };
202 initialState(SKEIN_256, 128, skein256o128);
203
204 final long[] skein256o160 = {
205 0x1420231472825e98L,
206 0x2ac4e9a25a77e590L,
207 0xd47a58568838d63eL,
208 0x2dd2e4968586ab7dL
209 };
210 initialState(SKEIN_256, 160, skein256o160);
211
212 final long[] skein256o224 = {
213 0xc6098a8c9ae5ea0bL,
214 0x876d568608c5191cL,
215 0x99cb88d7d7f53884L,
216 0x384bddb1aeddb5deL
217 };
218 initialState(SKEIN_256, 224, skein256o224);
219
220 final long[] skein256o256 = {
221 0xfc9da860d048b449L,
222 0x2fca66479fa7d833L,
223 0xb33bc3896656840fL,
224 0x6a54e920fde8da69L
225 };
226 initialState(SKEIN_256, 256, skein256o256);
227
228 final long[] skein512o128 = {
229 0xa8bc7bf36fbf9f52L,
230 0x1e9872cebd1af0aaL,
231 0x309b1790b32190d3L,
232 0xbcfbb8543f94805cL,
233 0x0da61bcd6e31b11bL,
234 0x1a18ebead46a32e3L,
235 0xa2cc5b18ce84aa82L,
236 0x6982ab289d46982dL
237 };
238 initialState(SKEIN_512, 128, skein512o128);
239
240 final long[] skein512o160 = {
241 0x28b81a2ae013bd91L,
242 0xc2f11668b5bdf78fL,
243 0x1760d8f3f6a56f12L,
244 0x4fb747588239904fL,
245 0x21ede07f7eaf5056L,
246 0xd908922e63ed70b8L,
247 0xb8ec76ffeccb52faL,
248 0x01a47bb8a3f27a6eL
249 };
250 initialState(SKEIN_512, 160, skein512o160);
251
252 final long[] skein512o224 = {
253 0xccd0616248677224L,
254 0xcba65cf3a92339efL,
255 0x8ccd69d652ff4b64L,
256 0x398aed7b3ab890b4L,
257 0x0f59d1b1457d2bd0L,
258 0x6776fe6575d4eb3dL,
259 0x99fbc70e997413e9L,
260 0x9e2cfccfe1c41ef7L
261 };
262 initialState(SKEIN_512, 224, skein512o224);
263
264 final long[] skein512o384 = {
265 0xa3f6c6bf3a75ef5fL,
266 0xb0fef9ccfd84faa4L,
267 0x9d77dd663d770cfeL,
268 0xd798cbf3b468fddaL,
269 0x1bc4a6668a0e4465L,
270 0x7ed7d434e5807407L,
271 0x548fc1acd4ec44d6L,
272 0x266e17546aa18ff8L
273 };
274 initialState(SKEIN_512, 384, skein512o384);
275
276 final long[] skein512o512 = {
277 0x4903adff749c51ceL,
278 0x0d95de399746df03L,
279 0x8fd1934127c79bceL,
280 0x9a255629ff352cb1L,
281 0x5db62599df6ca7b0L,
282 0xeabe394ca9d5c3f4L,
283 0x991112c71a75b523L,
284 0xae18a40b660fcc33L
285 };
286 initialState(SKEIN_512, 512, skein512o512);
287 }
288
289 private static void initialState(final int blockSize, final int outputSize, final long[] state) {
290 INITIAL_STATES.put(variantIdentifier(blockSize / 8, outputSize / 8), state);
291 }
292
293 private static Integer variantIdentifier(final int blockSizeBytes, final int outputSizeBytes) {
294 return Integers.valueOf((outputSizeBytes << 16) | blockSizeBytes);
295 }
296
297
298
299
300 private static class UbiTweak {
301
302
303
304 private static final long LOW_RANGE = Long.MAX_VALUE - Integer.MAX_VALUE;
305
306
307
308
309 private static final long T1_FINAL = 1L << 63;
310
311
312
313
314 private static final long T1_FIRST = 1L << 62;
315
316
317
318
319 private long[] tweak = new long[2];
320
321
322
323
324 private boolean extendedPosition;
325
326
327
328
329 UbiTweak() {
330 reset();
331 }
332
333
334
335
336
337
338 public void reset(final UbiTweak pTweak) {
339 this.tweak = Arrays.clone(pTweak.tweak, this.tweak);
340 this.extendedPosition = pTweak.extendedPosition;
341 }
342
343
344
345
346 public void reset() {
347 tweak[0] = 0;
348 tweak[1] = 0;
349 extendedPosition = false;
350 setFirst(true);
351 }
352
353 public void setType(final int type) {
354
355 tweak[1] = (tweak[1] & 0xFFFFFFC000000000L) | ((type & 0x3FL) << 56);
356 }
357
358 public int getType() {
359 return (int) ((tweak[1] >>> 56) & 0x3FL);
360 }
361
362 public void setTreeLocation(final int level, final int hiOffset, final long loOffset) {
363
364 tweak[1] = (tweak[1] & 0xFF00FFC000000000L) | ((level & 0xFFL) << 48);
365
366
367 tweak[0] = loOffset;
368 tweak[1] |= hiOffset;
369
370
371 extendedPosition = loOffset < 0 || loOffset > LOW_RANGE || hiOffset != 0;
372 }
373
374 public int getLevel() {
375 return (int) ((tweak[1] >>> 48) & 0xFFL);
376 }
377
378 public void setFirst(final boolean first) {
379 if (first) {
380 tweak[1] |= T1_FIRST;
381 } else {
382 tweak[1] &= ~T1_FIRST;
383 }
384 }
385
386 public boolean isFirst() {
387 return ((tweak[1] & T1_FIRST) != 0);
388 }
389
390 public void setFinal(final boolean last) {
391 if (last) {
392 tweak[1] |= T1_FINAL;
393 } else {
394 tweak[1] &= ~T1_FINAL;
395 }
396 }
397
398 public boolean isFinal() {
399 return ((tweak[1] & T1_FINAL) != 0);
400 }
401
402
403
404
405
406
407 public void advancePosition(final int advance) {
408
409 if (extendedPosition) {
410 final long[] parts = new long[3];
411 parts[0] = tweak[0] & 0xFFFFFFFFL;
412 parts[1] = (tweak[0] >>> 32) & 0xFFFFFFFFL;
413 parts[2] = tweak[1] & 0xFFFFFFFFL;
414
415 long carry = advance;
416 for (int i = 0; i < parts.length; i++) {
417 carry += parts[i];
418 parts[i] = carry;
419 carry >>>= 32;
420 }
421 tweak[0] = ((parts[1] & 0xFFFFFFFFL) << 32) | (parts[0] & 0xFFFFFFFFL);
422 tweak[1] = (tweak[1] & 0xFFFFFFFF00000000L) | (parts[2] & 0xFFFFFFFFL);
423 } else {
424 long position = tweak[0];
425 position += advance;
426 tweak[0] = position;
427 if (position > LOW_RANGE) {
428 extendedPosition = true;
429 }
430 }
431 }
432
433 public long[] getWords() {
434 return tweak;
435 }
436
437 public String toString() {
438 return getType() + " first: " + isFirst() + ", final: " + isFinal();
439 }
440
441 }
442
443
444
445
446 private class UBI {
447
448
449
450 private final UbiTweak tweak = new UbiTweak();
451
452
453
454
455 private byte[] currentBlock;
456
457
458
459
460 private int currentOffset;
461
462
463
464
465 private long[] message;
466
467 UBI(final int blockSize) {
468 currentBlock = new byte[blockSize];
469 message = new long[currentBlock.length / 8];
470 }
471
472 public void reset(final UBI pUbi) {
473 currentBlock = Arrays.clone(pUbi.currentBlock, currentBlock);
474 currentOffset = pUbi.currentOffset;
475 message = Arrays.clone(pUbi.message, this.message);
476 tweak.reset(pUbi.tweak);
477 }
478
479 public void reset(final int type) {
480 tweak.reset();
481 tweak.setType(type);
482 currentOffset = 0;
483 }
484
485 public void setTreeLocation(final int level, final long offSet, final int pShift) {
486 int shift = getBlockSize() << 3;
487 shift += pShift;
488 final int hiOffset = (int) offSet >>> 64 - shift;
489 final long loOffset = offSet << shift;
490 tweak.setTreeLocation(level, hiOffset, loOffset);
491 }
492
493 public void update(final byte[] value, final int offset, final int len, final long[] output) {
494
495
496
497
498 int copied = 0;
499 while (len > copied) {
500 if (currentOffset == currentBlock.length) {
501 processBlock(output);
502 tweak.setFirst(false);
503 currentOffset = 0;
504 }
505
506 final int toCopy = Math.min((len - copied), currentBlock.length - currentOffset);
507 System.arraycopy(value, offset + copied, currentBlock, currentOffset, toCopy);
508 copied += toCopy;
509 currentOffset += toCopy;
510 tweak.advancePosition(toCopy);
511 }
512 }
513
514 private void processBlock(final long[] output) {
515 threefish.init(true, chain, tweak.getWords());
516 for (int i = 0; i < message.length; i++) {
517 message[i] = Pack.littleEndianToLong(currentBlock, i * 8);
518 }
519
520 threefish.processBlock(message, output);
521
522 for (int i = 0; i < output.length; i++) {
523 output[i] ^= message[i];
524 }
525 }
526
527 public void doFinal(final long[] output) {
528
529 for (int i = currentOffset; i < currentBlock.length; i++) {
530 currentBlock[i] = 0;
531 }
532
533 tweak.setFinal(true);
534 processBlock(output);
535 }
536 }
537
538
539
540
541 private final ThreefishEngine threefish;
542
543
544
545
546 private final int outputSizeBytes;
547
548
549
550
551 private long[] chain;
552
553
554
555
556 private long[] initialState;
557
558
559
560
561 private byte[] key;
562
563
564
565
566 private Parameter[] preMessageParameters;
567
568
569
570
571 private Parameter[] postMessageParameters;
572
573
574
575
576 private final UBI ubi;
577
578
579
580
581 private final byte[] singleByte = new byte[1];
582
583
584
585
586 private Configuration theConfig;
587
588
589
590
591
592
593
594
595
596 public GordianSkeinBase(final int blockSizeBits, final int outputSizeBits) {
597 if (outputSizeBits % 8 != 0) {
598 throw new IllegalArgumentException("Output size must be a multiple of 8 bits. :" + outputSizeBits);
599 }
600 this.outputSizeBytes = outputSizeBits / Byte.SIZE;
601
602 this.threefish = new ThreefishEngine(blockSizeBits);
603 this.ubi = new UBI(threefish.getBlockSize());
604 }
605
606
607
608
609
610
611 public GordianSkeinBase(final GordianSkeinBase engine) {
612 this(engine.getBlockSize() * 8, engine.getOutputSize() * 8);
613 copyIn(engine);
614 }
615
616 private void copyIn(final GordianSkeinBase engine) {
617 this.ubi.reset(engine.ubi);
618 this.chain = Arrays.clone(engine.chain, this.chain);
619 this.initialState = Arrays.clone(engine.initialState, this.initialState);
620 this.key = Arrays.clone(engine.key, this.key);
621 this.preMessageParameters = clone(engine.preMessageParameters, this.preMessageParameters);
622 this.postMessageParameters = clone(engine.postMessageParameters, this.postMessageParameters);
623 }
624
625 private static Parameter[] clone(final Parameter[] data, final Parameter[] existing) {
626 if (data == null) {
627 return null;
628 }
629 Parameter[] myExisting = existing;
630 if ((myExisting == null) || (myExisting.length != data.length)) {
631 myExisting = new Parameter[data.length];
632 }
633 System.arraycopy(data, 0, myExisting, 0, myExisting.length);
634 return existing;
635 }
636
637 @Override
638 public Memoable copy() {
639 return new GordianSkeinBase(this);
640 }
641
642 @Override
643 public void reset(final Memoable other) {
644 final GordianSkeinBase s = (GordianSkeinBase) other;
645 if ((getBlockSize() != s.getBlockSize()) || (outputSizeBytes != s.outputSizeBytes)) {
646 throw new IllegalArgumentException("Incompatible parameters in provided SkeinEngine.");
647 }
648 copyIn(s);
649 }
650
651
652
653
654
655
656 public int getOutputSize() {
657 return outputSizeBytes;
658 }
659
660
661
662
663
664
665 public int getBlockSize() {
666 return threefish.getBlockSize();
667 }
668
669
670
671
672
673
674 void setConfiguration(final Configuration pConfig) {
675 theConfig = pConfig;
676 }
677
678
679
680
681
682
683
684 @SuppressWarnings("unchecked")
685 public void init(final GordianSkeinParameters params) {
686 this.chain = null;
687 this.key = null;
688 this.preMessageParameters = null;
689 this.postMessageParameters = null;
690
691 if (params != null) {
692 final byte[] theKey = params.getKey();
693 if (theKey != null && theKey.length < 16) {
694 throw new IllegalArgumentException("Skein key must be at least 128 bits.");
695 }
696 initParams(params.getParameters());
697 }
698 createInitialState();
699
700
701 ubiInit(PARAM_TYPE_MESSAGE);
702 }
703
704 private void initParams(final Hashtable<Integer, byte[]> parameters) {
705 final Enumeration<Integer> keys = parameters.keys();
706 final Vector<Parameter> pre = new Vector<>();
707 final Vector<Parameter> post = new Vector<>();
708
709 while (keys.hasMoreElements()) {
710 final Integer type = keys.nextElement();
711 final byte[] value = parameters.get(type);
712
713 if (type == PARAM_TYPE_KEY) {
714 this.key = value;
715 } else if (type < PARAM_TYPE_MESSAGE) {
716 pre.addElement(new Parameter(type, value));
717 } else {
718 post.addElement(new Parameter(type, value));
719 }
720 }
721 preMessageParameters = new Parameter[pre.size()];
722 pre.copyInto(preMessageParameters);
723 sort(preMessageParameters);
724
725 postMessageParameters = new Parameter[post.size()];
726 post.copyInto(postMessageParameters);
727 sort(postMessageParameters);
728 }
729
730 private static void sort(final Parameter[] params) {
731 if (params == null) {
732 return;
733 }
734
735
736 for (int i = 1; i < params.length; i++) {
737 final Parameter param = params[i];
738 int hole = i;
739 while (hole > 0 && param.getType() < params[hole - 1].getType()) {
740 params[hole] = params[hole - 1];
741 hole = hole - 1;
742 }
743 params[hole] = param;
744 }
745 }
746
747
748
749
750 private void createInitialState() {
751 final boolean xtendedConfig = theConfig != null;
752 final long[] precalc = xtendedConfig ? null : INITIAL_STATES.get(variantIdentifier(getBlockSize(), getOutputSize()));
753 if ((key == null) && (precalc != null)) {
754
755 chain = Arrays.clone(precalc);
756 } else {
757
758 chain = new long[getBlockSize() / 8];
759
760
761 if (key != null) {
762 ubiComplete(SkeinParameters.PARAM_TYPE_KEY, key);
763 }
764
765
766 final Configuration myConfig = xtendedConfig ? theConfig : new Configuration(outputSizeBytes * 8L);
767 ubiComplete(PARAM_TYPE_CONFIG, myConfig.getBytes());
768 }
769
770
771 if (preMessageParameters != null) {
772 for (int i = 0; i < preMessageParameters.length; i++) {
773 final Parameter param = preMessageParameters[i];
774 ubiComplete(param.getType(), param.getValue());
775 }
776 }
777 initialState = Arrays.clone(chain);
778 }
779
780
781
782
783
784 public void reset() {
785 System.arraycopy(initialState, 0, chain, 0, chain.length);
786
787 ubiInit(PARAM_TYPE_MESSAGE);
788 }
789
790 void initTreeNode(final int level, final long offset, final int shift) {
791 reset();
792 ubi.setTreeLocation(level, offset, shift);
793 }
794
795 private void ubiComplete(final int type, final byte[] value) {
796 ubiInit(type);
797 this.ubi.update(value, 0, value.length, chain);
798 ubiFinal();
799 }
800
801 private void ubiInit(final int type) {
802 this.ubi.reset(type);
803 }
804
805 private void ubiFinal() {
806 ubi.doFinal(chain);
807 }
808
809 private void checkInitialised() {
810 if (this.ubi == null) {
811 throw new IllegalArgumentException("Skein engine is not initialised.");
812 }
813 }
814
815
816
817
818
819
820 public void update(final byte in) {
821 singleByte[0] = in;
822 update(singleByte, 0, 1);
823 }
824
825
826
827
828
829
830
831
832 public void update(final byte[] in, final int inOff, final int len) {
833 checkInitialised();
834 ubi.update(in, inOff, len, chain);
835 }
836
837 void calculateNode(final byte[] out, final int outOff) {
838 checkInitialised();
839 if (out.length < (outOff + outputSizeBytes)) {
840 throw new OutputLengthException("Output buffer is too short to hold output");
841 }
842
843
844 ubiFinal();
845
846
847 for (int i = 0; i < chain.length; i++) {
848 Pack.longToLittleEndian(chain[i], out, outOff + (i * 8));
849 }
850 }
851
852
853
854
855
856
857
858
859 public int doFinal(final byte[] out, final int outOff) {
860 checkInitialised();
861 if (out.length < (outOff + outputSizeBytes)) {
862 throw new OutputLengthException("Output buffer is too short to hold output");
863 }
864
865
866 initiateOutput();
867
868
869 final int blockSize = getBlockSize();
870 final int blocksRequired = ((outputSizeBytes + blockSize - 1) / blockSize);
871 for (int i = 0; i < blocksRequired; i++) {
872 final int toWrite = Math.min(blockSize, outputSizeBytes - (i * blockSize));
873 output(i, out, outOff + (i * blockSize), toWrite);
874 }
875
876 reset();
877
878 return outputSizeBytes;
879 }
880
881 void postProcessMessage() {
882
883 if (postMessageParameters != null) {
884 for (int i = 0; i < postMessageParameters.length; i++) {
885 final Parameter param = postMessageParameters[i];
886 ubiComplete(param.getType(), param.getValue());
887 }
888 }
889 }
890
891 void initiateOutput() {
892
893 ubiFinal();
894
895
896 postProcessMessage();
897 }
898
899 void restoreForOutput(final byte[] pState) {
900
901 for (int i = 0; i < chain.length; i++) {
902 chain[i] = Pack.littleEndianToLong(pState, i * 8);
903 }
904
905
906 initiateOutput();
907 }
908
909 void output(final long outputSequence, final byte[] out, final int outOff, final int outputBytes) {
910 final byte[] currentBytes = new byte[8];
911 Pack.longToLittleEndian(outputSequence, currentBytes, 0);
912
913
914
915 final long[] outputWords = new long[chain.length];
916 ubiInit(PARAM_TYPE_OUTPUT);
917 this.ubi.update(currentBytes, 0, currentBytes.length, outputWords);
918 ubi.doFinal(outputWords);
919
920 final int wordsRequired = ((outputBytes + 8 - 1) / 8);
921 for (int i = 0; i < wordsRequired; i++) {
922 final int toWrite = Math.min(8, outputBytes - (i * 8));
923 if (toWrite == 8) {
924 Pack.longToLittleEndian(outputWords[i], out, outOff + (i * 8));
925 } else {
926 Pack.longToLittleEndian(outputWords[i], currentBytes, 0);
927 System.arraycopy(currentBytes, 0, out, outOff + (i * 8), toWrite);
928 }
929 }
930 }
931 }