Related
I am using AES - Standard SymmetricAlgorithm, for Encrypting and Decrypting string. String is encrypting successfully but when it comes to decrypt the compiler gives me the exception i.e
“Padding is invalid and cannot be removed”.
I have created a demo console application for testing, please have a look on below code.
The Main Method:
static void Main(string[] args)
{
var content = "5466160057107706";
var key = "E546C8DF278CD5931069B522E695D4F2";
var encrypted = EncryptString(content, key);
Console.WriteLine(encrypted);
var decrypted = DecryptString(encrypted, key);
Console.WriteLine(decrypted);
Console.ReadLine();
}
Method added for Encryption:
public static string EncryptString(string text, string keyString)
{
var key = Encoding.UTF8.GetBytes(keyString);
using (var aesAlg = Aes.Create())
{
using (var encryptor = aesAlg.CreateEncryptor(key, aesAlg.IV))
{
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(text);
}
var iv = aesAlg.IV;
var decryptedContent = msEncrypt.ToArray();
var result = new byte[iv.Length + decryptedContent.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(decryptedContent, 0, result, iv.Length, decryptedContent.Length);
return Convert.ToBase64String(result);
}
}
}
}
Method added for Decryption:
public static string DecryptString(string cipherText, string keyString)
{
var fullCipher = Convert.FromBase64String(cipherText);
var iv = new byte[16];
var cipher = new byte[16];
Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length);
Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, iv.Length);
var key = Encoding.UTF8.GetBytes(keyString);
using (var aesAlg = Aes.Create())
{
using (var decryptor = aesAlg.CreateDecryptor(key, iv))
{
string result;
using (var msDecrypt = new MemoryStream(cipher))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
result = srDecrypt.ReadToEnd();
}
}
}
return result;
}
}
}
Here is the Error Snap which occurs in the DecryptString() method:
Any advice, how to solve this issue?
Clue:
var cipher = new byte[16];
Why are you assuming your cipher would be ONLY 16 bytes ? What if it is more than this ?
In fact, if I run this program and debug, I see that your cipher is 32 bytes.
So, the following 2 line changes makes it work:
var cipher = new byte[32];
Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, cipher.Length);
In any case, you would need to dynamically determine your size of cipher.
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
I have this code which is meant to decrypt a file, but if I run it, it throws a CryptographicException (length of the data to decrypt is invalid) at the end of the using statement using (CryptoStream ...) { ... }
public static void DecryptFile(string path, string key, string saltkey, string ivkey)
{
try
{
byte[] cipherTextBytes;
using (StreamReader reader = new StreamReader(path)) cipherTextBytes = Encoding.UTF8.GetBytes(reader.ReadToEnd());
byte[] keyBytes = new Rfc2898DeriveBytes(key, Encoding.ASCII.GetBytes(saltkey)).GetBytes(256 / 8);
RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC, Padding = PaddingMode.None };
ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, Encoding.ASCII.GetBytes(ivkey));
byte[] plainTextBytes;
using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
plainTextBytes = new byte[Encoding.UTF8.GetByteCount((new StreamReader(cryptoStream)).ReadToEnd())];
cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
//plainTextBytes = memoryStream.ToArray();
cryptoStream.FlushFinalBlock();
}
}
string result = Encoding.ASCII.GetString(plainTextBytes, 0, plainTextBytes.Length).TrimEnd("\0".ToCharArray());
using (FileStream writer = new FileStream(path, FileMode.Create)) writer.Write(Encoding.ASCII.GetBytes(result), 0, Encoding.ASCII.GetBytes(result).Length);
MessageBox.Show("Decrypt succesfull");
}
catch (Exception ex)
{
MessageBox.Show("An error while decrypting the file:\n\n" + ex, "Error");
}
}
}
Does anybody know why this is or how I can fix it? (I don't know if it comes from my encrypting method, but I have another program which uses the exact same thing to encrypt strings and that one does work.)
My encrypting method:
public static void EncryptFile(string path, string key, string saltkey, string ivkey)
{
try
{
byte[] TextBytes;
using (StreamReader reader = new StreamReader(path)) TextBytes = Encoding.UTF8.GetBytes(reader.ReadToEnd());
byte[] KeyBytes = new Rfc2898DeriveBytes(key, Encoding.ASCII.GetBytes(saltkey)).GetBytes(256 / 8);
RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC, Padding = PaddingMode.Zeros };
ICryptoTransform encryptor = symmetricKey.CreateEncryptor(KeyBytes, Encoding.ASCII.GetBytes(ivkey));
byte[] CipherTextBytes;
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(TextBytes, 0, TextBytes.Length);
cs.FlushFinalBlock();
CipherTextBytes = ms.ToArray();
}
}
using (FileStream writer = new FileStream(path, FileMode.Create)) writer.Write(CipherTextBytes, 0, CipherTextBytes.Length);
MessageBox.Show("Encrypt succesfull");
}
catch (Exception ex)
{
MessageBox.Show("An error while encrypting the file:\n\n" + ex, "Error");
}
}
There are a few issues with your code:
You use a padding mode of Zeroes in Encrypt and None in Decrypt. These need to match
You load the bytes from your file using Encoding.UTF8, you need to read the raw bytes, you can do this by using the following instead:
byte[] cipherTextBytes = File.ReadAllBytes(path);
You call cryptoStream.FlushFinalBlock(); when only using a single iteration of a stream. You don't need this call in Decrypt if you are only doing a single block iteration.
You read the original text from your file in UTF8 and then write it back as ASCII. You should either change the result assignment in decrypt to use UTF8 or (preferably) change both to use raw bytes.
You use Create to interact with the files when you are overwriting in-place. If you know the file already exists (as you are replacing it) you should use truncate or better yet just call File.WriteAllBytes.
Your decrypt is all kinds of messed up. It looks like you're tying yourself into knots over byte retrieval. You should just use the raw bytes out of the CryptoStream and not try using UTF8
Here's a revised set of methods for you:
public static void DecryptFile(string path, string key, string saltkey, string ivkey)
{
byte[] cipherTextBytes = File.ReadAllBytes(path);
byte[] keyBytes = new Rfc2898DeriveBytes(key, Encoding.ASCII.GetBytes(saltkey)).GetBytes(256 / 8);
RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CFB, Padding = PaddingMode.PKCS7 };
ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, Encoding.ASCII.GetBytes(ivkey));
byte[] plainTextBytes;
const int chunkSize = 64;
using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
using (MemoryStream dataOut = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
using (var decryptedData = new BinaryReader(cryptoStream))
{
byte[] buffer = new byte[chunkSize];
int count;
while ((count = decryptedData.Read(buffer, 0, buffer.Length)) != 0)
dataOut.Write(buffer, 0, count);
plainTextBytes = dataOut.ToArray();
}
File.WriteAllBytes(path, plainTextBytes);
}
and:
public static void EncryptFile(string path, string key, string saltkey, string ivkey)
{
byte[] TextBytes = File.ReadAllBytes(path);
byte[] KeyBytes = new Rfc2898DeriveBytes(key, Encoding.ASCII.GetBytes(saltkey)).GetBytes(256 / 8);
RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CFB, Padding = PaddingMode.PKCS7 };
ICryptoTransform encryptor = symmetricKey.CreateEncryptor(KeyBytes, Encoding.ASCII.GetBytes(ivkey));
byte[] CipherTextBytes;
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(TextBytes, 0, TextBytes.Length);
cs.FlushFinalBlock();
CipherTextBytes = ms.ToArray();
}
File.WriteAllBytes(path, CipherTextBytes);
}
Most likely your problem comes from cipherTextBytes = Encoding.UTF8.GetBytes(reader.ReadToEnd());
You can't use UTF8 to encode arbitrary binary data, you will likely need to fix both your encrypting end decrypting end. You either must use cipherTextBytes = File.ReadAllBytes(path) or if you are forced to use strings you must first encode the bytes to a valid string using Convert.ToBase64String()
In my case it happened because I was decrypting a value which was never encrypted.
I had my values saved in the database without encryption. But when I introduced encryption and decryption routine in my code and executed my program first time, it was actually trying to decrypt a value which was never encrypted, hence the problem.
Simply clearing the existing values from the database for the initial run solved the problem. If you don't want to lose data even during the first run then you should write a separate routine to encrypt the existing values.
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
I decrypt data using PHP with this code:
$content="1234";
$cp = mcrypt_module_open('rijndael-128', '', 'cbc', '');
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$key = pack("H*",md5('a'));
mcrypt_generic_init($cp, $key, $iv);
$encrypted = mcrypt_generic($cp, $content);
echo base64_encode($key)."\n";
echo base64_encode($iv)."\n";
echo base64_encode($encrypted)."\n";
mcrypt_generic_deinit($cp);
mcrypt_module_close($cp);
$iv and $encrypted is then saved to file and read in the C# sample app:
var iv=...;
var encrypted=...;
var md5 = new MD5CryptoServiceProvider();
var key = md5.ComputeHash(Encoding.Default.GetBytes("a"));
md5.Clear();
Console.WriteLine(Convert.ToBase64String(key));
Console.WriteLine(Convert.ToBase64String(iv));
Console.WriteLine(Convert.ToBase64String(encrypted));
The output here is exactly the same as the output from PHP, so I can assure there is no encoding error inbetween.
var rd = new RijndaelManaged {
Key = key,
IV = iv,
Mode = CipherMode.CBC,
KeySize = 128,
Padding = PaddingMode.Zeros
};
var buffer = new byte[encrypted.Length];
using(var ms = new MemoryStream(buffer)) {
using(var cs = new CryptoStream(ms, rd.CreateDecryptor(), CryptoStreamMode.Write)) {
cs.Write(encrypted, 0, encrypted.Length);
ms.Read(buffer, 0, buffer.Length);
Console.WriteLine(Encoding.Default.GetString(buffer));
}
}
rd.Clear();
The result of the decryption varies on every program start, even with exactly the same input data:
First run:
DMF1ucDxtqgxw5niaXcmYQ== <-Key
GoCeRkrL/EMKNH/BYeLsqQ== <-IV
UBE3DkgbJgj1K/TISugLxA== <-Encrypted
OlOB99yiCYRDoLx+0xxZxQ== <-"Decrypted"
Second run:
DMF1ucDxtqgxw5niaXcmYQ== <-Key
GoCeRkrL/EMKNH/BYeLsqQ== <-IV
UBE3DkgbJgj1K/TISugLxA== <-Encrypted
w5fcY5Fbb9KRgoHfhqAztA== <-"Decrypted"
Key, IV, Encrypted data are identical, but still the decrypted date varies and is always wrong. buffer should contain "1234" or "1234" plus 12 trailing zeros.
I don't see why the results vary and what is not working, but I have been staring at this darn piece of code for several hours now, and probably miss the obvious error...
Reversing the CryptoStream like this creates identically wrong results:
using(var ms = new MemoryStream(encrypted)) {
using(var cs = new CryptoStream(ms, rd.CreateDecryptor(), CryptoStreamMode.Read)) {
cs.Read(buffer, 0, buffer.Length);
Console.WriteLine(Convert.ToBase64String(buffer));
}
}
Help?
Thanks!
Alexander
Well, modifying an old sample of my sins of the past I ended up with this:
static string Decrypt() {
byte[] keyBytes = Convert.FromBase64String("DMF1ucDxtqgxw5niaXcmYQ==");
byte[] iv = Convert.FromBase64String("GoCeRkrL/EMKNH/BYeLsqQ==");
byte[] cipherTextBytes = Convert.FromBase64String("UBE3DkgbJgj1K/TISugLxA==");
var symmetricKey = new RijndaelManaged { Mode = CipherMode.CBC, IV = iv, KeySize = 128, Key = keyBytes, Padding = PaddingMode.Zeros};
using (var decryptor = symmetricKey.CreateDecryptor())
using (var ms = new MemoryStream(cipherTextBytes))
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) {
var plainTextBytes = new byte[cipherTextBytes.Length];
int decryptedByteCount = cs.Read(plainTextBytes, 0, plainTextBytes.Length);
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
which gave "1234" with trailing \0 characters.. Did you just forget to convert the byte[] to a string again? What other difference am I missing?