2 // SymmetricKeyWrap.cs - Implements symmetric key wrap algorithms
5 // Tim Coleman (tim@timcoleman.com)
7 // Copyright (C) Tim Coleman, 2004
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System
.Security
.Cryptography
;
36 namespace System
.Security
.Cryptography
.Xml
{
38 internal class SymmetricKeyWrap
{
40 public SymmetricKeyWrap ()
44 public static byte[] AESKeyWrapEncrypt (byte[] rgbKey
, byte[] rgbWrappedKeyData
)
46 SymmetricAlgorithm symAlg
= SymmetricAlgorithm
.Create ("Rijndael");
48 // Apparently no one felt the need to document that this requires Electronic Codebook mode.
49 symAlg
.Mode
= CipherMode
.ECB
;
51 // This was also not documented anywhere.
52 symAlg
.IV
= new byte [16] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
;
54 ICryptoTransform transform
= symAlg
.CreateEncryptor (rgbKey
, symAlg
.IV
);
56 int N
= rgbWrappedKeyData
.Length
/ 8;
58 byte[] B
= new Byte
[16];
59 byte [] C
= new byte [8 * (N
+ 1)];
62 // B = AES(K)enc(0xA6A6A6A6A6A6A6A6|P(1))
66 A
= new byte [8] {0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6}
;
67 transform
.TransformBlock (Concatenate (A
, rgbWrappedKeyData
), 0, 16, B
, 0);
68 Buffer
.BlockCopy (MSB(B
), 0, C
, 0, 8);
69 Buffer
.BlockCopy (LSB(B
), 0, C
, 8, 8);
71 // if N > 1, perform the following steps:
72 // 2. Initialize variables:
73 // Set A to 0xA6A6A6A6A6A6A6A6
76 A
= new byte [8] {0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6}
;
78 byte[][] R
= new byte [N
+ 1][];
79 for (int i
= 1; i
<= N
; i
+= 1) {
81 Buffer
.BlockCopy (rgbWrappedKeyData
, 8 * (i
- 1), R
[i
], 0, 8);
84 // 3. Calculate intermediate values:
88 // B = AES(K)enc(A|R(i))
92 for (int j
= 0; j
<= 5; j
+= 1) {
93 for (int i
= 1; i
<= N
; i
+= 1) {
94 transform
.TransformBlock (Concatenate (A
, R
[i
]), 0, 16, B
, 0);
96 // Yawn. It was nice of those at NIST to document how exactly we should XOR
97 // an integer value with a byte array. Not.
98 byte[] T
= BitConverter
.GetBytes ((long) (N
* j
+ i
));
101 if (BitConverter
.IsLittleEndian
)
109 // 4. Output the results:
113 Buffer
.BlockCopy (A
, 0, C
, 0, 8);
114 for (int i
= 1; i
<= N
; i
+= 1)
115 Buffer
.BlockCopy (R
[i
], 0, C
, 8 * i
, 8);
120 public static byte[] AESKeyWrapDecrypt (byte[] rgbKey
, byte[] rgbEncryptedWrappedKeyData
)
122 SymmetricAlgorithm symAlg
= SymmetricAlgorithm
.Create ("Rijndael");
123 symAlg
.Mode
= CipherMode
.ECB
;
126 int N
= ( rgbEncryptedWrappedKeyData
.Length
/ 8 ) - 1;
128 // From RFC 3394 - Advanced Encryption Standard (AES) Key Wrap Algorithm
130 // Inputs: Ciphertext, (n+1) 64-bit values (C0, C1, ..., Cn), and Key, K (the KEK)
131 // Outputs: Plaintext, n 64-bit values (P1, P2, ..., Pn)
133 // 1. Initialize variables.
136 byte[] A
= new byte [8];
137 Buffer
.BlockCopy (rgbEncryptedWrappedKeyData
, 0, A
, 0, 8);
142 byte[] R
= new byte [N
* 8];
143 Buffer
.BlockCopy (rgbEncryptedWrappedKeyData
, 8, R
, 0, rgbEncryptedWrappedKeyData
.Length
- 8);
145 // 2. Compute intermediate values.
148 // B = AES-1(K, (A^t) | R[i]) where t = n*j+i
152 ICryptoTransform transform
= symAlg
.CreateDecryptor ();
154 for (int j
= 5; j
>= 0; j
-= 1) {
155 for (int i
= N
; i
>= 1; i
-= 1) {
156 byte[] T
= BitConverter
.GetBytes ((long) N
* j
+ i
);
157 if (BitConverter
.IsLittleEndian
)
160 byte[] B
= new Byte
[16];
161 byte[] r
= new Byte
[8];
162 Buffer
.BlockCopy (R
, 8 * (i
- 1), r
, 0, 8);
163 byte[] ciphertext
= Concatenate (Xor (A
, T
), r
);
164 transform
.TransformBlock (ciphertext
, 0, 16, B
, 0);
166 Buffer
.BlockCopy (LSB (B
), 0, R
, 8 * (i
- 1), 8);
171 // If A is an appropriate initial value
181 public static byte[] TripleDESKeyWrapEncrypt (byte[] rgbKey
, byte[] rgbWrappedKeyData
)
183 SymmetricAlgorithm symAlg
= SymmetricAlgorithm
.Create ("TripleDES");
185 // Algorithm from http://www.w3.org/TR/xmlenc-core/#sec-Alg-SymmetricKeyWrap
186 // The following algorithm wraps (encrypts) a key (the wrapped key, WK) under a TRIPLEDES
187 // key-encryption-key (KEK) as adopted from [CMS-Algorithms].
189 // 1. Represent the key being wrapped as an octet sequence. If it is a TRIPLEDES key,
190 // this is 24 octets (192 bits) with odd parity bit as the bottom bit of each octet.
192 // rgbWrappedKeyData is the key being wrapped.
194 // 2. Compute the CMS key checksum (Section 5.6.1) call this CKS.
196 byte[] cks
= ComputeCMSKeyChecksum (rgbWrappedKeyData
);
198 // 3. Let WKCKS = WK || CKS, where || is concatenation.
200 byte[] wkcks
= Concatenate (rgbWrappedKeyData
, cks
);
202 // 4. Generate 8 random octets and call this IV.
203 symAlg
.GenerateIV ();
205 // 5. Encrypt WKCKS in CBC mode using KEK as the key and IV as the initialization vector.
206 // Call the results TEMP1.
208 symAlg
.Mode
= CipherMode
.CBC
;
209 symAlg
.Padding
= PaddingMode
.None
;
211 byte[] temp1
= Transform (wkcks
, symAlg
.CreateEncryptor ());
213 // 6. Let TEMP2 = IV || TEMP1.
215 byte[] temp2
= Concatenate (symAlg
.IV
, temp1
);
217 // 7. Reverse the order of the octets in TEMP2 and call the result TEMP3.
219 Array
.Reverse (temp2
); // TEMP3 is TEMP2
221 // 8. Encrypt TEMP3 in CBC mode using the KEK and an initialization vector of 0x4adda22c79e82105.
222 // The resulting cipher text is the desired result. It is 40 octets long if a 168 bit key
225 symAlg
.IV
= new Byte
[8] {0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05}
;
227 byte[] rtnval
= Transform (temp2
, symAlg
.CreateEncryptor ());
232 public static byte[] TripleDESKeyWrapDecrypt (byte[] rgbKey
, byte[] rgbEncryptedWrappedKeyData
)
234 SymmetricAlgorithm symAlg
= SymmetricAlgorithm
.Create ("TripleDES");
236 // Algorithm from http://www.w3.org/TR/xmlenc-core/#sec-Alg-SymmetricKeyWrap
237 // The following algorithm unwraps (decrypts) a key as adopted from [CMS-Algorithms].
239 // 1. Check the length of the cipher text is reasonable given the key type. It must be
240 // 40 bytes for a 168 bit key and either 32, 40, or 48 bytes for a 128, 192, or 256 bit
241 // key. If the length is not supported or inconsistent with the algorithm for which the
242 // key is intended, return error.
244 // 2. Decrypt the cipher text with TRIPLEDES in CBC mode using the KEK and an initialization
245 // vector (IV) of 0x4adda22c79e82105. Call the output TEMP3.
247 symAlg
.Mode
= CipherMode
.CBC
;
248 symAlg
.Padding
= PaddingMode
.None
;
250 symAlg
.IV
= new Byte
[8] {0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05}
;
252 byte[] temp3
= Transform (rgbEncryptedWrappedKeyData
, symAlg
.CreateDecryptor ());
254 // 3. Reverse the order of the octets in TEMP3 and call the result TEMP2.
256 Array
.Reverse (temp3
); // TEMP2 is TEMP3.
258 // 4. Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets.
260 byte[] temp1
= new Byte
[temp3
.Length
- 8];
261 byte[] iv
= new Byte
[8];
263 Buffer
.BlockCopy (temp3
, 0, iv
, 0, 8);
264 Buffer
.BlockCopy (temp3
, 8, temp1
, 0, temp1
.Length
);
266 // 5. Decrypt TEMP1 using TRIPLEDES in CBC mode using the KEK and the IV found in the previous step.
267 // Call the result WKCKS.
270 byte[] wkcks
= Transform (temp1
, symAlg
.CreateDecryptor ());
272 // 6. Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are those octets before
275 byte[] cks
= new byte [8];
276 byte[] wk
= new byte [wkcks
.Length
- 8];
278 Buffer
.BlockCopy (wkcks
, 0, wk
, 0, wk
.Length
);
279 Buffer
.BlockCopy (wkcks
, wk
.Length
, cks
, 0, 8);
281 // 7. Calculate the CMS key checksum over the WK and compare with the CKS extracted in the above
282 // step. If they are not equal, return error.
284 // 8. WK is the wrapped key, now extracted for use in data decryption.
288 private static byte[] Transform (byte[] data
, ICryptoTransform t
)
290 MemoryStream output
= new MemoryStream ();
291 CryptoStream crypto
= new CryptoStream (output
, t
, CryptoStreamMode
.Write
);
293 crypto
.Write (data
, 0, data
.Length
);
294 crypto
.FlushFinalBlock ();
296 byte[] result
= output
.ToArray ();
304 private static byte[] ComputeCMSKeyChecksum (byte[] data
)
306 byte[] hash
= HashAlgorithm
.Create ("SHA1").ComputeHash (data
);
307 byte[] output
= new byte [8];
309 Buffer
.BlockCopy (hash
, 0, output
, 0, 8);
314 private static byte[] Concatenate (byte[] buf1
, byte[] buf2
)
316 byte[] output
= new byte [buf1
.Length
+ buf2
.Length
];
317 Buffer
.BlockCopy (buf1
, 0, output
, 0, buf1
.Length
);
318 Buffer
.BlockCopy (buf2
, 0, output
, buf1
.Length
, buf2
.Length
);
322 private static byte[] MSB (byte[] input
)
324 return MSB (input
, 8);
327 private static byte[] MSB (byte[] input
, int bytes
)
329 byte[] output
= new byte [bytes
];
330 Buffer
.BlockCopy (input
, 0, output
, 0, bytes
);
334 private static byte[] LSB (byte[] input
)
336 return LSB (input
, 8);
339 private static byte[] LSB (byte[] input
, int bytes
)
341 byte[] output
= new byte [bytes
];
342 Buffer
.BlockCopy (input
, bytes
, output
, 0, bytes
);
346 private static byte[] Xor (byte[] x
, byte[] y
)
348 // This should *not* happen.
349 if (x
.Length
!= y
.Length
)
350 throw new CryptographicException ("Error performing Xor: arrays different length.");
352 byte[] output
= new byte [x
.Length
];
353 for (int i
= 0; i
< x
.Length
; i
+= 1)
354 output
[i
] = (byte) (x
[i
] ^ y
[i
]);
358 private static byte[] Xor (byte[] x
, int n
)
360 byte[] output
= new Byte
[x
.Length
];
361 for (int i
= 0; i
< x
.Length
; i
+= 1)
362 output
[i
] = (byte) ((int) x
[i
] ^ n
);