63

目前我正在用明文写它哎呀!,这是一个内部程序,所以它不是那么糟糕,但我想把它做好。在写入注册表时我应该如何加密它以及如何解密它?

OurKey.SetValue("Password", textBoxPassword.Text);
4

12 回答 12

137

您不解密身份验证密码!

使用诸如 SHA256 提供程序之类的东西对它们进行哈希处理,当您必须挑战时,对来自用户的输入进行哈希处理并查看两个哈希值是否匹配。

byte[] data = System.Text.Encoding.ASCII.GetBytes(inputString);
data = new System.Security.Cryptography.SHA256Managed().ComputeHash(data);
String hash = System.Text.Encoding.ASCII.GetString(data);

让密码可逆是一个非常可怕的模型。

Edit2:我以为我们只是在谈论前线身份验证。当然,在某些情况下,您想为其他需要可逆的东西加密密码,但最重要的是应该有一个单向锁(只有极少数例外)。

我已经升级了散列算法,但为了获得最佳强度,您希望保留一个私有盐并在散列之前将其添加到您的输入中。当你比较时,你会再次这样做。这增加了另一层,使某人更难逆转。

于 2008-10-17T15:00:18.303 回答
25

还请考虑“加盐”你的哈希(不是烹饪概念!)。基本上,这意味着在对密码进行散列之前将一些随机文本附加到密码中。

"如果您的凭证存储受到威胁,盐值有助于减缓攻击者执行字典攻击的速度,从而为您提供额外的时间来检测和应对威胁。 "

要存储密码哈希:

a) 生成随机盐值:

byte[] salt = new byte[32];
System.Security.Cryptography.RNGCryptoServiceProvider.Create().GetBytes(salt);

b) 将盐附加到密码中。

// Convert the plain string pwd into bytes
byte[] plainTextBytes = System.Text UnicodeEncoding.Unicode.GetBytes(plainText);
// Append salt to pwd before hashing
byte[] combinedBytes = new byte[plainTextBytes.Length + salt.Length];
System.Buffer.BlockCopy(plainTextBytes, 0, combinedBytes, 0, plainTextBytes.Length);
System.Buffer.BlockCopy(salt, 0, combinedBytes, plainTextBytes.Length, salt.Length);

c)散列组合的密码和盐:

// Create hash for the pwd+salt
System.Security.Cryptography.HashAlgorithm hashAlgo = new System.Security.Cryptography.SHA256Managed();
byte[] hash = hashAlgo.ComputeHash(combinedBytes);

d) 将盐添加到生成的哈希中。

// Append the salt to the hash
byte[] hashPlusSalt = new byte[hash.Length + salt.Length];
System.Buffer.BlockCopy(hash, 0, hashPlusSalt, 0, hash.Length);
System.Buffer.BlockCopy(salt, 0, hashPlusSalt, hash.Length, salt.Length);

e) 将结果存储在您的用户存储数据库中。

这种方法意味着您不需要单独存储盐,然后使用盐值和从用户获得的明文密码值重新计算哈希。

编辑:随着原始计算能力变得更便宜和更快,散列的价值 - 或盐渍散列 - 已经下降。Jeff Atwood 有一个出色的 2012 年更新,太长了,无法在此完整重复,其中指出:

这(使用加盐哈希)将提供比任何实际安全更多的安全假象。由于您需要盐和散列算法的选择来生成散列并检查散列,因此攻击者不太可能有一个而没有另一个。如果您已经被入侵到攻击者拥有您的密码数据库的程度,那么可以合理地假设他们拥有或可以获得您的秘密隐藏盐。

安全的第一条规则是始终为最坏的情况做假设和计划。您是否应该使用盐,最好是为每个用户使用随机盐?当然,这绝对是一种很好的做法,至少它可以让您消除两个具有相同密码的用户的歧义。但是现在,单靠盐已经不能让你从愿意花几千美元购买显卡硬件的人手中拯救你了,如果你认为他们可以,那你就有麻烦了。

