1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.gordianknot.impl.jca;
18
19 import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
20 import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
21 import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
22 import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;
23 import io.github.tonywasher.joceanus.gordianknot.api.encrypt.GordianEncryptorSpec;
24 import io.github.tonywasher.joceanus.gordianknot.api.encrypt.GordianSM2EncryptionSpec;
25 import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
26 import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
27 import io.github.tonywasher.joceanus.gordianknot.impl.core.encrypt.GordianCoreEncryptor;
28 import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
29 import io.github.tonywasher.joceanus.gordianknot.impl.jca.JcaKeyPair.JcaPrivateKey;
30 import io.github.tonywasher.joceanus.gordianknot.impl.jca.JcaKeyPair.JcaPublicKey;
31
32 import javax.crypto.BadPaddingException;
33 import javax.crypto.Cipher;
34 import javax.crypto.IllegalBlockSizeException;
35 import javax.crypto.NoSuchPaddingException;
36 import java.security.InvalidKeyException;
37 import java.security.NoSuchAlgorithmException;
38 import java.util.Arrays;
39
40
41
42
43 public final class JcaEncryptor {
44
45
46
47 private static final String ERROR_INIT = "Failed to initialise";
48
49
50
51
52 private JcaEncryptor() {
53 }
54
55
56
57
58 public static class JcaBlockEncryptor
59 extends GordianCoreEncryptor {
60
61
62
63 private final Cipher theEncryptor;
64
65
66
67
68
69
70
71
72 JcaBlockEncryptor(final GordianBaseFactory pFactory,
73 final GordianEncryptorSpec pSpec) throws GordianException {
74
75 super(pFactory, pSpec);
76 theEncryptor = getJavaEncryptor(getAlgorithmName(pSpec), false);
77 }
78
79 @Override
80 protected JcaPublicKey getPublicKey() {
81 return (JcaPublicKey) super.getPublicKey();
82 }
83
84 @Override
85 protected JcaPrivateKey getPrivateKey() {
86 return (JcaPrivateKey) super.getPrivateKey();
87 }
88
89 @Override
90 public void initForEncrypt(final GordianKeyPair pKeyPair) throws GordianException {
91 try {
92
93 JcaKeyPair.checkKeyPair(pKeyPair);
94 super.initForEncrypt(pKeyPair);
95
96
97 theEncryptor.init(Cipher.ENCRYPT_MODE, getPublicKey().getPublicKey(), getRandom());
98 } catch (InvalidKeyException e) {
99 throw new GordianCryptoException(ERROR_INIT, e);
100 }
101 }
102
103 @Override
104 public void initForDecrypt(final GordianKeyPair pKeyPair) throws GordianException {
105 try {
106
107 JcaKeyPair.checkKeyPair(pKeyPair);
108 super.initForDecrypt(pKeyPair);
109
110
111 theEncryptor.init(Cipher.DECRYPT_MODE, getPrivateKey().getPrivateKey());
112 } catch (InvalidKeyException e) {
113 throw new GordianCryptoException(ERROR_INIT, e);
114 }
115 }
116
117 @Override
118 public byte[] encrypt(final byte[] pBytes) throws GordianException {
119
120 checkMode(GordianEncryptMode.ENCRYPT);
121
122
123 return processData(pBytes);
124 }
125
126 @Override
127 public byte[] decrypt(final byte[] pBytes) throws GordianException {
128
129 checkMode(GordianEncryptMode.DECRYPT);
130
131
132 return processData(pBytes);
133 }
134
135
136
137
138
139
140
141
142 private byte[] processData(final byte[] pData) throws GordianException {
143 try {
144
145 int myInLen = pData.length;
146 final int myInBlockLength = theEncryptor.getBlockSize();
147 final int myNumBlocks = getNumBlocks(myInLen, myInBlockLength);
148 final int myOutBlockLength = theEncryptor.getOutputSize(myInBlockLength);
149
150
151 final byte[] myOutput = new byte[myOutBlockLength * myNumBlocks];
152
153
154 int myInOff = 0;
155 int myOutOff = 0;
156 while (myInLen > 0) {
157
158 final int myLen = Math.min(myInLen, myInBlockLength);
159 final byte[] myBlock = theEncryptor.doFinal(pData, myInOff, myLen);
160
161
162 final int myOutLen = myBlock.length;
163 System.arraycopy(myBlock, 0, myOutput, myOutOff, myOutLen);
164 myOutOff += myOutLen;
165
166
167 myInOff += myInBlockLength;
168 myInLen -= myInBlockLength;
169 }
170
171
172 if (myOutOff == myOutput.length) {
173 return myOutput;
174 }
175
176
177 final byte[] myReturn = Arrays.copyOf(myOutput, myOutOff);
178 Arrays.fill(myOutput, (byte) 0);
179 return myReturn;
180
181 } catch (IllegalBlockSizeException
182 | BadPaddingException e) {
183 throw new GordianCryptoException("Failed to process data", e);
184 }
185 }
186
187
188
189
190
191
192
193
194 private static int getNumBlocks(final int pLength, final int pBlockLength) {
195 return (pLength + pBlockLength - 1) / pBlockLength;
196 }
197
198
199
200
201
202
203
204 private static String getAlgorithmName(final GordianEncryptorSpec pSpec) {
205
206 final String myBase = pSpec.getKeyPairType().name();
207
208
209 switch (pSpec.getDigestSpec().getDigestLength()) {
210 case LEN_224:
211 return myBase + "/ECB/OAEPWITHSHA224ANDMGF1PADDING";
212 case LEN_256:
213 return myBase + "/ECB/OAEPWITHSHA256ANDMGF1PADDING";
214 case LEN_384:
215 return myBase + "/ECB/OAEPWITHSHA384ANDMGF1PADDING";
216 case LEN_512:
217 default:
218 return myBase + "/ECB/OAEPWITHSHA512ANDMGF1PADDING";
219 }
220 }
221 }
222
223
224
225
226 public static class JcaHybridEncryptor
227 extends GordianCoreEncryptor {
228
229
230
231 private final Cipher theEncryptor;
232
233
234
235
236
237
238
239
240 JcaHybridEncryptor(final GordianBaseFactory pFactory,
241 final GordianEncryptorSpec pSpec) throws GordianException {
242
243 super(pFactory, pSpec);
244 theEncryptor = getJavaEncryptor(getAlgorithmName(pSpec), false);
245 }
246
247 @Override
248 protected JcaPublicKey getPublicKey() {
249 return (JcaPublicKey) super.getPublicKey();
250 }
251
252 @Override
253 protected JcaPrivateKey getPrivateKey() {
254 return (JcaPrivateKey) super.getPrivateKey();
255 }
256
257 @Override
258 public void initForEncrypt(final GordianKeyPair pKeyPair) throws GordianException {
259 try {
260
261 JcaKeyPair.checkKeyPair(pKeyPair);
262 super.initForEncrypt(pKeyPair);
263
264
265 theEncryptor.init(Cipher.ENCRYPT_MODE, getPublicKey().getPublicKey(), getRandom());
266 } catch (InvalidKeyException e) {
267 throw new GordianCryptoException(ERROR_INIT, e);
268 }
269 }
270
271 @Override
272 public void initForDecrypt(final GordianKeyPair pKeyPair) throws GordianException {
273 try {
274
275 JcaKeyPair.checkKeyPair(pKeyPair);
276 super.initForDecrypt(pKeyPair);
277
278
279 theEncryptor.init(Cipher.DECRYPT_MODE, getPrivateKey().getPrivateKey());
280 } catch (InvalidKeyException e) {
281 throw new GordianCryptoException(ERROR_INIT, e);
282 }
283 }
284
285 @Override
286 public byte[] encrypt(final byte[] pBytes) throws GordianException {
287
288 checkMode(GordianEncryptMode.ENCRYPT);
289
290
291 return processData(pBytes);
292 }
293
294 @Override
295 public byte[] decrypt(final byte[] pBytes) throws GordianException {
296
297 checkMode(GordianEncryptMode.DECRYPT);
298
299
300 return processData(pBytes);
301 }
302
303
304
305
306
307
308
309
310 private byte[] processData(final byte[] pData) throws GordianException {
311 try {
312 return theEncryptor.doFinal(pData, 0, pData.length);
313 } catch (IllegalBlockSizeException
314 | BadPaddingException e) {
315 throw new GordianCryptoException("Failed to process data", e);
316 }
317 }
318
319
320
321
322
323
324
325 private static String getAlgorithmName(final GordianEncryptorSpec pSpec) {
326
327 final GordianSM2EncryptionSpec mySpec = pSpec.getSM2EncryptionSpec();
328 final GordianDigestSpec myDigestSpec = mySpec.getDigestSpec();
329 final GordianDigestType myDigestType = myDigestSpec.getDigestType();
330 switch (myDigestType) {
331 case SHA2:
332 return "SM2withSHA" + myDigestSpec.getDigestLength();
333 case BLAKE2:
334 return "SM2withBlake2" + (GordianLength.LEN_512.equals(myDigestSpec.getDigestLength()) ? "b" : "s");
335 case WHIRLPOOL:
336 case SM3:
337 default:
338 return "SM2with" + myDigestType;
339 }
340 }
341 }
342
343
344
345
346
347
348
349
350
351 private static Cipher getJavaEncryptor(final String pAlgorithm,
352 final boolean postQuantum) throws GordianException {
353
354 try {
355
356 return Cipher.getInstance(pAlgorithm, postQuantum
357 ? JcaProvider.BCPQPROV
358 : JcaProvider.BCPROV);
359
360
361 } catch (NoSuchAlgorithmException
362 | NoSuchPaddingException e) {
363
364 throw new GordianCryptoException("Failed to create Cipher", e);
365 }
366 }
367 }