367 lines
11 KiB
C#
367 lines
11 KiB
C#
|
//
|
||
|
// SymmetricKeyWrap.cs - Implements symmetric key wrap algorithms
|
||
|
//
|
||
|
// Author:
|
||
|
// Tim Coleman (tim@timcoleman.com)
|
||
|
//
|
||
|
// Copyright (C) Tim Coleman, 2004
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||
|
// a copy of this software and associated documentation files (the
|
||
|
// "Software"), to deal in the Software without restriction, including
|
||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||
|
// the following conditions:
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be
|
||
|
// included in all copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
//
|
||
|
|
||
|
|
||
|
using System.IO;
|
||
|
using System.Security.Cryptography;
|
||
|
|
||
|
namespace System.Security.Cryptography.Xml {
|
||
|
|
||
|
internal class SymmetricKeyWrap {
|
||
|
|
||
|
public SymmetricKeyWrap ()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public static byte[] AESKeyWrapEncrypt (byte[] rgbKey, byte[] rgbWrappedKeyData)
|
||
|
{
|
||
|
SymmetricAlgorithm symAlg = SymmetricAlgorithm.Create ("Rijndael");
|
||
|
|
||
|
// Apparently no one felt the need to document that this requires Electronic Codebook mode.
|
||
|
symAlg.Mode = CipherMode.ECB;
|
||
|
|
||
|
// This was also not documented anywhere.
|
||
|
symAlg.IV = new byte [16] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||
|
|
||
|
ICryptoTransform transform = symAlg.CreateEncryptor (rgbKey, symAlg.IV);
|
||
|
|
||
|
int N = rgbWrappedKeyData.Length / 8;
|
||
|
byte[] A;
|
||
|
byte[] B = new Byte [16];
|
||
|
byte [] C = new byte [8 * (N + 1)];
|
||
|
|
||
|
// 1. if N is 1:
|
||
|
// B = AES(K)enc(0xA6A6A6A6A6A6A6A6|P(1))
|
||
|
// C(0) = MSB(B)
|
||
|
// C(1) = LSB(B)
|
||
|
if (N == 1) {
|
||
|
A = new byte [8] {0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6};
|
||
|
transform.TransformBlock (Concatenate (A, rgbWrappedKeyData), 0, 16, B, 0);
|
||
|
Buffer.BlockCopy (MSB(B), 0, C, 0, 8);
|
||
|
Buffer.BlockCopy (LSB(B), 0, C, 8, 8);
|
||
|
} else {
|
||
|
// if N > 1, perform the following steps:
|
||
|
// 2. Initialize variables:
|
||
|
// Set A to 0xA6A6A6A6A6A6A6A6
|
||
|
// For i = 1 to N,
|
||
|
// R(i) = P(i)
|
||
|
A = new byte [8] {0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6};
|
||
|
|
||
|
byte[][] R = new byte [N + 1][];
|
||
|
for (int i = 1; i <= N; i += 1) {
|
||
|
R [i] = new byte [8];
|
||
|
Buffer.BlockCopy (rgbWrappedKeyData, 8 * (i - 1), R [i], 0, 8);
|
||
|
}
|
||
|
|
||
|
// 3. Calculate intermediate values:
|
||
|
// For j = 0 to 5
|
||
|
// For i = 1 to N
|
||
|
// t = i + j * N
|
||
|
// B = AES(K)enc(A|R(i))
|
||
|
// A = XOR(t, MSB(B))
|
||
|
// R(i) = LSB(B)
|
||
|
|
||
|
for (int j = 0; j <= 5; j += 1) {
|
||
|
for (int i = 1; i <= N; i += 1) {
|
||
|
transform.TransformBlock (Concatenate (A, R [i]), 0, 16, B, 0);
|
||
|
|
||
|
// Yawn. It was nice of those at NIST to document how exactly we should XOR
|
||
|
// an integer value with a byte array. Not.
|
||
|
byte[] T = BitConverter.GetBytes ((long) (N * j + i));
|
||
|
|
||
|
// This is nice.
|
||
|
if (BitConverter.IsLittleEndian)
|
||
|
Array.Reverse (T);
|
||
|
|
||
|
A = Xor (T, MSB(B));
|
||
|
R [i] = LSB (B);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 4. Output the results:
|
||
|
// Set C(0) = A
|
||
|
// For i = 1 to N
|
||
|
// C(i) = R(i)
|
||
|
Buffer.BlockCopy (A, 0, C, 0, 8);
|
||
|
for (int i = 1; i <= N; i += 1)
|
||
|
Buffer.BlockCopy (R [i], 0, C, 8 * i, 8);
|
||
|
}
|
||
|
return C;
|
||
|
}
|
||
|
|
||
|
public static byte[] AESKeyWrapDecrypt (byte[] rgbKey, byte[] rgbEncryptedWrappedKeyData)
|
||
|
{
|
||
|
SymmetricAlgorithm symAlg = SymmetricAlgorithm.Create ("Rijndael");
|
||
|
symAlg.Mode = CipherMode.ECB;
|
||
|
symAlg.Key = rgbKey;
|
||
|
|
||
|
int N = ( rgbEncryptedWrappedKeyData.Length / 8 ) - 1;
|
||
|
|
||
|
// From RFC 3394 - Advanced Encryption Standard (AES) Key Wrap Algorithm
|
||
|
//
|
||
|
// Inputs: Ciphertext, (n+1) 64-bit values (C0, C1, ..., Cn), and Key, K (the KEK)
|
||
|
// Outputs: Plaintext, n 64-bit values (P1, P2, ..., Pn)
|
||
|
//
|
||
|
// 1. Initialize variables.
|
||
|
// Set A = C[0]
|
||
|
|
||
|
byte[] A = new byte [8];
|
||
|
Buffer.BlockCopy (rgbEncryptedWrappedKeyData, 0, A, 0, 8);
|
||
|
|
||
|
// For i = 1 to n
|
||
|
// R[i] = C[i]
|
||
|
|
||
|
byte[] R = new byte [N * 8];
|
||
|
Buffer.BlockCopy (rgbEncryptedWrappedKeyData, 8, R, 0, rgbEncryptedWrappedKeyData.Length - 8);
|
||
|
|
||
|
// 2. Compute intermediate values.
|
||
|
// For j = 5 to 0
|
||
|
// For i = n to 1
|
||
|
// B = AES-1(K, (A^t) | R[i]) where t = n*j+i
|
||
|
// A = MSB (64,B)
|
||
|
// R[i] = LSB (64,B)
|
||
|
|
||
|
ICryptoTransform transform = symAlg.CreateDecryptor ();
|
||
|
|
||
|
for (int j = 5; j >= 0; j -= 1) {
|
||
|
for (int i = N; i >= 1; i -= 1) {
|
||
|
byte[] T = BitConverter.GetBytes ((long) N * j + i);
|
||
|
if (BitConverter.IsLittleEndian)
|
||
|
Array.Reverse (T);
|
||
|
|
||
|
byte[] B = new Byte [16];
|
||
|
byte[] r = new Byte [8];
|
||
|
Buffer.BlockCopy (R, 8 * (i - 1), r, 0, 8);
|
||
|
byte[] ciphertext = Concatenate (Xor (A, T), r);
|
||
|
transform.TransformBlock (ciphertext, 0, 16, B, 0);
|
||
|
A = MSB (B);
|
||
|
Buffer.BlockCopy (LSB (B), 0, R, 8 * (i - 1), 8);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 3. Output results
|
||
|
// If A is an appropriate initial value
|
||
|
// Then
|
||
|
// For i = 1 to n
|
||
|
// P[i] = R[i]
|
||
|
// Else
|
||
|
// Return an error
|
||
|
|
||
|
return R;
|
||
|
}
|
||
|
|
||
|
public static byte[] TripleDESKeyWrapEncrypt (byte[] rgbKey, byte[] rgbWrappedKeyData)
|
||
|
{
|
||
|
SymmetricAlgorithm symAlg = SymmetricAlgorithm.Create ("TripleDES");
|
||
|
|
||
|
// Algorithm from http://www.w3.org/TR/xmlenc-core/#sec-Alg-SymmetricKeyWrap
|
||
|
// The following algorithm wraps (encrypts) a key (the wrapped key, WK) under a TRIPLEDES
|
||
|
// key-encryption-key (KEK) as adopted from [CMS-Algorithms].
|
||
|
|
||
|
// 1. Represent the key being wrapped as an octet sequence. If it is a TRIPLEDES key,
|
||
|
// this is 24 octets (192 bits) with odd parity bit as the bottom bit of each octet.
|
||
|
|
||
|
// rgbWrappedKeyData is the key being wrapped.
|
||
|
|
||
|
// 2. Compute the CMS key checksum (Section 5.6.1) call this CKS.
|
||
|
|
||
|
byte[] cks = ComputeCMSKeyChecksum (rgbWrappedKeyData);
|
||
|
|
||
|
// 3. Let WKCKS = WK || CKS, where || is concatenation.
|
||
|
|
||
|
byte[] wkcks = Concatenate (rgbWrappedKeyData, cks);
|
||
|
|
||
|
// 4. Generate 8 random octets and call this IV.
|
||
|
symAlg.GenerateIV ();
|
||
|
|
||
|
// 5. Encrypt WKCKS in CBC mode using KEK as the key and IV as the initialization vector.
|
||
|
// Call the results TEMP1.
|
||
|
|
||
|
symAlg.Mode = CipherMode.CBC;
|
||
|
symAlg.Padding = PaddingMode.None;
|
||
|
symAlg.Key = rgbKey;
|
||
|
byte[] temp1 = Transform (wkcks, symAlg.CreateEncryptor ());
|
||
|
|
||
|
// 6. Let TEMP2 = IV || TEMP1.
|
||
|
|
||
|
byte[] temp2 = Concatenate (symAlg.IV, temp1);
|
||
|
|
||
|
// 7. Reverse the order of the octets in TEMP2 and call the result TEMP3.
|
||
|
|
||
|
Array.Reverse (temp2); // TEMP3 is TEMP2
|
||
|
|
||
|
// 8. Encrypt TEMP3 in CBC mode using the KEK and an initialization vector of 0x4adda22c79e82105.
|
||
|
// The resulting cipher text is the desired result. It is 40 octets long if a 168 bit key
|
||
|
// is being wrapped.
|
||
|
|
||
|
symAlg.IV = new Byte [8] {0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05};
|
||
|
|
||
|
byte[] rtnval = Transform (temp2, symAlg.CreateEncryptor ());
|
||
|
|
||
|
return rtnval;
|
||
|
}
|
||
|
|
||
|
public static byte[] TripleDESKeyWrapDecrypt (byte[] rgbKey, byte[] rgbEncryptedWrappedKeyData)
|
||
|
{
|
||
|
SymmetricAlgorithm symAlg = SymmetricAlgorithm.Create ("TripleDES");
|
||
|
|
||
|
// Algorithm from http://www.w3.org/TR/xmlenc-core/#sec-Alg-SymmetricKeyWrap
|
||
|
// The following algorithm unwraps (decrypts) a key as adopted from [CMS-Algorithms].
|
||
|
|
||
|
// 1. Check 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.
|
||
|
|
||
|
// 2. Decrypt the cipher text with TRIPLEDES in CBC mode using the KEK and an initialization
|
||
|
// vector (IV) of 0x4adda22c79e82105. Call the output TEMP3.
|
||
|
|
||
|
symAlg.Mode = CipherMode.CBC;
|
||
|
symAlg.Padding = PaddingMode.None;
|
||
|
symAlg.Key = rgbKey;
|
||
|
symAlg.IV = new Byte [8] {0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05};
|
||
|
|
||
|
byte[] temp3 = Transform (rgbEncryptedWrappedKeyData, symAlg.CreateDecryptor ());
|
||
|
|
||
|
// 3. Reverse the order of the octets in TEMP3 and call the result TEMP2.
|
||
|
|
||
|
Array.Reverse (temp3); // TEMP2 is TEMP3.
|
||
|
|
||
|
// 4. Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets.
|
||
|
|
||
|
byte[] temp1 = new Byte [temp3.Length - 8];
|
||
|
byte[] iv = new Byte [8];
|
||
|
|
||
|
Buffer.BlockCopy (temp3, 0, iv, 0, 8);
|
||
|
Buffer.BlockCopy (temp3, 8, temp1, 0, temp1.Length);
|
||
|
|
||
|
// 5. Decrypt TEMP1 using TRIPLEDES in CBC mode using the KEK and the IV found in the previous step.
|
||
|
// Call the result WKCKS.
|
||
|
|
||
|
symAlg.IV = iv;
|
||
|
byte[] wkcks = Transform (temp1, symAlg.CreateDecryptor ());
|
||
|
|
||
|
// 6. Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are those octets before
|
||
|
// the CKS.
|
||
|
|
||
|
byte[] cks = new byte [8];
|
||
|
byte[] wk = new byte [wkcks.Length - 8];
|
||
|
|
||
|
Buffer.BlockCopy (wkcks, 0, wk, 0, wk.Length);
|
||
|
Buffer.BlockCopy (wkcks, wk.Length, cks, 0, 8);
|
||
|
|
||
|
// 7. Calculate the CMS key checksum over the WK and compare with the CKS extracted in the above
|
||
|
// step. If they are not equal, return error.
|
||
|
|
||
|
// 8. WK is the wrapped key, now extracted for use in data decryption.
|
||
|
return wk;
|
||
|
}
|
||
|
|
||
|
private static byte[] Transform (byte[] data, ICryptoTransform t)
|
||
|
{
|
||
|
MemoryStream output = new MemoryStream ();
|
||
|
CryptoStream crypto = new CryptoStream (output, t, CryptoStreamMode.Write);
|
||
|
|
||
|
crypto.Write (data, 0, data.Length);
|
||
|
crypto.FlushFinalBlock ();
|
||
|
|
||
|
byte[] result = output.ToArray ();
|
||
|
|
||
|
output.Close ();
|
||
|
crypto.Close ();
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private static byte[] ComputeCMSKeyChecksum (byte[] data)
|
||
|
{
|
||
|
byte[] hash = HashAlgorithm.Create ("SHA1").ComputeHash (data);
|
||
|
byte[] output = new byte [8];
|
||
|
|
||
|
Buffer.BlockCopy (hash, 0, output, 0, 8);
|
||
|
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
private static byte[] Concatenate (byte[] buf1, byte[] buf2)
|
||
|
{
|
||
|
byte[] output = new byte [buf1.Length + buf2.Length];
|
||
|
Buffer.BlockCopy (buf1, 0, output, 0, buf1.Length);
|
||
|
Buffer.BlockCopy (buf2, 0, output, buf1.Length, buf2.Length);
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
private static byte[] MSB (byte[] input)
|
||
|
{
|
||
|
return MSB (input, 8);
|
||
|
}
|
||
|
|
||
|
private static byte[] MSB (byte[] input, int bytes)
|
||
|
{
|
||
|
byte[] output = new byte [bytes];
|
||
|
Buffer.BlockCopy (input, 0, output, 0, bytes);
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
private static byte[] LSB (byte[] input)
|
||
|
{
|
||
|
return LSB (input, 8);
|
||
|
}
|
||
|
|
||
|
private static byte[] LSB (byte[] input, int bytes)
|
||
|
{
|
||
|
byte[] output = new byte [bytes];
|
||
|
Buffer.BlockCopy (input, bytes, output, 0, bytes);
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
private static byte[] Xor (byte[] x, byte[] y)
|
||
|
{
|
||
|
// This should *not* happen.
|
||
|
if (x.Length != y.Length)
|
||
|
throw new CryptographicException ("Error performing Xor: arrays different length.");
|
||
|
|
||
|
byte[] output = new byte [x.Length];
|
||
|
for (int i = 0; i < x.Length; i += 1)
|
||
|
output [i] = (byte) (x [i] ^ y [i]);
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
/* private static byte[] Xor (byte[] x, int n)
|
||
|
{
|
||
|
byte[] output = new Byte [x.Length];
|
||
|
for (int i = 0; i < x.Length; i += 1)
|
||
|
output [i] = (byte) ((int) x [i] ^ n);
|
||
|
return output;
|
||
|
}*/
|
||
|
}
|
||
|
}
|
||
|
|