Encrypt String in .NET Core
I would like to encrypt a string in .NET Core using a key. I have a client / server scenario and would like to encrypt a string on the client, send it to the server and decrypt it.
As .NET Core is still in a early stage (e.g. Rijndael is not yet available), what are my options?
You really shouldn't ever use Rijndael/RijndaelManaged in .NET. If you're using it with a BlockSize value of 128 (which is the default) then you're using AES, as I explained in a similar question.
The symmetric encryption options available in .NET Core are:
- AES (System.Security.Cryptography.Aes.Create())
- 3DES (System.Security.Cryptography.TripleDES.Create())
And for asymmetric encryption
- RSA (System.Security.Cryptography.RSA.Create())
Especially on .NET Core the factories are the best way to go, because they will give back an object which works on the currently executing operating system. For example, RSACng is a public type but only works on Windows; and RSAOpenSsl is a public type but is only supported on Linux and macOS.
There is already an answer to this but I think that we can provide a simpler solution.
If you simply want to protect your data, there is an implementation for this in .NET Core which relieves you from the headaches of encryption; DataProtectionProvider
.
In Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection(); //Add this
[..]
services.AddMvc();
}
If you would like, it is possible to specify algorithms (using Microsoft.AspNetCore.DataProtection
) used for encryption and validation, like this:
services.AddDataProtection()
.UseCryptographicAlgorithms(new AuthenticatedEncryptionSettings()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_GCM,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
Then encrypt/decrypt using a service as such:
public class CipherService : ICipherService
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private const string Key = "my-very-long-key-of-no-exact-size";
public CipherService(IDataProtectionProvider dataProtectionProvider)
{
_dataProtectionProvider = dataProtectionProvider;
}
public string Encrypt(string input)
{
var protector = _dataProtectionProvider.CreateProtector(Key);
return protector.Protect(input);
}
public string Decrypt(string cipherText)
{
var protector = _dataProtectionProvider.CreateProtector(Key);
return protector.Unprotect(cipherText);
}
}
Edit As mentioned in the comments below it might be a good idea to understand that using the DataProtectionProvider like this will only work on the same machine with keys stored on local disk.
Here is a trivial sample without authentication:
var text = "Hello World";
var buffer = Encoding.UTF8.GetBytes(text);
var iv = GetRandomData(128);
var keyAes = GetRandomData(256);
byte[] result;
using (var aes = Aes.Create())
{
aes.Key = keyAes;
aes.IV = iv;
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (var resultStream = new MemoryStream())
{
using (var aesStream = new CryptoStream(resultStream, encryptor, CryptoStreamMode.Write))
using (var plainStream = new MemoryStream(buffer))
{
plainStream.CopyTo(aesStream);
}
result = resultStream.ToArray();
}
}
For key generation:
private static byte[] GetRandomData(int bits)
{
var result = new byte[bits / 8];
RandomNumberGenerator.Create().GetBytes(result);
return result;
}
I have a different approach where I want to encrypt a string with a key and get a scrambled string which I can decrypt by the same key again. See the following extension methods:
public static string Encrypt(this string text, string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key must have valid value.", nameof(key));
if (string.IsNullOrEmpty(text))
throw new ArgumentException("The text must have valid value.", nameof(text));
var buffer = Encoding.UTF8.GetBytes(text);
var hash = new SHA512CryptoServiceProvider();
var aesKey = new byte[24];
Buffer.BlockCopy(hash.ComputeHash(Encoding.UTF8.GetBytes(key)), 0, aesKey, 0, 24);
using (var aes = Aes.Create())
{
if (aes == null)
throw new ArgumentException("Parameter must not be null.", nameof(aes));
aes.Key = aesKey;
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (var resultStream = new MemoryStream())
{
using (var aesStream = new CryptoStream(resultStream, encryptor, CryptoStreamMode.Write))
using (var plainStream = new MemoryStream(buffer))
{
plainStream.CopyTo(aesStream);
}
var result = resultStream.ToArray();
var combined = new byte[aes.IV.Length + result.Length];
Array.ConstrainedCopy(aes.IV, 0, combined, 0, aes.IV.Length);
Array.ConstrainedCopy(result, 0, combined, aes.IV.Length, result.Length);
return Convert.ToBase64String(combined);
}
}
}
public static string Decrypt(this string encryptedText, string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key must have valid value.", nameof(key));
if (string.IsNullOrEmpty(encryptedText))
throw new ArgumentException("The encrypted text must have valid value.", nameof(encryptedText));
var combined = Convert.FromBase64String(encryptedText);
var buffer = new byte[combined.Length];
var hash = new SHA512CryptoServiceProvider();
var aesKey = new byte[24];
Buffer.BlockCopy(hash.ComputeHash(Encoding.UTF8.GetBytes(key)), 0, aesKey, 0, 24);
using (var aes = Aes.Create())
{
if (aes == null)
throw new ArgumentException("Parameter must not be null.", nameof(aes));
aes.Key = aesKey;
var iv = new byte[aes.IV.Length];
var ciphertext = new byte[buffer.Length - iv.Length];
Array.ConstrainedCopy(combined, 0, iv, 0, iv.Length);
Array.ConstrainedCopy(combined, iv.Length, ciphertext, 0, ciphertext.Length);
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
using (var resultStream = new MemoryStream())
{
using (var aesStream = new CryptoStream(resultStream, decryptor, CryptoStreamMode.Write))
using (var plainStream = new MemoryStream(ciphertext))
{
plainStream.CopyTo(aesStream);
}
return Encoding.UTF8.GetString(resultStream.ToArray());
}
}
}
The data protection system is enabled by default for ASP.NET Core applications. You don't even need to do anything in your StartUp method unless you want to reconfigure the default key storage location or the life time of keys. In which case you'd do the following in your ConfigureServices method:
services.ConfigureDataProtection(dp =>
{
dp.PersistKeysToFileSystem(new DirectoryInfo(@"c:\keys"));
dp.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
});
Because the data protection system is in the application's services collection by default, it can be made available via dependency injection. Here's how you can inject the IDataProtectionProvider into a controller and then use it to create an instance of an IDataProtector in the controller's constructor:
public class HomeController : Controller
{
IDataProtector _protector;
public HomeController(IDataProtectionProvider provider)
{
_protector = provider.CreateProtector(GetType().FullName);
}
}
You can then call the protector to encrypt content like this:
public IActionResult Index()
{
var model = _service.GetAll().Select(c => new ContractViewModel {
Id = _protector.Protect(c.Id.ToString()),
Name = c.Name }).ToList();
return View(model);
}
I hope this helps :)