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   * Simon 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/SimonEngine.java
28   * </p>
29   */
30  public class GordianSimonEngine
31          implements BlockCipher {
32      /**
33       * Number of rounds.
34       */
35      private static final byte[] ROUNDS = {68, 69, 72};
36  
37      /**
38       * Pre-computed z0...z4 round constants.
39       */
40      private static final byte[][] Z = {
41              {1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0,
42                      0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1,
43                      0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0},
44              {1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0,
45                      0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
46                      1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0},
47              {1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
48                      1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0,
49                      0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1},
50              {1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
51                      1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0,
52                      1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1},
53              {1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0,
54                      1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0,
55                      1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1}
56      };
57  
58      /**
59       * Number of words in state.
60       */
61      private static final int NUMWORDS = 2;
62  
63      /**
64       * BlockSize.
65       */
66      private static final int BLOCKSIZE = NUMWORDS * Long.BYTES;
67  
68      /**
69       * Number of words in 128-bit key.
70       */
71      private static final int NUMWORDS128 = 2;
72  
73      /**
74       * Number of words in 192-bit key.
75       */
76      private static final int NUMWORDS192 = 3;
77  
78      /**
79       * Number of words in 256-bit key.
80       */
81      private static final int NUMWORDS256 = 4;
82  
83      /**
84       * Rotate1.
85       */
86      private static final int ROT1 = 1;
87  
88      /**
89       * Rotate2.
90       */
91      private static final int ROT2 = 2;
92  
93      /**
94       * Rotate3.
95       */
96      private static final int ROT3 = 3;
97  
98      /**
99       * Rotate8.
100      */
101     private static final int ROT8 = 8;
102 
103     /**
104      * The # of rounds.
105      */
106     private int theRounds;
107 
108     /**
109      * The expanded key schedule.
110      */
111     private long[] theRoundKeys;
112 
113     /**
114      * Are we encrypting?
115      */
116     private boolean forEncryption;
117 
118     @Override
119     public void init(final boolean pEncrypt,
120                      final CipherParameters pParams) {
121         /* Reject invalid parameters */
122         if (!(pParams instanceof KeyParameter)) {
123             throw new IllegalArgumentException("Invalid parameter passed to Speck init - "
124                     + pParams.getClass().getName());
125         }
126 
127         /* Validate keyLength */
128         final byte[] myKey = ((KeyParameter) pParams).getKey();
129         final int myKeyLen = myKey.length;
130         if ((((myKeyLen << 1) % BLOCKSIZE) != 0)
131                 || myKeyLen < BLOCKSIZE
132                 || myKeyLen > (BLOCKSIZE << 1)) {
133             throw new IllegalArgumentException("KeyBitSize must be 128, 192 or 256");
134         }
135 
136         /* Generate the round keys */
137         forEncryption = pEncrypt;
138         generateRoundKeys(myKey);
139     }
140 
141     @Override
142     public void reset() {
143         /* NoOp */
144     }
145 
146     @Override
147     public String getAlgorithmName() {
148         return "Simon";
149     }
150 
151     @Override
152     public int getBlockSize() {
153         return BLOCKSIZE;
154     }
155 
156     @Override
157     public int processBlock(final byte[] pInput,
158                             final int pInOff,
159                             final byte[] pOutput,
160                             final int pOutOff) {
161         /* Check buffers */
162         if (pInput == null || pInput.length - pInOff < BLOCKSIZE) {
163             throw new IllegalArgumentException("Invalid input buffer");
164         }
165         if (pOutput == null || pOutput.length - pOutOff < BLOCKSIZE) {
166             throw new IllegalArgumentException("Invalid output buffer");
167         }
168 
169         /* Perform the encryption/decryption */
170         return forEncryption
171                 ? encryptBlock(pInput, pInOff, pOutput, pOutOff)
172                 : decryptBlock(pInput, pInOff, pOutput, pOutOff);
173     }
174 
175     /**
176      * Encrypt a block.
177      *
178      * @param pInput  the input buffer
179      * @param pInOff  the input offset
180      * @param pOutput the output offset
181      * @param pOutOff the output offset
182      * @return the bytes processed
183      */
184     private int encryptBlock(final byte[] pInput,
185                              final int pInOff,
186                              final byte[] pOutput,
187                              final int pOutOff) {
188         /* Load the bytes into the block */
189         long myX = Pack.bigEndianToLong(pInput, pInOff);
190         long myY = Pack.bigEndianToLong(pInput, pInOff + Long.BYTES);
191 
192         /* Loop through the rounds */
193         for (int i = 0; i < theRounds; i++) {
194             /* Perform the encryption round */
195             final long myTmp = myX;
196             myX = myY ^ (rol64(myX, ROT1) & rol64(myX, ROT8)) ^ rol64(myX, ROT2) ^ theRoundKeys[i];
197             myY = myTmp;
198         }
199 
200         /* Output the bytes from the block */
201         Pack.longToBigEndian(myX, pOutput, pOutOff);
202         Pack.longToBigEndian(myY, pOutput, pOutOff + Long.BYTES);
203 
204         /* Return # of bytes processed */
205         return BLOCKSIZE;
206     }
207 
208     /**
209      * Decrypt a block.
210      *
211      * @param pInput  the input buffer
212      * @param pInOff  the input offset
213      * @param pOutput the output offset
214      * @param pOutOff the output offset
215      * @return the bytes processed
216      */
217     private int decryptBlock(final byte[] pInput,
218                              final int pInOff,
219                              final byte[] pOutput,
220                              final int pOutOff) {
221         /* Load the bytes into the block */
222         long myX = Pack.bigEndianToLong(pInput, pInOff);
223         long myY = Pack.bigEndianToLong(pInput, pInOff + Long.BYTES);
224 
225         /* Loop through the rounds */
226         for (int i = theRounds - 1; i >= 0; i--) {
227             /* Perform the decryption round */
228             final long myTmp = myY;
229             myY = myX ^ (rol64(myY, ROT1) & rol64(myY, ROT8)) ^ rol64(myY, ROT2) ^ theRoundKeys[i];
230             myX = myTmp;
231         }
232 
233         /* Output the bytes from the block */
234         Pack.longToBigEndian(myX, pOutput, pOutOff);
235         Pack.longToBigEndian(myY, pOutput, pOutOff + Long.BYTES);
236 
237         /* Return # of bytes processed */
238         return BLOCKSIZE;
239     }
240 
241     /**
242      * Generate the round keys.
243      *
244      * @param pKey the key
245      */
246     private void generateRoundKeys(final byte[] pKey) {
247         /* Determine number of key words */
248         final int numWords = pKey.length / Long.BYTES;
249         final byte[] myConstants = Z[numWords];
250 
251         /* Determine # of rounds and allocate round keys */
252         theRounds = ROUNDS[numWords - NUMWORDS128];
253         theRoundKeys = new long[theRounds];
254 
255         /* Load the key */
256         for (int i = 0; i < numWords; i++) {
257             theRoundKeys[i] = Pack.bigEndianToLong(pKey, (numWords - i - 1) * Long.BYTES);
258         }
259 
260         /* Key expansion */
261         for (int i = numWords; i < theRounds; i++) {
262             long tmp = ror64(theRoundKeys[i - 1], ROT3);
263             if (numWords == NUMWORDS256) {
264                 tmp ^= theRoundKeys[i - NUMWORDS192];
265             }
266             tmp = tmp ^ ror64(tmp, ROT1);
267             theRoundKeys[i] = tmp ^ theRoundKeys[i - numWords]
268                     ^ myConstants[(i - numWords) % myConstants.length] ^ ~NUMWORDS192;
269         }
270     }
271 
272     /**
273      * rotate left.
274      *
275      * @param pValue the value to rotate
276      * @param pBits  the # of bits to rotate
277      * @return the rotated value
278      */
279     private static long rol64(final long pValue,
280                               final long pBits) {
281         return (pValue << pBits) | (pValue >>> (Long.SIZE - pBits));
282     }
283 
284     /**
285      * rotate right.
286      *
287      * @param pValue the value to rotate
288      * @param pBits  the # of bits to rotate
289      * @return the rotated value
290      */
291     private static long ror64(final long pValue,
292                               final long pBits) {
293         return (pValue >>> pBits) | (pValue << (Long.SIZE - pBits));
294     }
295 }