View Javadoc
1   /*
2    * GordianKnot: Security Suite
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
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   * JCA Encryptor classes.
42   */
43  public final class JcaEncryptor {
44      /**
45       * Error string.
46       */
47      private static final String ERROR_INIT = "Failed to initialise";
48  
49      /**
50       * Private constructor.
51       */
52      private JcaEncryptor() {
53      }
54  
55      /**
56       * Block Encryptor.
57       */
58      public static class JcaBlockEncryptor
59              extends GordianCoreEncryptor {
60          /**
61           * The underlying encryptor.
62           */
63          private final Cipher theEncryptor;
64  
65          /**
66           * Constructor.
67           *
68           * @param pFactory the factory
69           * @param pSpec    the encryptorSpec
70           * @throws GordianException on error
71           */
72          JcaBlockEncryptor(final GordianBaseFactory pFactory,
73                            final GordianCoreEncryptorSpec pSpec) throws GordianException {
74              /* Initialise underlying cipher */
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                  /* Initialise underlying cipher */
93                  JcaKeyPair.checkKeyPair(pKeyPair);
94                  super.initForEncrypt(pKeyPair);
95  
96                  /* Initialise for encryption */
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                 /* Initialise underlying cipher */
107                 JcaKeyPair.checkKeyPair(pKeyPair);
108                 super.initForDecrypt(pKeyPair);
109 
110                 /* Initialise for decryption */
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             /* Check that we are in encryption mode */
120             checkMode(GordianEncryptMode.ENCRYPT);
121 
122             /* Encrypt the message */
123             return processData(pBytes);
124         }
125 
126         @Override
127         public byte[] decrypt(final byte[] pBytes) throws GordianException {
128             /* Check that we are in decryption mode */
129             checkMode(GordianEncryptMode.DECRYPT);
130 
131             /* Decrypt the message */
132             return processData(pBytes);
133         }
134 
135         /**
136          * Process a data buffer.
137          *
138          * @param pData the buffer to process
139          * @return the processed buffer
140          * @throws GordianException on error
141          */
142         private byte[] processData(final byte[] pData) throws GordianException {
143             try {
144                 /* Access input block length */
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                 /* Create the output buffer */
151                 final byte[] myOutput = new byte[myOutBlockLength * myNumBlocks];
152 
153                 /* Loop encrypting the blocks */
154                 int myInOff = 0;
155                 int myOutOff = 0;
156                 while (myInLen > 0) {
157                     /* Process the data */
158                     final int myLen = Math.min(myInLen, myInBlockLength);
159                     final byte[] myBlock = theEncryptor.doFinal(pData, myInOff, myLen);
160 
161                     /* Copy to the output buffer */
162                     final int myOutLen = myBlock.length;
163                     System.arraycopy(myBlock, 0, myOutput, myOutOff, myOutLen);
164                     myOutOff += myOutLen;
165 
166                     /* Move to next block */
167                     myInOff += myInBlockLength;
168                     myInLen -= myInBlockLength;
169                 }
170 
171                 /* Return full buffer if possible */
172                 if (myOutOff == myOutput.length) {
173                     return myOutput;
174                 }
175 
176                 /* Cut down buffer */
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          * Obtain the number of blocks required for the length in terms of blocks.
189          *
190          * @param pLength      the length of clear data
191          * @param pBlockLength the blockLength
192          * @return the number of blocks.
193          */
194         private static int getNumBlocks(final int pLength, final int pBlockLength) {
195             return (pLength + pBlockLength - 1) / pBlockLength;
196         }
197 
198         /**
199          * Obtain the algorithmName.
200          *
201          * @param pSpec the Spec
202          * @return the algorithm name
203          */
204         private static String getAlgorithmName(final GordianCoreEncryptorSpec pSpec) {
205             /* Determine the base algorithm */
206             final String myBase = pSpec.getKeyPairType().name();
207 
208             /* Switch on encryptor type */
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      * Hybrid Encryptor.
220      */
221     public static class JcaHybridEncryptor
222             extends GordianCoreEncryptor {
223         /**
224          * The underlying encryptor.
225          */
226         private final Cipher theEncryptor;
227 
228         /**
229          * Constructor.
230          *
231          * @param pFactory the factory
232          * @param pSpec    the encryptorSpec
233          * @throws GordianException on error
234          */
235         JcaHybridEncryptor(final GordianBaseFactory pFactory,
236                            final GordianCoreEncryptorSpec pSpec) throws GordianException {
237             /* Initialise underlying cipher */
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                 /* Initialise underlying cipher */
256                 JcaKeyPair.checkKeyPair(pKeyPair);
257                 super.initForEncrypt(pKeyPair);
258 
259                 /* Initialise for encryption */
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                 /* Initialise underlying cipher */
270                 JcaKeyPair.checkKeyPair(pKeyPair);
271                 super.initForDecrypt(pKeyPair);
272 
273                 /* Initialise for decryption */
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             /* Check that we are in encryption mode */
283             checkMode(GordianEncryptMode.ENCRYPT);
284 
285             /* Encrypt the message */
286             return processData(pBytes);
287         }
288 
289         @Override
290         public byte[] decrypt(final byte[] pBytes) throws GordianException {
291             /* Check that we are in decryption mode */
292             checkMode(GordianEncryptMode.DECRYPT);
293 
294             /* Decrypt the message */
295             return processData(pBytes);
296         }
297 
298         /**
299          * Process a data buffer.
300          *
301          * @param pData the buffer to process
302          * @return the processed buffer
303          * @throws GordianException on error
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          * Obtain the algorithmName.
316          *
317          * @param pSpec the Spec
318          * @return the algorithm name
319          */
320         private static String getAlgorithmName(final GordianCoreEncryptorSpec pSpec) {
321             /* Switch on encryptor type */
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      * Create the BouncyCastle KeyFactory via JCA.
336      *
337      * @param pAlgorithm  the Algorithm
338      * @param postQuantum is this a postQuantum algorithm?
339      * @return the KeyFactory
340      * @throws GordianException on error
341      */
342     private static Cipher getJavaEncryptor(final String pAlgorithm,
343                                            final boolean postQuantum) throws GordianException {
344         /* Protect against exceptions */
345         try {
346             /* Return a Cipher for the algorithm */
347             return Cipher.getInstance(pAlgorithm, postQuantum
348                     ? JcaProvider.BCPQPROV
349                     : JcaProvider.BCPROV);
350 
351             /* Catch exceptions */
352         } catch (NoSuchAlgorithmException
353                  | NoSuchPaddingException e) {
354             /* Throw the exception */
355             throw new GordianCryptoException("Failed to create Cipher", e);
356         }
357     }
358 }