Need to fix wrong encryption method in my c# web api - c#

I need to correct an encryption method written in C#.
First, a little background: I am taking charge of an existing web application with an ecma6 / html frontend and c# web api .net standard 4.6 backend.
It has many integrations with different customers for user identification. Some of the integrations simply navigate to a customers URL to do a login process on the infrastructure of the customer, and then return to the app with an encripted user token in the URL's query string.
This token is encripted using AES256 encryption.
The backend is correctly decrypting the tokens, but when I tried to use the encryption routine to build a unit test, I discovered something is wrong. When I encrypt and then decrypt a message, the decryption routine throws the following error:
Unhandled Exception:
System.Security.Cryptography.CryptographicException: Length of the data to decrypt is invalid.
Input message is "key1=value1;key2=value2" (without the quotes)
Encrypted message I get is NzcrOTc3Kzk3Nys5NzcrOTc3Kzk3Nys5NzcrOVpsVHZ2NzF3NzcrOUZ6UVlRZ3Z2djcxSVlPKy92U0V6NzcrOVNqZFY3Nys5VHpBZA==
I need to correct the implementation error in the encryption method. The implementation of the decryption method shows expected behavior, and you'll notice a double Base64 decoding done on the encrypted string: this is given, as we are integrated with an already developed encryption routine done by a customer in PERL which we detected did double encoding.
I inspected the order of operations to see a mismatch among the encryption and decryption and I was unable to detect an inconsistency, so the need to ask for help.
The code I synthesized for this test is:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class MainClass {
public static void Main (string[] args) {
var secretKey = "This is my secret key";
var secretIV = "This is my secret iv";
var originalMessage = "key1=value1;key2=value2";
var userToken = Cryptography.EncryptAES256CBCBase64x2(originalMessage, secretKey, secretIV);
Console.WriteLine(userToken);
var unencryptedToken = Cryptography.DecryptAES256CBCBase64x2(userToken, secretKey, secretIV);
if (originalMessage == unencryptedToken)
Console.WriteLine("All fine!");
else
Console.WriteLine("Error!");
}
}
public static class Cryptography
{
public static string DecryptAES256CBCBase64x2(string base64EncryptedString, string secretKey, string secretIV)
{
base64EncryptedString = SaveBase64String(base64EncryptedString);
var keyBytes = Encoding.UTF8.GetBytes(secretKey);
var ivBytes = Encoding.UTF8.GetBytes(secretIV);
var hash = SHA256.Create();
var keyHash = hash.ComputeHash(keyBytes);
Array.Resize(ref keyHash, 32);
var keyHashString = string.Empty;
foreach (byte x in keyHash)
keyHashString += string.Format("{0:x2}", x);
keyHash = Encoding.UTF8.GetBytes(keyHashString.Substring(0, 32));
var ivHash = hash.ComputeHash(ivBytes);
Array.Resize(ref ivHash, 16);
var ivHashString = string.Empty;
foreach (byte x in ivHash)
ivHashString += string.Format("{0:x2}", x);
ivHash = Encoding.UTF8.GetBytes(ivHashString.Substring(0, 16));
// Create an RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new RijndaelManaged())
{
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.Mode = CipherMode.CBC;
rijAlg.Key = keyHash;
rijAlg.IV = ivHash;
var encryptedBytes =
Convert.FromBase64String(
Encoding.UTF8.GetString(
Convert.FromBase64String(base64EncryptedString)));
// Create a decryptor to perform the stream transform.
var decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (var msDecrypt = new MemoryStream(encryptedBytes))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
return srDecrypt.ReadToEnd();
}
}
}
}
}
public static string EncryptAES256CBCBase64x2(string baseString, string secretKey, string secretIV)
{
var keyBytes = Encoding.UTF8.GetBytes(secretKey);
var ivBytes = Encoding.UTF8.GetBytes(secretIV);
var hash = SHA256.Create();
var keyHash = hash.ComputeHash(keyBytes);
Array.Resize(ref keyHash, 32);
var keyHashString = string.Empty;
foreach (byte x in keyHash)
keyHashString += string.Format("{0:x2}", x);
keyHash = Encoding.UTF8.GetBytes(keyHashString.Substring(0, 32));
var ivHash = hash.ComputeHash(ivBytes);
Array.Resize(ref ivHash, 16);
var ivHashString = string.Empty;
foreach (byte x in ivHash)
ivHashString += string.Format("{0:x2}", x);
ivHash = Encoding.UTF8.GetBytes(ivHashString.Substring(0, 16));
// Create an RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new RijndaelManaged())
{
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.Mode = CipherMode.CBC;
rijAlg.Key = keyHash;
rijAlg.IV = ivHash;
var encryptedBytes = Encoding.UTF8.GetBytes(baseString);
// Create a encryptor to perform the stream transform.
var encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (var msEncrypt = new MemoryStream(encryptedBytes))
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Read))
{
using (var srEncrypt = new StreamReader(csEncrypt))
{
// Read the encrypted bytes from the encrypting stream
// and place them in a string.
var result = srEncrypt.ReadToEnd();
return Convert.ToBase64String(
Encoding.UTF8.GetBytes(
Convert.ToBase64String(
Encoding.UTF8.GetBytes(result))));
}
}
}
}
}
public static string SaveBase64String(string data)
{
data = data.Replace("-", "+").Replace("_", "/");
var mod = data.Length % 4;
if (mod > 2)
mod = 1;
return data + string.Empty.PadRight(mod, '=');
}
}
At the following link an online example is available for you to try: https://repl.it/#ormasoftchile/Test-encrypt-decrypt
Thank you everyone.

