0. Introduction
In this article, we will introduce the encryption and decryption in .NET Core. In web applications, user passwords are stored as MD5 hashes. In other cases, encryption and decryption functionalities are also used.
Common encryption algorithms are divided into symmetric and asymmetric encryption. Symmetric encryption means that the encryption key and the decryption key are the same, while asymmetric encryption means that the encryption key and the decryption key are different. The MD5 used in saving user login passwords is essentially not an encryption algorithm but an information digest algorithm. However, MD5 ensures that the final computed value for each string is different, so MD5 is commonly used as a secure value for password storage.
1. Common Symmetric Encryption Algorithms
A symmetric encryption algorithm simply means that the same key is used for both encryption and decryption. For most encryption algorithms, decryption and encryption are inverse operations. The security of symmetric encryption algorithms depends on the length of the key; the longer the key, the more secure it is. However, using excessively long keys is not recommended.
Now, let’s take a look at the common symmetric encryption algorithms and how to implement them in C#.
1.1 DES and DESede Algorithms
The DES and DESede algorithms (also known as Triple DES) are collectively referred to as the DES series of algorithms. DES stands for Data Encryption Standard and is a block algorithm that uses a key for encryption. DESede performs three DES encryptions on the same block of data. We will not elaborate on the principles here; let’s see how to implement DES encryption/decryption in .NET Core.
Create a directory Security
in the Utils project:
Create a DESHelper class in the Security directory:
namespace Utils.Security
{
public class DesHelper
{
}
}
Implementation of encryption and decryption:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Utils.Security
{
public static class DesHelper
{
static DesHelper()
{
DesHandler = DES.Create("DES");
DesHandler.Key = Convert.FromBase64String("L1yzjGB2sI4=");
DesHandler.IV = Convert.FromBase64String("uEcGI4JSAuY=");
}
private static DES DesHandler { get; }
/// <summary>
/// Encrypts a string
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static string Encrypt(string source)
{
try
{
using (var memStream = new MemoryStream())
using (var cryptStream = new CryptoStream(memStream, DesHandler.CreateEncryptor(DesHandler.Key, DesHandler.IV),
CryptoStreamMode.Write))
{
var bytes = Encoding.UTF8.GetBytes(source);
cryptStream.Write(bytes, 0, bytes.Length);
cryptStream.FlushFinalBlock();
return Convert.ToBase64String(memStream.ToArray());
}
}
catch (Exception e)
{
Console.WriteLine(e);
return null;
}
}
/// <summary>
/// Decrypts a string
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static string Decrypt(string source)
{
try
{
using (var mStream = new MemoryStream(Convert.FromBase64String(source)))
using (var cryptoStream =
new CryptoStream(mStream, DesHandler.CreateDecryptor(DesHandler.Key, DesHandler.IV), CryptoStreamMode.Read))
using (var reader = new StreamReader(cryptoStream))
{
return reader.ReadToEnd();
}
}
catch (Exception e)
{
Console.WriteLine(e);
return null;
}
}
}
}
Every time DesHandler = DES.Create("DES");
is called, a new instance of the DES algorithm is obtained, which means the values of Key and IV change each time. If used directly, the data encrypted this time cannot be decrypted next time, so to reduce this situation, we manually assign values to the Key and IV properties in the code.
1.2 AES Encryption Algorithm
The AES algorithm (Advanced Encryption Standard) was proposed to solve the vulnerabilities found in the DES algorithm. The current core of the AES algorithm is the Rijndael algorithm. However, this detail is not crucial for our implementation. Let’s see how to implement it:
Similarly, create an AesHelper class in the Security directory:
namespace Utils.Security
{
public static class AesHelper
{
}
}
Specific encryption and decryption implementation:
using System;
using System.IO;
using System.Security.Cryptography;
namespace Utils.Security
{
public static class AesHelper
{
static AesHelper()
{
AesHandler = Aes.Create();
AesHandler.Key = Convert.FromBase64String("lB2BxrJdI4UUjK3KEZyQ0obuSgavB1SYJuAFq9oVw0Y=");
AesHandler.IV = Convert.FromBase64String("6lra6ceX26Fazwj1R4PCOg==");
}
private static Aes AesHandler { get; }
public static string Encrypt(string source)
{
using (var mem = new MemoryStream())
using (var stream = new CryptoStream(mem, AesHandler.CreateEncryptor(AesHandler.Key, AesHandler.IV),
CryptoStreamMode.Write))
{
using (var writer = new StreamWriter(stream))
{
writer.Write(source);
}
return Convert.ToBase64String(mem.ToArray());
}
}
public static string Decrypt(string source)
{
var data = Convert.FromBase64String(source);
using (var mem = new MemoryStream(data))
using (var crypto = new CryptoStream(mem, AesHandler.CreateDecryptor(AesHandler.Key, AesHandler.IV),
CryptoStreamMode.Read))
using (var reader = new StreamReader(crypto))
{
return reader.ReadToEnd();
}
}
}
}
2. Common Asymmetric Encryption Algorithms
Asymmetric encryption algorithms refer to those where the encryption key and the decryption key are not the same. Asymmetric encryption algorithms typically come in pairs, consisting of a public key and a private key. The public key can be shared openly with the data exchange party without the risk of leaking information. This is because, in asymmetric encryption, it is computationally infeasible to derive the private key from the public key, and vice versa.
Typically, asymmetric encryption algorithms use the public key for encryption and the private key for decryption.
2.1 RSA Algorithm
The RSA algorithm is a standard asymmetric encryption algorithm, named after the initials of its three inventors. The RSA public key cryptosystem uses different keys for encryption and decryption, and “deriving the decryption key from the known encryption key is computationally infeasible.” Its security depends on the length of the key; a 1024-bit key is nearly impossible to crack.
Similarly, create an RSAHelper class in the Utils.Security namespace:
namespace Utils.Security
{
public static class RsaHelper
{
}
}
Specific implementation:
using System;
using System.Security.Cryptography;
namespace Utils.Security
{
public static class RsaHelper
{
public static RSAParameters PublicKey { get; private set; }
public static RSAParameters PrivateKey { get; private set; }
static RsaHelper()
{
}
public static void InitWindows()
{
var parameters = new CspParameters()
{
KeyContainerName = "RSAHELPER" // Default RSA key container name
};
var handle = new RSACryptoServiceProvider(parameters);
PublicKey = handle.ExportParameters(false);
PrivateKey = handle.ExportParameters(true);
}
public static void ExportKeyPair(string publicKeyXmlString, string privateKeyXmlString)
{
var handle = new RSACryptoServiceProvider();
handle.FromXmlString(privateKeyXmlString);
PrivateKey = handle.ExportParameters(true);
handle.FromXmlString(publicKeyXmlString);
PublicKey = handle.ExportParameters(false);
}
public static byte[] Encrypt(byte[] dataToEncrypt)
{
try
{
byte[] encryptedData;
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
RSA.ImportParameters(PublicKey);
encryptedData = RSA.Encrypt(dataToEncrypt, true);
}
return encryptedData;
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}
public static byte[] Decrypt(byte[] dataToDecrypt)
{
try
{
byte[] decryptedData;
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(PrivateKey);
decryptedData = rsa.Decrypt(dataToDecrypt, true);
}
return decryptedData;
}
catch (CryptographicException e)
{
Console.WriteLine(e.ToString());
return null;
}
}
}
}
Due to the nature of RSA, it is necessary to pre-set the public and private keys. C# supports various methods for importing keys, which we will not elaborate on here.
3. Hashing Algorithms
This type of algorithm is not strictly an encryption algorithm because it is completely irreversible. In other words, once data is encrypted using this type of algorithm, it cannot be decrypted back to its original form. However, this characteristic is often used for password storage, as it prevents someone from easily deducing a user’s password if they gain access to the database and code.
3.1 MD5 Algorithm
The most commonly used hashing algorithm is the MD5 encryption algorithm. The MD5 Message-Digest Algorithm is a widely used cryptographic hash function that produces a 128-bit (16-byte) hash value to ensure the integrity of information transmission.
We will not explain the principles; let’s see how to implement it. As before, create an MD5Helper in the Security directory:
namespace Utils.Security
{
public static class Md5Helper
{
}
}
Specific implementation:
using System.Security.Cryptography;
using System.Text;
namespace Utils.Security
{
public static class Md5Helper
{
private static MD5 Handler { get; } = new MD5CryptoServiceProvider();
public static string GetMd5Str(string source)
{
var data = Encoding.UTF8.GetBytes(source);
var security = Handler.ComputeHash(data);
var sb = new StringBuilder();
foreach (var b in security)
{
sb.Append(b.ToString("X2"));
}
return sb.ToString();
}
}
}
4. Conclusion
This article briefly introduced the implementation of four commonly used encryption algorithms. Of course, the most commonly used is MD5, as it is the encryption algorithm used by most systems for password storage.