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.GordianKeySpec;
21  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
22  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParameters;
23  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherSpec;
24  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamCipher;
25  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamCipherSpec;
26  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec;
27  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipher;
28  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipherSpec;
29  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeySpec;
30  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeyType;
31  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseData;
32  import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
33  import io.github.tonywasher.joceanus.gordianknot.impl.core.cipher.GordianCoreCipher;
34  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
35  
36  import javax.crypto.BadPaddingException;
37  import javax.crypto.Cipher;
38  import javax.crypto.IllegalBlockSizeException;
39  import javax.crypto.SecretKey;
40  import javax.crypto.ShortBufferException;
41  import javax.crypto.spec.IvParameterSpec;
42  import javax.crypto.spec.RC2ParameterSpec;
43  import javax.crypto.spec.RC5ParameterSpec;
44  import java.security.InvalidAlgorithmParameterException;
45  import java.security.InvalidKeyException;
46  import java.security.spec.AlgorithmParameterSpec;
47  
48  /**
49   * Cipher for JCA BouncyCastle Ciphers.
50   *
51   * @param <T> the key Type
52   */
53  public abstract class JcaCipher<T extends GordianKeySpec>
54          extends GordianCoreCipher<T> {
55      /**
56       * Cipher.
57       */
58      private final Cipher theCipher;
59  
60      /**
61       * is the cipher encrypting?
62       */
63      private boolean isEncrypting;
64  
65      /**
66       * Constructor.
67       *
68       * @param pFactory    the Security Factory
69       * @param pCipherSpec the cipherSpec
70       * @param pCipher     the cipher
71       */
72      JcaCipher(final GordianBaseFactory pFactory,
73                final GordianCipherSpec<T> pCipherSpec,
74                final Cipher pCipher) {
75          super(pFactory, pCipherSpec);
76          theCipher = pCipher;
77      }
78  
79      @Override
80      public JcaKey<T> getKey() {
81          return (JcaKey<T>) super.getKey();
82      }
83  
84      @Override
85      public void init(final boolean pEncrypt,
86                       final GordianCipherParameters pParams) throws GordianException {
87          /* Process the parameters and access the key */
88          processParameters(pParams);
89          final JcaKey<T> myJcaKey = JcaKey.accessKey(getKey());
90  
91          /* Access details */
92          final int myMode = pEncrypt
93                  ? Cipher.ENCRYPT_MODE
94                  : Cipher.DECRYPT_MODE;
95          final SecretKey myKey = myJcaKey.getKey();
96          final byte[] myIV = getInitVector();
97  
98          /* Protect against exceptions */
99          try {
100             /* Careful of RC5 */
101             final T myKeyType = myJcaKey.getKeyType();
102             final boolean isRC5 = myKeyType instanceof GordianSymKeySpec
103                     && GordianSymKeyType.RC5.equals(((GordianSymKeySpec) myKeyType).getSymKeyType());
104 
105             /* Initialise as required */
106             if (myIV != null || isRC5) {
107                 final AlgorithmParameterSpec myParms = generateParameters(myJcaKey, myIV);
108                 theCipher.init(myMode, myKey, myParms);
109             } else {
110                 theCipher.init(myMode, myKey);
111             }
112             isEncrypting = pEncrypt;
113         } catch (InvalidKeyException
114                  | InvalidAlgorithmParameterException e) {
115             throw new GordianCryptoException("Failed to initialise cipher", e);
116         }
117     }
118 
119     /**
120      * Generate AlgorithmParameters.
121      *
122      * @param pKey the key
123      * @param pIV  the Initialisation vector
124      * @return the parameters
125      */
126     static AlgorithmParameterSpec generateParameters(final JcaKey<?> pKey,
127                                                      final byte[] pIV) {
128         final Object myKeyType = pKey.getKeyType();
129         if (myKeyType instanceof GordianSymKeySpec) {
130             final GordianSymKeySpec mySpec = (GordianSymKeySpec) myKeyType;
131             final GordianSymKeyType myType = mySpec.getSymKeyType();
132             final GordianLength myLen = mySpec.getBlockLength();
133             if (GordianSymKeyType.RC2.equals(myType)) {
134                 return new RC2ParameterSpec(pKey.getKeyBytes().length * Byte.SIZE, pIV);
135             }
136             if (GordianSymKeyType.RC5.equals(myType)) {
137                 return pIV == null
138                         ? new RC5ParameterSpec(1, GordianBaseData.RC5_ROUNDS, myLen.getLength() >> 1)
139                         : new RC5ParameterSpec(1, GordianBaseData.RC5_ROUNDS, myLen.getLength() >> 1, pIV);
140             }
141         }
142         return new IvParameterSpec(pIV);
143     }
144 
145     @Override
146     public int getOutputLength(final int pLength) {
147         return theCipher.getOutputSize(pLength);
148     }
149 
150     @Override
151     public int doUpdate(final byte[] pBytes,
152                         final int pOffset,
153                         final int pLength,
154                         final byte[] pOutput,
155                         final int pOutOffset) throws GordianException {
156         /* Protect against exceptions */
157         try {
158             /* Process the bytes */
159             return theCipher.update(pBytes, pOffset, pLength, pOutput, pOutOffset);
160 
161             /* Handle exceptions */
162         } catch (ShortBufferException e) {
163             throw new GordianCryptoException("Failed to process bytes", e);
164         }
165     }
166 
167     @Override
168     public int doFinish(final byte[] pOutput,
169                         final int pOutOffset) throws GordianException {
170         /* Protect against exceptions */
171         try {
172             /* Finish the operation */
173             return theCipher.doFinal(pOutput, pOutOffset);
174 
175             /* Handle exceptions */
176         } catch (ShortBufferException
177                  | IllegalBlockSizeException
178                  | BadPaddingException e) {
179             throw new GordianCryptoException("Failed to finish operation", e);
180         }
181     }
182 
183     @Override
184     public int getBlockSize() {
185         final GordianCipherSpec<T> mySpec = getCipherSpec();
186         return (mySpec instanceof GordianSymCipherSpec
187                 && ((GordianSymCipherSpec) mySpec).getCipherMode().hasPadding())
188                 ? theCipher.getBlockSize() : 0;
189     }
190 
191     /**
192      * JcaSymCipher.
193      */
194     public static class JcaSymCipher
195             extends JcaCipher<GordianSymKeySpec>
196             implements GordianSymCipher {
197         /**
198          * Constructor.
199          *
200          * @param pFactory    the Security Factory
201          * @param pCipherSpec the cipherSpec
202          * @param pCipher     the cipher
203          */
204         JcaSymCipher(final GordianBaseFactory pFactory,
205                      final GordianSymCipherSpec pCipherSpec,
206                      final Cipher pCipher) {
207             super(pFactory, pCipherSpec, pCipher);
208         }
209     }
210 
211     /**
212      * JcaStreamCipher.
213      */
214     public static class JcaStreamCipher
215             extends JcaCipher<GordianStreamKeySpec>
216             implements GordianStreamCipher {
217         /**
218          * Constructor.
219          *
220          * @param pFactory    the Security Factory
221          * @param pCipherSpec the cipherSpec
222          * @param pCipher     the cipher
223          */
224         JcaStreamCipher(final GordianBaseFactory pFactory,
225                         final GordianStreamCipherSpec pCipherSpec,
226                         final Cipher pCipher) {
227             super(pFactory, pCipherSpec, pCipher);
228         }
229     }
230 
231     @Override
232     public boolean equals(final Object pThat) {
233         /* Handle trivial cases */
234         if (this == pThat) {
235             return true;
236         }
237         if (pThat == null) {
238             return false;
239         }
240 
241         /* Make sure that the classes are the same */
242         if (!(pThat instanceof JcaCipher)) {
243             return false;
244         }
245         final JcaCipher<?> myThat = (JcaCipher<?>) pThat;
246 
247         /* Check that the fields are equal */
248         return isEncrypting == myThat.isEncrypting
249                 && super.equals(myThat);
250     }
251 
252     @Override
253     public int hashCode() {
254         return super.hashCode()
255                 + (isEncrypting ? 1 : 0);
256     }
257 }