In the current code, the ciphertext is stored in a string (StreamReader.ReadToEnd), which generally doesn't work, since the data are corrupted thereby. Instead, the ciphertext should be stored in a byte-array, which can be Base64-encoded if required.
To fix the problem
remove the line:
var encryptedBytes = Encoding.UTF8.GetBytes(baseString);
and replace the entire MemoryStream-block by:
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(baseString);
}
var encryptedBytes = msEncrypt.ToArray();
return Convert.ToBase64String(Encoding.UTF8.GetBytes(Convert.ToBase64String(encryptedBytes)));
}
}
Another point is the double Base64-encoding/decoding. This makes no sense and is simply redundant and degrades performance. If possible, this should be changed.

Related

Decrypt string in C# that was encrypted with CakePHP

I need to decrypt a string in C# that was encrypted via CakePHP's Security::rijndael library.
The string is being encrypted in PHP like this:
base64_encode(Security::rijndael($pw, Configure::read('SecretKey'), 'encrypt'))
The resulting encoding is 88 characters long and stored in the db.
Here is how I'm trying to decrypt it in C#:
public static string Decrypt(string encode, string secretKey)
{
var sha = SHA256Managed.Create();
var saltBytes = sha.ComputeHash(Encoding.ASCII.GetBytes(secretKey));
var key = new Rfc2898DeriveBytes(secretKey, saltBytes);
var rm = new RijndaelManaged();
rm.Mode = CipherMode.CBC;
rm.KeySize = 256;
rm.BlockSize = 256;
rm.Key = key.GetBytes(rm.KeySize / 8);
rm.IV = key.GetBytes(rm.BlockSize / 8);
var decryptor = rm.CreateDecryptor(rm.Key, rm.IV);
var cipher = Convert.FromBase64String(encode);
string decrypted;
using (var msDescrypt = new MemoryStream(cipher))
{
using (var csDescrypt = new CryptoStream(msDescrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDescrypt = new StreamReader(csDescrypt))
{
decrypted = srDescrypt.ReadToEnd();
}
}
}
return decrypted;
}
When reading the stream I get an exception of "Length of the data to decrypt is invalid". Do I need to manipulate the incoming encoded string to make this work?

Encrypt-then-MAC, how to afterwards add data to HMAC

