PROWAREtech
.NET: About the System.Security.Cryptography Namespace
There are three general types of cryptography algorithms in the .NET namespace System.Security.Cryptopgraphy
: Symmetric, Asymmetric and Hash.
Here is a table with the description of each Interface/Class in System.Security.Cryptopgraphy
:
Interface/Class | Description |
---|---|
RC2 | The RC2 symmetric algorithm abstraction |
DES | The DES symmetric algorithm abstraction |
TripleDES | The TripleDES symmetric algorithm abstraction |
Rijndael | The Rijndael symmetric algorithm abstraction |
SymmetricAlgorithm | Top-level abstraction for the symmetric cryptography algorithms above |
DSA | The DSA public key cipher asymmetric algorithm abstraction |
RSA | The RSA public key cipher asymmetric algorithm abstraction |
AsymmetricAlgorithm | Top-level abstraction for the asymmetric cryptography algorithms above |
ICryptoTransform | Interface for encryption and decryption transforms - generated by methods on SymmetricAlgorithm |
Hashing Algorithms
A hashing algorithm is a mathematical algorithm that maps data of arbitrary size to a hash of a fixed size.
Hashing algorithms are used to encrypt passwords so that they do no have to be stored as plain text and therefore should not be compromised. Many websites should store their passwords encrypted, but don't. When a password is given to a website, it should be hashed and then the hashed result is save to disk. To check that the user has entered their correct password, simply hash the submitted password and compare it with the hashed password on disk. If the hashed passwords match then the unhashed passwords match. There is no reason to store a password that is not encrypted.
Hashing algorithms are also used to verify that a downloaded file has not been corrupted for example. They are also commonly used for digital signatures, as well as many other uses.
There are several hashing algorithms, most of which have been broken/compromised. The broken ones which should not be used for new projects are MD5, SHA-0 and SHA-1. As of the writing of this the SHA-2 family of hashing functions have not been compromised. This includes SHA-224, SHA-256, SHA-512, and more.
Here is an example of hashing a password:
public string CreatePasswordHash(string password)
{
var salt = "hF^V3@#)(9k^*Yg#GE%!Y%!rCE2"; // NOTE: this should be unique - do not use this example
var sha512 = new System.Security.Cryptography.HMACSHA512(System.Text.Encoding.UTF8.GetBytes(salt + password));
byte[] data = sha512.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
return System.Convert.ToBase64String(data);
}
Symmetric Algorithms
A symmetric algorithm is a set of instructions in cryptography using a single key to encrypt and decrypt data. These encryption algorithms and keys are considered lightweight because they are designed for speed in processing large data blocks or data streams.
Symmetric algorithms are faster and more efficient than asymmetric algorithms. This is why they are used in bulk encryption, but the drawback is the key management.
Here is an example of using the DES symmetric cryptography algorithm.
using System;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Text;
class CryptoDESSample
{
public static void Main(string[] args)
{
var text =
@"This is a test! This is another test! This is test number three! This is test number four! This is test number five! This is test number six! This is test number seven!
this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is
a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! t
his is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a
test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! th
is is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a t
est! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this
is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a tes
t! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this i
s a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test!
this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a
test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! th
is is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a t
est! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this
is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a tes
t! this is a test! this is a test! 0123456789";
File.WriteAllText("TEMP.txt", text);
// the 8 byte DES secret key is "(@!#*^%)"
byte[] DES_KEY = Encoding.ASCII.GetBytes("(@!#*^%)");
// NOTE: start with the text file and convert to the ciphered file
using (FileStream inStream = File.Open("TEMP.txt", FileMode.Open))
{
using (FileStream cipheredStream = File.Create("TEMP.ciphered"))
{
using (DES DES = DES.Create())
{
DES.Key = DES.IV = DES_KEY;
using (ICryptoTransform desEncryptor = DES.CreateEncryptor())
{
using (CryptoStream cryptoStream = new CryptoStream(cipheredStream, desEncryptor, CryptoStreamMode.Write))
{
byte[] bytes = new byte[inStream.Length];
inStream.Read(bytes, 0, bytes.Length);
cryptoStream.Write(bytes, 0, bytes.Length);
}
}
}
}
}
// NOTE: start with the ciphered file and convert to the original text file
using (FileStream cipheredStream = File.Open("TEMP.ciphered", FileMode.Open))
{
using (FileStream decipheredStream = File.Create("TEMP.deciphered.txt"))
{
using (DES DES = DES.Create())
{
DES.Key = DES.IV = DES_KEY;
using (ICryptoTransform desDecryptor = DES.CreateDecryptor())
{
using (CryptoStream cryptoStream = new CryptoStream(cipheredStream, desDecryptor, CryptoStreamMode.Read))
{
byte[] bytes = new byte[1024 * 1024];
for(int read = cryptoStream.Read(bytes, 0, bytes.Length); read > 0; read = cryptoStream.Read(bytes, 0, bytes.Length))
decipheredStream.Write(bytes, 0, read);
}
}
}
}
}
}
}
Asymmetric Algorithms
An asymmetric cryptography algorithm works on two different keys i.e. a public key and a private key. The public key is given to others while the private key is kept a secret. An example is a client, like a web browser, sends its public key to the server and requests data, the server encrypts the data using the public key from the client and sends the data in an encrypted state, and finally, the client receives the data ready for decryption. No one else can decrypt the data without the private key.
Asymmetric algorithms are slower and less efficient when compared to symmetric algorithms.
Here is an example of using the RSA asymmetric cryptography algorithm — the standard at the time of writing this — in a console application that converts keys to JSON saving them to disk and encrypting whole files:
using System.Security.Cryptography;
using System.Text.Json;
namespace ConsoleApp1
{
class Program
{
const string privateKeyFilename = "private.key.json"; // NOTE: IMPORTANT - this file should not be freely available to the public
const string publicKeyFilename = "public.key.json";
static void Main(string[] args)
{
if (!File.Exists(privateKeyFilename) || !File.Exists(publicKeyFilename)) // NOTE: then keys have not been generated!
{
// NOTE: Create a new instance of RSACryptoServiceProvider to generate public and private key data.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
RSAKey.FromRSAParameters(RSA.ExportParameters(true)).ToDisk(privateKeyFilename); // NOTE: true == generate private key
RSAKey.FromRSAParameters(RSA.ExportParameters(false)).ToDisk(publicKeyFilename); // NOTE: false == generate public key
}
}
// NOTE: get the private and public keys from disk - these do not have to be stored on disk; they can be generated on as needed so long as the private and public keys match
var publicKey = RSAKey.FromDisk(publicKeyFilename).ToRSAParameters();
var privateKey = RSAKey.FromDisk(privateKeyFilename).ToRSAParameters();
var text =
@"This is a test! This is another test! This is test number three! This is test number four! This is test number five! This is test number six! This is test number seven!
this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is
a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! t
his is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a
test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! th
is is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a t
est! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this
is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a tes
t! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this i
s a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test!
this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a
test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! th
is is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a t
est! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test!this is a test! this is a test! this
is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a test! this is a tes
t! this is a test! this is a test! 0123456789";
File.WriteAllText("TEMP.txt", text); // NOTE: create a file to encrypt
using (FileStream origStream = File.Open("TEMP.txt", FileMode.Open)) // NOTE: these can be any Stream objects, not just FileStream objects
{
using (FileStream encryptedStream = File.Create("TEMP.encrypted"))
{
EncryptStream(origStream, encryptedStream, publicKey);
}
}
using (FileStream encryptedStream = File.Open("TEMP.encrypted", FileMode.Open))
{
using (FileStream decryptedStream = File.Create("TEMP.decrypted.txt"))
{
DecryptStream(encryptedStream, decryptedStream, privateKey);
}
}
}
public static void EncryptStream(Stream inStream, Stream encryptedStream, RSAParameters publicKey)
{
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024)) // NOTE: 1024 bit / 128 byte
{
byte[] dataToEncrypt = new byte[117]; // NOTE: 117 seems to be the limit chunk size
inStream.Position = 0;
for (int read = inStream.Read(dataToEncrypt, 0, dataToEncrypt.Length); true; read = inStream.Read(dataToEncrypt, 0, dataToEncrypt.Length))
{
if (read < dataToEncrypt.Length)
{
if (read > 0)
{
Array.Resize(ref dataToEncrypt, read);
byte[] encryptedData = EncryptData(dataToEncrypt, publicKey, RSA);
encryptedStream.Write(encryptedData, 0, encryptedData.Length);
}
break;
}
else
{
byte[] encryptedData = EncryptData(dataToEncrypt, publicKey, RSA);
encryptedStream.Write(encryptedData, 0, encryptedData.Length);
}
}
}
}
public static void DecryptStream(Stream encryptedStream, Stream outStream, RSAParameters privateKey)
{
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024)) // NOTE: 1024 bit / 128 byte
{
byte[] dataToDecrypt = new byte[RSA.KeySize / 8]; // NOTE: the number of bytes for the each chunk of data is always the KeySize divided by the number of bits in a byte
encryptedStream.Position = 0;
for (int read = encryptedStream.Read(dataToDecrypt, 0, dataToDecrypt.Length); read > 0; read = encryptedStream.Read(dataToDecrypt, 0, dataToDecrypt.Length))
{
byte[] decryptedData = DecryptData(dataToDecrypt, privateKey, RSA);
outStream.Write(decryptedData, 0, decryptedData.Length);
}
}
}
public static byte[] EncryptData(byte[] DataToEncrypt, RSAParameters RSAPublicKey, RSACryptoServiceProvider RSA)
{
try
{
RSA.ImportParameters(RSAPublicKey);
return RSA.Encrypt(DataToEncrypt, false);
}
catch (CryptographicException e)
{
Console.WriteLine(e.ToString());
return null;
}
}
public static byte[] DecryptData(byte[] DataToDecrypt, RSAParameters RSAPrivateKey, RSACryptoServiceProvider RSA)
{
try
{
RSA.ImportParameters(RSAPrivateKey);
return RSA.Decrypt(DataToDecrypt, false);
}
catch (CryptographicException e)
{
Console.WriteLine(e.ToString());
return null;
}
}
}
public class RSAKey // NOTE: this is basically boilerplate code for persisting an object to disk, etc.
{
public byte[] Modulus { get; set; }
public byte[] Exponent { get; set; }
public byte[] D { get; set; }
public byte[] P { get; set; }
public byte[] Q { get; set; }
public byte[] DP { get; set; }
public byte[] DQ { get; set; }
public byte[] InverseQ { get; set; }
public string ToJson()
{
return JsonSerializer.Serialize(this);
}
public static RSAKey FromJson(string json)
{
return JsonSerializer.Deserialize<RSAKey>(json);
}
public void ToDisk(string filename)
{
File.WriteAllText(filename, ToJson());
}
public static RSAKey FromDisk(string filename)
{
return JsonSerializer.Deserialize<RSAKey>(File.ReadAllText(filename));
}
public RSAParameters ToRSAParameters()
{
return new RSAParameters() { Modulus = Modulus, Exponent = Exponent, D = D, P = P, Q = Q, DP = DP, DQ = DQ, InverseQ = InverseQ };
}
public static RSAKey FromRSAParameters(RSAParameters key)
{
return new RSAKey()
{
Modulus = key.Modulus,
Exponent = key.Exponent,
D = key.D,
P = key.P,
Q = key.Q,
DP = key.DP,
DQ = key.DQ,
InverseQ = key.InverseQ
};
}
}
}