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.cipher.GordianAEADCipher;
22  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParams;
23  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamAEADCipher;
24  import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymAEADCipher;
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.impl.core.base.GordianBaseFactory;
31  import io.github.tonywasher.joceanus.gordianknot.impl.core.cipher.GordianCoreCipher;
32  import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
33  import io.github.tonywasher.joceanus.gordianknot.impl.jca.base.JcaKey;
34  import org.bouncycastle.jcajce.spec.AEADParameterSpec;
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 java.security.InvalidAlgorithmParameterException;
43  import java.security.InvalidKeyException;
44  import java.security.spec.AlgorithmParameterSpec;
45  import java.util.Arrays;
46  
47  /**
48   * Cipher for JCA BouncyCastle AEAD Ciphers.
49   *
50   * @param <T> the key Type
51   */
52  public class JcaAEADCipher<T extends GordianKeySpec>
53          extends GordianCoreCipher<T>
54          implements GordianAEADCipher {
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      JcaAEADCipher(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  
85      @Override
86      public void init(final boolean pEncrypt,
87                       final GordianCipherParams pParams) throws GordianException {
88          /* Process the parameters and access the key */
89          processParameters(pParams);
90          final JcaKey<T> myJcaKey = JcaKey.accessKey(getKey());
91  
92          /* Access details */
93          final int myMode = pEncrypt
94                  ? Cipher.ENCRYPT_MODE
95                  : Cipher.DECRYPT_MODE;
96          final SecretKey myKey = myJcaKey.getKey();
97          final byte[] myAEAD = getInitialAEAD();
98  
99          /* Protect against exceptions */
100         try {
101             /* Initialise as required */
102             final AlgorithmParameterSpec myParms = myAEAD == null
103                     ? new IvParameterSpec(getInitVector())
104                     : new AEADParameterSpec(getInitVector(), getAEADMacSize(), myAEAD);
105             theCipher.init(myMode, myKey, myParms);
106             isEncrypting = pEncrypt;
107         } catch (InvalidKeyException
108                  | InvalidAlgorithmParameterException e) {
109             throw new GordianCryptoException("Failed to initialise cipher", e);
110         }
111     }
112 
113     @Override
114     public int getOutputLength(final int pLength) {
115         return theCipher.getOutputSize(pLength);
116     }
117 
118     @Override
119     public int doUpdate(final byte[] pBytes,
120                         final int pOffset,
121                         final int pLength,
122                         final byte[] pOutput,
123                         final int pOutOffset) throws GordianException {
124         /* Protect against exceptions */
125         try {
126             /* Process the bytes */
127             return theCipher.update(pBytes, pOffset, pLength, pOutput, pOutOffset);
128 
129             /* Handle exceptions */
130         } catch (ShortBufferException e) {
131             throw new GordianCryptoException("Failed to process bytes", e);
132         }
133     }
134 
135     @Override
136     public void updateAAD(final byte[] pBytes,
137                           final int pOffset,
138                           final int pLength) throws GordianException {
139         /* Protect against exceptions */
140         try {
141             /* Process the bytes */
142             theCipher.updateAAD(pBytes, pOffset, pLength);
143 
144             /* Handle exceptions */
145         } catch (IllegalStateException e) {
146             throw new GordianCryptoException("Failed to process AAD bytes", e);
147         }
148     }
149 
150     @Override
151     public int doFinish(final byte[] pOutput,
152                         final int pOutOffset) throws GordianException {
153         /* Protect against exceptions */
154         try {
155             /* Finish the operation */
156             return theCipher.doFinal(pOutput, pOutOffset);
157 
158             /* Handle exceptions */
159         } catch (ShortBufferException
160                  | IllegalBlockSizeException
161                  | BadPaddingException e) {
162             throw new GordianCryptoException("Failed to finish operation", e);
163         }
164     }
165 
166     @Override
167     public int getBlockSize() {
168         return 0;
169     }
170 
171     /**
172      * JcaSymAADCipher.
173      */
174     public static class JcaSymAEADCipher
175             extends JcaAEADCipher<GordianSymKeySpec>
176             implements GordianSymAEADCipher {
177         /**
178          * Constructor.
179          *
180          * @param pFactory    the Security Factory
181          * @param pCipherSpec the cipherSpec
182          * @param pCipher     the cipher
183          */
184         JcaSymAEADCipher(final GordianBaseFactory pFactory,
185                          final GordianSymCipherSpec pCipherSpec,
186                          final Cipher pCipher) {
187             super(pFactory, pCipherSpec, pCipher);
188         }
189     }
190 
191     /**
192      * JcaStreamAEADCipher.
193      */
194     public static class JcaStreamAEADCipher
195             extends JcaAEADCipher<GordianStreamKeySpec>
196             implements GordianStreamAEADCipher {
197         /**
198          * Constructor.
199          *
200          * @param pFactory    the Security Factory
201          * @param pCipherSpec the cipherSpec
202          * @param pCipher     the cipher
203          */
204         JcaStreamAEADCipher(final GordianBaseFactory pFactory,
205                             final GordianStreamCipherSpec pCipherSpec,
206                             final Cipher pCipher) {
207             super(pFactory, pCipherSpec, pCipher);
208         }
209     }
210 
211     @Override
212     public boolean equals(final Object pThat) {
213         /* Handle trivial cases */
214         if (this == pThat) {
215             return true;
216         }
217         if (pThat == null) {
218             return false;
219         }
220 
221         /* Make sure that the classes are the same */
222         if (!(pThat instanceof JcaAEADCipher<?> myThat)) {
223             return false;
224         }
225 
226         /* Check that the fields are equal */
227         return isEncrypting == myThat.isEncrypting
228                 && Arrays.equals(getInitialAEAD(), myThat.getInitialAEAD())
229                 && super.equals(myThat);
230     }
231 
232     @Override
233     public int hashCode() {
234         return super.hashCode()
235                 + Arrays.hashCode(getInitialAEAD())
236                 + (isEncrypting ? 1 : 0);
237     }
238 }