I want to include iv and salt in the HMACSHA512 calculation without add then to the encrypted data.
At the moment someone could change the iv and a wouldn't noticed that.
I chain different streams to ensure Encrypt-then-MAC, later I want to encrypt large files, so this design is necessary.
So if I add the the iv and salt to a stream, with e.g. new MemoryStream(iv).CopyTo(hmacStream); the result will contain this data.
This is my code so far:
private static IHmacAndData EncryptInternal(byte[] key, byte[] iv, byte[] plainData, byte[] salt)
{
byte[] hmacHash;
byte[] encryptedBytes;
using (var aesManaged = CreateAesManaged(iv, key))
{
var encryptor = aesManaged.CreateEncryptor(aesManaged.Key, aesManaged.IV);
var hmacsha512 = new HMACSHA512(key);
using (var resultStream = new MemoryStream())
{
using (var hmacStream = new CryptoStream(resultStream, hmacsha512, CryptoStreamMode.Write))
{
using (var aesStream = new CryptoStream(hmacStream, encryptor, CryptoStreamMode.Write))
{
using (var plainStream = new MemoryStream(plainData))
{
plainStream.CopyTo(aesStream);
}
}
}
encryptedBytes = resultStream.ToArray();
}
hmacHash = hmacsha512.Hash;
}
return new Message {HMAC = hmacHash, Data = encryptedBytes};
}
private static AesManaged CreateAesManaged(byte[] iv, byte[] key)
{
var aesManaged = new AesManaged
{
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
KeySize = KeySize,
IV = iv,
Key = key
};
return aesManaged;
}
My temporary solution is to make a second HMACSHA512 calculation at the end.
But this seems not right in any way.
var overallHmac = new HMACSHA512(keyHmac);
hmacHash = overallHmac.ComputeHash(hmacHash.Concat(iv).Concat(saltPassword).Concat(saltHmac).ToArray());
Here is the full sample, search for CreateOverallHmacKey to find the spot.
https://gist.github.com/dhcgn/85b88b516953e8996af8544ee9d7b567

What in the Salt causes it to fail in decryption

