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.engines;
18
19 import org.bouncycastle.crypto.InvalidCipherTextException;
20 import org.bouncycastle.crypto.ec.ECElGamalDecryptor;
21 import org.bouncycastle.crypto.ec.ECElGamalEncryptor;
22 import org.bouncycastle.crypto.ec.ECPair;
23 import org.bouncycastle.crypto.params.ECDomainParameters;
24 import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
25 import org.bouncycastle.crypto.params.ECPublicKeyParameters;
26 import org.bouncycastle.crypto.params.ParametersWithRandom;
27 import org.bouncycastle.math.ec.ECCurve;
28 import org.bouncycastle.math.ec.ECPoint;
29
30 import java.math.BigInteger;
31 import java.security.SecureRandom;
32 import java.util.Arrays;
33
34
35
36
37
38 public class GordianEllipticEncryptor {
39
40
41
42 private static final int MAXCOFACTOR = 20;
43
44
45
46
47 private static final int MAXITERATION = 1 << Byte.SIZE;
48
49
50
51
52 private final ECElGamalEncryptor theEncryptor;
53
54
55
56
57 private final ECElGamalDecryptor theDecryptor;
58
59
60
61
62 private ECCurve theCurve;
63
64
65
66
67 private boolean isAvailable;
68
69
70
71
72 private boolean encrypting;
73
74
75
76
77 public GordianEllipticEncryptor() {
78 theEncryptor = new ECElGamalEncryptor();
79 theDecryptor = new ECElGamalDecryptor();
80 }
81
82
83
84
85
86
87
88 public void initForEncrypt(final ECPublicKeyParameters pPublicKey,
89 final SecureRandom pRandom) {
90
91 final ECDomainParameters myDomain = pPublicKey.getParameters();
92 if (isUnsupported(myDomain)) {
93 throw new IllegalArgumentException("Unsupported curve");
94 }
95
96
97 theCurve = myDomain.getCurve();
98 isAvailable = true;
99 encrypting = true;
100
101
102 final ParametersWithRandom myParms = new ParametersWithRandom(pPublicKey, pRandom);
103 theEncryptor.init(myParms);
104 }
105
106
107
108
109
110
111 public void initForDecrypt(final ECPrivateKeyParameters pPrivateKey) {
112
113 final ECDomainParameters myDomain = pPrivateKey.getParameters();
114 if (isUnsupported(myDomain)) {
115 throw new IllegalArgumentException("Unsupported curve");
116 }
117
118
119 theCurve = myDomain.getCurve();
120 isAvailable = true;
121 encrypting = false;
122
123
124 theDecryptor.init(pPrivateKey);
125 }
126
127
128
129
130
131
132
133 private boolean isUnsupported(final ECDomainParameters pDomain) {
134 return pDomain.getH().compareTo(BigInteger.valueOf(MAXCOFACTOR)) > 0;
135 }
136
137
138
139
140
141
142 private int getFieldLength() {
143 return (theCurve.getFieldSize() + Byte.SIZE - 1) / Byte.SIZE;
144 }
145
146
147
148
149
150
151 private int getBlockLength() {
152 return getFieldLength() - 1;
153 }
154
155
156
157
158
159
160 private int getPlainBlockLength() {
161 return getBlockLength() - 2;
162 }
163
164
165
166
167
168
169 private int getEncodedBlockLength() {
170 return (getFieldLength() + 1) << 1;
171 }
172
173
174
175
176
177
178
179 private int getDecryptedLength(final int pLength) {
180 return getPlainBlockLength() * getNumBlocks(pLength, getEncodedBlockLength());
181 }
182
183
184
185
186
187
188
189 private int getEncryptedLength(final int pLength) {
190 return getEncodedBlockLength() * getNumBlocks(pLength, getPlainBlockLength());
191 }
192
193
194
195
196
197
198
199
200 private static int getNumBlocks(final int pLength, final int pBlockLength) {
201 return (pLength + pBlockLength - 1) / pBlockLength;
202 }
203
204
205
206
207
208
209
210
211 public byte[] encrypt(final byte[] pData) throws InvalidCipherTextException {
212
213 if (!isAvailable || !encrypting) {
214 throw new IllegalStateException("Not initialised for encrypting");
215 }
216
217
218 int myInLen = pData.length;
219 final byte[] myOutput = new byte[getEncryptedLength(pData.length)];
220
221
222 final int myInBlockLength = getPlainBlockLength();
223
224
225 int myInOff = 0;
226 int myOutOff = 0;
227 while (myInLen > 0) {
228
229 final int myLen = Math.min(myInLen, myInBlockLength);
230 final ECPair myPair = encryptToPair(pData, myInOff, myLen);
231
232
233 myOutOff += convertFromECPair(myPair, myOutput, myOutOff);
234
235
236 myInOff += myInBlockLength;
237 myInLen -= myInBlockLength;
238 }
239
240
241 return myOutOff == myOutput.length
242 ? myOutput
243 : Arrays.copyOf(myOutput, myOutOff);
244 }
245
246
247
248
249
250
251
252
253
254
255 private ECPair encryptToPair(final byte[] pData,
256 final int pInOff,
257 final int pInLen) throws InvalidCipherTextException {
258
259 final ECPoint myPoint = convertToECPoint(pData, pInOff, pInLen);
260
261
262 return theEncryptor.encrypt(myPoint);
263 }
264
265
266
267
268
269
270
271
272
273
274 private ECPoint convertToECPoint(final byte[] pInBuffer,
275 final int pInOff,
276 final int pInLen) throws InvalidCipherTextException {
277
278 final int myLen = getBlockLength();
279 if (pInLen > myLen - 2
280 || pInLen <= 0) {
281 throw new IllegalArgumentException("Invalid input length");
282 }
283 if (pInBuffer.length - pInOff < pInLen) {
284 throw new IllegalArgumentException("Invalid input buffer");
285 }
286
287
288 final byte[] myX = new byte[myLen + 1];
289
290
291 final int myStart = myLen - pInLen;
292 System.arraycopy(pInBuffer, pInOff, myX, myStart, pInLen);
293 myX[myStart - 1] = 1;
294
295
296 for (int i = 0; i < MAXITERATION; i++) {
297
298 final ECPoint myPoint = checkOnCurve(myX);
299
300
301 if (myPoint != null) {
302 return myPoint;
303 }
304
305
306 myX[myLen]++;
307 }
308
309
310 throw new InvalidCipherTextException("Unable to find point on curve");
311 }
312
313
314
315
316
317
318
319 private ECPoint checkOnCurve(final byte[] pX) {
320
321 try {
322
323 final int myFieldLen = getFieldLength();
324 final byte[] myCompressed = new byte[myFieldLen + 1];
325 System.arraycopy(pX, 0, myCompressed, 1, myFieldLen);
326 myCompressed[0] = 2;
327 final ECPoint myPoint = theCurve.decodePoint(myCompressed);
328
329
330 return myPoint.isValid()
331 ? myPoint
332 : null;
333
334
335 } catch (IllegalArgumentException e) {
336 return null;
337 }
338 }
339
340
341
342
343
344
345
346
347
348
349 private int convertFromECPair(final ECPair pPair,
350 final byte[] pOutBuffer,
351 final int pOutOff) throws InvalidCipherTextException {
352
353 final int myLen = getFieldLength() + 1;
354 if (pOutBuffer.length - pOutOff < myLen << 1) {
355 throw new IllegalArgumentException("Output buffer too small");
356 }
357
358
359 final byte[] myX = pPair.getX().getEncoded(true);
360 final byte[] myY = pPair.getY().getEncoded(true);
361 if (myX.length != myLen || myY.length != myLen) {
362 throw new InvalidCipherTextException("Bad encoding");
363 }
364
365
366 System.arraycopy(myX, 0, pOutBuffer, pOutOff, myLen);
367 System.arraycopy(myY, 0, pOutBuffer, pOutOff + myLen, myLen);
368 return myLen << 1;
369 }
370
371
372
373
374
375
376
377
378 public byte[] decrypt(final byte[] pData) throws InvalidCipherTextException {
379
380 if (!isAvailable || encrypting) {
381 throw new IllegalStateException("Not initialised for decrypting");
382 }
383
384
385 int myInLen = pData.length;
386 final byte[] myOutput = new byte[getDecryptedLength(pData.length)];
387
388
389 final int myInBlockLength = getEncodedBlockLength();
390
391
392 int myInOff = 0;
393 int myOutOff = 0;
394 while (myInLen > 0) {
395
396 final ECPair myPair = convertToECPair(pData, myInOff);
397
398
399 myOutOff += decryptFromECPair(myPair, myOutput, myOutOff);
400
401
402 myInOff += myInBlockLength;
403 myInLen -= myInBlockLength;
404 }
405
406
407 if (myOutOff == myOutput.length) {
408 return myOutput;
409 }
410
411
412 final byte[] myReturn = Arrays.copyOf(myOutput, myOutOff);
413 Arrays.fill(myOutput, (byte) 0);
414 return myReturn;
415 }
416
417
418
419
420
421
422
423
424
425
426 private int decryptFromECPair(final ECPair pPair,
427 final byte[] pOutBuffer,
428 final int pOutOff) throws InvalidCipherTextException {
429
430 final ECPoint myPoint = theDecryptor.decrypt(pPair);
431 return convertFromECPoint(myPoint, pOutBuffer, pOutOff);
432 }
433
434
435
436
437
438
439
440
441
442
443 private int convertFromECPoint(final ECPoint pPoint,
444 final byte[] pOutBuffer,
445 final int pOutOff) throws InvalidCipherTextException {
446
447 final BigInteger myX = pPoint.getAffineXCoord().toBigInteger();
448 final byte[] myBuf = myX.toByteArray();
449
450
451 int myStart = -1;
452 final int myEnd = myBuf.length - 1;
453
454
455 for (int myIndex = 0; myIndex < myEnd; myIndex++) {
456
457
458 if (myBuf[myIndex] != 0
459 & myStart == -1) {
460 myStart = myIndex;
461 }
462 }
463
464
465 if (myStart == -1 || myBuf[myStart] != 1) {
466 throw new InvalidCipherTextException("Invalid data");
467 }
468
469
470 myStart++;
471
472
473 final int myOutLen = myEnd - myStart;
474 if (pOutBuffer.length - pOutOff < myOutLen) {
475 throw new IllegalArgumentException("Output buffer too small");
476 }
477
478
479 System.arraycopy(myBuf, myStart, pOutBuffer, pOutOff, myOutLen);
480 return myOutLen;
481 }
482
483
484
485
486
487
488
489
490 private ECPair convertToECPair(final byte[] pInBuffer,
491 final int pInOff) {
492
493 final int myLen = getFieldLength() + 1;
494 if (pInBuffer.length - pInOff < myLen << 1) {
495 throw new IllegalArgumentException("Invalid input buffer");
496 }
497
498
499 final byte[] myXbytes = new byte[myLen];
500 System.arraycopy(pInBuffer, pInOff, myXbytes, 0, myLen);
501 final ECPoint myX = theCurve.decodePoint(myXbytes);
502
503
504 final byte[] myYbytes = new byte[myLen];
505 System.arraycopy(pInBuffer, pInOff + myLen, myYbytes, 0, myLen);
506 final ECPoint myY = theCurve.decodePoint(myYbytes);
507
508
509 return new ECPair(myX, myY);
510 }
511 }