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 org.bouncycastle.crypto.BlockCipher;
20  import org.bouncycastle.crypto.CipherParameters;
21  import org.bouncycastle.crypto.params.KeyParameter;
22  import org.bouncycastle.util.Pack;
23  
24  /**
25   * Speck Cipher engine.
26   * <p>Cut down version of Tim Whittington's implementation available at
27   * https://github.com/timw/bc-java/blob/feature/simon-speck/core/src/main/java/org/bouncycastle/crypto/engines/SpeckEngine.java
28   * </p>
29   */
30  public class GordianSpeckEngine
31          implements BlockCipher {
32      /**
33       * Base number of rounds.
34       */
35      private static final int BASEROUNDS = 32;
36  
37      /**
38       * Number of words in state.
39       */
40      private static final int NUMWORDS = 2;
41  
42      /**
43       * BlockSize.
44       */
45      private static final int BLOCKSIZE = NUMWORDS * Long.BYTES;
46  
47      /**
48       * Rotate3.
49       */
50      private static final int ROT3 = 3;
51  
52      /**
53       * Rotate8.
54       */
55      private static final int ROT8 = 8;
56  
57      /**
58       * The # of rounds.
59       */
60      private int theRounds;
61  
62      /**
63       * The expanded key schedule.
64       */
65      private long[] theRoundKeys;
66  
67      /**
68       * Are we encrypting?
69       */
70      private boolean forEncryption;
71  
72      @Override
73      public void init(final boolean pEncrypt,
74                       final CipherParameters pParams) {
75          /* Reject invalid parameters */
76          if (!(pParams instanceof KeyParameter)) {
77              throw new IllegalArgumentException("Invalid parameter passed to Speck init - "
78                      + pParams.getClass().getName());
79          }
80  
81          /* Validate keyLength */
82          final byte[] myKey = ((KeyParameter) pParams).getKey();
83          final int myKeyLen = myKey.length;
84          if ((((myKeyLen << 1) % BLOCKSIZE) != 0)
85                  || myKeyLen < BLOCKSIZE
86                  || myKeyLen > (BLOCKSIZE << 1)) {
87              throw new IllegalArgumentException("KeyBitSize must be 128, 192 or 256");
88          }
89  
90          /* Generate the round keys */
91          forEncryption = pEncrypt;
92          generateRoundKeys(myKey);
93      }
94  
95      @Override
96      public void reset() {
97          /* NoOp */
98      }
99  
100     @Override
101     public String getAlgorithmName() {
102         return "Speck";
103     }
104 
105     @Override
106     public int getBlockSize() {
107         return BLOCKSIZE;
108     }
109 
110     @Override
111     public int processBlock(final byte[] pInput,
112                             final int pInOff,
113                             final byte[] pOutput,
114                             final int pOutOff) {
115         /* Check buffers */
116         if (pInput == null || pInput.length - pInOff < BLOCKSIZE) {
117             throw new IllegalArgumentException("Invalid input buffer");
118         }
119         if (pOutput == null || pOutput.length - pOutOff < BLOCKSIZE) {
120             throw new IllegalArgumentException("Invalid output buffer");
121         }
122 
123         /* Perform the encryption/decryption */
124         return forEncryption
125                 ? encryptBlock(pInput, pInOff, pOutput, pOutOff)
126                 : decryptBlock(pInput, pInOff, pOutput, pOutOff);
127     }
128 
129     /**
130      * Encrypt a block.
131      *
132      * @param pInput  the input buffer
133      * @param pInOff  the input offset
134      * @param pOutput the output offset
135      * @param pOutOff the output offset
136      * @return the bytes processed
137      */
138     private int encryptBlock(final byte[] pInput,
139                              final int pInOff,
140                              final byte[] pOutput,
141                              final int pOutOff) {
142         /* Load the bytes into the block */
143         long myX = Pack.bigEndianToLong(pInput, pInOff);
144         long myY = Pack.bigEndianToLong(pInput, pInOff + Long.BYTES);
145 
146         /* Loop through the rounds */
147         for (int i = 0; i < theRounds; i++) {
148             /* Perform the encryption round */
149             myX = (ror64(myX, ROT8) + myY) ^ theRoundKeys[i];
150             myY = rol64(myY, ROT3) ^ myX;
151         }
152 
153         /* Output the bytes from the block */
154         Pack.longToBigEndian(myX, pOutput, pOutOff);
155         Pack.longToBigEndian(myY, pOutput, pOutOff + Long.BYTES);
156 
157         /* Return # of bytes processed */
158         return BLOCKSIZE;
159     }
160 
161     /**
162      * Decrypt a block.
163      *
164      * @param pInput  the input buffer
165      * @param pInOff  the input offset
166      * @param pOutput the output offset
167      * @param pOutOff the output offset
168      * @return the bytes processed
169      */
170     private int decryptBlock(final byte[] pInput,
171                              final int pInOff,
172                              final byte[] pOutput,
173                              final int pOutOff) {
174         /* Load the bytes into the block */
175         long myX = Pack.bigEndianToLong(pInput, pInOff);
176         long myY = Pack.bigEndianToLong(pInput, pInOff + Long.BYTES);
177 
178         /* Loop through the rounds */
179         for (int i = theRounds - 1; i >= 0; i--) {
180             /* Perform the decryption round */
181             myY = ror64(myX ^ myY, ROT3);
182             myX = rol64((myX ^ theRoundKeys[i]) - myY, ROT8);
183         }
184 
185         /* Output the bytes from the block */
186         Pack.longToBigEndian(myX, pOutput, pOutOff);
187         Pack.longToBigEndian(myY, pOutput, pOutOff + Long.BYTES);
188 
189         /* Return # of bytes processed */
190         return BLOCKSIZE;
191     }
192 
193     /**
194      * Generate the round keys.
195      *
196      * @param pKey the key
197      */
198     private void generateRoundKeys(final byte[] pKey) {
199         /* Determine number of key words */
200         final int numWords = pKey.length / Long.BYTES;
201 
202         /* Number of rounds is increased by 1 for each key word > 2 */
203         theRounds = BASEROUNDS + (numWords - 2);
204         theRoundKeys = new long[theRounds];
205 
206         /* Load base key */
207         theRoundKeys[0] = Pack.bigEndianToLong(pKey, (numWords - 1) * Long.BYTES);
208 
209         /* Load remaining key bytes */
210         final long[] myL = new long[numWords];
211         for (int i = 0; i < numWords - 1; i++) {
212             myL[i] = Pack.bigEndianToLong(pKey, (numWords - i - 2) * Long.BYTES);
213         }
214 
215         /* Key expansion using round function with round number as key */
216         for (int i = 0; i < theRounds - 1; i++) {
217             final int lw = (i + numWords - 1) % numWords;
218             myL[lw] = (ror64(myL[i % numWords], ROT8) + theRoundKeys[i]) ^ i;
219             theRoundKeys[i + 1] = rol64(theRoundKeys[i], ROT3) ^ myL[lw];
220         }
221     }
222 
223     /**
224      * rotate left.
225      *
226      * @param pValue the value to rotate
227      * @param pBits  the # of bits to rotate
228      * @return the rotated value
229      */
230     private static long rol64(final long pValue,
231                               final long pBits) {
232         return (pValue << pBits) | (pValue >>> (Long.SIZE - pBits));
233     }
234 
235     /**
236      * rotate right.
237      *
238      * @param pValue the value to rotate
239      * @param pBits  the # of bits to rotate
240      * @return the rotated value
241      */
242     private static long ror64(final long pValue,
243                               final long pBits) {
244         return (pValue >>> pBits) | (pValue << (Long.SIZE - pBits));
245     }
246 }