Skip to content
Simon Hughes edited this page Dec 8, 2021 · 2 revisions

Overview

The project is split into four main areas

  • Strings – Encryption/Decryption/Password and Salt generation
  • Hash – Creation and verification of hashes using MD5, SHA1, SHA256, SHA384, SHA512.
  • Digest – Creation and verification of digests (data + hash). Plus two handy ToString() and CreateFromString() functions which come in handy if you want to store data in a Cookie.
  • Bytes – The core of the library. This uses the Rijndael algorithm and works at the byte[] level for most functions.

Give a Star! ⭐

If you like or are using this project please give it a star. Thanks!

Strings

This is a static class with static functions.

string CreatePassword(int size, bool allowPunctuation)

Creates a password with the required length. You can specify if you want to allow punctuation characters in the returned password. For more information on punctuation characters, see http://msdn.microsoft.com/en-us/library/6w3ahtyy.aspx

string password = Strings.CreatePassword(10, true);
// password = eE6/k1beps

allowPunctuation = false;
password = Strings.CreatePassword(10, false);
// password = 6kyGMrKNF6

string CreateSalt(int numChars)

Create a salt of exactly the number of characters required. Under the hood, it calls CreateSaltFull() and trims the string to the required length.

string salt = Strings.CreateSalt(30);
int length = salt.Length; // length will be 30 characters

string CreateSaltFull(int numBytes)

Create a salt. The numBytes is the number of non-zero random bytes that will converted into a base-64 string. The resulting string length can be larger than numBytes.

string salt = Strings.CreateSaltFull(30);
int length = salt.Length; // length will be 40 characters

string Encrypt(string clearString, byte[] key, byte[] iv) string Decrypt(string cipherString, byte[] key, byte[] iv)

string plainText = "My secret text";

byte[] key = Bytes.GenerateKey();
byte[] iv = Bytes.GenerateIV();

string encrypted = Strings.Encrypt(plainText, key, iv);
string decrypted = Strings.Decrypt(encrypted, key, iv);

Assert.AreEqual(plainText, decrypted);

string Encrypt(string clearString, string password, string salt, string iv, Bytes.KeySize keySize) string Decrypt(string cipherString, string password, string salt, string iv, Bytes.KeySize keySize)

string password = "Hello world"; // Preferably generated
string salt = "saltsaltsalt";    // Preferably generated
string iv = string.Empty.PadLeft(32, '#');
string plainText = "My secret text";

string encrypted = Strings.Encrypt(plainText, password, salt, iv, Bytes.KeySize.Size256);
string decrypted = Strings.Decrypt(encrypted, password, salt, iv, Bytes.KeySize.Size256);

Assert.AreEqual(plainText, decrypted);

Hash

This is a static class with 2 static functions.

A hash can help ensure authentication and integrity of data that may be modified when transmitted between two parties. The sharedKey is shared by the two parties who independently calculate the hash. The data is passed between parties together with the hash. The hash will be identical if the data is unmodified. Use a sharedKey that is sufficiently long and complex for the application see https://www.grc.com/passwords.htm - and share the sharedKey once over a secure channel. See http://en.wikipedia.org/wiki/Cryptographic_hash_function for more information.

Looking at the code you will see that the Hash function uses Lazy loading of the five main hash algorithms. This is for performance and means only the hash algorithm asked for is created and loaded into memory.

The algorithms are: MD5 (128 bit), SHA1 (160 bit), SHA256 (256 bit), SHA384 (384 bit), SHA512 (512 bit)

public static string Create(HashType hashType, string data, string sharedKey, bool showBytes)

With showBytes = true we get:

string md5 = Hash.Create(HashType.MD5, "Hello", "key", true);
// result = 6E721FFDDD9974CC99A10A3D04385B33

string sha1 = Hash.Create(HashType.SHA1, "Hello", "key", true);
// result = E483166C1BCA40E5A1289D6416C6DE1A271F2ACE

string sha256 = Hash.Create(HashType.SHA256, "Hello", "key", true);
// result = C63338687D9BC4E95350C465D392DB3518C777AE3A04284005B358350767A710

string sha384 = Hash.Create(HashType.SHA384, "Hello", "key", true);
// result = 584BE855D030A6E25C07909751D3762429C3C811935CB57A34AD686F82FDAFAF1F72594BBE38CA0C95EDD2DD81E9035A

string sha512 = Hash.Create(HashType.SHA512, "Hello", "key", true);
// result = ABCB5D5F7DE874D6AB172E69106FEE23B9957CF074DDE23CD0A9A29D8E56E4EC0D73C42F63C633FFB68C8E8955F2C220EA97FF65C12402DFC9B2911422062842

