17

我正在使用 .NET System.Security.Cryptography 类对密码进行哈希处理。它有一些散列算法,例如 MD5、SHA1、SHA256、SHA384、SHA512

生成的散列值是一个字节数组。我应该将其转换为用于存储的十六进制字符串,还是转换为 Convert.ToBase64String() 或其他东西?(我喜欢 Base64,因为它比 Hex 短)。

顺便说一句,有这么多哈希算法可供选择,我随机选择了 SHA384,但有没有“更好”或适合该任务的算法?

请发表评论。

阅读前八条评论后更新:
根据我所做的答案和进一步阅读,似乎 MD5、SHA1 或多或少是等效的(SHA1 稍微安全一些)。SHA256、384、512 以递增的顺序提供更好的安全性。

由于我不需要 fort-knox(这是针对没有 url、浏览器、互联网、内联网或外联网的内部公司系统),我将绕过“盐渍”业务——我想是否有人可以窃取密码表,他们还不如窃取其他表中的实际数据。

但我会保留“盐”的概念以供将来参考;不确定是否应该在散列之前将盐附加(在末尾)或附加在密码的前面(在前面),这会有所不同吗?另外我正在考虑使用密码本身的前几个字符作为盐,以避免额外的字段来存储它,但我想它不够长 - 而且盐应该足够长。

共识认为base64转换是存储和比较的合理选择。考虑到最大密码长度为 15 个字符,我仍然需要弄清楚哈希存储所需的最大数据库列长度是多少。也许是 Varchar(64)?

谢谢大家的贡献。

4

10 回答 10

12

即使您的解决方案不是诺克斯堡,您也应该勤奋并实施加盐。因为许多人在其他地方重复使用他们的密码,并且如果攻击者选择将破解的密码数据库用于其他目的,那么入侵将在您的组织之外造成额外的损害。

加盐使字典攻击更加昂贵。通过决定使用什么盐的大小,您可以微调您的机会。这是 Bruce Schneier 的“应用密码学”中的引述:

“Salt 不是灵丹妙药;增加 salt 位的数量并不能解决所有问题。Salt 只能防止对密码文件的一般字典攻击,而不是对单个密码的协同攻击。它可以保护拥有相同密码的人多台机器,但不会使选择不当的密码变得更好。”

这是 C# 中的示例。没那么难。您可以选择要使用的盐大小和哈希函数。免责声明:如果您真的关心密码完整性,请使用bcrypt 之类的东西。


using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;

public class PassHash {
    private static readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
    public static readonly int DefaultSaltSize = 8; // 64-bit salt
    public readonly byte[] Salt;
    public readonly byte[] Passhash;

    internal PassHash(byte[] salt, byte[] passhash) {
        Salt = salt;
        Passhash = passhash;
    }

    public override String ToString() {
        return String.Format("{{'salt': '{0}', 'passhash': '{1}'}}",
                             Convert.ToBase64String(Salt),
                             Convert.ToBase64String(Passhash));
    }

    public static PassHash Encode<HA>(String password) where HA : HashAlgorithm {
        return Encode<HA>(password, DefaultSaltSize);
    }

    public static PassHash Encode<HA>(String password, int saltSize) where HA : HashAlgorithm {
        return Encode<HA>(password, GenerateSalt(saltSize));
    }

    private static PassHash Encode<HA>(string password, byte[] salt) where HA : HashAlgorithm {
        BindingFlags publicStatic = BindingFlags.Public | BindingFlags.Static;
        MethodInfo hasher_factory = typeof (HA).GetMethod("Create", publicStatic, Type.DefaultBinder, Type.EmptyTypes, null);
        using (HashAlgorithm hasher = (HashAlgorithm) hasher_factory.Invoke(null, null))
        {
            using (MemoryStream hashInput = new MemoryStream())
            {
                hashInput.Write(salt, 0, salt.Length);
                byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
                hashInput.Write(passwordBytes, 0, passwordBytes.Length);
                hashInput.Seek(0, SeekOrigin.Begin);
                byte[] passhash = hasher.ComputeHash(hashInput);
                return new PassHash(salt, passhash);
            }
        }
    }

    private static byte[] GenerateSalt(int saltSize) {
        // This generates salt.
        // Rephrasing Schneier:
        // "salt" is a random string of bytes that is
        // combined with password bytes before being
        // operated by the one-way function.
        byte[] salt = new byte[saltSize];
        rng.GetBytes(salt);
        return salt;
    }

    public static bool Verify<HA>(string password, byte[] salt, byte[] passhash) where HA : HashAlgorithm {
        // OMG: I don't know how to compare byte arrays in C#.
        return Encode<HA>(password, salt).ToString() == new PassHash(salt, passhash).ToString();
    }
}

用法:

新用户提交他们的凭据。

PassHash ph = PassHash.Encode<SHA384>(new_user_password);

Store ph.Salt&ph.Passhash某处...稍后,当用户再次登录时,您查找具有 salt & passhash 的用户记录,然后执行以下操作:

