Created
November 4, 2011 16:12
-
-
Save caspencer/1339719 to your computer and use it in GitHub Desktop.
Derives a key from a password using an OpenSSL-compatible version of the PBKDF1 algorithm.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace OpenSslCompat | |
{ | |
/// <summary> | |
/// Derives a key from a password using an OpenSSL-compatible version of the PBKDF1 algorithm. | |
/// </summary> | |
/// <remarks> | |
/// based on the OpenSSL EVP_BytesToKey method for generating key and iv | |
/// http://www.openssl.org/docs/crypto/EVP_BytesToKey.html | |
/// </remarks> | |
public class OpenSslCompatDeriveBytes : DeriveBytes | |
{ | |
private readonly byte[] _data; | |
private readonly HashAlgorithm _hash; | |
private readonly int _iterations; | |
private readonly byte[] _salt; | |
private byte[] _currentHash; | |
private int _hashListReadIndex; | |
private List<byte> _hashList; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="OpenSslCompat.OpenSslCompatDeriveBytes"/> class specifying the password, key salt, hash name, and iterations to use to derive the key. | |
/// </summary> | |
/// <param name="password">The password for which to derive the key.</param> | |
/// <param name="salt">The key salt to use to derive the key.</param> | |
/// <param name="hashName">The name of the hash algorithm for the operation. (e.g. MD5 or SHA1)</param> | |
/// <param name="iterations">The number of iterations for the operation.</param> | |
public OpenSslCompatDeriveBytes(string password, byte[] salt, string hashName, int iterations) | |
: this(new UTF8Encoding(false).GetBytes(password), salt, hashName, iterations) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="OpenSslCompat.OpenSslCompatDeriveBytes"/> class specifying the password, key salt, hash name, and iterations to use to derive the key. | |
/// </summary> | |
/// <param name="password">The password for which to derive the key.</param> | |
/// <param name="salt">The key salt to use to derive the key.</param> | |
/// <param name="hashName">The name of the hash algorithm for the operation. (e.g. MD5 or SHA1)</param> | |
/// <param name="iterations">The number of iterations for the operation.</param> | |
public OpenSslCompatDeriveBytes(byte[] password, byte[] salt, string hashName, int iterations) | |
{ | |
if (iterations <= 0) | |
throw new ArgumentOutOfRangeException("iterations", iterations, "iterations is out of range. Positive number required"); | |
_data = password; | |
_salt = salt; | |
_hash = HashAlgorithm.Create(hashName); | |
_iterations = iterations; | |
} | |
/// <summary> | |
/// Returns a pseudo-random key from a password, salt and iteration count. | |
/// </summary> | |
/// <param name="cb">The number of pseudo-random key bytes to generate.</param> | |
/// <returns>A byte array filled with pseudo-random key bytes.</returns> | |
public override byte[] GetBytes(int cb) | |
{ | |
if (cb <= 0) | |
throw new ArgumentOutOfRangeException("cb", cb, "cb is out of range. Positive number required."); | |
if (_currentHash == null) | |
{ | |
_hashList = new List<byte>(); | |
_currentHash = new byte[0]; | |
_hashListReadIndex = 0; | |
int preHashLength = _data.Length + ((_salt != null) ? _salt.Length : 0); | |
var preHash = new byte[preHashLength]; | |
Buffer.BlockCopy(_data, 0, preHash, 0, _data.Length); | |
if (_salt != null) | |
Buffer.BlockCopy(_salt, 0, preHash, _data.Length, _salt.Length); | |
_currentHash = _hash.ComputeHash(preHash); | |
for (int i = 1; i < _iterations; i++) | |
{ | |
_currentHash = _hash.ComputeHash(_currentHash); | |
} | |
_hashList.AddRange(_currentHash); | |
} | |
while (_hashList.Count < (cb + _hashListReadIndex)) | |
{ | |
int preHashLength = _currentHash.Length + _data.Length + ((_salt != null) ? _salt.Length : 0); | |
var preHash = new byte[preHashLength]; | |
Buffer.BlockCopy(_currentHash, 0, preHash, 0, _currentHash.Length); | |
Buffer.BlockCopy(_data, 0, preHash, _currentHash.Length, _data.Length); | |
if (_salt != null) | |
Buffer.BlockCopy(_salt, 0, preHash, _currentHash.Length + _data.Length, _salt.Length); | |
_currentHash = _hash.ComputeHash(preHash); | |
for (int i = 1; i < _iterations; i++) | |
{ | |
_currentHash = _hash.ComputeHash(_currentHash); | |
} | |
_hashList.AddRange(_currentHash); | |
} | |
byte[] dst = new byte[cb]; | |
_hashList.CopyTo(_hashListReadIndex, dst, 0, cb); | |
_hashListReadIndex += cb; | |
return dst; | |
} | |
/// <summary> | |
/// Resets the state of the operation. | |
/// </summary> | |
public override void Reset() | |
{ | |
_hashListReadIndex = 0; | |
_currentHash = null; | |
_hashList = null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I know it's been eleven years, but I just wanted to say thank you for this. It is the exact solution I needed for a very important implementation!