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