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