(1) CryptoAPI Symmetric Encryption:
We start by generating a RSA "ExponentOfOne" keypair using the procedure and sample code provided at: HOWTO: Export/Import PlainText Session Key Using CryptoAPI. This will allow us to access the key material of a randomly generated symmetric key on any Windows platform, using CryptExportKey() with SIMPLEBLOB. (Exporting session keys as PLAINTEXTKEYBLOB is only supported on WinXP+). Using an ExponentOfOne RSA asymmetric key means that the encryption block of the exported SIMPLEBLOB is not actually encrypted at all, so we can easily extract the symmetric key bytes for use and comparison with encryption in .NET and Java 2.
Using the symmetric encryption C sample code from:
Example C Program: Encrypting a File and changing the algorithm to CALG_3DES
and specifying an Initialization Vector, the basic changes are:
#define ENCRYPT_ALGORITHM CALG_3DES
LPCSTR KeyContainer = "myExponentOfOneContainer";
BYTE pIV[] = {50,51,52,53,54,55,56,57} ; //simple test IV for 3DES
CryptAcquireContext(&hCryptProv, KeyContainer, MS_ENHANCED_PROV, PROV_RSA_FULL, 0);
....
CryptGenKey(hCryptProv, ENCRYPT_ALGORITHM, CRYPT_EXPORTABLE, &hKey) ;
...
CryptSetKeyParam(hKey, KP_IV, pIV, 0) ;
...
CryptGetUserKey(hCryptProv, AT_KEYEXCHANGE, &hXchgExpOneKey) ;
...
CryptExportKey(hKey, hXchgExpOneKey, SIMPLEBLOB, 0, pbKeyBlob, &dwKeyBlobLen) ;
//write SIMPLEBLOB key to file ...
//encrypt the data with random-generated symmetric key and specified IV
CryptEncrypt(hKey, ....
For a randomly generated symmetric key, the referenced C sample code writes a file consisting of the DWORD SIMPLEBLOB size, the SIMPLEBLOB itself in blue (consisting of a 12 byte BLOBHEADER followed by a 128 byte (1024 bit) "encryption block" which in this case is not actually encrypted) and finally the 3DES encrypted ciphertext in red (02 D0 ..). A partial bin hex dump of the resultant file follows:
0000 8C 00 00 00 01 02 00 00 03 66 00 00 00 A4 00 00 0010 92 31 16 94 85 7A 67 01 79 4F 13 D0 DF 97 F1 F8 0020 D3 C1 62 CB 07 37 15 A2 00 5E 17 10 93 EB E4 22 0030 83 87 3B 49 DC B9 2D E4 E0 1A 64 72 AE DC 8E 90 0040 C8 59 A3 15 70 EE 35 EE B0 2F 91 88 80 E0 37 64 0050 4A 62 58 92 B5 C3 5D F4 1E 5B A4 38 0B 86 2D BD 0060 8D FA E7 D6 1F FD 27 8B 13 67 B9 E2 EF 35 D7 93 0070 64 51 03 70 B6 9C AC 64 60 C0 D1 65 37 04 63 52 0080 91 08 BB 31 6D BC AC 63 D4 4B 4B 46 34 3B 02 00 0090 02 D0 BE E0 7A B9 21 39 2E DD 0A 52 3B C0 1F B3 00a0 20 56 28 FE F6 EB 34 97 6B 49 CF 00 04 08 DD 9B .........The first highlighted 24 bytes above (92 31 ...) is the 3DES symmetric key at the start of the PKCS #1, type 2 encryption block. Note that for 3DES keys, this is the full (192 bit) key including parity bits. However, since the encryption block must be built in big-endian order, the actual 3DES symmetric key used by CryptoAPI for encryption is reversed:
BYTE[] 3deskey = { 0xA2, 0x15, 0x37, 0x07, 0xCB, 0x62, 0xC1, 0xD3, 0xF8, 0xF1, 0x97, 0xDF, 0xD0, 0x13, 0x4F, 0x79, 0x01, 0x67, 0x7A, 0x85, 0x94, 0x16, 0x31, 0x92 };
[Note on .NET RSA Encryption Block:
Within .NET, oRSACryptoServiceProvider.Encrypt() generates a byte array representing
a PKCS #1 type 2 encryption block. In .NET, the byte array is returned in big-endian
format, exactly the reverse order to that formatted within a CryptoAPI SIMPLEBLOB or
the encrypted buffer returned by CryptEncrypt() with RSA publickey encryption.
So using the same ExponentOfOne exchange key for encryption, .NET returns:
00 02 4E BD 53 1B 67 DC F2 69 B3 7B 58 85 81 5F
C7 DF 21 47 DD 59 45 81 28 0B AF 83 F3 2A 11 5D
53 66 EE CE 2F 8B D1 CF 90 DA 4E 23 1F A3 39 A0
67 A8 F2 02 F1 95 A4 98 56 B5 21 67 5C 46 75 08
27 C5 C2 07 D4 0C 6A 7B 4F 66 E4 F6 17 41 68 80
10 72 A0 96 9F 2C BA DD FA BD 45 AA CB 27 9F 21
99 E3 1F F0 F0 77 43 00
A2 15 37 07 CB 62 C1 D3
F8 F1 97 DF D0 13 4F 79 01 67 7A 85 94 16 31 92
where the symmetric key bytes are now at the end of the encryption block. The
encryption block starts with a null byte, followed by the block type (2), then
random padding data and finally the key material.]
(2) .NET Symmetric Encryption:
To reproduce exactly the same 3DES ciphertext as that created by CryptoAPI above,
we use exactly the same 3deskey above (reversed as shown) and exactly the same
pIV bytes (no byte reversal) to initialize a
TripleDESCryptoServiceProvider encryptor instance
.
For the same file plaintext, the following .NET code snippet produces exactly the
same ciphertext output file as the CryptoAPI sample above:
byte[] plaintext = GetContentFileBytes(); //get file bytes to encrypt
FileStream fout = new FileStream(outName, FileMode.OpenOrCreate, FileAccess.Write);
byte[] tdesKey = {
0xA2, 0x15, 0x37, 0x07, 0xCB, 0x62,
0xC1, 0xD3, 0xF8, 0xF1, 0x97, 0xDF,
0xD0, 0x13, 0x4F, 0x79, 0x01, 0x67,
0x7A, 0x85, 0x94, 0x16, 0x31, 0x92 };
byte[] tdesIV = {50,51,52,53,54,55,56,57} ;
....
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
CryptoStream encStream = new CryptoStream(fout, tdes.CreateEncryptor(tdesKey, tdesIV), CryptoStreamMode.Write);
encStream.Write(plaintext, 0, plaintext.Length);
encStream.Close();
(3) Java 2 Symmetric Encryption:
Java 2 v 1.4+ includes an implementation of
JCE API
(Java Cryptography Extension) and the
included "SunJCE" provider supports 3DES cipher with the common modes and padding. (By comparison,
RSA encryption is NOT supported with the standard Java 2 1.4 distribution and a 3rd party provider
must be added). The following code sample again produces exactly the same 3DES ciphertext
file as the CryptoAPI and .NET samples above, for the same input file:
//---- Use specified 3DES key and IV from other source -------------------------
byte[] plaintext = getContentBytes();
byte[] tdesKeyData = {
(byte)0xA2, (byte)0x15, (byte)0x37, (byte)0x07, (byte)0xCB, (byte)0x62,
(byte)0xC1, (byte)0xD3, (byte)0xF8, (byte)0xF1, (byte)0x97, (byte)0xDF,
(byte)0xD0, (byte)0x13, (byte)0x4F, (byte)0x79, (byte)0x01, (byte)0x67,
(byte)0x7A, (byte)0x85, (byte)0x94, (byte)0x16, (byte)0x31, (byte)0x92 };
byte[] myIV = {(byte)50,(byte)51,(byte)52,(byte)53,(byte)54,(byte)55,(byte)56,(byte)57};
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(myIV);
c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);
/* Save the 3DES ciphertext to file */
FileOutputStream fos = new FileOutputStream("tdesJencrypted");
fos.write(cipherText);
fos.close();
(4) Password-derived symmetric keys:
The
Example C Program: Encrypting a File also demonstrates deriving a symmetric key
from a supplied password using CryptoAPI functions CryptCreateHash(), CryptHashData() and CryptDeriveKey().
The key is derived from the hash of the supplied password in a way which depends to some
extent on the symmetric key algorithm requested. (In a simple case such as RC2 128 (a 16 byte key)
and a SHA-1 hash, the first 16 bytes of the 20 byte SHA-1 hash of the password is the derived key.
The procedure for deriving 3DES key material (24 bytes) from a 20 byte SHA-1 hash is
slightly more complex.)
For the same password string, exactly the same password-derived symmetric key can be derived
in .NET using a method intended for CryptoAPI compatibility:
PasswordDeriveBytes pderiver = new PasswordDeriveBytes("yourpswd", null);
byte[] ivZeros = new byte[8]; //Not used but required.
byte[] pbeKey = pderiver.CryptDeriveKey("TripleDES", "SHA1", 192, ivZeros);
Again, data encrypted in .NET using this password-derived key (and a supplied IV) to
initialize a TripleDESCryptoServiceProvider yields identical ciphertext (same byte order)
to that generated by an unmanaged CryptoAPI approach as in the C Program sample.
Michel I. Gallant
neutron@istar.ca