The code below and in the Fiddle isn't for production, it is for educational purposes. I do not want to fix anything, as I have a viable solution. However, I would like to know why:
var password = "password";
var salt = Encoding.ASCII.GetBytes(password.Length.ToString());
var secret = new PasswordDeriveBytes(password, salt);
When the above is implemented, in the following method FixedEncryptor will work.
// Valid:
public static string FixedEncryptor(string content)
{
var cipher = new RijndaelManaged();
var plain = Encoding.Unicode.GetBytes(content);
var key = new PasswordDeriveBytes(password, salt);
using (var encrypt = cipher.CreateEncryptor(key.GetBytes(32), key.GetBytes(16)))
using (var stream = new MemoryStream())
using (var crypto = new CryptoStream(stream, encrypt, CryptoStreamMode.Write))
{
crypto.Write(plain, 0, plain.Length);
crypto.FlushFinalBlock();
return Convert.ToBase64String(stream.ToArray());
}
}
However, if you implement:
var secret = new PasswordDeriveBytes("password",
Encoding.ASCII.GetBytes("password"));
The code will suddenly produce:
Run-time exception (line 70): Padding is invalid and cannot be
removed.
Stack Trace:
[System.Security.Cryptography.CryptographicException: Padding is
invalid and cannot be removed.] at Crypt.Decryptor(String content):
line 70 at Program.Main(): line 17
As denoted in the following method:
// Invalid:
public static string Encryptor(string content)
{
var cipher = new RijndaelManaged();
var plain = Encoding.Unicode.GetBytes(content);
var key = new PasswordDeriveBytes("password", Encoding.ASCII.GetBytes("password"));
using (var encrypt = cipher.CreateEncryptor(key.GetBytes(32), key.GetBytes(16)))
using (var stream = new MemoryStream())
using (var crypto = new CryptoStream(stream, encrypt, CryptoStreamMode.Write))
{
crypto.Write(plain, 0, plain.Length);
crypto.FlushFinalBlock();
return Convert.ToBase64String(stream.ToArray());
}
}
So why can one successfully decrypt, while the other doesn't decrypt correctly and produces the above error?
A Fiddle with a small example is here.
From your posted code example your problem comes from the fact you are using two different salts.
In FixedEncryptor you use a salt of
Encoding.ASCII.GetBytes(password.Length.ToString());
That encodes to be a byte array equal to { 56 }, this is because Length returns 8 then calling ToString() on that returns the string "8" which you convert in to the ascii value 56.
In Encryptor you use a salt of
Encoding.ASCII.GetBytes("password")
That encodes to be a byte array equal to { 112, 97, 115, 115, 119, 111, 114, 100}, which is the ascii values of the characters "p", "a", "s", "s", "w", "o", "r", and "d".
The problem you are running in to is you only attempt to use { 56 } in your decrypt function, so your problem comes down to your encrypt function and your decrypt function are using two different salts.
If I make a make a new Decrypter to use the same salt and password as Encryptor then make a separate FixedDecryptor to match the salt of FixedEncryptor everything will work fine
public class Program
{
public static void Main()
{
var message = "Hello World!";
var fixedCipherText = Crypt.FixedEncryptor(message);
var cipherText = Crypt.Encryptor(message);
Console.WriteLine(cipherText);
Console.WriteLine(fixedCipherText);
var plainText = Crypt.Decryptor(cipherText);
var fixedPlainText = Crypt.FixedDecryptor(fixedCipherText);
Console.WriteLine(plainText);
Console.WriteLine(fixedPlainText);
}
}
public static class Crypt
{
private const string password = "password";
private readonly static byte[] salt = Encoding.ASCII.GetBytes(password.Length.ToString());
public static string FixedEncryptor(string content)
{
var cipher = new RijndaelManaged();
var plain = Encoding.Unicode.GetBytes(content);
var key = new PasswordDeriveBytes(password, salt);
using (var encrypt = cipher.CreateEncryptor(key.GetBytes(32), key.GetBytes(16)))
using (var stream = new MemoryStream())
using (var crypto = new CryptoStream(stream, encrypt, CryptoStreamMode.Write))
{
crypto.Write(plain, 0, plain.Length);
crypto.FlushFinalBlock();
return Convert.ToBase64String(stream.ToArray());
}
}
public static string Encryptor(string content)
{
var cipher = new RijndaelManaged();
var plain = Encoding.Unicode.GetBytes(content);
var key = new PasswordDeriveBytes("password", Encoding.ASCII.GetBytes("password"));
using (var encrypt = cipher.CreateEncryptor(key.GetBytes(32), key.GetBytes(16)))
using (var stream = new MemoryStream())
using (var crypto = new CryptoStream(stream, encrypt, CryptoStreamMode.Write))
{
crypto.Write(plain, 0, plain.Length);
crypto.FlushFinalBlock();
return Convert.ToBase64String(stream.ToArray());
}
}
public static string FixedDecryptor(string content)
{
var cipher = new RijndaelManaged();
var encrypted = Convert.FromBase64String(content);
var key = new PasswordDeriveBytes(password, salt);
using (var decryptor = cipher.CreateDecryptor(key.GetBytes(32), key.GetBytes(16)))
using (var stream = new MemoryStream(encrypted))
using (var crypto = new CryptoStream(stream, decryptor, CryptoStreamMode.Read))
{
byte[] plain = new byte[encrypted.Length];
int decrypted = crypto.Read(plain, 0, plain.Length);
string data = Encoding.Unicode.GetString(plain, 0, decrypted);
return data;
}
}
public static string Decryptor(string content)
{
var cipher = new RijndaelManaged();
var encrypted = Convert.FromBase64String(content);
var key = new PasswordDeriveBytes("password", Encoding.ASCII.GetBytes("password"));
using (var decryptor = cipher.CreateDecryptor(key.GetBytes(32), key.GetBytes(16)))
using (var stream = new MemoryStream(encrypted))
using (var crypto = new CryptoStream(stream, decryptor, CryptoStreamMode.Read))
{
byte[] plain = new byte[encrypted.Length];
int decrypted = crypto.Read(plain, 0, plain.Length);
string data = Encoding.Unicode.GetString(plain, 0, decrypted);
return data;
}
}
}
Fiddel of the code.
However this still is not the "correct" way to do things. See Sine Nomen's answer
First of all, the method by which you generate a salt is not secure at all; secondly, PasswordDerivedBytes is deprecated and you should be looking at its successor, Rfc2898DeriveBytes.
Try something like the following - note that this requires a few using statements: System, System.IO, System.Security.Cryptography and System.Text.
Simply encrypt the data with Encrypt(PlainText, Password) and decrypt it again with Decrypt(EncryptedData, Password). The salt is rolled into the encrypted data as the first 16 bytes and it is completely random for each encryption/decryption round.
This code is part of my own open source password manager.
/*
* Encryption/Decryption, based on AES256 and PBKDF2
*/
public string Encrypt (string plainText, string passPhrase, bool fast_encrypt = false)
{
string result;
using (Rijndael algR = Rijndael.Create ()) {
RNGCryptoServiceProvider rngC = new RNGCryptoServiceProvider ();
byte[] iv = new byte[16];
rngC.GetBytes (iv);
Rfc2898DeriveBytes derived = new Rfc2898DeriveBytes (passPhrase, iv, fast_encrypt ? 10 : 3000);
algR.KeySize = 256;
algR.BlockSize = 128;
algR.Key = derived.GetBytes (32);
algR.IV = iv;
using (MemoryStream memoryStream = new MemoryStream ()) {
memoryStream.Write (iv, 0, 16);
using (CryptoStream cryptoStreamEncrypt = new CryptoStream (memoryStream, algR.CreateEncryptor (algR.Key, algR.IV), CryptoStreamMode.Write)) {
using (StreamWriter streamWriterEncrypt = new StreamWriter (cryptoStreamEncrypt)) {
streamWriterEncrypt.Write (plainText);
}
}
result = Convert.ToBase64String (memoryStream.ToArray ());
}
}
return result;
}
public string Decrypt (string cipherText, string passPhrase, bool fast_decrypt = false)
{
string result;
using (Rijndael algR = Rijndael.Create ()) {
using (MemoryStream memoryStream = new MemoryStream (Convert.FromBase64String (cipherText))) {
byte[] iv = new byte[16];
memoryStream.Read (iv, 0, 16);
Rfc2898DeriveBytes derived = new Rfc2898DeriveBytes (passPhrase, iv, fast_decrypt ? 10 : 3000);
algR.KeySize = 256;
algR.BlockSize = 128;
algR.Key = derived.GetBytes (32);
algR.IV = iv;
using (CryptoStream cryptoStreamDecrypt = new CryptoStream (memoryStream, algR.CreateDecryptor (algR.Key, algR.IV), CryptoStreamMode.Read)) {
using (StreamReader streamReaderDecrypt = new StreamReader (cryptoStreamDecrypt)) {
result = streamReaderDecrypt.ReadToEnd ();
}
}
}
}
return result;
}

