C++ and C#/.NET Interoperability for RSA Public-key Cryptography and AES Symmetric Cipher
by RyuK ( )
Nov 9, 2006
When you have to write a secure network application, cryptography is one of topics you can't escape from. In most cases there are high-level packages such as SSL available, but it's not always like that and you may have to go lower-level. Besides, even if you don't program a custom security solution by yourself, it's not a bad idea to know how these secure protocols actually work as it helps you to choose a right solution for your problem. This article provides a basic idea of secure communication by illustrating C++ and C# code examples. Also this article will be useful for those who want to write a custom secure protocol between a C++ application and a C# application. (Disclaimer: but don't use the example explained here as is in your mission-critical application! This article is only for the education purpose. Realworld secure communication libraries implement countermeasures for many kinds of known cryptographic attacks and this sample doesn't.)
For the sample code of this article I use .NET Framework 1.1 for the C# side and Crypto++ Library 5.2.1 for the C++ side. .NET Framework is an obvious solution for a .NET application and it has necessary tools for security albeit lacks a bit of variety. On the other hand Crypto++ Library is a full of variety and even supports many obscure crypto algorithms in the Open-Source form. Though the Platform SDK provided by Microsoft also offers a security framework and related utilities, I prefer Crypto++ Library because it's cross-platform, Open Source with a lenient license and updated more quickly though maintained by a single brilliant person. The only point in Crypto++ Library that may not be easy for a beginner is its relatively complicated template framework. In the C++ example of this article I'll show you minimal but effective use of it.
The basic flow and interaction between these C# and C++ programs is like this:
1. The C# program generates a 1024-bit RSA public and private key pair (pubkey.txt and prvkey.txt respectively)
2. The C++ program receives the public key (pubkey.txt). It generates a 128-bit key and a 128-bit initialization vector (IV) for 128-bit AES symmetric cipher and encrypts them using the public key sent from the C# program, producing a ciphertext (cipher.txt). The key and the IV in a plaintext are saved locally (aeskey.txt) while the ciphertext is sent to the C# program.
3. The C# program decrypts the ciphertext (cipher.txt) with the RSA private key. With the obtained key and IV, it encrypts a message and sends the resulted ciphertext (aescipher.txt) to the C++ program.
4. The C++ program receives the ciphertext and decrypts it with the AES key
(aeskey.txt).
The step 1 and 2 are the preparation for establishing a secure channel. The 3 and 4 are the actual part for exchanging a message. As you notice, establishing a secure channel equals secure key exchange. It exchanges an AES key under the protection of RSA public-key crypto.
To do secure key exchange, a public-key cryptographic algorithm is essential. Anyone can use a public key to encrypt a message, but only the one with its correspondent private key can decrypt it. As the US patent for the RSA algorithm expired some years ago, it's the most popular public-key crypto algorithm and supported in the .NET framework. However, you have to understand the key format to exchange a public key between a .NET application and a C++ application as there's no agreement on which format to use. In the example code I'll show you the most straightforward way to exchange an RSA public key, which in fact doesn't encode it in a certain format and passes it in its raw form. Subsequent messages after key exchange are encrypted by the AES symmetric cipher because public-key encryption eats relatively larger processing power. Symmetric cipher (also called "block cipher" because of the encryption method) has various operation modes and the one that uses an IV is more secure than the most basic mode.
Let's take a look at the C# code which is compiled into a console application RSATestCSharp.exe.
When it's called without an argument it shows the usage help. When it's called with the "gen" argument
it executes the step 1 of the explanation above. When it's called with the "dec" argument
it execute the step 3. You have to manually copy ciphertext files between the C# program and
the C++ program.
|
An RSA public key is composed of 2 parts, a modulus and a public exponent. A modulus is a product
of 2 random prime numbers. For 1024-bit RSA, its modulus becomes 1024-bit (128 bytes). A public
exponent is an arbitrary integer. To pass a public key to the C++ program, this program exports
a modulus and a public key in the binary form. A private key can be easily exported in the XML
form by using the ToXmlString method of the RSACryptoServiceProvider class.
When decrypting a message encrypted by a public key it deserialize the private key from the XML
file by FromXmlString.
The real name of the algorithm used in the AES symmetric cipher is called Rijndael. In .NET
Framework 1.1 only a Managed implementation of the algorithm is provided and the performance
may suffer compared to a native implementation, but it won't matter for most use cases. When
you execute it with the "dec" argument after it received a cipher text, you'll see the
deafault padding merhod for the Rijndael implementation in .NET is PKCS7 as shown by myRijndael.Padding.ToString(),
and the symmetric cipher mode is CBC (cipher-block chaining) as shown by myRijndael.Mode.ToString().
The CBC mode uses an IV. The C++ program has to use PKCS#7 and CBC to communicate with the C# program.
RSA also does padding for a message block and in this case the method called OAEP (Optimal Asymmetric
Encryption Padding) is used. The second parameter of RSA.Decrypt is set true to make
it aware of OAEP. As the C++ program chooses a 128-bit block for AES, it assumes the size of key
and IV are both 16 bytes. The plaintext message is 4-bytes length and has no terminating null character.
The code for the C++ side (RSATestCPP) is as follows. When it's called with the "rsa" it executes the step 2 in the flow, and with "aes" the step 4. You have to build Crypto++ Library and put its header path and library path in your project to build this code.
|
Conveniently Crypto++
Library predefines CryptoPP::RSAES_OAEP_SHA_Encryptor as one of the RSA encryption
schemes in PKCS #1 v2.0. It can interact with the C# counterpart with OAEP without a hassle. CryptoPP::Integer is
a CryptoPP class that can hold an integer with arbitrary figures. Cryptography often uses these
big integers including a public key modulus for RSA which is 1024-bit (128 bytes) in this sample
code. It imports a modulus and an exponent from the binary file and sets them in the properties
of the RSA encryptor object that is an instance of the CryptoPP::RSAES_OAEP_SHA_Encryptor class.
The usage of StringSource and StringSink in Crypto++ may be a bit
complicated for those who are not familiar with Crypto++, but the basic idea is you set source
data in StringSource with
a transformation filter that has a destination data in its own parameter. A destination data
for a filter must be wrapped with StringSink. These StringSource / StringSink classes
can handle the standard C++ classes such as basic_string fairly well.
For an AES key and an IV, it sets 128-bit (16 bytes) random data in them. Each message block
for 128-bit AES is 128-bit too. The last block in a message that is smaller than 16 bytes
needs padding. After setting a key and IV by SetKeyWithIV the AES decryptor decrypts
the ciphertext with ProcessData. As you know the plaintext is 4-bytes length, so
this block has paddings. In PKCS#7, if actual padding is 3 bytes the last 3 bytes are filled
with 0x03. This sample code manually strips PKCS#7 padding. After the padding is removed,
the plaintext is successfully recovered.
