First, I realize there are dozens of other posts that have answers to this question and I have read and tried them all. I still can't seem to get past this issue so am looking for a little help from somebody that knows more about crypto than I do.
Second, the code I am going to share is legacy and because I am not a crypto expert it is still not 100% clear on what everything means. It may be that some or all of this code is total rubbish and should be scrapped; however, there are a lot of other systems already using it and have stored encrypted values that have gone through this code. Changing things like the crypto algorithm is not exactly an option at this point. With that said, the private methods are the legacy code as well as the testing values (i.e. the encryption key) are all things that can't change. The two public static methods are what is new and likely causing problems, but I can't seem to figure it out.
On with the code......
class Program
{
public static string Encrypt(string key, string toEncrypt)
{
var keyArray = Convert.FromBase64String(key);
var info = Encoding.ASCII.GetBytes(toEncrypt);
var encrypted = Encrypt(keyArray, info);
return Encoding.ASCII.GetString(encrypted);
}
public static string Decrypt(string key, string cipherString)
{
var keyArray = Convert.FromBase64String(key);
var cipherText = Encoding.ASCII.GetBytes(cipherString);
var decrypted = Decrypt(keyArray, cipherText);
return Encoding.ASCII.GetString(decrypted);
}
private static byte[] Encrypt(byte[] key, byte[] info)
{
using (var cipher = Aes.Create())
{
cipher.Key = key;
cipher.Mode = CipherMode.CBC;
cipher.Padding = PaddingMode.ISO10126;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, cipher.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(info, 0, info.Length);
}
var ciphertext = ms.ToArray();
var message = new byte[cipher.IV.Length + ciphertext.Length];
cipher.IV.CopyTo(message, 0);
ciphertext.CopyTo(message, cipher.IV.Length);
return message;
}
}
}
private static byte[] Decrypt(byte[] key, byte[] ciphertext)
{
using (var cipher = Aes.Create())
{
cipher.Key = key;
cipher.Mode = CipherMode.CBC;
cipher.Padding = PaddingMode.ISO10126;
var ivSize = cipher.IV.Length;
var iv = new byte[ivSize];
Array.Copy(ciphertext, iv, ivSize);
cipher.IV = iv;
var data = new byte[ciphertext.Length - ivSize];
Array.Copy(ciphertext, ivSize, data, 0, data.Length);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, cipher.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
}
return ms.ToArray();
}
}
}
static void Main(string[] args)
{
var newEncryptionKey = Guid.NewGuid().ToString().Replace("-", string.Empty);
var encryptedValue = Encrypt(newEncryptionKey, "test");
Console.WriteLine($"New encrypted value: {encryptedValue}");
var decryptedValue = Decrypt(newEncryptionKey, encryptedValue);
Console.WriteLine($"New decrypted value: {decryptedValue}");
}
}
So there it is. Basically, I am trying to use a test string of "test" and encrypt it using a GUID as a key. Again, I didn't choose this key and there are encrypted values already using a GUID as a key so I can't change that if at all possible. The encryption works fine, but when I go to do the decryption, I get the exception noted in the title of this question.
Any help would be GREATLY appreciated.
You can't just convert a byte[] of ciphertext to ASCII. It doesn't work like that. Character encodings are scary beasts and should not be muddled with if you don't understand them. I don't think there is a real person alive that does ;)
What you should do instead is return your result as base64, which is still a collection of ASCII characters but they are safe to be moved around as a string, and don't result in the loss of any characters.
See the modified code below:
public static string Encrypt(string key, string toEncrypt)
{
var keyArray = Convert.FromBase64String(key);
var info = Encoding.ASCII.GetBytes(toEncrypt);
var encrypted = Encrypt(keyArray, info);
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string key, string cipherString)
{
var keyArray = Convert.FromBase64String(key);
var cipherText = Convert.FromBase64String(cipherString);
var decrypted = Decrypt(keyArray, cipherText);
return Encoding.ASCII.GetString(decrypted);
}
Related
I just know I will get referenced to a billion SO questions, BUT, everytime I try to use one for use with .net Maui and therefore using .Net 6 and to be .Net 7 I get a warning about OBSOLETE (and deprecated) functions. I can't tell you how many solutions I've tried. And of course I simply don't have the brains or time to dig deep.
So I'd be very grateful if someone could simply post some code that does the following:
(password could be ANY length, but I'll accept restrictions, but min 4 chars (like a pin). Yes I know it might not be very secure, but it's all I need for my use case.)
public static string Encrypt(string plainText, string password)
{
}
public static string Decrypt(string cipherText, string password)
{
}
I have seen that there are all kinds of extras like salts and pbkdf2 for hashing the password, but I'd really like all that gubbins to be hidden from me, so I don't have to bother. But please feel free to add explanations of why I should bother...
Many thanks and please be gentle. :)
So I seem to have figured out a solution using
https://www.c-sharpcorner.com/article/encryption-and-decryption-using-a-symmetric-key-in-c-sharp/
generating AES 256 bit key value
namespace Census.Classes;
public static class EncryptionHelper
{
public static string Encrypt(string plainText, byte[] encryptionKeyBytes)
{
byte[] iv = new byte[16];
byte[] array;
using (Aes aes = Aes.Create())
{
aes.Key = encryptionKeyBytes;
aes.IV = iv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new())
{
using (CryptoStream cryptoStream = new((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter streamWriter = new((Stream)cryptoStream))
{
streamWriter.Write(plainText);
}
array = memoryStream.ToArray();
}
}
}
return Convert.ToBase64String(array);
}
public static string Decrypt(string cipherText, byte[] encryptionKeyBytes)
{
byte[] iv = new byte[16];
byte[] buffer = Convert.FromBase64String(cipherText);
using (Aes aes = Aes.Create())
{
aes.Key = encryptionKeyBytes;
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new(buffer))
{
using (CryptoStream cryptoStream = new((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new((Stream)cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
private static readonly byte[] Salt = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80 };
public static byte[] CreateKey(string password, int keyBytes = 32)
{
const int Iterations = 300;
var keyGenerator = new Rfc2898DeriveBytes(password, Salt, Iterations);
return keyGenerator.GetBytes(keyBytes);
}
}
I use it as follows:
//get an encryption key from the password
byte[] encryptionKeyBytes = EncryptionHelper.CreateKey(Password);
mystring = EncryptionHelper.Encrypt(plaintext, encryptionKeyBytes);
I do it like this because I call it very many times.
I'd be grateful if someone more knowledgeable could cast their eye over it and see if there are any improvements that could be made, but at least it works and doesn't throw up any errors about obsolete or deprecated code - yay! :)
If you want to store a secret on a particular computer only, and for a particular username then use the ProtectedData class provided in System.Security.Cryptography.
public static string Encrypt(string plainText, string password = null)
{
var data = Encoding.Default.GetBytes(plainText);
var pwd = !string.IsNullOrEmpty(password) ? Encoding.Default.GetBytes(password) : Array.Empty<byte>();
var cipher = ProtectedData.Protect(data, pwd, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(cipher);
}
public static string Decrypt(string cipherText, string password = null)
{
var cipher = Convert.FromBase64String(cipherText);
var pwd = !string.IsNullOrEmpty(password) ? Encoding.Default.GetBytes(password) : Array.Empty<byte>();
var data = ProtectedData.Unprotect(cipher, pwd, DataProtectionScope.CurrentUser);
return Encoding.Default.GetString(data);
}
it used the windows cryptographic store to keep the secret. Optionally you can make it available to all users of the machine (provided they have the password) by changing the scope to DataProtectionScope.LocalMachine.
Note that the password is optional, as it uses your windows credentials for key generation.
I need to implement AES encryption in 2 different projects, but one must use the .NET standard crypto libraries and the other must use BouncyCastle. Both are C# code. Relevant methods are as follows:
.NET:
internal class NETAesCryptor : IAesCryptor
{
public Tuple<byte[], byte[]> Encrypt(string plaintext, byte[] key)
{
byte[] ciphertext, iv;
using (var aes_provider = new AesCryptoServiceProvider())
{
aes_provider.Padding = PaddingMode.PKCS7;
aes_provider.GenerateIV();
iv = aes_provider.IV;
var encryptor = aes_provider.CreateEncryptor(key, iv);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
using (var sw = new StreamWriter(cs))
{
sw.Write(plaintext);
}
ciphertext = ms.ToArray();
}
}
}
var result = new Tuple<byte[], byte[](ciphertext, iv);
return result;
}
public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
{
string plaintext;
using (var aes_provider = new AesCryptoServiceProvider())
{
aes_provider.Padding = PaddingMode.PKCS7;
aes_provider.IV = iv;
var decryptor = aes_provider.CreateDecryptor(key, iv);
using (var ms = new MemoryStream(ciphertext))
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
plaintext = sr.ReadToEnd();
}
}
}
}
return plaintext;
}
}
Bouncycastle:
internal class BCAesCryptor : IAesCryptor
{
private SecureRandom _r;
public BCAesCryptor()
{
_r = new SecureRandom();
}
public Tuple<byte[], byte[]> Encrypt(string plaintext, byte[] key)
{
var plaintext_bytes = Encoding.UTF8.GetBytes(plaintext);
var iv = GenerateRandomBytes(16);
var engine = new AesEngine();
var cbc_cipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
var key_param = new KeyParameter(key);
var key_param_with_iv = new ParametersWithIV(key_param, iv);
cipher.Init(true, key_param_with_iv);
var ciphertext = new byte[cipher.GetOutputSize(plaintext_bytes.Length)];
var length = cipher.ProcessBytes(plaintext_bytes, ciphertext, 0);
cipher.DoFinal(ciphertext, length);
var result = new Tuple<byte[], byte[]>(ciphertext, iv);
return result;
}
public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
{
var engine = new AesEngine();
var cbc_cipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
var key_param = new KeyParameter(key);
var key_param_with_iv = new ParametersWithIV(key_param, iv);
cipher.Init(false, key_param_with_iv);
var plaintext = new byte[cipher.GetOutputSize(ciphertext.Length)];
var length = cipher.ProcessBytes(ciphertext, plaintext, 0);
cipher.DoFinal(plaintext, length);
var result = Encoding.UTF8.GetString(plaintext);
return result;
}
private byte[] GenerateRandomBytes(int length = 16)
{
var result = new byte[length];
_r.NextBytes(result);
return result;
}
}
Encryption/decryption between .NET methods works OK, and Bouncycastle encryption/.NET decryption also works OK. But for some reason, Bouncycastle decryption adds a variable number of \0 characters at the end of the plaintext, and I don't know why is this happening.
Test code I'm using:
[TestClass]
public class AesCryptorTests
{
private byte[] _key;
private string _plaintext;
public AesCryptorTests()
{
_key = GenerateRandomBytes();
_plaintext = "Lorem ipsum dolor sit amet";
}
[TestMethod]
public void TestMethod2()
{
var bc = new BCAesCryptor();
var net = new NETAesCryptor();
var result = net.Encrypt(_plaintext, _key);
var new_plaintext = bc.Decrypt(result.Ciphertext, result.IV, _key);
Assert.AreEqual(_plaintext, new_plaintext);
}
private byte[] GenerateRandomBytes(int cantidad = 16)
{
var result = new byte[cantidad];
using (var r = new RNGCryptoServiceProvider())
{
r.GetBytes(result);
}
return result;
}
}
In the previous test, the decryption returns Lorem ipsum dolor sit amet\0\0\0\0\0\0 instead of the plaintext.
Any advice/comment would be greatly appreciated.
The Bouncy Castle can only guess the output size of the plaintext message in advance during the call to GetOutputSize. It cannot know how many padding bytes are used, because those are only available after decryption. So they would have to partially decrypt the ciphertext to know the amount of padding, and that's taking it a step too far. Therefore you get just an estimate on the high side so that the maximum number of bytes can still fit in your newly created buffer.
You'll need the return value of the ProcessBytes and DoFinal to see the actual number of bytes that are decrypted from the ciphertext (in the input buffer and internal buffer) when the methods are called. DoFinal decrypts the last block(s) and then removes the padding from the final block, so only at that time is the size of the (remaining) plaintext known.
What you're currently seeing as zero valued bytes are just the unused bytes of the buffer, as the plaintext size is smaller than the value returned by GetOutputSize.
Of course, this is all hidden in the streaming code of the .NET sample, where ReadToEnd is required to doing some advanced buffering (probably using a MemoryStream internally itself).
Following instructions from Maarten Bodewes, the final working code is as follows:
public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
{
var engine = new AesEngine();
var cbc_cipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
var key_param = new KeyParameter(key);
var key_param_with_iv = new ParametersWithIV(key_param, iv);
cipher.Init(false, key_param_with_iv);
var decryption_buffer = new byte[cipher.GetOutputSize(ciphertext.Length)];
var initial_length = cipher.ProcessBytes(ciphertext, decryption_buffer, 0);
var last_bytes = cipher.DoFinal(decryption_buffer, initial_length);
var total_bytes = initial_length + last_bytes;
var plaintext = new byte[total_bytes];
Array.Copy(decryption_buffer, plaintext, total_bytes);
var result = Encoding.UTF8.GetString(plaintext);
return result;
}
Note that the length of the plaintext is now calculated with the integer outputs of the decryption methods, and a simple array copy is able to create a plaintext without extra characters.
I've been working on a simple helper for dotnet core that should encode and decode a string, based on a user provided password (key) and a salt.
In contradiction to the full .NET Framework, dotnet core currently does not have an implementation for the RijndaelManaged class. Pretty much every decent encrypt/decrypt sample for C# is based on this class, rendering it useless for dotnet core. There is a lot of debate on it on the CoreFX repo on GitHub.
However, with the combination of both the (old) MSDN article on AesManaged and an article by Troy Hunt - not to mention some refactoring - I was able to brew the following semi-working example:
internal class CryptographyHelpers
{
internal static string Decrypt(string password, string salt, string encrypted_value)
{
string decrypted;
using (var aes = Aes.Create())
{
var keys = GetAesKeyAndIV(password, salt, aes);
aes.Key = keys.Item1;
aes.IV = keys.Item2;
// create a decryptor to perform the stream transform.
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
// create the streams used for encryption.
var encrypted_bytes = ToByteArray(encrypted_value);
using (var memory_stream = new MemoryStream(encrypted_bytes))
{
using (var crypto_stream = new CryptoStream(memory_stream, decryptor, CryptoStreamMode.Read))
{
using (var reader = new StreamReader(crypto_stream))
{
decrypted = reader.ReadToEnd();
}
}
}
}
return decrypted;
}
internal static string Encrypt(string password, string salt, string plain_text)
{
string encrypted;
using (var aes = Aes.Create())
{
var keys = GetAesKeyAndIV(password, salt, aes);
aes.Key = keys.Item1;
aes.IV = keys.Item2;
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (var memory_stream = new MemoryStream())
{
using (var crypto_stream = new CryptoStream(memory_stream, encryptor, CryptoStreamMode.Write))
{
using (var writer = new StreamWriter(crypto_stream))
{
writer.Write(plain_text);
}
var encrypted_bytes = memory_stream.ToArray();
encrypted = ToString(encrypted_bytes);
}
}
}
return encrypted;
}
private static byte[] ToByteArray(string input)
{
return Encoding.Unicode.GetBytes(input);
}
private static string ToString(byte[] input)
{
return Encoding.Unicode.GetString(input);
}
private static Tuple<byte[], byte[]> GetAesKeyAndIV(string password, string salt, SymmetricAlgorithm symmetricAlgorithm)
{
const int bits = 8;
var key = new byte[16];
var iv = new byte[16];
var derive_bytes = new Rfc2898DeriveBytes(password, ToByteArray(salt));
key = derive_bytes.GetBytes(symmetricAlgorithm.KeySize / bits);
iv = derive_bytes.GetBytes(symmetricAlgorithm.BlockSize / bits);
return new Tuple<byte[], byte[]>(key, iv);
}
}
I said semi-working example, which means it works... sometimes. And there lies the problem. When I run my test arbitrarily, it succeeds most of the time. Randomly, about one out of four times, it fails with the following message:
Message: Expected string to be "lorem ipsum dom dolor sit amet", but " �R��k6��o��do�Lr sit amet" differs near ">�R" (index 0).
It looks like a unicode problem, but changing the Encoding.Unicode in the helpers to Encoding.UTF8, leads to the error:
System.Security.Cryptography.CryptographicException : The input data is not a complete block.
Changing the encoding to Encoding.ASCII leads to the following error:
System.Security.Cryptography.CryptographicException : Specified padding mode is not valid for this algorithm.
The test I'm using is:
[Fact]
public void Decrypt_Should_Decode_An_Encrypted_String()
{
// arrange
var key = Guid.NewGuid().ToString();
var salt = Guid.NewGuid().ToString();
var original_value = "lorem ipsum dom dolor sit amet";
var encrypted_value = CryptographyHelpers.Encrypt(key, salt, original_value);
// act
var target = CryptographyHelpers.Decrypt(key, salt, encrypted_value);
// assert
target.Should().NotBeNullOrEmpty();
target.Should().Be(original_value);
}
So, my primary question is: why does my imlementation (test) sometimes fail?
I am definitely not an expert on cryptography, but on a high level am aware of some basic concepts. Also, a similar question "How to use Rijndael encryption with a .Net Core class library?" was posted, but the only answer stays on a conceptual level, lacking concrete implementation.
Any tips and arguments as to if this is a 'good enough' implementation would also be very much appreciated.
Thanks!
Your problem has nothing to do with cryptography and instead is caused by this pair of functions
private static byte[] ToByteArray(string input)
{
return Encoding.Unicode.GetBytes(input);
}
private static string ToString(byte[] input)
{
return Encoding.Unicode.GetString(input);
}
You are not allowed to call GetString on arbitrary byte arrays, you will cause a loss of information if you do. You need to use a encoding that allows for arbitrary byte arrays, for example Base64:
private static byte[] ToByteArray(string input)
{
return Convert.FromBase64String(input);
}
private static string ToString(byte[] input)
{
return Convert.ToBase64String(input);
}
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 am building a iPhone app which uses c# web services. My c# web services takes user details and validates against my DB and returns xml files.
So Now the issue is how to encrypt user details(username and password are 10chars each) in objective c and decrypt in C#.
I am very new to cryptography, which method is the best. will it be possible to encrypt in Objective c and Decrypt in C#.
thanks..
Thanks for the rapid replies. I appreciate your help. I found a blog which explains my problem. Here is the link for it.
http://dotmac.rationalmind.net/2009/02/aes-interoperability-between-net-and-iphone/
I am implementing it now. I will let you know the status soon.
Thanks a lot..
Happy coding..
On the assumption that you're encrypting this information in order to protect it over the network, the best solution is to connect over SSL. This will address the problem without creating new complexities in the code. SSL handling is generally available in both .NET and Cocoa.
Is there some other reason that you're trying to encrypt this data?
The following is from the The CSharp Cookbook. It is about as straight forwand an example as exists. You would of course have to port the encrypt portion to Objective C, but so long as you used the same Key, it should generate the same result.
You need to use Rijndael cipher which is available here in ObjC compatible code
public static void EncDecString()
{
string encryptedString = CryptoString.Encrypt("MyPassword");
Console.WriteLine("encryptedString: " + encryptedString);
// get the key and IV used so you can decrypt it later
byte [] key = CryptoString.Key;
byte [] IV = CryptoString.IV;
CryptoString.Key = key;
CryptoString.IV = IV;
string decryptedString = CryptoString.Decrypt(encryptedString);
Console.WriteLine("decryptedString: " + decryptedString);
}
public sealed class CryptoString
{
private CryptoString() {}
private static byte[] savedKey = null;
private static byte[] savedIV = null;
public static byte[] Key
{
get { return savedKey; }
set { savedKey = value; }
}
public static byte[] IV
{
get { return savedIV; }
set { savedIV = value; }
}
private static void RdGenerateSecretKey(RijndaelManaged rdProvider)
{
if (savedKey == null)
{
rdProvider.KeySize = 256;
rdProvider.GenerateKey();
savedKey = rdProvider.Key;
}
}
private static void RdGenerateSecretInitVector(RijndaelManaged rdProvider)
{
if (savedIV == null)
{
rdProvider.GenerateIV();
savedIV = rdProvider.IV;
}
}
public static string Encrypt(string originalStr)
{
// Encode data string to be stored in memory
byte[] originalStrAsBytes = Encoding.ASCII.GetBytes(originalStr);
byte[] originalBytes = {};
// Create MemoryStream to contain output
MemoryStream memStream = new MemoryStream(originalStrAsBytes.Length);
RijndaelManaged rijndael = new RijndaelManaged();
// Generate and save secret key and init vector
RdGenerateSecretKey(rijndael);
RdGenerateSecretInitVector(rijndael);
if (savedKey == null || savedIV == null)
{
throw (new NullReferenceException(
"savedKey and savedIV must be non-null."));
}
// Create encryptor, and stream objects
ICryptoTransform rdTransform =
rijndael.CreateEncryptor((byte[])savedKey.Clone(),
(byte[])savedIV.Clone());
CryptoStream cryptoStream = new CryptoStream(memStream, rdTransform,
CryptoStreamMode.Write);
// Write encrypted data to the MemoryStream
cryptoStream.Write(originalStrAsBytes, 0, originalStrAsBytes.Length);
cryptoStream.FlushFinalBlock();
originalBytes = memStream.ToArray();
// Release all resources
memStream.Close();
cryptoStream.Close();
rdTransform.Dispose();
rijndael.Clear();
// Convert encrypted string
string encryptedStr = Convert.ToBase64String(originalBytes);
return (encryptedStr);
}
public static string Decrypt(string encryptedStr)
{
// Unconvert encrypted string
byte[] encryptedStrAsBytes = Convert.FromBase64String(encryptedStr);
byte[] initialText = new Byte[encryptedStrAsBytes.Length];
RijndaelManaged rijndael = new RijndaelManaged();
MemoryStream memStream = new MemoryStream(encryptedStrAsBytes);
if (savedKey == null || savedIV == null)
{
throw (new NullReferenceException(
"savedKey and savedIV must be non-null."));
}
// Create decryptor, and stream objects
ICryptoTransform rdTransform =
rijndael.CreateDecryptor((byte[])savedKey.Clone(),
(byte[])savedIV.Clone());
CryptoStream cryptoStream = new CryptoStream(memStream, rdTransform,
CryptoStreamMode.Read);
// Read in decrypted string as a byte[]
cryptoStream.Read(initialText, 0, initialText.Length);
// Release all resources
memStream.Close();
cryptoStream.Close();
rdTransform.Dispose();
rijndael.Clear();
// Convert byte[] to string
string decryptedStr = Encoding.ASCII.GetString(initialText);
return (decryptedStr);
}
}
I can't speak regarding the Objective C implementation of Rijndael cipher, but I have used this(C#) code for the basis of some production work and it has worked wonderfully.
Your question is very vague, but in short; yes, it is possible. You will need to figure out what the expectations of your cryptography are (high security or high speed?) and then weigh the benefits of various algorthms and their implementation difficulties in Objective-C and C#.
I don't know Objective C, but for the C# decryption, look into the various CryptoServiceProviders in System.Security.Cryptography.
Here is one example I wrote using TripleDES:
public class TripleDES
{
private byte[] mbKey;
private byte[] mbIV;
private TripleDESCryptoServiceProvider tdProvider = new TripleDESCryptoServiceProvider();
private UTF8Encoding UTEncode = new UTF8Encoding();
// Key: **YOUR KEY**
// Project IV: **YOUR IV**
public TripleDES(string strKey, string strIV)
{
mbKey = UTEncode.GetBytes(strKey);
mbIV = UTEncode.GetBytes(strIV);
}
public TripleDES()
{
//
// TODO: Add constructor logic here
//
}
public string EncryptToString(string strInput)
{
return Convert.ToBase64String(this.EncryptToBytes(strInput));
}
public byte[] EncryptToBytes(string strInput)
{
byte[] bInput = UTEncode.GetBytes(strInput);
byte[] bOutput = ProcessInput(bInput, tdProvider.CreateEncryptor(mbKey, mbIV));
return bOutput;
}
public string DecryptToString(string strInput)
{
return UTEncode.GetString(DecryptToBytes(strInput));
}
public byte[] DecryptToBytes(string strInput)
{
byte[] bInput = Convert.FromBase64String(strInput);
byte[] bOutput = ProcessInput(bInput, tdProvider.CreateDecryptor(mbKey, mbIV));
return bOutput;
}
private byte[] ProcessInput(byte[] input, ICryptoTransform ctProcessor)
{
MemoryStream memStream = new MemoryStream();
CryptoStream crpStream = new CryptoStream(memStream, ctProcessor, CryptoStreamMode.Write);
crpStream.Write(input, 0, input.Length);
crpStream.FlushFinalBlock();
memStream.Position = 0;
byte[] output;
output = new byte[memStream.Length];
memStream.Read(output, 0, output.Length);
memStream.Close();
crpStream.Close();
return output;
}
}
}