Welcome to mirror list, hosted at ThFree Co, Russian Federation.

RC2WrapEngine.java « engines « crypto « spongycastle « org « java « main « src « core - gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 4203954e624d548f5b8578c94e69b5526df0b2f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
package org.spongycastle.crypto.engines;

import java.security.SecureRandom;

import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.InvalidCipherTextException;
import org.spongycastle.crypto.Wrapper;
import org.spongycastle.crypto.digests.SHA1Digest;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.params.ParametersWithIV;
import org.spongycastle.crypto.params.ParametersWithRandom;
import org.spongycastle.util.Arrays;

/**
 * Wrap keys according to RFC 3217 - RC2 mechanism
 */
public class RC2WrapEngine
    implements Wrapper
{
   /** Field engine */
   private CBCBlockCipher engine;

   /** Field param */
   private CipherParameters param;

   /** Field paramPlusIV */
   private ParametersWithIV paramPlusIV;

   /** Field iv */
   private byte[] iv;

   /** Field forWrapping */
   private boolean forWrapping;
   
   private SecureRandom sr;

   /** Field IV2           */
   private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2,
                                       (byte) 0x2c, (byte) 0x79, (byte) 0xe8,
                                       (byte) 0x21, (byte) 0x05 };

    //
    // checksum digest
    //
    Digest  sha1 = new SHA1Digest();
    byte[]  digest = new byte[20];

   /**
    * Method init
    *
    * @param forWrapping
    * @param param
    */
   public void init(boolean forWrapping, CipherParameters param)
   {
        this.forWrapping = forWrapping;
        this.engine = new CBCBlockCipher(new RC2Engine());

        if (param instanceof ParametersWithRandom)
        {
            ParametersWithRandom pWithR = (ParametersWithRandom)param;
            sr = pWithR.getRandom();
            param = pWithR.getParameters();
        }
        else
        {
            sr = new SecureRandom();
        }
        
        if (param instanceof ParametersWithIV)
        {
            this.paramPlusIV = (ParametersWithIV)param;
            this.iv = this.paramPlusIV.getIV();
            this.param = this.paramPlusIV.getParameters();

            if (this.forWrapping)
            {
                if ((this.iv == null) || (this.iv.length != 8))
                {
                    throw new IllegalArgumentException("IV is not 8 octets");
                }
            }
            else
            {
                throw new IllegalArgumentException(
                        "You should not supply an IV for unwrapping");
            }
        }
        else
        {
            this.param = param;

            if (this.forWrapping)
            {

                // Hm, we have no IV but we want to wrap ?!?
                // well, then we have to create our own IV.
                this.iv = new byte[8];

                sr.nextBytes(iv);

                this.paramPlusIV = new ParametersWithIV(this.param, this.iv);
            }
        }

   }

   /**
    * Method getAlgorithmName
    *
    * @return the algorithm name "RC2".
    */
   public String getAlgorithmName() 
   {
      return "RC2";
   }

   /**
    * Method wrap
    *
    * @param in
    * @param inOff
    * @param inLen
    * @return the wrapped bytes.
    */
   public byte[] wrap(byte[] in, int inOff, int inLen)
    {

        if (!forWrapping)
        {
            throw new IllegalStateException("Not initialized for wrapping");
        }

        int length = inLen + 1;
        if ((length % 8) != 0)
        {
            length += 8 - (length % 8);
        }

        byte keyToBeWrapped[] = new byte[length];

        keyToBeWrapped[0] = (byte)inLen;
        System.arraycopy(in, inOff, keyToBeWrapped, 1, inLen);
        
        byte[] pad = new byte[keyToBeWrapped.length - inLen - 1];

        if (pad.length > 0)
        {
            sr.nextBytes(pad);
            System.arraycopy(pad, 0, keyToBeWrapped, inLen + 1, pad.length);
        }

        // Compute the CMS Key Checksum, (section 5.6.1), call this CKS.
        byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped);

        // Let WKCKS = WK || CKS where || is concatenation.
        byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length];

        System.arraycopy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.length);
        System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length, CKS.length);

        // Encrypt WKCKS in CBC mode using KEK as the key and IV as the
        // initialization vector. Call the results TEMP1.
        byte TEMP1[] = new byte[WKCKS.length];

        System.arraycopy(WKCKS, 0, TEMP1, 0, WKCKS.length);

        int noOfBlocks = WKCKS.length / engine.getBlockSize();
        int extraBytes = WKCKS.length % engine.getBlockSize();

        if (extraBytes != 0)
        {
            throw new IllegalStateException("Not multiple of block length");
        }

        engine.init(true, paramPlusIV);

        for (int i = 0; i < noOfBlocks; i++)
        {
            int currentBytePos = i * engine.getBlockSize();

            engine.processBlock(TEMP1, currentBytePos, TEMP1, currentBytePos);
        }

        // Left TEMP2 = IV || TEMP1.
        byte[] TEMP2 = new byte[this.iv.length + TEMP1.length];

        System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length);
        System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length);

        // Reverse the order of the octets in TEMP2 and call the result TEMP3.
        byte[] TEMP3 = new byte[TEMP2.length];

        for (int i = 0; i < TEMP2.length; i++)
        {
            TEMP3[i] = TEMP2[TEMP2.length - (i + 1)];
        }

        // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector
        // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the
        // desired
        // result. It is 40 octets long if a 168 bit key is being wrapped.
        ParametersWithIV param2 = new ParametersWithIV(this.param, IV2);

        this.engine.init(true, param2);

        for (int i = 0; i < noOfBlocks + 1; i++)
        {
            int currentBytePos = i * engine.getBlockSize();

            engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos);
        }

        return TEMP3;
   }

   /**
    * Method unwrap
    *
    * @param in
    * @param inOff
    * @param inLen
    * @return the unwrapped bytes.
    * @throws InvalidCipherTextException
    */
   public byte[] unwrap(byte[] in, int inOff, int inLen)
            throws InvalidCipherTextException
    {

        if (forWrapping)
        {
            throw new IllegalStateException("Not set for unwrapping");
        }

        if (in == null)
        {
            throw new InvalidCipherTextException("Null pointer as ciphertext");
        }

        if (inLen % engine.getBlockSize() != 0)
        {
            throw new InvalidCipherTextException("Ciphertext not multiple of "
                    + engine.getBlockSize());
        }

        /*
         * // Check if the length of the cipher text is reasonable given the key //
         * type. It must be 40 bytes for a 168 bit key and either 32, 40, or //
         * 48 bytes for a 128, 192, or 256 bit key. If the length is not
         * supported // or inconsistent with the algorithm for which the key is
         * intended, // return error. // // we do not accept 168 bit keys. it
         * has to be 192 bit. int lengthA = (estimatedKeyLengthInBit / 8) + 16;
         * int lengthB = estimatedKeyLengthInBit % 8;
         * 
         * if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) { throw
         * new XMLSecurityException("empty"); }
         */

        // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK
        // and an initialization vector (IV) of 0x4adda22c79e82105. Call the
        // output TEMP3.
        ParametersWithIV param2 = new ParametersWithIV(this.param, IV2);

        this.engine.init(false, param2);

        byte TEMP3[] = new byte[inLen];

        System.arraycopy(in, inOff, TEMP3, 0, inLen);

        for (int i = 0; i < (TEMP3.length / engine.getBlockSize()); i++)
        {
            int currentBytePos = i * engine.getBlockSize();

            engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos);
        }

        // Reverse the order of the octets in TEMP3 and call the result TEMP2.
        byte[] TEMP2 = new byte[TEMP3.length];

        for (int i = 0; i < TEMP3.length; i++)
        {
            TEMP2[i] = TEMP3[TEMP3.length - (i + 1)];
        }

        // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining
        // octets.
        this.iv = new byte[8];

        byte[] TEMP1 = new byte[TEMP2.length - 8];

        System.arraycopy(TEMP2, 0, this.iv, 0, 8);
        System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8);

        // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV
        // found in the previous step. Call the result WKCKS.
        this.paramPlusIV = new ParametersWithIV(this.param, this.iv);

        this.engine.init(false, this.paramPlusIV);

        byte[] LCEKPADICV = new byte[TEMP1.length];

        System.arraycopy(TEMP1, 0, LCEKPADICV, 0, TEMP1.length);

        for (int i = 0; i < (LCEKPADICV.length / engine.getBlockSize()); i++)
        {
            int currentBytePos = i * engine.getBlockSize();

            engine.processBlock(LCEKPADICV, currentBytePos, LCEKPADICV,
                    currentBytePos);
        }

        // Decompose LCEKPADICV. CKS is the last 8 octets and WK, the wrapped
        // key, are
        // those octets before the CKS.
        byte[] result = new byte[LCEKPADICV.length - 8];
        byte[] CKStoBeVerified = new byte[8];

        System.arraycopy(LCEKPADICV, 0, result, 0, LCEKPADICV.length - 8);
        System.arraycopy(LCEKPADICV, LCEKPADICV.length - 8, CKStoBeVerified, 0,
                8);

        // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and
        // compare
        // with the CKS extracted in the above step. If they are not equal,
        // return error.
        if (!checkCMSKeyChecksum(result, CKStoBeVerified))
        {
            throw new InvalidCipherTextException(
                    "Checksum inside ciphertext is corrupted");
        }

        if ((result.length - ((result[0] & 0xff) + 1)) > 7)
        {
            throw new InvalidCipherTextException("too many pad bytes ("
                    + (result.length - ((result[0] & 0xff) + 1)) + ")");
        }

        // CEK is the wrapped key, now extracted for use in data decryption.
        byte[] CEK = new byte[result[0]];
        System.arraycopy(result, 1, CEK, 0, CEK.length);
        return CEK;
    }

    /**
     * Some key wrap algorithms make use of the Key Checksum defined
     * in CMS [CMS-Algorithms]. This is used to provide an integrity
     * check value for the key being wrapped. The algorithm is
     *
     * - Compute the 20 octet SHA-1 hash on the key being wrapped.
     * - Use the first 8 octets of this hash as the checksum value.
     *
     * For details see  http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
     * @param key
     * @return
     * @throws RuntimeException
     *
     */
    private byte[] calculateCMSKeyChecksum(
        byte[] key)
    {
        byte[]  result = new byte[8];

        sha1.update(key, 0, key.length);
        sha1.doFinal(digest, 0);

        System.arraycopy(digest, 0, result, 0, 8);

        return result;
    }

    /**
     * For details see  http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
     *
     * @param key
     * @param checksum
     * @return
     */
    private boolean checkCMSKeyChecksum(
        byte[] key,
        byte[] checksum)
    {
        return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum);
    }
}