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.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   * 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 GordianEncryptorSpec 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 GordianEncryptorSpec pSpec) {
205             /* Determine the base algorithm */
206             final String myBase = pSpec.getKeyPairType().name();
207 
208             /* Switch on encryptor type */
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      * Hybrid Encryptor.
225      */
226     public static class JcaHybridEncryptor
227             extends GordianCoreEncryptor {
228         /**
229          * The underlying encryptor.
230          */
231         private final Cipher theEncryptor;
232 
233         /**
234          * Constructor.
235          *
236          * @param pFactory the factory
237          * @param pSpec    the encryptorSpec
238          * @throws GordianException on error
239          */
240         JcaHybridEncryptor(final GordianBaseFactory pFactory,
241                            final GordianEncryptorSpec pSpec) throws GordianException {
242             /* Initialise underlying cipher */
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                 /* Initialise underlying cipher */
261                 JcaKeyPair.checkKeyPair(pKeyPair);
262                 super.initForEncrypt(pKeyPair);
263 
264                 /* Initialise for encryption */
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                 /* Initialise underlying cipher */
275                 JcaKeyPair.checkKeyPair(pKeyPair);
276                 super.initForDecrypt(pKeyPair);
277 
278                 /* Initialise for decryption */
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             /* Check that we are in encryption mode */
288             checkMode(GordianEncryptMode.ENCRYPT);
289 
290             /* Encrypt the message */
291             return processData(pBytes);
292         }
293 
294         @Override
295         public byte[] decrypt(final byte[] pBytes) throws GordianException {
296             /* Check that we are in decryption mode */
297             checkMode(GordianEncryptMode.DECRYPT);
298 
299             /* Decrypt the message */
300             return processData(pBytes);
301         }
302 
303         /**
304          * Process a data buffer.
305          *
306          * @param pData the buffer to process
307          * @return the processed buffer
308          * @throws GordianException on error
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          * Obtain the algorithmName.
321          *
322          * @param pSpec the Spec
323          * @return the algorithm name
324          */
325         private static String getAlgorithmName(final GordianEncryptorSpec pSpec) {
326             /* Switch on encryptor type */
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      * Create the BouncyCastle KeyFactory via JCA.
345      *
346      * @param pAlgorithm  the Algorithm
347      * @param postQuantum is this a postQuantum algorithm?
348      * @return the KeyFactory
349      * @throws GordianException on error
350      */
351     private static Cipher getJavaEncryptor(final String pAlgorithm,
352                                            final boolean postQuantum) throws GordianException {
353         /* Protect against exceptions */
354         try {
355             /* Return a Cipher for the algorithm */
356             return Cipher.getInstance(pAlgorithm, postQuantum
357                     ? JcaProvider.BCPQPROV
358                     : JcaProvider.BCPROV);
359 
360             /* Catch exceptions */
361         } catch (NoSuchAlgorithmException
362                  | NoSuchPaddingException e) {
363             /* Throw the exception */
364             throw new GordianCryptoException("Failed to create Cipher", e);
365         }
366     }
367 }