View Javadoc
1   /*
2    * GordianKnot: Security Suite
3    * Copyright 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  
18  package io.github.tonywasher.joceanus.gordianknot.impl.jca.encrypt;
19  
20  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
21  import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
22  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
23  import io.github.tonywasher.joceanus.gordianknot.impl.core.encrypt.GordianCoreEncryptor;
24  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
25  import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.encrypt.GordianCoreEncryptorSpec;
26  import io.github.tonywasher.joceanus.gordianknot.impl.jca.keypair.JcaKeyPair;
27  import io.github.tonywasher.joceanus.gordianknot.impl.jca.keypair.JcaKeyPair.JcaPrivateKey;
28  import io.github.tonywasher.joceanus.gordianknot.impl.jca.keypair.JcaKeyPair.JcaPublicKey;
29  
30  import javax.crypto.BadPaddingException;
31  import javax.crypto.Cipher;
32  import javax.crypto.IllegalBlockSizeException;
33  import java.security.InvalidKeyException;
34  import java.util.Arrays;
35  
36  /**
37   * Block Encryptor.
38   */
39  public class JcaBlockEncryptor
40          extends GordianCoreEncryptor {
41      /**
42       * Error string.
43       */
44      private static final String ERROR_INIT = "Failed to initialise";
45  
46      /**
47       * The underlying encryptor.
48       */
49      private final Cipher theEncryptor;
50  
51      /**
52       * Constructor.
53       *
54       * @param pFactory the factory
55       * @param pSpec    the encryptorSpec
56       * @throws GordianException on error
57       */
58      JcaBlockEncryptor(final GordianBaseFactory pFactory,
59                        final GordianCoreEncryptorSpec pSpec) throws GordianException {
60          /* Initialise underlying cipher */
61          super(pFactory, pSpec);
62          theEncryptor = JcaEncryptor.getJavaEncryptor(getAlgorithmName(pSpec));
63      }
64  
65      @Override
66      protected JcaPublicKey getPublicKey() {
67          return (JcaPublicKey) super.getPublicKey();
68      }
69  
70      @Override
71      protected JcaPrivateKey getPrivateKey() {
72          return (JcaPrivateKey) super.getPrivateKey();
73      }
74  
75      @Override
76      public void initForEncrypt(final GordianKeyPair pKeyPair) throws GordianException {
77          try {
78              /* Initialise underlying cipher */
79              JcaKeyPair.checkKeyPair(pKeyPair);
80              super.initForEncrypt(pKeyPair);
81  
82              /* Initialise for encryption */
83              theEncryptor.init(Cipher.ENCRYPT_MODE, getPublicKey().getPublicKey(), getRandom());
84          } catch (InvalidKeyException e) {
85              throw new GordianCryptoException(ERROR_INIT, e);
86          }
87      }
88  
89      @Override
90      public void initForDecrypt(final GordianKeyPair pKeyPair) throws GordianException {
91          try {
92              /* Initialise underlying cipher */
93              JcaKeyPair.checkKeyPair(pKeyPair);
94              super.initForDecrypt(pKeyPair);
95  
96              /* Initialise for decryption */
97              theEncryptor.init(Cipher.DECRYPT_MODE, getPrivateKey().getPrivateKey());
98          } catch (InvalidKeyException e) {
99              throw new GordianCryptoException(ERROR_INIT, e);
100         }
101     }
102 
103     @Override
104     public byte[] encrypt(final byte[] pBytes) throws GordianException {
105         /* Check that we are in encryption mode */
106         checkMode(GordianEncryptMode.ENCRYPT);
107 
108         /* Encrypt the message */
109         return processData(pBytes);
110     }
111 
112     @Override
113     public byte[] decrypt(final byte[] pBytes) throws GordianException {
114         /* Check that we are in decryption mode */
115         checkMode(GordianEncryptMode.DECRYPT);
116 
117         /* Decrypt the message */
118         return processData(pBytes);
119     }
120 
121     /**
122      * Process a data buffer.
123      *
124      * @param pData the buffer to process
125      * @return the processed buffer
126      * @throws GordianException on error
127      */
128     private byte[] processData(final byte[] pData) throws GordianException {
129         try {
130             /* Access input block length */
131             int myInLen = pData.length;
132             final int myInBlockLength = theEncryptor.getBlockSize();
133             final int myNumBlocks = getNumBlocks(myInLen, myInBlockLength);
134             final int myOutBlockLength = theEncryptor.getOutputSize(myInBlockLength);
135 
136             /* Create the output buffer */
137             final byte[] myOutput = new byte[myOutBlockLength * myNumBlocks];
138 
139             /* Loop encrypting the blocks */
140             int myInOff = 0;
141             int myOutOff = 0;
142             while (myInLen > 0) {
143                 /* Process the data */
144                 final int myLen = Math.min(myInLen, myInBlockLength);
145                 final byte[] myBlock = theEncryptor.doFinal(pData, myInOff, myLen);
146 
147                 /* Copy to the output buffer */
148                 final int myOutLen = myBlock.length;
149                 System.arraycopy(myBlock, 0, myOutput, myOutOff, myOutLen);
150                 myOutOff += myOutLen;
151 
152                 /* Move to next block */
153                 myInOff += myInBlockLength;
154                 myInLen -= myInBlockLength;
155             }
156 
157             /* Return full buffer if possible */
158             if (myOutOff == myOutput.length) {
159                 return myOutput;
160             }
161 
162             /* Cut down buffer */
163             final byte[] myReturn = Arrays.copyOf(myOutput, myOutOff);
164             Arrays.fill(myOutput, (byte) 0);
165             return myReturn;
166 
167         } catch (IllegalBlockSizeException
168                  | BadPaddingException e) {
169             throw new GordianCryptoException("Failed to process data", e);
170         }
171     }
172 
173     /**
174      * Obtain the number of blocks required for the length in terms of blocks.
175      *
176      * @param pLength      the length of clear data
177      * @param pBlockLength the blockLength
178      * @return the number of blocks.
179      */
180     private static int getNumBlocks(final int pLength, final int pBlockLength) {
181         return (pLength + pBlockLength - 1) / pBlockLength;
182     }
183 
184     /**
185      * Obtain the algorithmName.
186      *
187      * @param pSpec the Spec
188      * @return the algorithm name
189      */
190     private static String getAlgorithmName(final GordianCoreEncryptorSpec pSpec) {
191         /* Determine the base algorithm */
192         final String myBase = pSpec.getKeyPairType().name();
193 
194         /* Switch on encryptor type */
195         return switch (pSpec.getDigestSpec().getDigestLength()) {
196             case LEN_224 -> myBase + "/ECB/OAEPWITHSHA224ANDMGF1PADDING";
197             case LEN_256 -> myBase + "/ECB/OAEPWITHSHA256ANDMGF1PADDING";
198             case LEN_384 -> myBase + "/ECB/OAEPWITHSHA384ANDMGF1PADDING";
199             default -> myBase + "/ECB/OAEPWITHSHA512ANDMGF1PADDING";
200         };
201     }
202 }