存储散列密码 - base64,或十六进制字符串,还是其他什么?

时间:2009-06-04 08:33:35

标签: hash base64

我使用.NET System.Security.Cryptography类散列密码。 它有一些用于散列的算法,例如MD5,SHA1,SHA256,SHA384,SHA512

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

顺便提一下,有很多哈希算法可供选择,我随机选择了SHA384,但有没有“更好”或适合这项任务?

请评论。

阅读前八条评论后更新:
通过答案和我已经完成的进一步阅读,似乎MD5,SHA1或多或少相当(SHA1稍微更安全)。 SHA256,384,512在递增顺序时提供更好的安全性。

由于我不需要fort-knox(这是一个没有网址,浏览器,互联网,内联网或外联网的内部企业系统),我将绕过“腌制”业务 - 我想如果有人他们可以窃取密码表,也可以窃取其他表中的实际数据。

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

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

感谢大家的贡献。

10 个答案:

答案 0 :(得分:12)

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

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

  

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

这是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);

商店ph.Salt&amp; ph.Passhash某个地方...... 之后,当用户再次登录时,您会查找 有盐和用户的用户记录passhash,然后你这样做:

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

}

答案 1 :(得分:8)

Base64确实比hex更短,如果它最终在URL中(由于涉及符号),你需要小心。这取决于你使用它的地方,基本上。 Hex通常具有以下优点:用肉眼更容易“读取”,但因为这基本上是无意义的数据,这与散列无关。 (如果需要,您可以随时使用在线base64解码。)

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

答案 2 :(得分:4)

  

顺便提一下,有很多哈希算法可供选择,我随机选择了SHA384,但有没有“更好”或适合这项任务?

一般情况下,我会避免使用MD5或SHA-0或SHA-1。但SHA-384应该足以满足您的需求。有关算法的一些讨论,请参阅this question

答案 3 :(得分:4)

有趣的说明我以前见过,如果你有一个字节数组,并且你对它进行了base64编码,那么一些结果字符可能会导致网址出现问题 - 但是,如果你基于64编码它会再次编码,那些冒犯了往往会删除字符,并且可以在url中使用base64字符串。您可以使用双重编码的字符串进行比较。

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

答案 4 :(得分:4)

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

如果您没有这样做,请确保在对密码进行散列之前对其进行加密,以防止更常见的黑客攻击(识别类似的密码,彩虹表等)。有关详细信息,请阅读此article

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

答案 5 :(得分:3)

您也可以将其转换为普通字节。因此,128位MD5散列值将导致128位/ 8位/字节= 16字节长字符串。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

答案取决于您可能拥有的背景和约束。与十六进制编码相比,Base64将更紧凑,但生成计算成本更高。

您应该为散列数据添加一个salt。它是一个随机值放在您散列的密码前面。然后使用散列值存储salt值,以便可以重新生成散列值。

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

您还应该使用散列值添加魔术字节序列(常量字节序列)。攻击者必须知道这个神奇的字节序列才能生成适当的哈希值。如果它唯一拥有的是哈希密码文件,那么他就不会走得太远。他需要获得最有可能在你的代码中隐藏的神奇词汇。

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

答案 8 :(得分:0)

我会使用Base64 ....哦,如果你的密码是哈希的话,别忘了给你的哈希加盐:)如果我们谈论密码,任何小于SHA384的东西都是安全风险,因为它很容易获得质量SHA1的彩虹表和其他常见的哈希算法。

答案 9 :(得分:0)

如何使用仅包含字母和数字的Base36或Base62表示法(可以安全地在URL中传输)。我只是在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,并使用余数保持追加从查找表中检索的字符。

欢迎提出建议。