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.spec.GordianDigestSpec;
22 import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestType;
23 import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
24 import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
25 import io.github.tonywasher.joceanus.gordianknot.impl.core.encrypt.GordianCoreEncryptor;
26 import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
27 import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.encrypt.GordianCoreEncryptorSpec;
28 import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.encrypt.GordianCoreSM2EncryptionSpec;
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 GordianCoreEncryptorSpec 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 GordianCoreEncryptorSpec pSpec) {
205
206 final String myBase = pSpec.getKeyPairType().name();
207
208
209 return switch (pSpec.getDigestSpec().getDigestLength()) {
210 case LEN_224 -> myBase + "/ECB/OAEPWITHSHA224ANDMGF1PADDING";
211 case LEN_256 -> myBase + "/ECB/OAEPWITHSHA256ANDMGF1PADDING";
212 case LEN_384 -> myBase + "/ECB/OAEPWITHSHA384ANDMGF1PADDING";
213 default -> myBase + "/ECB/OAEPWITHSHA512ANDMGF1PADDING";
214 };
215 }
216 }
217
218
219
220
221 public static class JcaHybridEncryptor
222 extends GordianCoreEncryptor {
223
224
225
226 private final Cipher theEncryptor;
227
228
229
230
231
232
233
234
235 JcaHybridEncryptor(final GordianBaseFactory pFactory,
236 final GordianCoreEncryptorSpec pSpec) throws GordianException {
237
238 super(pFactory, pSpec);
239 theEncryptor = getJavaEncryptor(getAlgorithmName(pSpec), false);
240 }
241
242 @Override
243 protected JcaPublicKey getPublicKey() {
244 return (JcaPublicKey) super.getPublicKey();
245 }
246
247 @Override
248 protected JcaPrivateKey getPrivateKey() {
249 return (JcaPrivateKey) super.getPrivateKey();
250 }
251
252 @Override
253 public void initForEncrypt(final GordianKeyPair pKeyPair) throws GordianException {
254 try {
255
256 JcaKeyPair.checkKeyPair(pKeyPair);
257 super.initForEncrypt(pKeyPair);
258
259
260 theEncryptor.init(Cipher.ENCRYPT_MODE, getPublicKey().getPublicKey(), getRandom());
261 } catch (InvalidKeyException e) {
262 throw new GordianCryptoException(ERROR_INIT, e);
263 }
264 }
265
266 @Override
267 public void initForDecrypt(final GordianKeyPair pKeyPair) throws GordianException {
268 try {
269
270 JcaKeyPair.checkKeyPair(pKeyPair);
271 super.initForDecrypt(pKeyPair);
272
273
274 theEncryptor.init(Cipher.DECRYPT_MODE, getPrivateKey().getPrivateKey());
275 } catch (InvalidKeyException e) {
276 throw new GordianCryptoException(ERROR_INIT, e);
277 }
278 }
279
280 @Override
281 public byte[] encrypt(final byte[] pBytes) throws GordianException {
282
283 checkMode(GordianEncryptMode.ENCRYPT);
284
285
286 return processData(pBytes);
287 }
288
289 @Override
290 public byte[] decrypt(final byte[] pBytes) throws GordianException {
291
292 checkMode(GordianEncryptMode.DECRYPT);
293
294
295 return processData(pBytes);
296 }
297
298
299
300
301
302
303
304
305 private byte[] processData(final byte[] pData) throws GordianException {
306 try {
307 return theEncryptor.doFinal(pData, 0, pData.length);
308 } catch (IllegalBlockSizeException
309 | BadPaddingException e) {
310 throw new GordianCryptoException("Failed to process data", e);
311 }
312 }
313
314
315
316
317
318
319
320 private static String getAlgorithmName(final GordianCoreEncryptorSpec pSpec) {
321
322 final GordianCoreSM2EncryptionSpec mySpec = pSpec.getSM2EncryptionSpec();
323 final GordianDigestSpec myDigestSpec = mySpec.getDigestSpec();
324 final GordianDigestType myDigestType = myDigestSpec.getDigestType();
325 return switch (myDigestType) {
326 case SHA2 -> "SM2withSHA" + myDigestSpec.getDigestLength();
327 case BLAKE2 ->
328 "SM2withBlake2" + (GordianLength.LEN_512.equals(myDigestSpec.getDigestLength()) ? "b" : "s");
329 default -> "SM2with" + myDigestType;
330 };
331 }
332 }
333
334
335
336
337
338
339
340
341
342 private static Cipher getJavaEncryptor(final String pAlgorithm,
343 final boolean postQuantum) throws GordianException {
344
345 try {
346
347 return Cipher.getInstance(pAlgorithm, postQuantum
348 ? JcaProvider.BCPQPROV
349 : JcaProvider.BCPROV);
350
351
352 } catch (NoSuchAlgorithmException
353 | NoSuchPaddingException e) {
354
355 throw new GordianCryptoException("Failed to create Cipher", e);
356 }
357 }
358 }