39

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?

dknaack
  • 60,192
  • 27
  • 155
  • 202
  • 2
    As per the comment [here](http://stackoverflow.com/questions/38333722/how-to-use-rijndael-encryption-with-a-net-core-class-library-not-net-framewo), it's looking to be implemented in version 1.1. Till then, you could use AES as shown [here](http://stackoverflow.com/questions/35912849/rijndael-in-class-library-package-not-avaiable-for-dotnet5-4) – keyboardP Aug 05 '16 at 17:53
  • Check this nuget package https://www.nuget.org/packages/CryptoNet/ – Maytham Fahmi Dec 21 '21 at 23:24

7 Answers7

46

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.

Community
  • 1
  • 1
bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • I'm working with .Net Core with System.Security.Cryptograph, and it appears the Aes "Create()" function is missing. I've included the "System.Security.Cryptography.Algorithms": "4.3.0" to my project.json, am I missing a reference? This is the compile error below. Error CS0426 The type name 'Create' does not exist in the type 'Aes' – phanf Feb 11 '17 at 21:23
  • 1
    @phanf https://apisof.net/catalog/System.Security.Cryptography.Aes.Create() says it was there in all versions of .NET Core. Are you perhaps using a different type of the same name? – bartonjs Feb 11 '17 at 22:10
  • Also not able to find create() methods on various classes in this package. Latest dotnet core on macsosx. – kevinc May 09 '17 at 11:05
  • 2
    Here is a tutorial related to AES https://code-maze.com/csharp-string-encryption-decryption/ – Christian Jan 15 '23 at 15:18
46

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.

Marcus
  • 8,230
  • 11
  • 61
  • 88
  • 2
    Is this able to be used in `.NET MVC` as well? – LatentDenis May 30 '17 at 19:23
  • 18
    It's worth noting that the keys here are stored on the machine (by default), so it can only be decrypted by the machine that encrypted it. Use distributed DataProtection like `Microsoft.AspNetCore.DataProtection.Redis` to share keys if you need to be able to decrypt it on multiple instances of an app or different services (like in Single Sign On). – hofnarwillie Aug 27 '17 at 22:31
  • 24
    The `"my-very-long-key-of-no-exact-size"` is not an excrpytion key, just a unique name for the protector. The actual keys are stored as files on disk by default (so watch out if on a web farm) – alastairtree Mar 01 '18 at 13:50
  • 8
    You should edit your answer since someone will have a bad day when they deploy this code on more then one instance and one instance and the key will not be the same. – user1852503 Sep 12 '18 at 10:34
  • 6
    Microsoft recommends to not use this for long lived protected data. – Nick Turner Feb 21 '19 at 16:25
  • @Marcus, does this approach work in linux deployments as well? Windows DP API may not be available there. – Ravi M Patel Jan 20 '20 at 21:19
  • @Marcus, "will only work on the same machine with keys stored on local disk" ... because the IDatProtectionProvider stuff in .Net Core supplies *assymetric* encryption? – Aidanapword Feb 26 '20 at 16:00
  • I'm having a very hard time finding examples of .NET Core 3.x symmetric encryption that runs on multiple servers. The fact that DataProtection only stores keys on the machine where encryption happens makes is practically useless, except maybe as a high-level example about how the code should be structured. – Mass Dot Net Sep 15 '20 at 14:35
  • Unless you are using a VM or physical machine. – Marcus Sep 15 '20 at 17:00
  • Does this produce the same cipherText for same plainText when used multiple times? – Sunil Kumar Apr 12 '21 at 14:10
  • 4
    DataProtection keys can be persisted in the database as well using [EF Core DbContext](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/key-storage-providers?view=aspnetcore-5.0&tabs=visual-studio#entity-framework-core), and then it should work with multiple instances encrypting and decrypting the data. – Mihir May 12 '21 at 13:47
19

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());
            }
        }
    }