PassHash.Verify<SHA384>(user_login_password, user_rec.salt, user_rec.passhash)

}

于 2009-06-05T00:06:35.027 回答
8

Base64 确实比十六进制短,如果它最终出现在 URL 中(由于涉及的符号),您需要小心一点。基本上,这取决于您在哪里使用它。十六进制通常具有肉眼更容易“阅读”的优点,但由于这将是本质上无意义的数据,因此与哈希无关。(如有必要,您始终可以使用在线 base64 解码。)

当然,这一切都假设你想要一个字符串。与不透明的字节数组相比,字符串很好并且易于存储、传输等。

于 2009-06-04T08:36:59.633 回答
4

顺便说一句,有这么多哈希算法可供选择,我随机选择了 SHA384,但有没有“更好”或适合该任务的算法?

一般来说,我会避免使用 MD5 或 SHA-0 或 SHA-1。但 SHA-384 目前应该足以满足您的需求。有关算法的一些讨论,请参阅此问题

于 2009-06-04T08:41:28.877 回答
4

我之前看到的有趣的注释,如果你有一个字节数组,并且你对它进行 base64 编码,那么一些结果字符可能会导致 url 出现问题 - 但是,如果你再次对它进行 base 64 编码,那些有问题的字符往往会被删除,base64 字符串可以在 urls 中使用。您可以使用双编码字符串进行比较。

在散列算法上,sha384 可能是一个足够的算法,但请确保对散列进行加盐!

于 2009-06-04T08:42:00.123 回答
4

要回答您问题的第二部分,SHA384 可能是矫枉过正。SHA1 可以很好地满足您的目的,但它有一个理论上的利用(涉及能够创建一个被篡改的文档,其哈希值相同,您无需担心)。

如果您不这样做,请确保在散列密码之前对密码进行加盐,以防止更常见的黑客攻击(识别相似密码、彩虹表等)。阅读本文了解更多信息。

作为记录,我喜欢 Base64,因为 .NET 具有用于转换到/从它的内置例程。

于 2009-06-04T08:50:28.997 回答
3

您也可以将其转换为纯字节。因此,128 位 MD5 哈希值将导致 128 位/8 位/字节 = 16 字节长的字符串。

于 2009-06-04T08:36:26.030 回答
0

对于密码,我相信它们中的任何一个都可以完美地发挥作用。SHA 512 将创建一个更高的密钥,减少“冲突”的机会,知道不同的密码可能具有相同的哈希值,但可能性非常低。

于 2009-06-04T08:42:39.967 回答
0

答案取决于您可能遇到的上下文和限制。与十六进制编码相比,Base64 将更紧凑,但生成的计算成本更高。

您应该在散列数据中添加盐。它是放在您散列的密码前面的随机值。然后将盐值与哈希值一起存储,以便您可以重新生成哈希值。

该盐的作用是防止构建将常用密码映射到散列值的字典。如果有人可以获得您的哈希密码文件的副本,他可以使用此字典查找明文密码。使用 16 位盐,对于相同的明文密码,您有 65536 个不同的哈希值。字典攻击变得不那么有效。最好选择 64 位盐,因为它不会增加更多的哈希和验证密码的工作量。

您还应该使用散列值添加一个魔术字节序列(恒定字节序列)。攻击者必须知道这个神奇的字节序列才能生成适当的哈希值。因此,如果它唯一的东西是散列密码文件,他不会走得太远。他将需要获得最有可能隐藏在您的代码中某个地方的神奇词。

关于使用 SHA1 的算法非常好,而 SHA256 将是皮带和皮带。MD5 的计算成本要低得多,并且可以满足大多数用例。如果您的处理和存储资源可能是一个限制因素,请考虑这一点。

于 2009-06-04T08:57:22.260 回答
0

我会使用 Base64 .... 哦,如果您正在哈希密码,请不要忘记给您的哈希加盐 :) 如果我们谈论密码,任何小于 SHA384 的东西都会带来安全风险,因为很容易获得高质量的彩虹表SHA1 和其他常见的散列算法。

于 2009-06-04T09:57:41.203 回答
0

如何使用仅包含字母和数字的 Base36 或 Base62 表示法(可以安全地在 URLS 中传输)。我只是在java中尝试了以下

BigInteger hexNumber = new BigInteger(hexString, 16);
// Convert it to Base 36 yields smaller string than hexString
hexNumber.toString(36);

将 Base36 数字转换回十六进制

BigInteger b36String= new BigInteger(b36String, 36);
// Convert it to Base 16 yields original hex string
hexNumber.toString(16);

BTW Java 的 BigInteger 最大基数是 36。

对于 Base62,我们可以使用 62 个字符(26 个低位、26 个高位和 10 个数字)在数学上将十六进制数除以 64 的查找表,并使用余数保留从查找表中检索到的字符。

欢迎提出建议。

于 2013-11-19T12:56:29.447 回答