于 2008-10-17T15:13:10.087 回答
15

Tom Scott 在Computerphile 上关于如何(不)存储密码的报道中是正确的。

https://www.youtube.com/watch?v=8ZtInClXe1Q

  1. 如果您可以完全避免它,请不要尝试自己存储密码。改为使用单独的、预先建立的、值得信赖的用户身份验证平台(例如:OAuth 提供商、您公司的 Active Directory 域等)。

  2. 如果您必须存储密码,请不要遵循此处的任何指导。至少,不要不咨询适用于您选择的语言的最新且知名的出版物。

这里肯定有很多聪明人,甚至可能给出了一些好的指导。但是很有可能,当您阅读本文时,这里的所有答案(包括这个答案)都已经过时了。


存储密码的正确方式会随着时间而改变。

可能比某些人更频繁地更换内衣。


说了这么多,这里有一些一般性的指导,希望在一段时间内仍然有用。

  1. 不要加密密码。任何允许恢复存储数据的存储方法对于保存密码来说都是不安全的——包括所有形式的加密。
  2. 完全按照用户在创建过程中输入的密码进行处理。在将密码发送到加密模块之前对密码所做的任何事情都可能会削弱它。执行以下任何操作也只会增加密码存储和验证过程的复杂性,这可能会导致其他问题(甚至可能引入漏洞)。

    • 不要转换为全大写/全小写。
    • 不要删除空格。
    • 不要剥离不可接受的字符或字符串。
    • 不要更改文本编码。
    • 不要进行任何字符或字符串替换。
    • 不要截断任何长度的密码。
  3. 拒绝创建任何未经修改就无法存储的密码。加强上述。如果由于某种原因您的密码存储机制无法正确处理某些字符、空格、字符串或密码长度,则返回错误并让用户了解系统的限制,以便他们可以使用适合它们的密码重试。为了获得更好的用户体验,请预先列出用户可以访问的这些限制。甚至不用担心,更不用说麻烦,向攻击者隐藏列表 - 无论如何,他们自己会很容易地弄清楚。

  4. 为每个帐户使用一个长的、随机的、唯一的盐。任何两个帐户的密码在存储中都不应该看起来相同,即使密码实际上是相同的。
  5. 使用专为与密码一起使用而设计的慢速且加密强度高的散列算法。MD5肯定出来了。SHA-1/SHA-2 是不行的。但我也不会告诉你应该在这里使用什么。(请参阅这篇文章中的第一个 #2 项目符号。)
  6. 尽可能多地迭代。虽然您的系统可能比整天使用哈希密码更能处理其处理器周期,但破解您密码的人的系统却没有。尽可能对他们施加压力,而不是对你“太苛刻”。

最重要的是...

不要只听这里的任何人。

去查找有关您选择的语言的正确密码存储方法的著名且最近的出版物。实际上,在您确定一种方法之前,您应该从多个不同的来源找到多个最近的出版物,这些出版物是一致的。

极有可能这里的每个人(包括我自己)所说的一切都已经被更好的技术所取代,或者被新开发的攻击方法变得不安全。去寻找更可能不是的东西。

于 2015-10-23T21:38:40.050 回答
12

这就是你想要做的:

OurKey.SetValue("Password", StringEncryptor.EncryptString(textBoxPassword.Text));
OurKey.GetValue("Password", StringEncryptor.DecryptString(textBoxPassword.Text));

您可以使用以下课程来做到这一点。这个类是一个泛型类,是客户端端点。它使用Ninject启用各种加密算法的 IOC 。

public class StringEncryptor
{
    private static IKernel _kernel;

    static StringEncryptor()
    {
        _kernel = new StandardKernel(new EncryptionModule());
    }

    public static string EncryptString(string plainText)
    {
        return _kernel.Get<IStringEncryptor>().EncryptString(plainText);
    }

    public static string DecryptString(string encryptedText)
    {
        return _kernel.Get<IStringEncryptor>().DecryptString(encryptedText);
    }
}

下一个类是允许您注入各种算法的 ninject 类:

public class EncryptionModule : StandardModule
{
    public override void Load()
    {
        Bind<IStringEncryptor>().To<TripleDESStringEncryptor>();
    }
}

这是任何算法都需要实现加密/解密字符串的接口:

public interface IStringEncryptor
{
    string EncryptString(string plainText);
    string DecryptString(string encryptedText);
}

这是使用 TripleDES 算法的实现:

public class TripleDESStringEncryptor : IStringEncryptor
{
    private byte[] _key;
    private byte[] _iv;
    private TripleDESCryptoServiceProvider _provider;

    public TripleDESStringEncryptor()
    {
        _key = System.Text.ASCIIEncoding.ASCII.GetBytes("GSYAHAGCBDUUADIADKOPAAAW");
        _iv = System.Text.ASCIIEncoding.ASCII.GetBytes("USAZBGAW");
        _provider = new TripleDESCryptoServiceProvider();
    }

    #region IStringEncryptor Members

    public string EncryptString(string plainText)
    {
        return Transform(plainText, _provider.CreateEncryptor(_key, _iv));
    }

    public string DecryptString(string encryptedText)
    {
        return Transform(encryptedText, _provider.CreateDecryptor(_key, _iv));
    }

    #endregion

    private string Transform(string text, ICryptoTransform transform)
    {
        if (text == null)
        {
            return null;
        }
        using (MemoryStream stream = new MemoryStream())
        {
            using (CryptoStream cryptoStream = new CryptoStream(stream, transform, CryptoStreamMode.Write))
            {
                byte[] input = Encoding.Default.GetBytes(text);
                cryptoStream.Write(input, 0, input.Length);
                cryptoStream.FlushFinalBlock();

                return Encoding.Default.GetString(stream.ToArray());
            }
        }
    }
}

您可以在以下网址观看我的视频并下载代码:http ://www.wrightin.gs/2008/11/how-to-encryptdecrypt-sensitive-column-contents-in-nhibernateactive-record-video.html

于 2008-11-07T14:31:40.667 回答
10

一种选择是存储密码的散列(SHA1、MD5)而不是明文密码,每当您想查看密码是否正确时,只需将其与该散列进行比较即可。

如果您需要安全存储(例如用于连接服务的密码),那么问题就更复杂了。

如果只是用于身份验证,那么使用哈希就足够了。

于 2008-10-17T14:59:43.467 回答
9

如果您希望能够解密密码,我认为最简单的方法是使用 DPAPI(用户存储模式)来加密/解密。这样您就不必摆弄加密密钥,将它们存储在某个地方或将它们硬编码到您的代码中——在这两种情况下,有人可以通过查看注册表、用户设置或使用 Reflector 来发现它们。

否则,像其他人在这里所说的那样使用哈希 SHA1 或 MD5。

于 2008-10-17T15:02:23.353 回答
6

就像 ligget78 所说,DPAPI 将是存储密码的好方法。查看 MSDN 上的 ProtectedData 类以了解示例用法。

于 2009-03-23T17:04:57.070 回答
6

我到处寻找加密和解密过程的一个很好的例子,但大多数都过于复杂。

无论如何,有人可能想要解密某些文本值(包括密码)的原因有很多。我需要在我目前正在处理的网站上解密密码的原因是因为他们想确保当有人在密码过期时被迫更改密码时,我们不会让他们使用相同密码的紧密变体来更改密码他们在过去的 x 个月内使用过。

因此,我编写了一个流程,以简化的方式执行此操作。我希望这段代码对某人有益。据我所知,我可能最终会在另一个时间为不同的公司/站点使用它。

public string GenerateAPassKey(string passphrase)
    {
        // Pass Phrase can be any string
        string passPhrase = passphrase;
        // Salt Value can be any string(for simplicity use the same value as used for the pass phrase)
        string saltValue = passphrase;
        // Hash Algorithm can be "SHA1 or MD5"
        string hashAlgorithm = "SHA1";
        // Password Iterations can be any number
        int passwordIterations = 2;
        // Key Size can be 128,192 or 256
        int keySize = 256;
        // Convert Salt passphrase string to a Byte Array
        byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
        // Using System.Security.Cryptography.PasswordDeriveBytes to create the Key
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);
        //When creating a Key Byte array from the base64 string the Key must have 32 dimensions.
        byte[] Key = pdb.GetBytes(keySize / 11);
        String KeyString = Convert.ToBase64String(Key);

        return KeyString;
    }

 //Save the keystring some place like your database and use it to decrypt and encrypt
