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)) {
95              final ParametersWithIV ivParams = (ParametersWithIV) myParams;
96              newIV = ivParams.getIV();
97              myParams = ivParams.getParameters();
98          }
99          if (myParams instanceof KeyParameter) {
100             final KeyParameter keyParam = (KeyParameter) myParams;
101             newKey = keyParam.getKey();
102         }
103         if (newKey == null || newIV == null) {
104             throw new IllegalArgumentException("A key and IV must be provided");
105         }
106 
107         /* Initialise engine and mark as initialised */
108         final GordianBlake2ParametersBuilder myBuilder = new GordianBlake2ParametersBuilder()
109                 .setKey(newKey)
110                 .setSalt(newIV)
111                 .setMaxOutputLen(-1);
112         theBlake2X.init(myBuilder.build());
113 
114         /* Save reset state */
115         theResetState = theBlake2X.copy();
116 
117         /* Initialise the stream block */
118         theIndex = 0;
119         makeStreamBlock();
120     }
121 
122     @Override
123     public String getAlgorithmName() {
124         return theBlake2X.getAlgorithmName();
125     }
126 
127     @Override
128     public int processBytes(final byte[] in,
129                             final int inOff,
130                             final int len,
131                             final byte[] out,
132                             final int outOff) {
133         /* Check for errors */
134         if (theResetState == null) {
135             throw new IllegalStateException(getAlgorithmName() + " not initialised");
136         }
137         if ((inOff + len) > in.length) {
138             throw new DataLengthException("input buffer too short");
139         }
140         if ((outOff + len) > out.length) {
141             throw new OutputLengthException("output buffer too short");
142         }
143 
144         /* Loop through the input bytes */
145         for (int i = 0; i < len; i++) {
146             out[i + outOff] = returnByte(in[i + inOff]);
147         }
148         return len;
149     }
150 
151     @Override
152     public void reset() {
153         if (theResetState != null) {
154             theBlake2X.reset(theResetState);
155             theIndex = 0;
156             makeStreamBlock();
157         }
158     }
159 
160     @Override
161     public byte returnByte(final byte in) {
162         final byte out = (byte) (theKeyStream[theIndex] ^ in);
163         theIndex = (theIndex + 1) % theKeyStream.length;
164 
165         if (theIndex == 0) {
166             makeStreamBlock();
167         }
168         return out;
169     }
170 
171     /**
172      * Generate keystream.
173      */
174     private void makeStreamBlock() {
175         /* Generate next output block */
176         theBlake2X.doOutput(theKeyStream, 0, theKeyStream.length);
177     }
178 
179     @Override
180     public GordianBlake2XEngine copy() {
181         return new GordianBlake2XEngine(this);
182     }
183 
184     @Override
185     public void reset(final Memoable pState) {
186         final GordianBlake2XEngine e = (GordianBlake2XEngine) pState;
187         if (theKeyStream.length != e.theKeyStream.length) {
188             throw new IllegalArgumentException();
189         }
190         theBlake2X.reset(e.theBlake2X);
191         System.arraycopy(e.theKeyStream, 0, theKeyStream, 0, theKeyStream.length);
192         theIndex = e.theIndex;
193         theResetState = e.theResetState;
194     }
195 }
196