With showBytes = false we get:

string md5 = Hash.Create(HashType.MD5, "Hello", "key", false);
// result = bnIf/d2ZdMyZoQo9BDhbMw==

string sha1 = Hash.Create(HashType.SHA1, "Hello", "key", false);
// result = 5IMWbBvKQOWhKJ1kFsbeGicfKs4=

string sha256 = Hash.Create(HashType.SHA256, "Hello", "key", false);
// result = xjM4aH2bxOlTUMRl05LbNRjHd646BChABbNYNQdnpxA=

string sha384 = Hash.Create(HashType.SHA384, "Hello", "key", false);
// result = WEvoVdAwpuJcB5CXUdN2JCnDyBGTXLV6NK1ob4L9r68fcllLvjjKDJXt0t2B6QNa

string sha512 = Hash.Create(HashType.SHA512, "Hello", "key", false);
// result = q8tdX33odNarFy5pEG/uI7mVfPB03eI80KminY5W5OwNc8QvY8Yz/7aMjolV8sIg6pf/ZcEkAt/JspEUIgYoQg==

With no sharedKey (just showing MD5 for brevity) we get:

string a = Hash.Create(HashType.MD5, "Hello", string.Empty, true);
// result = 8B1A9953C4611296A827ABF8C47804D7

string b = Hash.Create(HashType.MD5, "Hello", string.Empty, false);
// result = ixqZU8RhEpaoJ6v4xHgE1w==

public static bool Verify(HashType hashType, string data, string sharedKey, bool showBytes, string hash)

string hash = Hash.Create(HashType.MD5, "Hello", string.Empty, true);
// result = 8B1A9953C4611296A827ABF8C47804D7

bool ok = Hash.Verify(HashType.MD5, "Hello", string.Empty, true, hash);
// result = true

ok = Hash.Verify(HashType.MD5, "World", string.Empty, true, hash);
// result = false

Digest

The digest basically stores the data and hash together.

public Digest(string data, string hash, HashType hashType)

Constructor where you can define all the properties.

public string Data

Returns the data.

public string Hash

Returns the pre-computed hash.

public HashType HashType

Returns the hash type used to generate the hash

public static Digest Create(HashType hashType, string data, string sharedKey)

Static function to create a Digest.

Digest digest1 = Digest.Create(HashType.SHA512, "Hello", "secretKey");

// Check its reversable
var digest1String = digest1.ToString();
var digest2 = Digest.CreateFromString(digest1String, "secretKey");
Assert.AreEqual(digest1.Data, digest2.Data);
Assert.AreEqual(digest1.Hash, digest2.Hash);

public override string ToString()

Returns a string in the following format: XXYYYHD Where XX is the hashType, YYYY is the length of the hash, H* is the hash, and D* is the data. This can be handy for storing in a cookie for instance.

Digest md5 = Digest.Create(HashType.MD5, "Hello", "secretKey");
var md5String = md5.ToString();
// md5String = 0003274E5D61D5FEA40EA042E1C1954A4356EHello

Digest sha1 = Digest.Create(HashType.SHA1, "Hello", "secretKey");
var sha1String = sha1.ToString();
// sha1String = 0104012245A5A5C8CC2FD25A2745F2036B98C9308C112Hello

public static Digest CreateFromString(string hashedData, string sharedKey)

This is the opposite of ToString(). It takes the data and re-creates the Digest. If the data passed in does not pass Hash validation, then null is returned.

Bytes

The examples below will use the following data setup.

