Symmetric Encryption: .NET, CryptoAPI and Java 2

This article discusses compatibility of symmetric key encryption using .NET 1.1, CryptoAPI and Java 2. A specific example using Triple DES encryption is used to demonstrate generating the identical ciphertext using CryptoAPI, .NET and Java 2.

(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:

  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);
  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);

(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");

(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