#region Copyright 2010 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.IO;
namespace CSharpTest.Net.Crypto
{
///
/// Provided an implementation very similiar to that of the Rfc2898DeriveBytes with the following
/// excpetions: 1) any hash size can be used, 2) original key bytes are always hashed, 3) bytes
/// generated are always rounded to hash size, thus GetBytes(4) + GetBytes(4) != GetBytes(8)
///
public class HashDerivedBytes : DeriveBytes, IPasswordDerivedBytes
where THash : HMAC, new()
{
// Fields
private readonly HMAC _hashAlgo;
private uint _iterations;
private byte[] _salt;
private int _block;
#region Create() - Workaround for unpatched .Net 2.0
static HMAC Create()
{
if (typeof(THash) == typeof(global::System.Security.Cryptography.HMACSHA512))
return new HMAC();
if (typeof(THash) == typeof(global::System.Security.Cryptography.HMACSHA384))
return new HMAC();
return new THash();
}
class HMAC : HMAC where T : HashAlgorithm, new()
{
public HMAC()
{
HashAlgorithm h = new T();
HashName = typeof(T).Name.Replace("Managed", "");
HashSizeValue = h.HashSize;
BlockSizeValue = 128;
base.Key = Crypto.Salt.CreateBytes(Crypto.Salt.Size.b128);
}
}
#endregion
private HashDerivedBytes(HMAC algo, Stream password, Salt salt, int iterations)
{
_block = 1;
_hashAlgo = algo;
Check.Assert(_hashAlgo.CanReuseTransform);
// delta from RFC2898 - key with a hash of password, it would happen anyway if password.Length > 32
HashAlgorithm ha = (HashAlgorithm)CryptoConfig.CreateFromName(_hashAlgo.HashName);
_hashAlgo.Key = ha.ComputeHash(password);
_salt = salt.ToArray();
_iterations = (uint)Check.InRange(iterations, 1, int.MaxValue);
}
///
/// Constructs the byte generation routine with the specified key, salt, and iteration count
///
public HashDerivedBytes(THash algo, Stream password, Salt salt, int iterations)
: this((HMAC)algo, password, salt, iterations)
{ }
///
/// Constructs the byte generation routine with the specified key, salt, and iteration count
///
public HashDerivedBytes(Stream password, Salt salt, int iterations)
: this(Create(), password, salt, iterations)
{ }
///
/// Constructs the byte generation routine with the specified key, salt, and iteration count
///
public HashDerivedBytes(bool clear, byte[] password, Salt salt, int iterations)
: this(Create(), new MemoryStream(Check.NotNull(password), 0, password.Length, false, false), salt, iterations)
{
if (clear) Array.Clear(password, 0, password.Length);
}
///
/// Constructs the byte generation routine with the specified key, salt, and iteration count
///
public HashDerivedBytes(byte[] password, Salt salt, int iterations)
: this(false, password, salt, iterations)
{ }
private byte[] ComputeBlock(int block, int size)
{
byte[] inputBuffer = BitConverter.GetBytes(block);
if (BitConverter.IsLittleEndian)
Array.Reverse(inputBuffer, 0, 4);
_hashAlgo.TransformBlock(_salt, 0, _salt.Length, _salt, 0);
_hashAlgo.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
byte[] hash = _hashAlgo.Hash;
_hashAlgo.Initialize();
byte[] result = hash;
for (int i = 2; i <= _iterations; i++)
{
hash = _hashAlgo.ComputeHash(hash);
for (int j = 0; j < size; j++)
{
result[j] = (byte)(result[j] ^ hash[j]);
}
}
return result;
}
///
/// Returns a pseudo-random key from a password, salt and iteration count.
///
public override byte[] GetBytes(int cb)
{
Check.Assert(cb > 0);
int blockSize = Check.InRange(_hashAlgo.HashSize / 8, 8, 64);
if (cb == blockSize)
return ComputeBlock(_block++, cb);
byte[] buffer = new byte[cb];
for (int offset = 0; offset < cb; offset += blockSize)
{
int size = Math.Min(blockSize, cb - offset);
Array.Copy(ComputeBlock(_block++, blockSize), 0, buffer, offset, size);
}
return buffer;
}
#if NET20 || NET35 // NOTE: .NET 4.0 finally implemented
/// Disposes of the object
public void Dispose()
#else
/// Disposes of the object
protected override void Dispose(bool disposing)
#endif
{
_block = -1;
_salt = new byte[8];
_iterations = 1;
_hashAlgo.Clear();
}
///
/// Resets the state of the operation.
///
public override void Reset()
{
_block = 1;
}
///
/// Gets or sets the number of iterations for the operation.
///
public int IterationCount
{
get
{
return (int)_iterations;
}
set
{
Check.Assert(value > 0);
_iterations = (uint)value;
Reset();
}
}
///
/// Gets or sets the key salt value for the operation.
///
public byte[] Salt
{
get
{
return (byte[])_salt.Clone();
}
set
{
Check.ArraySize(value, 8, int.MaxValue);
_salt = (byte[])value.Clone();
Reset();
}
}
}
}