Skip to content

Instantly share code, notes, and snippets.

@AyrA
Created February 20, 2018 07:45
Show Gist options
  • Save AyrA/5cbb0ed3cbef52c1a94faf72b76f446d to your computer and use it in GitHub Desktop.
Save AyrA/5cbb0ed3cbef52c1a94faf72b76f446d to your computer and use it in GitHub Desktop.
C# Custom Key derivation
using System;
using System.Linq;
using System.Security.Cryptography;
/*
I am in no way claiming that this is secure.
It merely serves as an example on how a key derivation function could work.
Features:
- Configurable Hash algorithm and iteration count
- Can be reset to initial state
- (Probably) Thread safe
- Gets slower the more bytes are requested
- Fully commented
*/
namespace Test
{
/// <summary>
/// Generic Key Derivation Class
/// </summary>
public class KDF : DeriveBytes
{
/// <summary>
/// Default number of bytes for Salt
/// </summary>
public const int SALT_SIZE = 32;
/// <summary>
/// Default number of bytes for Key
/// </summary>
public const int KEY_SIZE = 32;
/// <summary>
/// Default rounds for each iteration
/// </summary>
public const int DEFAULT_ROUNDS = 10000;
/// <summary>
/// Hasher for Key Derivation
/// </summary>
private HashAlgorithm Hasher;
/// <summary>
/// Number of Rounds
/// </summary>
private int Rounds;
/// <summary>
/// Salt
/// </summary>
private byte[] _Salt;
/// <summary>
/// Key
/// </summary>
private byte[] _Key;
/// <summary>
/// KDF State
/// </summary>
private byte[] Data;
/// <summary>
/// Initial KDF State
/// </summary>
private byte[] InitialData;
/// <summary>
/// Gets the user supplied or generated salt
/// </summary>
public byte[] Salt
{
get
{
return (byte[])_Salt.Clone();
}
}
/// <summary>
/// Gets the user supplied or generated Key
/// </summary>
public byte[] Key
{
get
{
return (byte[])_Key.Clone();
}
}
/// <summary>
/// Initializes a new instance of KDF
/// </summary>
/// <param name="Hasher">Hash Algorithm for key derivation</param>
/// <param name="Salt">Salt, uses random bytes if null</param>
/// <param name="Key">Key, uses random bytes if null</param>
/// <param name="Rounds">Number of rounds for each requested byte</param>
public KDF(HashAlgorithm Hasher, byte[] Salt = null, byte[] Key = null, int Rounds = DEFAULT_ROUNDS)
{
var RNG = RandomNumberGenerator.Create();
//Hasher must be specified
if (Hasher == null)
{
throw new ArgumentNullException("Hasher");
}
//Rounds must be sensible number
if (Rounds < 1)
{
throw new ArgumentOutOfRangeException("Rounds needs to be at least one");
}
//Generate salt if needed
if (Salt == null || Salt.Length == 0)
{
Salt = new byte[SALT_SIZE];
RNG.GetBytes(Salt);
}
//Generate key if needed
if (Key == null || Key.Length == 0)
{
Key = new byte[KEY_SIZE];
RNG.GetBytes(Key);
}
//Store initial State
this.Hasher = Hasher;
this.Rounds = Rounds;
_Salt = (byte[])Salt.Clone();
_Key = (byte[])Key.Clone();
Data = Salt.Concat(Key).ToArray();
InitialData = (byte[])Data.Clone();
}
/// <summary>
/// Requests a number of bytes from the generator
/// </summary>
/// <param name="cb">Number of bytes</param>
/// <returns>Number of bytes requested</returns>
public override byte[] GetBytes(int cb)
{
//cb must make sense
if (cb < 0)
{
throw new ArgumentOutOfRangeException("cb");
}
var ret = new byte[cb];
lock (Hasher)
{
//Apply for each individual byte
for (var pos = 0; pos < cb; pos++)
{
//Skip iterations according to user specified number of rounds
for (var iter = 0; iter < Rounds; iter++)
{
Data = Hasher.ComputeHash(Data);
//Optionally do some CONSTANT fuckery with the hash
//Data[Data.Length - 1] = Data[Data[0] % Data.Length];
//Data = Data.Reverse().ToArray();
}
//Get pseudorandom byte
//This doesn't directly adds security but makes an attacker care for the entire result
ret[pos] = Data[Data[0] % Data.Length];
//Simple version:
//ret[pos] = Data[0];
}
}
return ret;
}
/// <summary>
/// Reset Component to initial State
/// </summary>
public override void Reset()
{
lock (Hasher)
{
Data = (byte[])InitialData.Clone();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment