For some applications however, it is more convenient to implement data encryption based on a password-derived symmetric key approach. CAPICOM supports password-derived data encryption with the EncryptedData object. However the format of CAPICOM EncryptedData uses a non-standard ASN format (different from the PKCS#7 EncryptedData content type), the details are not public and currently decryption of the CAPICOM EncryptedData is only supported in CAPICOM. In some cases it is desirable to be able to decrypt a CAPICOM EncryptedData structure without requiring CAPICOM to be installed locally. This requires a basic understanding of some details of the CAPICOM EncryptedData ASN format and how the symmetric key is derived from the CAPICOM EncryptedData contents. This note provides the information necessary to decode and decrypt a CAPICOM EncryptedData using .NET or CryptoAPI alone.
ASN.1 dump of CAPICOM EncryptedData:
The following script code snippet, based on the CAPICOM sample Encrypt.vbs, reads a content file,
specifies RC2 128 bit encryption, and encrypts the data file read into the Content string:
Set EncryptedData = CreateObject("CAPICOM.EncryptedData")
LoadFile InFile, Content
EncryptedData.Content = Content
EncryptedData.SetSecret "mypswd"
EncryptedData.Algorithm.Name = CAPICOM_ENCRYPTION_ALGORITHM_RC2
EncryptedData.Algorithm.KeyLength = CAPICOM_ENCRYPTION_KEY_LENGTH_128_BITS
Message = EncryptedData.Encrypt
The following is a dump of the CAPICOM EncryptedData blob for RC2 128 bit encryption, in binary form, generated
by the sample code above:
0 30 572: SEQUENCE {
4 06 9: OBJECT IDENTIFIER '1 3 6 1 4 1 311 88 3'
15 A0 557: [0] {
19 30 553: SEQUENCE {
23 06 10: OBJECT IDENTIFIER '1 3 6 1 4 1 311 88 3 1'
35 A0 537: [0] {
39 30 533: SEQUENCE {
43 02 3: INTEGER 131072
48 02 2: INTEGER 26114
52 02 2: INTEGER 128
56 04 8: OCTET STRING
: 08 6D D8 A7 80 09 41 9A
66 04 16: OCTET STRING
: 95 F1 7C 8C CC FA 62 18 DF 55 65 F0 04 C7 63 5A
84 04 488: OCTET STRING
: 24 CD 4C 1A FA CF 89 A4 B2 AA B0 B8 A8 E7 C6 C6
: 45 04 84 B6 0A 31 AD BB 84 45 8B EE 51 F7 F9 6E
.... remaining encrypted blob ......
The first two OIDs are
Microsoft-specific OIDs
referring to CAPICOM EncryptedData.
The 1.3.6.1.4.1.311.88.3.1 "CAPICOM content of encrypted data" has sequence members which describe
the encryption algorithm, key size, IV for symmetric encryption, and the Salt value used for
key derivation. So, for the example above:
131072 - CAPICOM version information ??
26114 - RC2 algorithm identifier (CryptoAPI CALG_RC2)
128 - key size (128 bits)
08 6D D8 A7 80 09 41 9A - Initialization Vector for RC2 encryption
95 F1 7C 8C CC FA 62 18 DF 55 65 F0 04 C7 63 5A - Salt value for key derivation
24 CD 4C 1A FA CF 89 A4 B2 AA B0 ............... - Cipher text
How the CAPICOM EncryptedData symmetric key is derived:
The symmetric key used to encrypt data in EncryptedData is derived from
a supplied password as well as a randomly generated Salt value (which is embedded in the EncryptedData blob).
[Note that while .NET provides a PasswordDeriveBytes(String pswd, byte[] salt) object, the
method used by CAPICOM to derive a symmetric key cannot be simulated directly with the current
.NET implemention. Also, the CryptoAPI compatibility method PasswordDeriveBytes.CryptDeriveKey() does not use the salt parameter.]
The CAPICOM password, initialized with oEncryptedData.SetSecret is internally passed
as a UNICODE byte array and is hashed first, followed by the random Salt bytes (extracted
from EncryptedData blob in the same byte-order). Then, for RC2, the first 16 bytes of
the resultant 20 byte SHA1 hash are extracted as the encryption key:
byte[] mysalt = {0x95, 0xF1, 0x7C, 0x8C, 0xCC, 0xFA, 0x62, 0x18,
0xDF, 0x55, 0x65, 0xF0, 0x04, 0xC7, 0x63, 0x5A};
String capicompswd = "mypswd"; //CAPICOM pswd (hashed as UNICODE by EncryptedData)
byte[] pswd = (new UnicodeEncoding()).GetBytes(capicompswd);
byte[] sha1hash = CHashSaltPswd(mysalt, pswd) ; //hash of salt+pswd
byte[] pbeKey = new byte[16]; //For RC2 128bit, derived key is first 16 bytes of SHA1 hash
Array.Copy(sha1hash, pbeKey, pbeKey.Length);
...
...
private static byte[] CHashSaltPswd(byte[] salt, byte[] pswd)
{
SHA1 sha1 = SHA1.Create();
CryptoStream cs = new CryptoStream(Stream.Null, sha1, CryptoStreamMode.Write);
cs.Write(pswd, 0, pswd.Length);
cs.Write(salt, 0, salt.Length);
cs.FlushFinalBlock();
return sha1.Hash;
}
How CAPICOM EncryptedData encrypts data:
Having derived the symmetric key based on supplied password and Salt, CAPICOM
EncryptedData.Encrypt performs standard encryption (CBC mode) using a randomly
generated (underlying CryptoAPI CryptGenRandom()) IV vector. For decryption, this
IV is extracted from the EncryptedData blob, again in the same byte order (no byte
reversal) for use in either .NET Crypto classes or CryptoAPI. Note however that in
CAPICOM, data is often initialized as a text string. When the data is passed to CAPICOM
in this way via oEncryptedData.Content = <string data>, the actual content encrypted
is the UNICODE representation of the data. The distribution CAPICOM script samples which
read text files have the data encrypted as UNICODE (meaning that the cipher block
size is roughly twice the size of the plain text file). The following .NET encryption
sample shows RC2 encryption in .NET which generates the identical cipher text block as CAPICOM
EncryptedData, in the same byte order, using the same derived key and IV as CAPICOM.
In this sample, the RC2 encryption block is written to a binary file:
byte[] IV = {0x08, 0x6D, 0xD8, 0xA7, 0x80, 0x09, 0x41, 0x9A} ;
Rc2EncryptData("sigline.txt", "netencrypted", pbeKey, IV);
private static void Rc2EncryptData(String contentFile, String outName, byte[] rc2Key, byte[] rc2IV)
{
byte[] contentbytes = GetFileBytes(contentFile);
String content = (new ASCIIEncoding()).GetString(contentbytes);
byte[] unicontent = (new UnicodeEncoding()).GetBytes(content);
FileStream fout = new FileStream(outName, FileMode.OpenOrCreate, FileAccess.Write);
RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
CryptoStream encStream = new CryptoStream(fout, rc2.CreateEncryptor(rc2Key, rc2IV), CryptoStreamMode.Write);
encStream.Write(unicontent, 0, unicontent.Length);
encStream.Close();
}
private static byte[] GetFileBytes(String filename){
if(!File.Exists(filename))
return null;
Stream stream=new FileStream(filename,FileMode.Open);
int datalen = (int)stream.Length;
byte[] filebytes =new byte[datalen];
stream.Seek(0,SeekOrigin.Begin);
stream.Read(filebytes,0,datalen);
stream.Close();
return filebytes;
}
A hex display of the .NET generated RC2 encryption block is shown below and is
identical to that displayed in the original CAPICOM EncryptedData blob:
24 CD 4C 1A FA CF 89 A4 B2 AA B0 B8 A8 E7 C6 C6
45 04 84 B6 0A 31 AD BB 84 45 8B EE 51 F7 F9 6E
D8 C0 A9 39 D3 DB 83 B9 DE C2 BA 57 B1 0D C7 D3
.............
3DES Case: How the CAPICOM EncryptedData symmetric key is derived:
The above example demonstrated RC2 128 bit (16 bytes) key derivation, where the symmetric key material was a subset of the SHA1 hash of the contatenated password and salt.
Internally, CAPICOM EncryptedData invokes CryptoAPI methods such CryptCreateHash(), CryptHashData(), CryptDeriveKey(), CryptSetKeyParam().
For 3DES keys with a key length of 192 bits (24 bytes), which includes the parity bits, the
algorithm used by CryptDeriveKey() is more complex.
The following example explicitly demonstrates this algorithm in C# code. Of course P/Invoke in .NET
could also be used directly to call the CryptoAPI functions.
The following is a dump of the CAPICOM EncryptedData blob for 3DES 192 bit encryption, in binary form, generated
by sample code similar to that above:
0000 30 7A: SEQUENCE {
0002 06 9: OBJECT IDENTIFIER '1 3 6 1 4 1 311 88 3'
000D A0 6D: [0] {
000F 30 6B: SEQUENCE {
0011 06 A: OBJECT IDENTIFIER '1 3 6 1 4 1 311 88 3 1'
001D A0 5D: [0] {
001F 30 5B: SEQUENCE {
0021 02 3: INTEGER 131073
0026 02 2: INTEGER 26115
002A 02 2: INTEGER 192
002E 04 8: OCTET STRING
: EE A8 74 F7 CE BF 76 A0
0038 04 10: OCTET STRING
: 4B B0 EF FF 03 30 ED C2 B1 BF FF E2 8C E5 16 2B
004A 04 30: OCTET STRING
: 6F DA 47 06 B2 78 7E C7 14 E1 86 43 56 5D 5A A9
: 52 50 EB 8F B7 42 D3 4D E5 89 6E 46 23 8B B5 3D
: 6E B0 42 B1 4E EE 74 12 AB 26 B7 E0 5F 2D 71 71
: }
: }
: }
: }
: }
Similar to the RC2 case,
131073 - CAPICOM version information
26115 - 3DES algorithm identifier
192 - key size (192 bits or 24 bytes)
EE A8 74 F7 CE BF 76 A0 - Initialization Vector for 3DES encryption
4B B0 EF FF 03 30 ED C2 B1 BF FF E2 8C E5 16 2B - Salt value for key derivation
6F DA 47 06 B2 78 7E C7 14 E1 86 43 56 5D 5A A9 - Cipher text (48 bytes)
52 50 EB 8F B7 42 D3 4D E5 89 6E 46 23 8B B5 3D
6E B0 42 B1 4E EE 74 12 AB 26 B7 E0 5F 2D 71 71
The following code snippet demonstrates the algorithm used by CryptDeriveKey() to derive a 3DES key:
byte[] iv = {0xEE, 0xA8, 0x74, 0xF7, 0xCE, 0xBF, 0x76, 0xA0} ;
byte[] mysalt = {0x4B, 0xB0, 0xEF, 0xFF, 0x03, 0x30, 0xED, 0xC2,
0xB1, 0xBF, 0xFF, 0xE2, 0x8C, 0xE5, 0x16, 0x2B};
String capicompswd = "mypassword"; //CAPICOM pswd (hashed as UNICODE by EncryptedData)
byte[] pswd = (new UnicodeEncoding()).GetBytes(capicompswd);
byte[] tdeskey = new byte[24] ;
byte[] buff1 = new byte[64];
byte[] buff2 = new byte[64];
for (int i = 0; i<64; i++)
{
buff1[i] = 0x36;
buff2[i] = 0x5C;
}
byte[] psalthash = CHashSaltPswd(mysalt, pswd) ; //hash of pswd + salt
byte[] concathash = new byte[40] ; //to hold contatenated SHA1 hash data
// ----- Step 1; XOR first 24 bytes of buff1 with SHA1 hash
for (int i = 0; i<20; i++)
buff1[i] = (byte) (buff1[i] ^ psalthash[i]) ;
// ----- Step 2; XOR first 24 bytes of buff2 with SHA1 hash
for (int i = 0; i<20; i++)
buff2[i] = (byte) (buff2[i] ^ psalthash[i]) ;
// ----- Step 3; hash the entire first buffer
byte[] hash3 = sha1hash(buff1);
// ----- Step 4; hash the entire second buffer
byte[] hash4 = sha1hash(buff2);
// ----- Step 5: Concatenate the two hashes --------
Array.Copy(hash3, 0, concathash, 0, hash3.Length);
Array.Copy(hash4, 0, concathash, hash3.Length, hash4.Length);
// ----- Step 6: Use the first 24 bytes of the contatenated hash as the 3DES key
Array.Copy(concathash, 0, tdeskey, 0, tdeskey.Length);
// Encrypt or decrypt with this derived 3DES key and IV:
-----
Summary:
This note has provided some information required to decode and decrypt CAPICOM EncryptedData blobs using
either .NET or CryptoAPI alone. It is fairly easy to write a decoder to easily extract
the Salt, IV, key algorithm and keysize from an EncryptedData structure completely in .NET.
PKCS #7 - Cryptographic Message Syntax Standard
Michel I. Gallant
neutron@istar.ca