I’ve recently been asked to write some code to decrypt some data using C# which was originally encrypted in Java. While I managed to get this to work, I had to piece information together from many different sources on the web. Therefore I have decided to simplify the procedure for the next person who comes looking by putting this into one place.
I was provided with the following piece of information by the Java developer (obviously the values here are created to be used for demo purposes):
Cyphered data (base64) | r/w1xZb85PokABCycED5Tw== |
Deciphered data | I am Zeb |
Encryption key (hex) | 142eb4a7ab52dbfb971e18daed7056488446b4b2167cf61187f4bbc60fc9d96d |
Initialisation Vector (hex) | 26744a68b53dd87bb395584c00f7290a |
Cipher method | AES/CBC/PKCS5Padding |
Though what needed to be done was straight forward, I faced the following difficulties when discovering how to code my solution:
- Almost all examples I came across were discussing the use of the Rfc2898DeriveBytes class to salt a password which is very specific to the security around passwords.
- No on example pointed out the difference between a Base32 encoding , a Hexadecimal string and a Base64 string.
- When you are working with decryption, your result is either going to be right or wrong. If you algorithm doesn’t work, then you do not exactly what is going wrong. There doesn’t seem to be an online tool to ensure that the data that you are working with is valid.
- Since my data was coming from Java, I was not sure whether Java has a slightly different algorithm for the cipher method used which was causing my decoding to fail. (Obviously this was just a doubt).
In our scenario, we will not be working to encrypt passwords. For that, I would recommend the use of Rfc2898DeriveBytes. There are plenty of examples of this on the web.
Here is my code
MyCryptoClass.cs:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace CypherExample
{
public sealed class MyCryptoClass
{
protected RijndaelManaged myRijndael;
private static string encryptionKey = "142eb4a7ab52dbfb971e18daed7056488446b4b2167cf61187f4bbc60fc9d96d";
private static string initialisationVector = "26744a68b53dd87bb395584c00f7290a";
// Singleton pattern used here with ensured thread safety
protected static readonly MyCryptoClass _instance = new MyCryptoClass();
public static MyCryptoClass Instance
{
get { return _instance; }
}
public MyCryptoClass()
{
}
public string DecryptText(string encryptedString)
{
using (myRijndael = new RijndaelManaged())
{
myRijndael.Key = HexStringToByte(encryptionKey);
myRijndael.IV = HexStringToByte(initialisationVector);
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
Byte[] ourEnc = Convert.FromBase64String(encryptedString);
string ourDec = DecryptStringFromBytes(ourEnc, myRijndael.Key, myRijndael.IV);
return ourDec;
}
}
public string EncryptText(string plainText)
{
using (myRijndael = new RijndaelManaged())
{
myRijndael.Key = HexStringToByte(encryptionKey);
myRijndael.IV = HexStringToByte(initialisationVector);
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
byte[] encrypted = EncryptStringToBytes(plainText, myRijndael.Key, myRijndael.IV);
string encString = Convert.ToBase64String(encrypted);
return encString;
}
}
protected byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
byte[] encrypted;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
protected string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
public static void GenerateKeyAndIV()
{
// This code is only here for an example
RijndaelManaged myRijndaelManaged = new RijndaelManaged();
myRijndaelManaged.Mode = CipherMode.CBC;
myRijndaelManaged.Padding = PaddingMode.PKCS7;
myRijndaelManaged.GenerateIV();
myRijndaelManaged.GenerateKey();
string newKey = ByteArrayToHexString(myRijndaelManaged.Key);
string newinitVector = ByteArrayToHexString(myRijndaelManaged.IV);
}
protected static byte[] HexStringToByte(string hexString)
{
try
{
int bytesCount = (hexString.Length) / 2;
byte[] bytes = new byte[bytesCount];
for (int x = 0; x < bytesCount; ++x)
{
bytes[x] = Convert.ToByte(hexString.Substring(x * 2, 2), 16);
}
return bytes;
}
catch
{
throw;
}
}
public static string ByteArrayToHexString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
}
}
To be able to test our Cipher, the following is a Console Application which would output the results of our Encryption / Decryption.
Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CypherExample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("********************Encryption Example******************");
MyCryptoClass crypto = MyCryptoClass.Instance;
string inputText = "I am Zeb";
Console.WriteLine("Plain text is: '{0}'", inputText);
string encryptedText = crypto.EncryptText(inputText);
Console.WriteLine("Encrypted text is '{0}'", encryptedText);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("********************Decryption Example******************");
string inputEncryptedText = "r/w1xZb85PokABCycED5Tw==";
Console.WriteLine("Input Encrypted text is '{0}'", inputEncryptedText);
string decryptedText = crypto.DecryptText(inputEncryptedText);
Console.WriteLine("Decrypted text is: '{0}'", decryptedText);
Console.ReadLine();
}
}
}
This is what this code outputs:
********************Encryption Example******************
Plain text is: 'I am Zeb'
Encrypted text is 'r/w1xZb85PokABCycED5Tw=='
********************Decryption Example******************
Input Encrypted text is 'r/w1xZb85PokABCycED5Tw=='
Decrypted text is: 'I am Zeb'
Key notes about the code above:
- The cypher data is a Base64 string. This is why we use the Convert.FromBase64String() method on line 38.
- Method HexStringToByte() converts hex strings to bytes. Remember that these strings are different to Base64 strings.
- Method ByteArrayToHexString() on line 182 is the reverse process for HexStringToByte().
- Notice, line 33 to 36 in MyCryptoClass.cs. This is where i am defining the Cipher method.
- Notice that the encrypted data is base64 and the key and initialisation vector is a hex value. If you get these mixed up, your solution will not work.
- PaddingMode.PKCS7 accommodates PKCS5Padding.
Hope this helps!