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.digests;
18  
19  import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianSkeinBase.Configuration;
20  import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianSkeinParameters;
21  import org.bouncycastle.crypto.ExtendedDigest;
22  import org.bouncycastle.crypto.Xof;
23  import org.bouncycastle.util.Memoable;
24  
25  /**
26   * SkeinXof implementation.
27   * <p>This implementation supports the following elements of SkeinXParameters
28   * <ul>
29   *     <li>Key
30   *     <li>Nonce
31   *     <li>Personalisation
32   *     <li>PublicKey
33   *     <li>Key Identifier
34   *     <li>Max Output length
35   * </ul>
36   * <p>If OutputLen is set to zero, then SkeinXof defaults to the underlying digestLength.
37   */
38  public class GordianSkeinXof
39          implements ExtendedDigest, Memoable, Xof {
40      /**
41       * The maximum Xof length.
42       */
43      private static final int MAX_XOFLEN = -2;
44  
45      /**
46       * The underlying Skein instance.
47       */
48      private final GordianSkeinBase theUnderlying;
49  
50      /**
51       * The single byte buffer.
52       */
53      private final byte[] singleByte = new byte[1];
54  
55      /**
56       * The next output index.
57       */
58      private byte[] outputCache;
59  
60      /**
61       * The XofLen.
62       */
63      private long theXofLen;
64  
65      /**
66       * The XofRemaining.
67       */
68      private long theXofRemaining;
69  
70      /**
71       * The next output index.
72       */
73      private long nextOutputIdx;
74  
75      /**
76       * Bytes in cache.
77       */
78      private int bytesInCache;
79  
80      /**
81       * Constructor.
82       *
83       * @param pDigest the underlying digest.
84       */
85      public GordianSkeinXof(final GordianSkeinBase pDigest) {
86          /* Store the digest */
87          theUnderlying = pDigest;
88          outputCache = new byte[theUnderlying.getBlockSize()];
89  
90          /* Clear outputting flag */
91          theXofRemaining = -1L;
92          theXofLen = -1;
93      }
94  
95      /**
96       * Constructor.
97       *
98       * @param pSource the source digest.
99       */
100     public GordianSkeinXof(final GordianSkeinXof pSource) {
101         /* Create hashes */
102         theUnderlying = (GordianSkeinBase) pSource.theUnderlying.copy();
103         outputCache = new byte[theUnderlying.getBlockSize()];
104 
105         /* Initialise from source */
106         reset(pSource);
107     }
108 
109     /**
110      * Constructor.
111      *
112      * @param pDigest the underlying digest.
113      */
114     public GordianSkeinXof(final GordianSkeinDigest pDigest) {
115         this(pDigest.getBase());
116     }
117 
118     /**
119      * Initialise.
120      *
121      * @param pParams the parameters.
122      */
123     public void init(final GordianSkeinParameters pParams) {
124         /* Reject a negative Xof length */
125         final long myXofLen = pParams.getMaxOutputLength();
126         if (myXofLen < -1) {
127             throw new IllegalArgumentException("Invalid output length");
128         }
129         theXofLen = myXofLen;
130 
131         /* Declare the configuration */
132         declareConfig();
133 
134         /* Pass selective parameters to the underlying hash */
135         theUnderlying.init(pParams);
136         theXofRemaining = -1L;
137     }
138 
139     /**
140      * Declare extended configuration.
141      */
142     private void declareConfig() {
143         /* Declare the configuration */
144         long myLen = theXofLen == 0 ? theUnderlying.getOutputSize() : theXofLen;
145         myLen = myLen == -1 ? -1L : myLen * Byte.SIZE;
146         final Configuration myConfig = new Configuration(myLen);
147         theUnderlying.setConfiguration(myConfig);
148     }
149 
150     @Override
151     public String getAlgorithmName() {
152         final String myBase = "SkeinXof";
153         if (theXofLen == -1) {
154             return myBase;
155         }
156         final long myLen = theXofLen == 0 ? theUnderlying.getOutputSize() : theXofLen;
157         return myBase + "-" + (theUnderlying.getBlockSize() * Byte.SIZE) + "-" + (myLen * Byte.SIZE);
158     }
159 
160     @Override
161     public int getDigestSize() {
162         return (theXofLen == 0 || theXofLen == -1) ? theUnderlying.getOutputSize() : (int) theXofLen;
163     }
164 
165     @Override
166     public int getByteLength() {
167         return theUnderlying.getBlockSize();
168     }
169 
170     @Override
171     public void update(final byte b) {
172         singleByte[0] = b;
173         update(singleByte, 0, 1);
174     }
175 
176     @Override
177     public void update(final byte[] pMessage,
178                        final int pOffset,
179                        final int pLen) {
180         if (theXofRemaining != -1) {
181             throw new IllegalStateException("Already outputting");
182         }
183         theUnderlying.update(pMessage, pOffset, pLen);
184     }
185 
186     @Override
187     public int doFinal(final byte[] pOut,
188                        final int pOutOffset) {
189         return doFinal(pOut, pOutOffset, getDigestSize());
190     }
191 
192     @Override
193     public int doFinal(final byte[] pOut,
194                        final int pOutOffset,
195                        final int pOutLen) {
196         /* Build the required output */
197         final int length = doOutput(pOut, pOutOffset, pOutLen);
198 
199         /* reset the underlying digest and return the length */
200         reset();
201         return length;
202     }
203 
204     @Override
205     public int doOutput(final byte[] pOut,
206                         final int pOutOffset,
207                         final int pOutLen) {
208         /* If wa are switching to output */
209         if (theXofRemaining == -1) {
210             /* Initialise values */
211             nextOutputIdx = 0;
212             bytesInCache = 0;
213 
214             /* If we have a null Xof */
215             if (theXofLen == 0) {
216                 /* Calculate the number of bytes available */
217                 theXofRemaining = theUnderlying.getOutputSize();
218 
219                 /* Else we are handling a normal Xof */
220             } else {
221                 /* Calculate the number of bytes available */
222                 theXofRemaining = theXofLen == -1
223                         ? MAX_XOFLEN
224                         : theXofLen;
225             }
226 
227             /* Initiate output */
228             theUnderlying.initiateOutput();
229         }
230 
231         /* Reject if there is insufficient Xof remaining */
232         if (pOutLen < 0
233                 || (theXofRemaining > 0 && pOutLen > theXofRemaining)) {
234             throw new IllegalArgumentException("Insufficient bytes remaining");
235         }
236 
237         /* If we have some remaining data in the cache */
238         int dataLeft = pOutLen;
239         int outPos = pOutOffset;
240         if (bytesInCache > 0) {
241             /* Copy data from current hash */
242             final int dataToCopy = Math.min(dataLeft, bytesInCache);
243             System.arraycopy(outputCache, outputCache.length - bytesInCache, pOut, outPos, dataToCopy);
244 
245             /* Adjust counters */
246             theXofRemaining -= dataToCopy;
247             bytesInCache -= dataToCopy;
248             outPos += dataToCopy;
249             dataLeft -= dataToCopy;
250         }
251 
252         /* Loop until we have completed the request */
253         while (dataLeft > 0) {
254             /* Obtain the next set of output bytes */
255             theUnderlying.output(nextOutputIdx++, outputCache, 0, outputCache.length);
256             bytesInCache = outputCache.length;
257 
258             /* Copy data from current hash */
259             final int dataToCopy = Math.min(dataLeft, outputCache.length);
260             System.arraycopy(outputCache, 0, pOut, outPos, dataToCopy);
261 
262             /* Adjust counters */
263             theXofRemaining -= dataToCopy;
264             bytesInCache -= dataToCopy;
265             outPos += dataToCopy;
266             dataLeft -= dataToCopy;
267         }
268 
269         /* Return the number of bytes transferred */
270         return pOutLen;
271     }
272 
273     @Override
274     public void reset() {
275         theUnderlying.reset();
276         theXofRemaining = -1L;
277     }
278 
279     @Override
280     public void reset(final Memoable pSource) {
281         /* Access source */
282         final GordianSkeinXof mySource = (GordianSkeinXof) pSource;
283 
284         /* Reset digests */
285         theUnderlying.reset(mySource.theUnderlying);
286 
287         /* Copy state */
288         theXofLen = mySource.theXofLen;
289         theXofRemaining = mySource.theXofRemaining;
290         bytesInCache = mySource.bytesInCache;
291         nextOutputIdx = mySource.nextOutputIdx;
292 
293         /* Copy cache */
294         System.arraycopy(mySource.outputCache, 0, outputCache, 0, outputCache.length);
295 
296         /* Declare extended configuration */
297         declareConfig();
298     }
299 
300     @Override
301     public GordianSkeinXof copy() {
302         return new GordianSkeinXof(this);
303     }
304 }