【问题标题】:Password Hashing with SQL Server 2008 CLR使用 SQL Server 2008 CLR 进行密码散列
【发布时间】:2013-10-30 20:54:06
【问题描述】:

我正在尝试使用 SHA-512 算法在 SQL Server 2008 中实现对密码进行散列和加盐的解决方案。该解决方案基于 Michael Coles 所著的“Expert SQL Server 2008 Encryption”一书。根据他的示例,我能够在 Visual Studio 2010(C# 中的 .NET 3.5)中构建项目并部署到 SQL Server 2008(如下面的代码所示)。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Security.Cryptography;

namespace Apress.Samples
{
    public partial class CustomEncryption
    {
    [Microsoft.SqlServer.Server.SqlFunction
    (
      IsDeterministic = true,
      DataAccess = DataAccessKind.None
    )]
    public static SqlBytes SaltedHash
    (
      SqlString Algorithm,
      [SqlFacet(MaxSize = -1)] SqlBytes PlainText,
      SqlBytes Salt
    )
    {
        // Return NULL if any of the parameters is NULL
        if (Algorithm.IsNull || PlainText.IsNull || Salt.IsNull)
            return SqlBytes.Null;

        // Determine which algorithm to use
        bool HashDefined = true;
        HashAlgorithm Hash = null;
        switch (Algorithm.Value.ToUpper())
        {
            case "SHA256":
                Hash = new SHA256Managed();
                break;

            case "SHA384":
                Hash = new SHA384Managed();
                break;

            case "SHA512":
                Hash = new SHA512Managed();
                break;

            default:
                HashDefined = false;
                break;
        }
        if (!HashDefined)
            throw new Exception
              ("Unsupported hash algorithm - use SHA256, SHA384 or SHA512");

        // Combine the plaintext with the salt
        byte[] PlainTextWithSalt = new byte[PlainText.Length + Salt.Length];
        for (long i = 0; i < Salt.Length; i++)
            PlainTextWithSalt[i] = Salt[i];
        for (long i = Salt.Length; i < PlainText.Length; i++)
            PlainTextWithSalt[i] = PlainText.Value[i - Salt.Length];

        // Generate the hash and return the result
        byte[] HashBytes = Hash.ComputeHash(PlainTextWithSalt);
        return new SqlBytes(HashBytes);
    }
}
}

当我使用下面的代码从 SQL 执行测试时,每个算法都会按预期生成哈希。

DECLARE @plaintext varchar(15);
SET @plaintext = 'ABCDEFGHIJ';

DECLARE @salt varbinary(16);
SET @salt = Crypt_Gen_Random(16);

DECLARE @sha256 varbinary(32)
DECLARE @sha384 varbinary(48)
DECLARE @sha512 varbinary(64)

SELECT @sha256 = dbo.SaltedHash('SHA256', CAST(@plaintext AS varbinary(max)), @salt);
SELECT @sha384 = dbo.SaltedHash('SHA384', CAST(@plaintext AS varbinary(max)), @salt);
SELECT @sha512 = dbo.SaltedHash('SHA512', CAST(@plaintext AS varbinary(max)), @salt);

SELECT 'SHA-256' AS algorithm, @sha256 AS hash
UNION ALL
SELECT 'SHA-384', @sha384
UNION ALL
SELECT 'SHA-512', @sha512;

我想用它来验证登录,我假设我需要检索为用户记录存储的盐值并将其传递给 SaltedHash 函数,它会返回散列值.从那里,我会将函数返回的散列值与存储在用户记录中的散列值进行比较。

我遇到的问题是,当我测试传递硬编码的盐值 ('0x0E5ECC235FF6BD7337FFDDE5799D4EEA') 以及明文 ('ABCDEFGHIJ') 以模拟检索散列值时(例如,用于比较散列密码)。如果我提供具有相同硬编码盐值的 ('1234567890') 的明文值,它将返回完全相同的哈希值。实际上,任何 10 个字符的明文值都会返回相同的散列值。

DECLARE @plaintext varchar(15);
SET @plaintext = 'ABCDE12345';

DECLARE @salt varbinary(16);
SET @salt = 0x0E5ECC235FF6BD7337FFDDE5799D4EEA; // Hardcoded salt value!

SELECT @salt

DECLARE @sha256 varbinary(32)
DECLARE @sha384 varbinary(48)
DECLARE @sha512 varbinary(64)

SELECT @sha256 = dbo.SaltedHash('SHA256', CAST(@plaintext AS varbinary(max)), @salt);
SELECT @sha384 = dbo.SaltedHash('SHA384', CAST(@plaintext AS varbinary(max)), @salt);
SELECT @sha512 = dbo.SaltedHash('SHA512', CAST(@plaintext AS varbinary(max)), @salt);

SELECT 'SHA-256' AS algorithm, @sha256 AS hash
UNION ALL
SELECT 'SHA-384', @sha384
UNION ALL
SELECT 'SHA-512', @sha512;

我假设问题在于“将明文与盐结合”代码,但不确定。

关于如何解决这个问题的任何想法?

【问题讨论】:

  • 请注意,如果盐在最终解决方案中被硬编码,则它不是盐。那将是Pepper,每个密码的盐都是唯一的,您通常只需将盐存储在下一列中或将其连接到密文字节数组的前面或后面。
  • 感谢您的说明,因为我不熟悉“Pepper”。我的意图是按照您的建议为每个密码创建一个唯一的盐。

标签: c# sql-server-2008 hash password-hash


【解决方案1】:

是的,您在数组副本中出错,您永远不会将PlainText 的最后一个Salt.Length 字节复制到目标数组中。这是您原始代码的更正版本。所有需要做的就是i需要上升到PlainText.Length + Salt.Length

// Combine the plaintext with the salt
byte[] PlainTextWithSalt = new byte[PlainText.Length + Salt.Length];
for (long i = 0; i < Salt.Length; i++)
    PlainTextWithSalt[i] = Salt[i];
for (long i = Salt.Length; i < PlainText.Length + Salt.Length; i++)
    PlainTextWithSalt[i] = PlainText.Value[i - Salt.Length];

但是使用Array.Copy 会容易得多

byte[] PlainTextWithSalt = new byte[PlainText.Length + Salt.Length];
Array.Copy(Salt, PlainTextWithSalt, Salt.Length);
Array.Copy(PlainText, PlainTextWithSalt, Salt.Length, PlainText.Length);

还有一点需要注意。加盐密码很好,但是that is only half the battle,您还需要使散列SLOW。通常这是通过 bcryptPBKDF2 之类的函数来完成的,这些函数可以为您管理混合盐和密码,此外它们还允许您设置对密码执行的哈希迭代次数(您通常会选择一个大数字,你希望这个数字在你的终端系统可以容忍的范围内运行缓慢)。

【讨论】:

  • 我已经应用了您更正的代码并确认它现在可以工作了。另外,谢谢你的功课。我决定使用 CLR/.NET SHA2 方法,因为它似乎是一种已被更广泛采用的解决方案。但似乎 bcrypt 也是我要投入一些时间的东西。bcrypt 的“调整”部分确实是一个很有吸引力的功能。
  • @vanexellent PBKDF2 还通过它的ItterationCount 属性内置了该调整因子(但是您将希望使用数字&gt;10000 进行计数,以使现代算法有意义地减速硬件)使用可调工作因子的好处是,当您获得更快的硬件并且需要使哈希变慢时,只需在用户下次登录时使用更大的因子重新哈希,您需要做的就是存储您的工作因子上次与哈希和盐一起使用
  • 谢谢,我对这些算法进行了一些研究,很高兴知道它们可以在 SQL 2008 (.NET 3.5) 中使用,这是我们目前坚持使用的版本。感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 2013-12-16
  • 2021-06-04
  • 1970-01-01
  • 1970-01-01
  • 2014-12-03
  • 2011-06-05
  • 1970-01-01
  • 2017-07-15
相关资源
最近更新 更多