Good AES Initialization Vector practice

per my question Aes Encryption... missing an important piece, I have now learned that my assumption for creating a reversible encryption on a string was a bit off. I now have
public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
{
var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
{
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
cs.FlushFinalBlock();
}
return ms.ToArray();
}
}
}
}
and this produces consistent results; however, I will not be able to decrypt without knowing/ setting the initialization vector. I really do not want to pass three values into this method (on for the IV), which leaves me with hardcoding the IV or deriving it from the key. I'd like to know if this is a good practice, or if it will render the encrypted value vulnerable to attack somehow... or am I really overthinking this and should just hardcode the IV?
UPDATE
Per Iridium's suggestion, I tried something like this instead:
public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
{
if (string.IsNullOrEmpty(toEncrypt)) throw new ArgumentException("toEncrypt");
if (encryptionKey == null || encryptionKey.Length == 0) throw new ArgumentException("encryptionKey");
var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
{
using (var ms = new MemoryStream())
{
ms.Write(provider.IV, 0, 16);
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
cs.FlushFinalBlock();
}
return ms.ToArray();
}
}
}
}
public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
{
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var ms = new MemoryStream(encryptedString))
{
byte[] buffer;
ms.Read(buffer, 0, 16);
provider.IV = buffer;
using (var decryptor = provider.CreateDecryptor(provider.Key, provider.IV))
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
byte[] decrypted = new byte[encryptedString.Length];
var byteCount = cs.Read(decrypted, 0, encryptedString.Length);
return Encoding.UTF8.GetString(decrypted, 0, byteCount);
}
}
}
}
}
however, this shows something odd in my unit test:
[TestMethod]
public void EncryptionClosedLoopTest()
{
var roundtrip = "This is the data I am encrypting. There are many like it but this is my encryption.";
var encrypted = Encryption.EncryptString(roundtrip, encryptionKey);
var decrypted = Encryption.DecryptString(encrypted, encryptionKey);
Assert.IsTrue(roundtrip == decrypted);
}
my decrypted text shows up as "92ʪ�F"�,hpv0�� I am encrypting. There are many like it but this is my encryption." which seems almost right but of course completely wrong. It looks like I'm close though. Am I missing an offset on the memory stream?
The IV should be random and unique for every run of your encryption method. Deriving it from the key/message or hard-coding it is not sufficiently secure. The IV can be generated within this method, instead of passed into it, and written to the output stream prior to the encrypted data.
When decrypting, the IV can then be read from the input before the encrypted data.
When Encrypting, generate your IV and pre-pend it to the cipher text (something like this)
using (var aes= new AesCryptoServiceProvider()
{
Key = PrivateKey,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
var input = Encoding.UTF8.GetBytes(originalPayload);
aes.GenerateIV();
var iv = aes.IV;
using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
using (var cipherStream = new MemoryStream())
{
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
//Prepend IV to data
//tBinaryWriter.Write(iv); This is the original broken code, it encrypts the iv
cipherStream.Write(iv); //Write iv to the plain stream (not tested though)
tBinaryWriter.Write(input);
tCryptoStream.FlushFinalBlock();
}
string encryptedPayload = Convert.ToBase64String(cipherStream.ToArray());
}
}
When decrypting this back, get first 16 bytes out and use it in crypto stream
var aes= new AesCryptoServiceProvider()
{
Key = PrivateKey,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
};
//get first 16 bytes of IV and use it to decrypt
var iv = new byte[16];
Array.Copy(input, 0, iv, 0, iv.Length);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
using (var binaryWriter = new BinaryWriter(cs))
{
//Decrypt Cipher Text from Message
binaryWriter.Write(
input,
iv.Length,
input.Length - iv.Length
);
}
return Encoding.Default.GetString(ms.ToArray());
}
Great input from folks. I took the combined answers from ankurpatel and Konstantin and cleaned it up and added some convenient method overrides. This works as of June 2019 in .NET Core 2.2.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
private const int AesKeySize = 16;
public static void Main()
{
// the data to encrypt
var message = "Here is some data to encrypt!";
// create KeySize character key
var key = "g(KMDu(EEw63.*V`";
// encrypt the string to a string
var encrypted = AesEncrypt(message, key);
// decrypt the string to a string.
var decrypted = AesDecrypt(encrypted, key);
// display the original data and the decrypted data
Console.WriteLine($"Original: text: {encrypted}");
Console.WriteLine($"Round Trip: text: {decrypted}");
}
static string AesEncrypt(string data, string key)
{
return AesEncrypt(data, Encoding.UTF8.GetBytes(key));
}
static string AesDecrypt(string data, string key)
{
return AesDecrypt(data, Encoding.UTF8.GetBytes(key));
}
static string AesEncrypt(string data, byte[] key)
{
return Convert.ToBase64String(AesEncrypt(Encoding.UTF8.GetBytes(data), key));
}
static string AesDecrypt(string data, byte[] key)
{
return Encoding.UTF8.GetString(AesDecrypt(Convert.FromBase64String(data), key));
}
static byte[] AesEncrypt(byte[] data, byte[] key)
{
if (data == null || data.Length <= 0)
{
throw new ArgumentNullException($"{nameof(data)} cannot be empty");
}
if (key == null || key.Length != AesKeySize)
{
throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
}
using (var aes = new AesCryptoServiceProvider
{
Key = key,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
aes.GenerateIV();
var iv = aes.IV;
using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
using (var cipherStream = new MemoryStream())
{
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
// prepend IV to data
cipherStream.Write(iv);
tBinaryWriter.Write(data);
tCryptoStream.FlushFinalBlock();
}
var cipherBytes = cipherStream.ToArray();
return cipherBytes;
}
}
}
static byte[] AesDecrypt(byte[] data, byte[] key)
{
if (data == null || data.Length <= 0)
{
throw new ArgumentNullException($"{nameof(data)} cannot be empty");
}
if (key == null || key.Length != AesKeySize)
{
throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
}
using (var aes = new AesCryptoServiceProvider
{
Key = key,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
// get first KeySize bytes of IV and use it to decrypt
var iv = new byte[AesKeySize];
Array.Copy(data, 0, iv, 0, iv.Length);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
using (var binaryWriter = new BinaryWriter(cs))
{
// decrypt cipher text from data, starting just past the IV
binaryWriter.Write(
data,
iv.Length,
data.Length - iv.Length
);
}
var dataBytes = ms.ToArray();
return dataBytes;
}
}
}
I modified your decryption method as follows and it works:
public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
{
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
using (var ms = new MemoryStream(encryptedString))
{
// Read the first 16 bytes which is the IV.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
using (var decryptor = provider.CreateDecryptor())
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
}
The problem with your implementation is that you are reading too many bytes into the CryptoStream. You really need to read encryptedText.Length - 16. Using a StreamReader simplifies this, since you don't need to worry about offsets anywhere anymore.
The accepted answer is correct, but doesn't provide a good example of how to get a random IV.
It turns out that this is a lot easier than people are trying to make it. The AesCryptoServiceProvider in .NET automatically generates a cryptographically random IV every time you construct one. And if you need to use the same instance for multiple encryptions, you can call GenerateIV()
You can also prepend the IV to the encrypted value before returning it and have the decrypting end pull it off
private static void Main(string[] args) {
var rnd = new Random();
var key = new byte[32]; // For this example, I'll use a random 32-byte key.
rnd.NextBytes(key);
var message = "This is a test";
// Looping to encrypt the same thing twice just to show that the IV changes.
for (var i = 0; i < 2; ++i) {
var encrypted = EncryptString(message, key);
Console.WriteLine(encrypted);
Console.WriteLine(DecryptString(encrypted, key));
}
}
public static string EncryptString(string message, byte[] key) {
var aes = new AesCryptoServiceProvider();
var iv = aes.IV;
using (var memStream = new System.IO.MemoryStream()) {
memStream.Write(iv, 0, iv.Length); // Add the IV to the first 16 bytes of the encrypted value
using (var cryptStream = new CryptoStream(memStream, aes.CreateEncryptor(key, aes.IV), CryptoStreamMode.Write)) {
using (var writer = new System.IO.StreamWriter(cryptStream)) {
writer.Write(message);
}
}
var buf = memStream.ToArray();
return Convert.ToBase64String(buf, 0, buf.Length);
}
}
public static string DecryptString(string encryptedValue, byte[] key) {
var bytes = Convert.FromBase64String(encryptedValue);
var aes = new AesCryptoServiceProvider();
using (var memStream = new System.IO.MemoryStream(bytes)) {
var iv = new byte[16];
memStream.Read(iv, 0, 16); // Pull the IV from the first 16 bytes of the encrypted value
using (var cryptStream = new CryptoStream(memStream, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read)) {
using (var reader = new System.IO.StreamReader(cryptStream)) {
return reader.ReadToEnd();
}
}
}
}
[EDIT: I modified my answer to include how to pass the IV in the encrypted value and get it when decrypting. I also refactored the example a bit]
In order to resolve the setting of IV on the provider (As Iridium pointed out):
ms.Read(provider.IV, 0, 16);
I added the following to your code:
var iv = new byte[provider.IV.Length];
memoryStream.Read(iv, 0, provider.IV.Length);
using (var decryptor = provider.CreateDecryptor(key, iv);
granted, my key is not set by the provider on each run. I generated it once and then stored it. The IV is randomly generated off of the provider for each encryption.
In my case, to generate the IV, I use something like this
/// <summary>
/// Derives password bytes
/// </summary>
/// <param name="Password">password</param>
/// <returns>derived bytes</returns>
private Rfc2898DeriveBytes DerivePass(string Password)
{
byte[] hash = CalcHash(Password);
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Password, hash, _KEY_ITER);
return pdb;
}
/// <summary>
/// calculates the hash of the given string
/// </summary>
/// <param name="buffer">string to hash</param>
/// <returns>hash value (byte array)</returns>
private byte[] CalcHash(string buffer)
{
RIPEMD160 hasher = RIPEMD160.Create();
byte[] data = Encoding.UTF8.GetBytes(buffer);
return hasher.ComputeHash(data);
}
that is, I calculate the password hash using RIPEMD160 and use it to generate the derived bytes, at that point, when it comes to intializing the encryption/decryption I just use something like this
Rfc2898DeriveBytes pdb = DerivePass(Password);
SymmetricAlgorithm alg = _engine;
alg.Key = pdb.GetBytes(_keySize);
alg.IV = pdb.GetBytes(_IVSize);
I don't know if it's "correct" (probably crypto gurus here will shoot at me :D), but, at least, it gives me a decent IV and I don't have to store it "somewhere" since just entering the correct password will give back the needed IV value; as a note, the _engine in the above example is declared as "SymmetricAlgorithm" and initialized using something like this
_engine = Rijndael.Create();
_keySize = (_engine.KeySize / 8);
_IVSize = (_engine.BlockSize / 8);
which creates the desired crypto objects and initializes the key and IV sizes
To generate random IV you would need a truly random number. Whichever language specific API you use for generating the random number, should generate true random number. Both android and ios have apis which generate random numbers based on sensor data.
I recently implemented AES 256 with random IV (Generated using really random numbers) and hashed key. For more secure(random IV + hashed key) cross platform (android, ios, c#) implementation of AES see my answer here - https://stackoverflow.com/a/24561148/2480840

Encryption-decryption between Ruby and Dot net

I need tyhe dot net equivalent of the following code. Matter is, I am encrypting using Ruby on client side, here is the code.
The encoded string will be passed to a C# web service. That has to decrypt the string.
If someone can provide the dot net equivalent of this code, then it will be helpful.
require 'rubygems'
require 'ezcrypto'
require 'crypt/rijndael'
plaintext = '24.9195N 17.821E'
aes_key = Crypt::Rijndael.new('0123456789abcdef0123456789abcdef')
aes_cyphertext = aes_key.encrypt_string(plaintext)
print "\n"
print aes_cyphertext +"\n"
print Base64.encode64(aes_cyphertext)
print "\n"
print aes_key.decrypt_string(aes_cyphertext)
print "\n"
It's going to be something like this code shown below as a unit test. The first part does the encryption - the second half does the decryption.
Paste the code into a new MSTest unit test (Create New Test Project or add to an existing one).
The key and the iv are what you'll need to set accordingly.
//needed to convert from hex string
public static byte[] FromHexString(string hexString)
{
int NumberChars = hexString.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
return bytes;
}
[TestMethod]
public void Test()
{
string toEncryptString = "24.9195N 17.821E";
//initialise key and IV (note - all zero IV is not recommended!)
byte[] key = FromHexString("0123456789abcdef0123456789abcdef");
byte[] iv = FromHexString("00000000000000000000000000000000");
byte[] toEncrypt = System.Text.Encoding.UTF8.GetBytes(toEncryptString);
byte[] cipherBytes = null;
string cipherText = null;
//encrypt
using (System.Security.Cryptography.Rijndael r = new RijndaelManaged())
{
r.Key = key;
r.IV = iv;
using(System.Security.Cryptography.ICryptoTransform transform
= r.CreateEncryptor())
{
using (var mStream = new System.IO.MemoryStream())
{
using (var cStream =
new CryptoStream(mStream, transform, CryptoStreamMode.Write))
{
cStream.Write(toEncrypt, 0, toEncrypt.Length);
cStream.FlushFinalBlock();
cipherBytes = mStream.ToArray();
cipherText = Convert.ToBase64String(cipherBytes);
}
}
}
}
//decrypt
byte[] toDecrypt = Convert.FromBase64String(cipherText);
string decryptedString = null;
using (System.Security.Cryptography.Rijndael r = new RijndaelManaged())
{
r.Key = key;
r.IV = iv;
using(System.Security.Cryptography.ICryptoTransform transform2
= r.CreateDecryptor()) // <-- difference here
{
using (var mStream2 = new System.IO.MemoryStream())
{
using (var cStream2 =
new CryptoStream(mStream2, transform2, CryptoStreamMode.Write))
{
cStream2.Write(toDecrypt, 0, toDecrypt.Length);
cStream2.FlushFinalBlock();
decryptedString =
System.Text.Encoding.UTF8.GetString(mStream2.ToArray());
}
}
}
}
Assert.AreEqual(toEncryptString, decryptedString);
}

Categories