//any text string or text file etc. Make sure you dont lose it though.

 private static string Encrypt(string plainStr, string KeyString)        
    {            
        RijndaelManaged aesEncryption = new RijndaelManaged();
        aesEncryption.KeySize = 256;
        aesEncryption.BlockSize = 128;
        aesEncryption.Mode = CipherMode.ECB;
        aesEncryption.Padding = PaddingMode.ISO10126;
        byte[] KeyInBytes = Encoding.UTF8.GetBytes(KeyString);
        aesEncryption.Key = KeyInBytes;
        byte[] plainText = ASCIIEncoding.UTF8.GetBytes(plainStr);
        ICryptoTransform crypto = aesEncryption.CreateEncryptor();
        byte[] cipherText = crypto.TransformFinalBlock(plainText, 0, plainText.Length);
        return Convert.ToBase64String(cipherText);
    }

 private static string Decrypt(string encryptedText, string KeyString) 
    {
        RijndaelManaged aesEncryption = new RijndaelManaged(); 
        aesEncryption.KeySize = 256;
        aesEncryption.BlockSize = 128; 
        aesEncryption.Mode = CipherMode.ECB;
        aesEncryption.Padding = PaddingMode.ISO10126;
        byte[] KeyInBytes = Encoding.UTF8.GetBytes(KeyString);
        aesEncryption.Key = KeyInBytes;
        ICryptoTransform decrypto = aesEncryption.CreateDecryptor(); 
        byte[] encryptedBytes = Convert.FromBase64CharArray(encryptedText.ToCharArray(), 0, encryptedText.Length); 
        return ASCIIEncoding.UTF8.GetString(decrypto.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length)); 
    }

 String KeyString = GenerateAPassKey("PassKey");
 String EncryptedPassword = Encrypt("25Characterlengthpassword!", KeyString);
 String DecryptedPassword = Decrypt(EncryptedPassword, KeyString);
于 2011-05-27T20:20:15.873 回答
5

如果它是您的应用程序用于身份验证的密码,则按照其他人的建议对密码进行哈希处理。

如果您要存储外部资源的密码,您通常希望能够提示用户输入这些凭据并让他有机会安全地保存它们。Windows 为此提供了 Credentials UI (CredUI) - 有许多示例展示了如何在 .NET 中使用它,包括MSDN 上的这个

于 2008-10-17T17:21:49.330 回答
1

..NET 在 System.Security.Cryptography 命名空间中包含的类中提供加密服务。

于 2008-10-17T15:02:36.520 回答
1

如果您需要更多,例如保护连接字符串(用于连接到数据库),请查看这篇文章,因为它为此提供了最佳“选项”。

Oli 的回答也很好,因为它展示了如何为字符串创建散列。

于 2008-10-17T15:06:47.323 回答
-1

您应该通过散列算法 md5/sha512 或类似算法传递密码,而不是加密/解密。理想情况下你会做的是散列密码并存储散列,然后当需要密码时,你散列​​条目并比较条目。密码将永远不会被“解密”,只是简单地散列然后比较。

于 2008-10-17T15:01:09.690 回答