关于c#:当我将密码保存到注册表时,加密密码的最简单方法是什么?

What is the easiest way to encrypt a password when I save it to the registry?

目前,我正在写它的纯文本哎呀!,这是一个内部计划,所以没那么糟糕,但我想做对。在写入注册表时,我应该如何对其进行加密,以及如何对其进行解密?

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


您不解密身份验证密码!

使用类似于sha256提供程序的方法散列它们,当您需要挑战时,散列来自用户的输入,看看这两个散列是否匹配。

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

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

伊迪丝2:我以为我们只是在谈论一线认证。当然,在某些情况下,您希望为其他需要可逆的东西加密密码,但在所有这些东西之上应该有一个单向锁(除了极少数例外)。

我已经升级了散列算法,但是为了尽可能获得最好的强度,您需要保留一个私有的salt,并在散列之前将其添加到您的输入中。当你比较的时候你会再做一次。这又增加了一层,使得有人更难逆转。


请考虑"加盐"你的土豆泥(不是烹饪概念!)基本上,这意味着在散列密码之前,在密码上附加一些随机文本。

salt值有助于减缓攻击者在凭据存储受到破坏时执行字典攻击的速度,为您提供额外的时间来检测和响应破坏。

要存储密码哈希:

a)生成随机盐值:

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

b)将salt附加到密码。

1
2
3
4
5
6
// 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)散列组合密码&salt:

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

d)将盐添加到结果散列中。

1
2
3
4
// 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)将结果存储在用户存储数据库中。

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

编辑:随着原始计算能力变得越来越便宜和更快,散列(即盐渍散列)的价值已经下降。Jeff Atwood有一个优秀的2012年更新,太长了,无法在这里全部重复,说明:

This (using salted hashes) will provide the illusion of security more than any actual security. Since you need both the salt and the choice of hash algorithm to generate the hash, and to check the hash, it's unlikely an attacker would have one but not the other. If you've been compromised to the point that an attacker has your password database, it's reasonable to assume they either have or can get your secret, hidden salt.

The first rule of security is to always assume and plan for the worst.
Should you use a salt, ideally a random salt for each user? Sure, it's
definitely a good practice, and at the very least it lets you
disambiguate two users who have the same password. But these days,
salts alone can no longer save you from a person willing to spend a
few thousand dollars on video card hardware, and if you think they
can, you're in trouble.


汤姆·斯科特在他关于如何(不)在computerphile上存储密码的报道中说得对。

https://www.youtube.com/watch?V= 8ZTnCl x1Eq

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

  • 如果您必须存储密码,请不要遵循此处的任何指导。至少,在没有咨询适用于您选择的语言的最新和信誉良好的出版物的情况下,您也可以。

  • 这里当然有很多聪明人,甚至可能有一些很好的指导。但很有可能,当你读到这篇文章时,所有的答案(包括这篇)都已经过时了。

    存储密码的正确方法会随着时间的推移而改变。可能比一些人换内衣更频繁。

    尽管如此,这里有一些通用的指导,希望在一段时间内仍然有用。

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

    • 不要转换为全部大写/全部小写。
    • 不要删除空白。
    • 不要去掉不可接受的字符或字符串。
    • 不要更改文本编码。
    • 不要做任何字符或字符串替换。
    • 不要截断任何长度的密码。
  • 拒绝创建任何未经修改就无法存储的密码。加强上述内容。如果由于某种原因,您的密码存储机制无法正确处理某些字符、空白、字符串或密码长度,则返回一个错误并让用户知道系统的限制,以便他们可以使用适合自己的密码重试。为了获得更好的用户体验,请预先列出用户可以访问的这些限制。甚至不用担心,更不用说麻烦了,把列表藏起来不让攻击者看到——不管怎样,他们自己都能很容易地找到它。

  • 为每个帐户使用长的、随机的和唯一的盐。在存储中,任何两个帐户的密码都不应该看起来相同,即使密码实际上是相同的。
  • 使用设计用于密码的缓慢且加密的强哈希算法。MD5当然不在了。sha-1/sha-2不行。但我也不会告诉你在这里应该用什么。(见本帖第一个2项目符号。)
  • 尽可能多地重复。虽然你的系统可能有比整天散列密码更好的处理它的处理器周期的事情,但那些将要破解你的密码的人拥有的系统不是这样的。尽你所能让它们变得困难,而不是让它对你"太难"。
  • 最重要的是…

    不要只听这里任何人的话。

    请查阅一本声誉良好的最近出版的出版物,了解您选择的语言的正确密码存储方法。实际上,在确定一种方法之前,您应该从多个不同的来源找到多个最近的出版物,它们是一致的。

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


    这是您想要做的:

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

    您可以通过下面的类来实现这一点。此类是通用类,是客户端终结点。它支持使用ninject的各种加密算法的ioc。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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类,它允许您注入各种算法:

    1
    2
    3
    4
    5
    6
    7
    public class EncryptionModule : StandardModule
    {
        public override void Load()
        {
            Bind<IStringEncryptor>().To<TripleDESStringEncryptor>();
        }
    }

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

    1
    2
    3
    4
    5
    public interface IStringEncryptor
    {
        string EncryptString(string plainText);
        string DecryptString(string encryptedText);
    }

    这是使用Tripledes算法实现的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    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


    一个选项是存储密码的散列(sha1,md5),而不是明文密码,每当您想查看密码是否正确时,只需将其与散列进行比较。

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

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


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

    否则使用hashes sha1或md5,就像其他人在这里所说的那样。


    正如ligget78所说,dpapi是存储密码的好方法。查看msdn上的ProtectedData类,例如用法。


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

    不管怎么说,有很多原因可能有人想要解密一些文本值,包括密码。我需要在我目前工作的站点上解密密码的原因是,他们希望确保当有人在密码过期时被迫更改密码时,我们不会让他们使用与过去x个月中使用的相同密码的近似变体来更改密码。

    所以我写了一个过程,它将以一种简化的方式来完成这个任务。我希望这个代码对某人有益。据我所知,我可能会在另一个时间将其用于不同的公司/网站。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    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);

    如果它是应用程序用于身份验证的密码,那么按照其他人的建议散列该密码。

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


    如果您需要更多信息,例如保护连接字符串(用于连接到数据库),请检查本文,因为它提供了最好的"选项"。

    奥利的答案也很好,因为它显示了如何为字符串创建哈希。


    .NET在包含在System.Security.Cryptography命名空间。


    与其加密/解密,不如通过哈希算法MD5/SHA512或类似算法来传递密码。理想的做法是散列密码并存储散列,然后当需要密码时,散列条目并比较条目。密码将永远不会"解密",只需散列然后进行比较。