This post looks at symmetric and asymmetric encryption and how this could be implemented in .NET Core. Symmetric encryption is fast and can encrypt or decrypt large amounts of text, streams or files but requires a shared key. Asymmetric encryption can be used without shared a key, but can only encrypt or decrypt small texts depending of the key size.
Code: https://github.com/damienbod/SendingEncryptedData
Symmetric Encryption in .NET Core
System.Security.Cryptography implements and provides the APIs for encryption in .NET Core. In this example, a simple text will be encrypted. The key is created using a random string created using the RNGCryptoServiceProvider class. Each session for encryption and decryption is created and returned as base 64 strings. Using the key and the IV properties, strings can be encrypted or decrypted.
public (string Key, string IVBase64) InitSymmetricEncryptionKeyIV() { var key = GetEncodedRandomString(32); // 256 Aes cipher = CreateCipher(key); var IVBase64 = Convert.ToBase64String(cipher.IV); return (key, IVBase64); } private string GetEncodedRandomString(int length) { var base64 = Convert.ToBase64String(GenerateRandomBytes(length)); return base64; } private Aes CreateCipher(string keyBase64) { // Default values: Keysize 256, Mode CBC, Padding PKC27 Aes cipher = Aes.Create(); cipher.Padding = PaddingMode.ISO10126; cipher.Key = Convert.FromBase64String(keyBase64); return cipher; } private byte[] GenerateRandomBytes(int length) { using var randonNumberGen = new RNGCryptoServiceProvider(); var byteArray = new byte[length]; randonNumberGen.GetBytes(byteArray); return byteArray; }
The Encrypt method takes the three parameters and produces an encrypted text which can only be decrypted using the same key and IV base 64 strings. If encrypting large amounts of text, then a CryptoStream should be used. See the example in the Microsoft docs.
public string Encrypt(string text, string IV, string key) { Aes cipher = CreateCipher(key); cipher.IV = Convert.FromBase64String(IV); ICryptoTransform cryptTransform = cipher.CreateEncryptor(); byte[] plaintext = Encoding.UTF8.GetBytes(text); byte[] cipherText = cryptTransform.TransformFinalBlock(plaintext, 0, plaintext.Length); return Convert.ToBase64String(cipherText); }
The Decrypt method takes the same three parameters as the Encrypt method and produces a decrypted text.
public string Decrypt(string encryptedText, string IV, string key) { Aes cipher = CreateCipher(key); cipher.IV = Convert.FromBase64String(IV); ICryptoTransform cryptTransform = cipher.CreateDecryptor(); byte[] encryptedBytes = Convert.FromBase64String(encryptedText); byte[] plainBytes = cryptTransform.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length); return Encoding.UTF8.GetString(plainBytes); }
A simple console application can be used to demonstrate that the symmetric encryption in .NET Core works using AES. For this to work, the key and the IV needs to be shared to decrypt the encrypted text. This would also work with small changes for streams or files.
using EncryptDecryptLib; using System; namespace ConsoleCreateEncryptedText { class Program { static void Main(string[] args) { var text = "I have a big dog. You've got a cat. We all love animals!"; Console.WriteLine("-- Encrypt Decrypt symmetric --"); Console.WriteLine(""); var symmetricEncryptDecrypt = new SymmetricEncryptDecrypt(); var (Key, IVBase64) = symmetricEncryptDecrypt.InitSymmetricEncryptionKeyIV(); var encryptedText = symmetricEncryptDecrypt.Encrypt(text, IVBase64, Key); Console.WriteLine("-- Key --"); Console.WriteLine(Key); Console.WriteLine("-- IVBase64 --"); Console.WriteLine(IVBase64); Console.WriteLine(""); Console.WriteLine("-- Encrypted Text --"); Console.WriteLine(encryptedText); var decryptedText = symmetricEncryptDecrypt.Decrypt(encryptedText, IVBase64, Key); Console.WriteLine("-- Decrypted Text --"); Console.WriteLine(decryptedText); } } }
Asymmetric Encryption in .NET Core
Asymmetric encryption is great in that a shared secret is not required. The text is encrypted with a public key and can be decrypted with the private key from same RSA. The text is limited in size depending on the key size.
public string Encrypt(string text, RSA rsa) { byte[] data = Encoding.UTF8.GetBytes(text); byte[] cipherText = rsa.Encrypt(data, RSAEncryptionPadding.Pkcs1); return Convert.ToBase64String(cipherText); } public string Decrypt(string text, RSA rsa) { byte[] data = Convert.FromBase64String(text); byte[] cipherText = rsa.Decrypt(data, RSAEncryptionPadding.Pkcs1); return Encoding.UTF8.GetString(cipherText); }
The CreateRsaPublicKey and the CreateRsaPrivateKey static utility methods create an RSA from a X509Certificate2. The private key RSA is used for decryption, the public key RSA is used for Encryption.
using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace EncryptDecryptLib { public static class Utils { public static RSA CreateRsaPublicKey(X509Certificate2 certificate) { RSA publicKeyProvider = certificate.GetRSAPublicKey(); return publicKeyProvider; } public static RSA CreateRsaPrivateKey(X509Certificate2 certificate) { RSA privateKeyProvider = certificate.GetRSAPrivateKey(); return privateKeyProvider; } } }
A X509Certificate2 certificate is used to encrypt and decrypt the strings. CertificateManager was used to create the certificate.
using CertificateManager; using CertificateManager.Models; using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; namespace EncryptDecryptLib { public class CreateRsaCertificates { public static X509Certificate2 CreateRsaCertificate( CreateCertificates createCertificates, int keySize) { var basicConstraints = new BasicConstraints { CertificateAuthority = true, HasPathLengthConstraint = true, PathLengthConstraint = 2, Critical = false }; var subjectAlternativeName = new SubjectAlternativeName { DnsName = new List<string> { "SigningCertificateTest", } }; var x509KeyUsageFlags = X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.NonRepudiation | X509KeyUsageFlags.KeyAgreement; var enhancedKeyUsages = new OidCollection { OidLookup.CodeSigning, OidLookup.SecureEmail, OidLookup.TimeStamping }; var certificate = createCertificates.NewRsaSelfSignedCertificate( new DistinguishedName { CommonName = "SigningCertificateTest" }, basicConstraints, new ValidityPeriod { ValidFrom = DateTimeOffset.UtcNow, ValidTo = DateTimeOffset.UtcNow.AddYears(1) }, subjectAlternativeName, enhancedKeyUsages, x509KeyUsageFlags, new RsaConfiguration { KeySize = keySize, RSASignaturePadding = RSASignaturePadding.Pkcs1, HashAlgorithmName = HashAlgorithmName.SHA256 }); return certificate; } } }
A console application is used to create a RSA certificate with a key size of 2048. This certificate is then used to encrypt a text, and then decrypt the encrypted text again.
using CertificateManager; using EncryptDecryptLib; using Microsoft.Extensions.DependencyInjection; using System; namespace ConsoleAsymmetricEncryption { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); var serviceProvider = new ServiceCollection() .AddCertificateManager() .BuildServiceProvider(); var cc = serviceProvider.GetService<CreateCertificates>(); var cert2048 = CreateRsaCertificates.CreateRsaCertificate(cc, 2048); var text = "I have a big dog. You've got a cat. We all love animals!"; Console.WriteLine("-- Encrypt Decrypt asymmetric --"); Console.WriteLine(""); var asymmetricEncryptDecrypt = new AsymmetricEncryptDecrypt(); var encryptedText = asymmetricEncryptDecrypt.Encrypt(text, Utils.CreateRsaPublicKey(cert2048)); Console.WriteLine(""); Console.WriteLine("-- Encrypted Text --"); Console.WriteLine(encryptedText); var decryptedText = asymmetricEncryptDecrypt.Decrypt(encryptedText, Utils.CreateRsaPrivateKey(cert2048)); Console.WriteLine("-- Decrypted Text --"); Console.WriteLine(decryptedText); } } }
Asymmetric encryption is slow compared to symmetric encryption and has a size limit. Symmetric encryption requires a shared key. In the next blog, we will use the asymmetric encryption and the symmetric encryption together and get the benefits of both to send encrypted texts to targeted identities.
Links:
https://docs.microsoft.com/en-us/dotnet/standard/security/encrypting-data
https://docs.microsoft.com/en-us/dotnet/standard/security/decrypting-data
https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview
https://edi.wang/post/2019/1/15/caveats-in-aspnet-core-data-protection
https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata.unprotect
https://docs.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection
https://edi.wang/post/2019/1/15/caveats-in-aspnet-core-data-protection
https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aes?view=netcore-3.1
https://docs.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography
https://dev.to/stratiteq/cryptography-with-practical-examples-in-net-core-1mc4
https://www.tpeczek.com/2020/08/supporting-encrypted-content-encoding.html
https://docs.microsoft.com/en-us/dotnet/standard/security/vulnerabilities-cbc-mode