string _fileEncryptedData = @"C:\testEncrypted.txt");
string _filePlainData = @"C:\testPlain.txt");
byte[] _plainData = GetBytes("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
var _rijndaelManaged = new RijndaelManaged
{
    KeySize = 256,
    BlockSize = 256,
    Padding = PaddingMode.ISO10126,
    Mode = CipherMode.CBC
};

public static byte[] GenerateKey()

Returns an encryption key to be used with the Rijndael algorithm

byte[] key = Bytes.GenerateKey();

public static byte[] GenerateKey(string password, string salt, KeySize keySize, int iterationCount)

Returns an encryption key to be used with the Rijndael algorithm. This used the function Rfc2898DeriveBytes which is used  to generate a password-based key derivation functionality, PBKDF2, by using a pseudo-random number generator based on HMACSHA1.

byte[] key = Bytes.GenerateKey("password", "saltsaltsalt", Bytes.KeySize.Size128, 12000);

public static byte[] GenerateIV()

Returns the encryption IV to be used with the Rijndael algorithm

byte[] iv = Bytes.GenerateIV();

public static byte[] Encrypt(byte[] clearData, byte[] key, byte[] iv)

public static byte[] Decrypt(byte[] cipherData, byte[] key, byte[] iv)

Encrypt/Decrypt byte array into a byte array using the given Key and an IV.

byte[] key = Bytes.GenerateKey();
byte[] iv = Bytes.GenerateIV();

var rng = new RNGCryptoServiceProvider();
var data = new byte[1024];
rng.GetBytes(data);

byte[] encrypted = Bytes.Encrypt(data, key, iv);
byte[] decrypted = Bytes.Decrypt(encrypted, key, iv);
Assert.AreEqual(data, decrypted);

public static void Encrypt(Stream clearStreamIn, string encryptedFileOut, RijndaelManaged alg)

public static void Decrypt(Stream encryptedStreamIn, Stream clearStreamOut, RijndaelManaged alg)

Encrypt/Decrypt a file into another file.

// Encrypt file
using(var fsIn = new FileStream(_filePlainData, FileMode.Open, FileAccess.Read))
{
    Bytes.Encrypt(fsIn, _fileEncryptedData, _rijndaelManaged);
}

// Decrypt file data
using(var memoryStream = new MemoryStream())
{
    using(var fsIn = new FileStream(_fileEncryptedData, FileMode.Open, FileAccess.Read))
    {
        Bytes.Decrypt(fsIn, memoryStream, _rijndaelManaged);
    }

    // Verify
    memoryStream.Seek(0, SeekOrigin.Begin);
    Assert.AreEqual(_plainData.Length, memoryStream.Length);

    foreach(var expected in _plainData)
    {
        var b = memoryStream.ReadByte();
        Assert.AreEqual(expected, b);
    }
}

public static void Encrypt(string clearFileIn, string encryptedFileOut, byte[] key, byte[] iv)

public static void Decrypt(string encryptedFileIn, string clearFileOut, byte[] key, byte[] iv)

Encrypt/Decrypt a file into another file

byte[] key = Bytes.GenerateKey();
byte[] iv = Bytes.GenerateIV();

Bytes.Encrypt(_filePlainData, _fileEncryptedData, key, iv);
Bytes.Decrypt(_fileEncryptedData, _filePlainData, key, iv);

var decryptedData = File.ReadAllBytes(_filePlainData);
Assert.AreEqual(_plainData.Length, decryptedData.Length);

for(int i = 0; i < _plainData.Length; i++)
{
    Assert.AreEqual(_plainData[i], decryptedData[i]);
}

public static void Encrypt(Stream clearStreamIn, string encryptedFileOut, byte[] key, byte[] iv)

Encrypt a stream into a file. This is a wrapper function for Encrypt(Stream clearStreamIn, string encryptedFileOut, RijndaelManaged alg)

public static void Encrypt(string clearFileIn, string encryptedFileOut, out string key, out string iv)

public static void Decrypt(string encryptedFileIn, string clearFileOut, string key, string iv)

Encrypt/Decrypt a file into another file. The Key and an IV are automatically generated. These will be required when Decrypting the data.

string key, iv;
Bytes.Encrypt(_filePlainData, _fileEncryptedData, out key, out iv);
Bytes.Decrypt(_fileEncryptedData, _filePlainData, key, iv);

var decryptedData = File.ReadAllBytes(_filePlainData);
Assert.AreEqual(_plainData.Length, decryptedData.Length);

for(int i = 0; i < _plainData.Length; i++)
{
    Assert.AreEqual(_plainData[i], decryptedData[i]);
}

public static void Encrypt(Stream clearStreamIn, string encryptedFileOut, out string key, out string iv)

public static void Decrypt(string encryptedFileIn, Stream clearStreamOut, string key, string iv)

Encrypt/Decrypt a file into another file. The Key and an IV are automatically generated. These will be required when Decrypting the data.

// Encrypt file
string key, iv;
using(var fsIn = new FileStream(_filePlainData, FileMode.Open, FileAccess.Read))
{
    Bytes.Encrypt(fsIn, _fileEncryptedData, out key, out iv);
}

// Decrypt file data
using(var memoryStream = new MemoryStream())
{
    Bytes.Decrypt(_fileEncryptedData, memoryStream, key, iv);

    // Verify
    memoryStream.Seek(0, SeekOrigin.Begin);
    Assert.AreEqual(_plainData.Length, memoryStream.Length);

    foreach(var expected in _plainData)
    {
        var b = memoryStream.ReadByte();
        Assert.AreEqual(expected, b);
    }
}

Clone this wiki locally