11

我想找到一种解决方案或方法,让我可以添加盐并控制迭代次数。本机 Rfc2898DeriveBytes 基于 HMACSHA1。理想情况下,使用 SHA-256 或 SHA-512 将使系统面向未来。

这是迄今为止我发现的最好的例子:http: //jmedved.com/2012/04/pbkdf2-with-sha-256-and-others/ 但是当我使用 SHA-256 运行它时,它实际上比使用慢SHA-512。我使用了 64k 次迭代、盐指南和不同长度的密码进行比较。

我还找到了这个解决方案:http: //sourceforge.net/projects/pwdtknet/ ,它提供了完整的源代码。它似乎更健壮。

到目前为止,我无法从每个人那里获得相同的输出。

4

6 回答 6

4

PWDTK.NET 库(http://sourceforge.net/projects/pwdtknet/)似乎是我能找到的唯一实现 PBKDF2 HMAC SHA-512 并允许加盐和迭代的实现。我无法找到 PBKDF2 HMAC SHA-512 的测试向量进行测试。

我很惊讶已经没有更多的开发人员使用它。

不是很喜欢回答我自己的问题,但由于评论退化为关于速度的讨论,而且还没有其他人回答,我也不妨。

感谢所有评论的人。

于 2013-05-03T15:44:13.300 回答
2

我的CryptSharp库可以使用任意 HMAC 执行 PBKDF2。可以控制盐和迭代。查看 CryptSharp.Utility 命名空间。它与 C# Scrypt 实现和其他一些东西一起存在。

于 2013-05-04T02:35:17.953 回答
2

这是由 SecurityDriven.NET 的 Inferno 库提供的。

安装包 Inferno

Inferno 推广 SHA-384,因为它被NSA Suite B用于保护绝密信息,并且“其截断设计可有效防御长度扩展攻击” (1)

using SecurityDriven.Inferno;
using SecurityDriven.Inferno.Extensions;
using static SecurityDriven.Inferno.SuiteB;
using static SecurityDriven.Inferno.Utils;
using PBKDF2 = SecurityDriven.Inferno.Kdf.PBKDF2;

存储用户密码:

var sha384Factory = HmacFactory;
var random = new CryptoRandom();

byte[] derivedKey
string hashedPassword = null;
string passwordText = "foo";

byte[] passwordBytes = SafeUTF8.GetBytes(passwordText);
var salt = random.NextBytes(384/8);

using (var pbkdf2 = new PBKDF2(sha384Factory, passwordBytes, salt, 256*1000))
    derivedKey=  pbkdf2.GetBytes(384/8);


using (var hmac = sha384Factory()) 
{
    hmac.Key = derivedKey;
    hashedPassword = hmac.ComputeHash(passwordBytes).ToBase16();
}

保留 salt 和 hashedPassword。请注意,您可以将它们保存为二进制文件,也可以使用帮助程序将它们存储为字符串。请注意,盐是随机创建的。

验证用户的登录:

var user = GetUserByUserName("bob")

var sha384Factory = HmacFactory;

byte[] derivedKey
string hashedPassword = null;
string suppliedPassword = "foo";

byte[] passwordBytes = SafeUTF8.GetBytes(suppliedPassword);

using (var pbkdf2 = new PBKDF2(sha384Factory, passwordBytes, user.UserSalt, 256*1000))
    derivedKey=  pbkdf2.GetBytes(384/8);


using (var hmac = sha384Factory()) 
{
    hmac.Key = derivedKey;
    hashedPassword = hmac.ComputeHash(passwordBytes).ToBase16();
}

isAuthenticated = hashedPassword == user.UserHashedPassword; //true for bob

正如您在此处看到的,该过程几乎相同。关键区别在于没有使用,CryptoRandom我们在创建实例时使用持久化的 UserSalt PBKDF2

来源在 GitHub

于 2016-07-29T07:27:18.900 回答
1

我在Google Code上的开源 C#密码实用程序库目前使用 HMAC SHA1-160 和 HMAC SHA2-256,以及 salt 和迭代 ( PKDBF2 )。如随附的 Windows 窗体 gui 所示,库中内置了密码和哈希生成的计时。

我的代码目前在我的机器上需要 0.80 秒来进行 65,536 次迭代的 SHA2-256 哈希。它肯定会更有效,因为我还没有对其进行分析。

我的 SHA2-256 代码产生与此处所示相同的测试结果。

于 2013-05-03T21:24:41.660 回答
1

另一种实现——在我发现像 RoadWarrior、Zer 和 thasiznets 这样的其他人之前已经做到了。

这就像Rfc2898DeriveBytes源自 .NET 的System.Cryptography.DeriveBytes. 换句话说,用法是一样的——尽管我只实现了我使用的一个构造函数。

除了那个血统之外,它根本不是基于微软的实现。这也需要免责声明 - 请参阅此答案的底部。

它允许任意的伪随机函数,这意味着我们可以插入 HMAC SHA256 或 HMAC SHA512——或者比我更有密码洞察力和勇气的人可以插入他们想要的任何东西——就像 RFC 允许的那样。它还使用long而不是int用于迭代计数 - 仅用于疯狂的迭代计数。

/// <summary>
/// More generic version of the built-in Rfc2898DeriveBytes class. This one
/// allows an arbitrary Pseudo Random Function, meaning we can use e.g. 
/// HMAC SHA256 or HMAC SHA512 rather than the hardcoded HMAC SHA-1 of the 
/// built-in version.
/// </summary>
public class PBKDF2DeriveBytes : DeriveBytes
{
    // Initialization:

    private readonly IPseudoRandomFunction prf;
    private readonly byte[] salt;
    private readonly long iterationCount;

    private readonly byte[] saltAndBlockNumber;

    // State:

    // Last result of prf.Transform - also used as buffer
    // between GetBytes() calls:
    private byte[] buffer;

    private int bufferIndex;
    private int nextBlock;

    /// <param name="prf">
    ///    The Pseudo Random Function to use for calculating the derived key
    /// </param>
    /// <param name="salt">
    ///    The initial salt to use in calculating the derived key
    /// </param>
    /// <param name="iterationCount">
    ///    Number of iterations. RFC 2898 recommends a minimum of 1000
    ///    iterations (in the year 2000) ideally with number of iterations
    ///    adjusted on a regular basis (e.g. each year).
    /// </param>
    public PBKDF2DeriveBytes(
       IPseudoRandomFunction prf, byte[] salt, long iterationCount)
    {
        if (prf == null)
        {
            throw new ArgumentNullException("prf");
        }

        if (salt == null)
        {
            throw new ArgumentNullException("salt");
        }

        this.prf = prf;
        this.salt = salt;
        this.iterationCount = iterationCount;

        // Prepare combined salt = concat(original salt, block number)
        saltAndBlockNumber = new byte[salt.Length + 4];
        Buffer.BlockCopy(salt, 0, saltAndBlockNumber, 0, salt.Length);

        Reset();
    }

    /// <summary>
    ///    Retrieves a derived key of the length specified.
    ///    Successive calls to GetBytes will return different results -
    ///    calling GetBytes(20) twice is equivalent to calling
    ///    GetBytes(40) once. Use Reset method to clear state.
    /// </summary>
    /// <param name="keyLength">
    ///    The number of bytes required. Note that for password hashing, a
    ///    key length greater than the output length of the underlying Pseudo
    ///    Random Function is redundant and does not increase security.
    /// </param>
    /// <returns>The derived key</returns>
    public override byte[] GetBytes(int keyLength)
    {
        var result = new byte[keyLength];

        int resultIndex = 0;

        // If we have bytes in buffer from previous run, use those first:
        if (buffer != null && bufferIndex > 0)
        {
            int bufferRemaining = prf.HashSize - bufferIndex;

            // Take at most keyLength bytes from the buffer:
            int bytesFromBuffer = Math.Min(bufferRemaining, keyLength);

            if (bytesFromBuffer > 0)
            {
                Buffer.BlockCopy(buffer, bufferIndex, result, 0,
                   bytesFromBuffer);
                bufferIndex += bytesFromBuffer;
                resultIndex += bytesFromBuffer;
            }
        }

        // If, after filling from buffer, we need more bytes to fill
        // the result, they need to be computed:
        if (resultIndex < keyLength)
        {
            ComputeBlocks(result, resultIndex);

            // If we used the entire buffer, reset index:
            if (bufferIndex == prf.HashSize)
            {
                bufferIndex = 0;
            }
        }

        return result;
    }

    /// <summary>
    ///    Resets state. The next call to GetBytes will return the same
    ///    result as an initial call to GetBytes.
    ///    Sealed since it's called from constructor.
    /// </summary>
    public sealed override void Reset()
    {
        buffer = null;
        bufferIndex = 0;
        nextBlock = 1;
    }

    private void ComputeBlocks(byte[] result, int resultIndex)
    {
        int currentBlock = nextBlock;

        // Keep computing blocks until we've filled the result array:
        while (resultIndex < result.Length)
        {
            // Run iterations for block:
            F(currentBlock);

            // Populate result array with the block, but only as many bytes
            // as are needed - keep the rest in buffer:
            int bytesFromBuffer = Math.Min(
                   prf.HashSize,
                   result.Length - resultIndex
            );
            Buffer.BlockCopy(buffer, 0, result, resultIndex, bytesFromBuffer);

            bufferIndex = bytesFromBuffer;
            resultIndex += bytesFromBuffer;
            currentBlock++;
        }
        nextBlock = currentBlock;
    }

    private void F(int currentBlock)
    {
        // First iteration:
        // Populate initial salt with the current block index:
        Buffer.BlockCopy(
           BlockNumberToBytes(currentBlock), 0, 
           saltAndBlockNumber, salt.Length, 4
        );

        buffer = prf.Transform(saltAndBlockNumber);

        // Remaining iterations:
        byte[] result = buffer;
        for (long iteration = 2; iteration <= iterationCount; iteration++)
        {
            // Note that the PRF transform takes the immediate result of the
            // last iteration, not the combined result (in buffer):
            result = prf.Transform(result);

            for (int byteIndex = 0; byteIndex < buffer.Length; byteIndex++)
            {
                buffer[byteIndex] ^= result[byteIndex];
            }
        }
    }

    private static byte[] BlockNumberToBytes(int blockNumber)
    {
        byte[] result = BitConverter.GetBytes(blockNumber);

        // Make sure the result is big endian:
        if (BitConverter.IsLittleEndian)
        {
            Array.Reverse(result);
        }

        return result;
    }
}

IPseudoRandomFunction声明为:

public interface IPseudoRandomFunction : IDisposable
{
    int HashSize { get; }
    byte[] Transform(byte[] input);
}

一个 HMAC-SHA512 IPseudoRandomFunction 示例(为简洁起见——我使用了一个允许任何 .NET 的 HMAC 类的泛型类):

public class HMACSHA512PseudoRandomFunction : IPseudoRandomFunction
{
    private HMAC hmac;
    private bool disposed;

    public HmacPseudoRandomFunction(byte[] input)
    {
        hmac = new HMACSHA512(input);
    }

    public int HashSize
    {
        // Might as well return a constant 64
        get { return hmac.HashSize / 8; }
    }

    public byte[] Transform(byte[] input)
    {
        return hmac.ComputeHash(input);
    }

    public void Dispose()
    {
        if (!disposed)
        {
            hmac.Dispose();
            hmac = null;
            disposed = true;
        }
    }
}

结果...这个:

using (var prf = new HMACSHA512PseudoRandomFunction(input))
{
    using (var hash = new PBKDF2DeriveBytes(prf, salt, 1000))
    {
        hash.GetBytes(32);
    }
}

...是 HMAC-SHA512 的等效项:

using (var hash = new Rfc2898DeriveBytes(input, salt, 1000))
{
    hash.GetBytes(32);
}

测试

PBKDF2DeriveBytes 类已经过测试

它还通过简单的测试Reset()和多次调用GetBytes().

一些初步的性能测试表明它与 SHA-1 的 .NET 实现相当,在“pass”/“saltSALT”上运行 1000 次迭代,转换为 ASCII 编码的字节GetBytes(200)。有时比内置实现快一点,有时慢一点——我们在我的古老计算机上谈论的是 84 秒对 83 秒。不过,所有这些都是通过PBKDF2DeriveBytes.

免责声明

我不是密码学天才。如上所述,这还没有经过大量测试。我不做任何保证。但也许,连同其他答案和实现,它可以帮助理解方法论。

于 2014-03-01T19:39:14.237 回答
1

较新的替代方案是Microsoft.AspNetCore.Cryptography.KeyDerivation NuGet 包,它允许将 PBKDF2 与 SHA-256 和 SHA-512 哈希函数一起使用,这比Rfc2898DeriveBytes. 与其他答案中提到的第三方库相比,它的优势在于它是由 Microsoft 实现的,因此一旦您已经依赖 .NET 平台,您就不需要对其执行安全审计。文档可从docs.microsoft.com 获得

于 2018-01-26T20:19:04.933 回答