Cross Programming Language Encryption – CSharp, Part 1
See a demo at Github
One of our challenges at work, lately, was to send encrypted messages between multiple applications, written in different programming languages: C#, Go and JavaScript.
In our case, it was more like TypeScript, but from this point of view, it is safe to talk about JavaScript from two simple reasons:
- TypeScript compiles to JavaScript
- The library used for cryptography was written with JavaScript.
Our team debated between two encryption algorithms for this job: AES and RSA.
AES
The Advanced Encryption Standard (AES), also known by its original name Rijndael (Dutch pronunciation: [ˈrɛindaːl]), is a specification for the encryption of electronic data established by the U.S. National Institute of Standards and Technology (NIST) in 2001.
AES is a subset of the Rijndael block cipher developed by two Belgian cryptographers, Vincent Rijmen and Joan Daemen, who submitted a proposal[5] to NIST during the AES selection process.[6] Rijndael is a family of ciphers with different key and block sizes.
For AES, NIST selected three members of the Rijndael family, each with a block size of 128 bits, but three different key lengths: 128, 192 and 256 bits.
AES has been adopted by the U.S. government and is now used worldwide. It supersedes the Data Encryption Standard (DES), which was published in 1977. The algorithm described by AES is a symmetric-key algorithm, meaning the same key is used for both encrypting and decrypting the data.
RSA
RSA (Rivest–Shamir–Adleman) is one of the first public-key cryptosystems and is widely used for secure data transmission. In such a cryptosystem, the encryption key is public and distinct from the decryption key which is kept secret (private). In RSA, this asymmetry is based on the practical difficulty of factoring the product of two large prime numbers, the “factoring problem“. The acronym RSA is the initial letters of the surnames of Ron Rivest, Adi Shamir, and Leonard Adleman, who publicly described the algorithm in 1977. Clifford Cocks, an English mathematician working for the British intelligence agency Government Communications Headquarters (GCHQ), had developed an equivalent system in 1973, which was not declassified until 1997.
A user of RSA creates and then publishes a public key based on two large prime numbers, along with an auxiliary value. The prime numbers must be kept secret. Anyone can use the public key to encrypt a message, but only someone with knowledge of the prime numbers can decode the message. Breaking RSA encryption is known as the RSA problem. Whether it is as difficult as the factoring problem is an open question. There are no published methods to defeat the system if a large enough key is used.
RSA is a relatively slow algorithm, and because of this, it is less commonly used to directly encrypt user data. More often, RSA passes encrypted shared keys for symmetric key cryptography which in turn can perform bulk encryption-decryption operations at much higher speed.
Coding AES With C#
Please note that AES has three different key lengths: 128, 192 and 256 bits, which I have tried to simplify here by using as key a Hash string who’s length can only be 16, 24 or 32 bytes.
In C#, the AES class is represented through the System.Security.Cryptography.Rijandel class, however I chose to use the Managed class.
As we’ve already read, AES will require key of 128, 192 or 256 bits, which translated into 16, 24 or 32 bytes and an Initialization Vector (IV) of 16 bytes.
For my demo, I have chosen to define a class receiving a Hash parameter, which will later become our Key, while our IV will be randomly generated.
using System.Security.Cryptography;
using System.Collections.Generic;
class AesEncrypt
{
private RijndaelManaged aes;
public AesEncrypt(String Hash)
{
if (Hash.Length != 16 && Hash.Length != 24 && Hash.Length != 32)
{
throw new Exception("Invalid hash length. Must be 16, 24 or 32.");
}
aes = new RijndaelManaged();
aes.KeySize = Hash.Length * 8;
aes.Mode = CipherMode.CFB;
aes.Padding = PaddingMode.None;
One of the things you will need to check and make sure it is supported by each language will be the Block Cipher Mode. Another one will be the Padding hence, as you will find out, some Cipher modes do require certain padding values.
aes.Key = Encoding.UTF8.GetBytes(Hash);
aes.GenerateIV();
}
Since our IV is randomly generated, when encrypting our content, we will have to send the IV as well. Otherwise, the other party will be missing a part of the decryption information. A common thing would be to concatenate our IV in front or at the end of the encrypted content.
/**
* Encrypt will encrypt a string password using AES algorithm, returning a Base64 for of the encrypt result
*/
public string Encrypt(String password)
{
byte[] data = Encoding.UTF8.GetBytes(password);
using (var encryptor = this.aes.CreateEncryptor())
using (var msEncrypt = new MemoryStream())
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (var bw = new BinaryWriter(csEncrypt, Encoding.UTF8))
{
bw.Write(data);
bw.Close();
List<byte> list = new List<byte>();
list.AddRange(this.aes.IV);
list.AddRange(msEncrypt.ToArray());
Another common practice is to encode everything in an easy to read, easy to transmit mode, to prevent wrong data conversions. The easiest way to do this is to convert our encrypted result to Base64.
return Convert.ToBase64String(list.ToArray());
}
}
Now, the decryption part will have to handle the things, in the opposite way. When encrypted, we have 1st initialized our Rijandel class using our Key and IV, encrypted our content then appended our IV and nevertheless encoded our result in an easy to transmit way by using Base64.
When decrypting, we will first need to decode our data from Base64, then separate our IV and encrypted content.
/**
* Decrypt will decrypt a string password using AES algorithm, expecting a Base64 form of the encrypted password
*/
public String Decrypt(String ciphertext)
{
byte[] data = Convert.FromBase64String(ciphertext);
Array.Copy(data, 0, this.aes.IV, 0, 16);
byte[] cryptedData = new byte[data.Length - 16];
Array.Copy(data, 16, cryptedData, 0, data.Length - 16);
When this is done, we will have all the elements needed to decrypt our content. Please keep in mind the Key will be known for both parties, being used to both encrypt and decrypt the content, while the IV is transported along with the encrypted content.
using (var decrytpor = this.aes.CreateDecryptor())
using (var msEncrypt = new MemoryStream())
using (var csEncrypt = new CryptoStream(msEncrypt, decrytpor, CryptoStreamMode.Write))
using (var bw = new BinaryWriter(csEncrypt, Encoding.UTF8))
{
bw.Write(cryptedData);
bw.Close();
return Encoding.UTF8.GetString(msEncrypt.ToArray());
}
}
}
Nevertheless, in the end, we should be able to obtain the same content we have encrypted.
Coding RSA With C#
Using RSA, as we will see, is a bit less complicated, however, RSA will require generating a certificate, which can be easily done using the OpenSSL set of tools.
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out cert.pem
openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem
In order to be to use RSA encryption, we will be using the System.Security.Cryptography.X509Certificates.X509Certificate2 class, which will allow us to load our generated certificate, and used it for both encryption and decryption.
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
class RsaEncrypt
{
private X509Certificate2 Cert;
public RsaEncrypt(String CertPath)
{
Cert = new X509Certificate2(CertPath);
}
/**
* Encrypt will encrypt a string password using an SSL certificate, returning a Base64 for of the encrypt result
*/
public String Encrypt(String Password)
{
using (RSA Rsa = Cert.GetRSAPublicKey())
{
byte[] PasswordBytes = Encoding.Default.GetBytes(Password);
byte[] EncryptedPassword = Rsa.Encrypt(PasswordBytes, RSAEncryptionPadding.OaepSHA512);
return Convert.ToBase64String(EncryptedPassword);
}
}
/**
* Decrypt will decrypt a string password using an SSL certificate, expecting a Base64 form of the encrypted password
*/
public String Decrypt(String Password)
{
using (RSA Rsa = Cert.GetRSAPrivateKey())
{
byte[] PasswordBytes = Convert.FromBase64String(Password);
byte[] DecryptedPassword = Rsa.Decrypt(PasswordBytes, RSAEncryptionPadding.OaepSHA512);
return Encoding.Default.GetString(DecryptedPassword);
}
}
}
Conclusion
Of course one of the wonders we had, was which encryption algorithm is more powerful. The answer to this question is both and none. It depends a lot on the design of your application and the purpose of the encryption.
RSA and AES are algorithms of two different cryptographic types. Former is a public key algorithm while later is a symmetric key algorithm. The encryption and decryption keys of RSA are different while that of AES are same. Hence AES key has to be shared between the parties securely before encryption.
Given Alice and Bob, RSA can be used by Alice and Bob even if they have never met before but Bob’s authentic public key is available to Alice. Alice uses Bob’s public key to encrypt a message. Bob uses his private key to decrypt the message. Only Bob can decrypt and no other person amounts to confidentiality without having exchanged a key.
What we can definitely tell you and recommend is: RSA is more computationally intensive than AES, and much slower. It’s normally used to encrypt only small amounts of data.
As for our problem of Cross Programming Language Encryption, I have only presented C# so far. Stay tuned.
References
- Wikipedia / RSA_(cryptosystem)
- Wikipedia / Advanced Encryption Standard
- Wikipedia / Block Cipher Mode
- Encryption – should I be using RSA or AES?
- AES vs RSA – Which is stronger given two scenarios?
- AES vs. RSA to encrypt large size of data
- MSDN / System.Security.Cryptography Namespace
- MSDN / RijndaelManaged
- MSDN / X509Certificate2