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.ext.engines;
18  
19  import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianBlake2Base;
20  import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianBlake2Xof;
21  import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianBlake2Parameters.GordianBlake2ParametersBuilder;
22  import org.bouncycastle.crypto.CipherParameters;
23  import org.bouncycastle.crypto.DataLengthException;
24  import org.bouncycastle.crypto.OutputLengthException;
25  import org.bouncycastle.crypto.StreamCipher;
26  import org.bouncycastle.crypto.params.KeyParameter;
27  import org.bouncycastle.crypto.params.ParametersWithIV;
28  import org.bouncycastle.util.Memoable;
29  
30  /**
31   * Blake2X used as a stream Cipher.
32   */
33  public class GordianBlake2XEngine
34          implements StreamCipher, Memoable {
35      /**
36       * index of next byte in keyStream.
37       */
38      private int theIndex;
39  
40      /**
41       * Advanced stream.
42       */
43      private final byte[] theKeyStream;
44  
45      /**
46       * Underlying digest.
47       */
48      private final GordianBlake2Xof theBlake2X;
49  
50      /**
51       * Reset state.
52       */
53      private GordianBlake2Xof theResetState;
54  
55      /**
56       * Constructor.
57       *
58       * @param pDigest the underlying digest
59       */
60      public GordianBlake2XEngine(final GordianBlake2Base pDigest) {
61          theBlake2X = new GordianBlake2Xof(pDigest);
62          theKeyStream = new byte[pDigest.getDigestSize()];
63      }
64  
65      /**
66       * Constructor.
67       *
68       * @param pSource the source engine
69       */
70      private GordianBlake2XEngine(final GordianBlake2XEngine pSource) {
71          theBlake2X = new GordianBlake2Xof(pSource.theBlake2X);
72          theKeyStream = new byte[theBlake2X.getByteLength() >> 1];
73          reset(pSource);
74      }
75  
76      /**
77       * initialise a Blake2X cipher.
78       *
79       * @param forEncryption whether or not we are for encryption.
80       * @param params        the parameters required to set up the cipher.
81       * @throws IllegalArgumentException if the params argument is inappropriate.
82       */
83      public void init(final boolean forEncryption,
84                       final CipherParameters params) {
85          /*
86           * Blake2X encryption and decryption is completely symmetrical, so the 'forEncryption' is
87           * irrelevant. (Like 90% of stream ciphers)
88           */
89  
90          /* Determine parameters */
91          CipherParameters myParams = params;
92          byte[] newKey = null;
93          byte[] newIV = null;
94          if ((myParams instanceof ParametersWithIV ivParams)) {
95              newIV = ivParams.getIV();
96              myParams = ivParams.getParameters();
97          }
98          if (myParams instanceof KeyParameter keyParam) {
99              newKey = keyParam.getKey();
100         }
101         if (newKey == null || newIV == null) {
102             throw new IllegalArgumentException("A key and IV must be provided");
103         }
104 
105         /* Initialise engine and mark as initialised */
106         final GordianBlake2ParametersBuilder myBuilder = new GordianBlake2ParametersBuilder()
107                 .setKey(newKey)
108                 .setSalt(newIV)
109                 .setMaxOutputLen(-1);
110         theBlake2X.init(myBuilder.build());
111 
112         /* Save reset state */
113         theResetState = theBlake2X.copy();
114 
115         /* Initialise the stream block */
116         theIndex = 0;
117         makeStreamBlock();
118     }
119 
120     @Override
121     public String getAlgorithmName() {
122         return theBlake2X.getAlgorithmName();
123     }
124 
125     @Override
126     public int processBytes(final byte[] in,
127                             final int inOff,
128                             final int len,
129                             final byte[] out,
130                             final int outOff) {
131         /* Check for errors */
132         if (theResetState == null) {
133             throw new IllegalStateException(getAlgorithmName() + " not initialised");
134         }
135         if ((inOff + len) > in.length) {
136             throw new DataLengthException("input buffer too short");
137         }
138         if ((outOff + len) > out.length) {
139             throw new OutputLengthException("output buffer too short");
140         }
141 
142         /* Loop through the input bytes */
143         for (int i = 0; i < len; i++) {
144             out[i + outOff] = returnByte(in[i + inOff]);
145         }
146         return len;
147     }
148 
149     @Override
150     public void reset() {
151         if (theResetState != null) {
152             theBlake2X.reset(theResetState);
153             theIndex = 0;
154             makeStreamBlock();
155         }
156     }
157 
158     @Override
159     public byte returnByte(final byte in) {
160         final byte out = (byte) (theKeyStream[theIndex] ^ in);
161         theIndex = (theIndex + 1) % theKeyStream.length;
162 
163         if (theIndex == 0) {
164             makeStreamBlock();
165         }
166         return out;
167     }
168 
169     /**
170      * Generate keystream.
171      */
172     private void makeStreamBlock() {
173         /* Generate next output block */
174         theBlake2X.doOutput(theKeyStream, 0, theKeyStream.length);
175     }
176 
177     @Override
178     public GordianBlake2XEngine copy() {
179         return new GordianBlake2XEngine(this);
180     }
181 
182     @Override
183     public void reset(final Memoable pState) {
184         final GordianBlake2XEngine e = (GordianBlake2XEngine) pState;
185         if (theKeyStream.length != e.theKeyStream.length) {
186             throw new IllegalArgumentException();
187         }
188         theBlake2X.reset(e.theBlake2X);
189         System.arraycopy(e.theKeyStream, 0, theKeyStream, 0, theKeyStream.length);
190         theIndex = e.theIndex;
191         theResetState = e.theResetState;
192     }
193 }
194