iss0
  • 191
  • 1
  • 3
  • I like what you've posted and am interested in using the same method but have a couple of questions: What does `combined` contain before being base64 encoded and why would we want to do this instead of just using `result`? – Professor of programming Nov 07 '18 at 17:23
  • 1
    The array `combined` contains bytes or rather 8-bit unsigned integers. This is totally fine depending on what you want to do with it. In my use case I needed a valid string representation of those encrypted data. Encoding using UTF-8 or something similar wouldn't work because not every byte value has a valid UTF-8 representation. Base64 on the other hand is exactly designed to represent binary data as ASCII string. Have a look at [Wikipedia](https://en.wikipedia.org/wiki/Base64) if you're interested. If you don't need a string the code can be easily changed to return just the bytes. – iss0 Nov 08 '18 at 18:25
  • 2
    The cipertext produced is not deterministic, different runs of Encrypt() with same key and plaintext produce different ciphertexts – Richard Anderssen Jul 04 '19 at 16:59
  • @RichardAnderssen The combined ciphertext in this case includes the aes.IV initialization vector which by default is a randomly generated value. Decryption will require this value plus the user-supplied key text (i.e. string key parameter), so that's why the final output will vary and why the IV is included in the combined result. As long as you specify the same user-supplied key, the decryption will still be successful. This is intentional. If you insist on having it deterministic, you could supply a specific IV each time, but that defeats the security and makes hacking the key easier. – C Perkins Sep 19 '21 at 05:45
  • @iss0 Why generate a 192-bits (24 bytes) hashed key instead of 256 bits (32 bytes)? – C Perkins Sep 19 '21 at 06:28
  • 1
    @iss0 : That's a great solution, only downside is that all providers including AESProvider, SHA512CryptoServiceProvider are obsolete, so recommended way is to use Create : **using (var hash = SHA512.Create())** – ssthakur Apr 25 '22 at 14:24
17

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;
}
hdev
  • 6,097
  • 1
  • 45
  • 62
  • You can also avoid use of streams if your byte[] is small enough, and use encryptor.TransformFinalBlock(buffer, 0, buffer.Length) – Jim W Jan 30 '17 at 17:54
  • Less code, less objects to create, what's not to like? – Jim W Feb 19 '17 at 21:15
  • this just shifts the problem. now you have to store the key & iv somewhere secure. – Mike D. Nov 01 '19 at 01:07
  • @MikeD. only key must be stored secure,the IV can be public. This is a sample for a pure symmetric-key algorithm, of course you should implement a key establishment. Take a look at [ECDiffieHellman.DeriveKeyFromHash](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellman.derivekeyfromhash?view=netframework-4.8) – hdev Jan 22 '20 at 13:13
5

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 :)

Webezine
  • 345
  • 7
  • 22
  • Your approach was mentioned above from Marcus already, but you will find a lots of comment there, which say, that real keys are saved at the file system and you are not able to use the same encrypted values in other environments for decryption purposes – Tobias Raphael Dieckmann Mar 26 '19 at 19:59
  • @Webezine, does this approach work in linux deployments? – Ravi M Patel Jan 20 '20 at 21:17
  • 1
    From what I could find out yes it does. – Webezine Jan 20 '20 at 22:47
  • @Webezine - except it will need ot use linux-style path for the key location if you want to put them on the file system. That said: putting keys on the file system (as raw files) is probably not a great idea? – Aidanapword Feb 26 '20 at 16:02
  • @Webezine, I found out the hard way that it doesn't work on linux... there must be alternatives given that .net core is cross-platform. Need to explore those. – Ravi M Patel Mar 22 '20 at 10:15
4

You can do by using System.Security.Cryptography

string keyString = "encrypt123456789";
var key = Encoding.UTF8.GetBytes(keyString);//16 bit or 32 bit key string

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);
        }
    }
}

For Decryption

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);//same key string

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;
    }
}
Arasuvel
  • 2,971
  • 1
  • 25
  • 40
sundarraj
  • 51
  • 2
  • For the decryption portion, the second Buffer.BlockCopy should read: Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, cipher.Length); instead of iv.Length a second time. – Mike Burger Jun 10 '20 at 19:41
  • This decrypt method throws an error System.Security.Cryptography.CryptographicException: 'Padding is invalid and cannot be removed.' I've added aesAlg.Padding = PaddingMode.PKCS7; to both encrypt and decrypt methods, error still occurs. How do I get around this? What x-bit encryption level is this example? 128? 256? Supposing the key was able to be kept secure, would this code be good to use in Production? – Dragick Aug 11 '20 at 12:24
  • I'm a bit confused. You have `var decryptedContent=msEncrypt.ToArray();` but it's **en**crypting part not **de**crypting, right? Shouldn't it say `var encryptedContent`? – Konrad Viltersten May 14 '21 at 11:46
  • This is a poor way of generating a key from password text. It should involve some sort of hashing algorithm, not just dumping text into a byte array. – C Perkins Sep 19 '21 at 06:36
1

For those using the above solution posted by @sundarraj and getting the System.Security.Cryptography.CryptographicException: 'Padding is invalid and cannot be removed.' error when decrypting use these lines of code to fix the decryption:

var cipher = new byte[full.Length - iv.Length];

Buffer.BlockCopy(full, 0, iv, 0, iv.Length);
Buffer.BlockCopy(full, iv.Length, cipher, 0, cipher.Length);
Mason
  • 11
  • 2
  • Why not just use iss0's answer posted a year prior to sundarraj's answer? isso's code does the same thing, but includes a basic password hashing scheme and doesn't suffer from this error. sundarray's answer does not add anything to the prior answers. – C Perkins Sep 19 '21 at 06:41