存储密码——要做对

存储密码——要做对 !

作者: Christoph Wille

英文翻译: Bernhard Spuida

日期: 2004/01/05

在很多(如果不说几乎所有的话) Web 应用(从 Web 论坛到 Web 商店)中,管理着用户数据。这些用户数据包括除用户名外还含有密码的用户登录信息,并且是纯文本的。一个安全漏洞!

为什么存储用户名和密码为纯文本是一个安全漏洞呢?那么,想象一个骇客通过 OS 或服务器软件错误获得了系统访问权,并能够看到用户数据库。现在他就知道了任何用户的名字和密码,他可以登录为一个“真正”的用户并用那个用户的权限为所欲为,在 Web 商店订货到在论坛上“角色暗杀”( character assassination )。而你是管理员……

怎么消除这个安全威胁呢?数十年前就有一个被证明安全有效的方法来存放密码:在 UNIX 中,用户密码被存储为所谓的“ salted hashes ”。

** 何谓“ ** ** salted hashes ** ** ” ** ** **

哈希是一个不等的标识任意长度文件的定长数值。哈希算法的一个例子是 SHAI 。读者现在可能想说将密码存放为一个哈希值是不够的,但为什么呢?

这是因为总是有所谓的“字典攻击”来破坏哈希化密码( Hashed password )。一个很好的例子就是 NT4 的 MD5 哈希化密码。这是一种暴力攻击:字典中的所有项目用 MD5 哈希计算后将结果与密码数据库比对。猜猜这样的话密码有多快被发现?

Salted Hash 的目的就是在每个密码后面添加随机值(称为 salt ),然后才计算密码和 salt 的哈希值,以此防范上述类型的攻击。为了比对密码, salt 必须和 salted hash 存放在一起。但只有攻击的 vector 才会为每一个保存的密码和 salt 对字典重新编码,这可要消耗大量时间。

如前所述,我们现在要存放三个域——用户名、 salt 和密码的 salted hash ,而不是用户名和密码了。我还提到当这些数据落到骇客手中时,他将无法用典型的攻击方法,而很可能转向其他更容易攻击点的受害者了。

有一点要记住:现在无法发送“密码提醒”的电邮了——能做的只是为用户生成并发送一个新的密码。由于这里发生过很多错误,我们从生成真正随机密码的 .NET 代码开始。

** 生成密码——要做对! ** ** **

整个类是在一个 (C# ASP.NET) 社区项目中和另一位 AspHeute 作者 Alexander Zeitler 共同创建的。这个例子同样碰到了如何生成好密码并正确存储的问题。

为这个目的,我们创建了类 Password ,并有以下方法。

namespace DotNetGermanUtils


{


  public class Password


  {


    public Password(string strPassword, int nSalt)


 


    public static string CreateRandomPassword(int PasswordLength)


 


    public static int CreateRandomSalt()


 


    public string ComputeSaltedHash()


  }


}

生成一个新密码的方法是静态的,我们可以设定生成的密码长度。

    public static string CreateRandomPassword(int PasswordLength)


    {


      String _allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789";


      Byte[] randomBytes = new Byte[PasswordLength];


      RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();


      rng.GetBytes(randomBytes);


      char[] chars = new char[PasswordLength];


      int allowedCharCount = _allowedChars.Length;


 


      for(int i = 0;i
  1<passwordlength;i++) %="" (="" ((((int)_saltbytes[0])="" (((int)_saltbytes[1])="" (((int)_saltbytes[2])="" ((int)_saltbytes[3]));="" )="" **="" +="" 16)="" 24)="" 8)="" <<="" _allowedchars="" _password="strPassword;" _password;="" _salt="nSalt;" _salt;="" _saltbytes="new" _saltbytes[0]="(byte)(_salt" _secretbytes="encoder.GetBytes(_password);" a="" allowedcharcount];="" array="" asciiencoding="" asciiencoding();="" asp="" byte="" byte[4];="" byte[]="" chars[i]="_allowedChars[(int)randomBytes[i]" class="" computesaltedhash="" computesaltedhash()="" create="" createrandomsalt()="" encoder="new" encryption="" generating="" hash="" int="" new="" nsalt)="" of="" one="" pads="" password="" password(string="" private="" public="" return="" rng="new" rng.getbytes(_saltbytes);="" rngcryptoserviceprovider="" rngcryptoserviceprovider();="" salt="" salted="" secure="" shai="" static="" string="" string(chars);="" strpassword,="" time="" unbreakable="" using="" {="" }="" 中挑选密码的字符。类="" 中的="" 中讨论过。="" 了。="" 创建了一个四字节的="" 只返回="" 和生成的密码组成了计算="" 在文章="" 对="" 并不接受任何参数。计算哈希值是利用有名的="" 德="" 我们创建一个="" 文="" 的基础。="" 的计算是操作两个由构造器设置的成员变量的一个实例方法。="" 算法。="" 英文="" 解决方法是类似的,不过我们在这里添加了些特别之处:我们用加密的安全随机数来从“数组”="" 计算="" 这样,我们生成了可用作用户初始密码的真正随机密码——现在只需要一个="" 这样,方法="" 这里的原理和文章="" (一个整数,以方便在数据库表中的存储)。这个="">&gt; 24);
  2    
  3    
  4          _saltBytes[1] = (byte)(_salt &gt;&gt; 16);
  5    
  6    
  7          _saltBytes[2] = (byte)(_salt &gt;&gt; 8);
  8    
  9    
 10          _saltBytes[3] = (byte)(_salt);
 11    
 12    
 13     
 14    
 15    
 16          // append the two arrays
 17    
 18    
 19          Byte[] toHash = new Byte[_secretBytes.Length + _saltBytes.Length];
 20    
 21    
 22          Array.Copy(_secretBytes, 0, toHash, 0, _secretBytes.Length);
 23    
 24    
 25          Array.Copy(_saltBytes, 0, toHash, _secretBytes.Length, _saltBytes.Length);
 26    
 27    
 28     
 29    
 30    
 31          SHA1 sha1 = SHA1.Create();
 32    
 33    
 34          Byte[] computedHash = sha1.ComputeHash(toHash);
 35    
 36    
 37     
 38    
 39    
 40          return encoder.GetString(computedHash);
 41    
 42    
 43        }
 44
 45现在我们有了所有需要的函数,接着来用这个类。 
 46
 47** 日常使用的类  ** ** Password  **
 48
 49我创建了一个小例子来演示一个新密码的创建、一个新  salt  和最终的  salted hash  。 
 50    
 51    
 52    using System;
 53    
 54    
 55    using DotNetGermanUtils;
 56    
 57    
 58     
 59    
 60    
 61    namespace HashPassword
 62    
 63    
 64    {
 65    
 66    
 67      class TestApplication
 68    
 69    
 70      {
 71    
 72    
 73        [STAThread]
 74    
 75    
 76        static void Main(string[] args)
 77    
 78    
 79        {
 80    
 81    
 82          // Generate a new random password string
 83    
 84    
 85          string myPassword = Password.CreateRandomPassword(8);
 86    
 87    
 88     
 89    
 90    
 91          // Debug output
 92    
 93    
 94          Console.WriteLine(myPassword);
 95    
 96    
 97     
 98    
 99    
100          // Generate a new random salt
101    
102    
103          int mySalt = Password.CreateRandomSalt();
104    
105    
106     
107    
108    
109          // Initialize the Password class with the password and salt
110    
111    
112          Password pwd = new Password(myPassword, mySalt);
113    
114    
115     
116    
117    
118          // Compute the salted hash
119    
120    
121          // NOTE: you store the salt and the salted hash in the datbase
122    
123    
124          string strHashedPassword = pwd.ComputeSaltedHash();
125    
126    
127     
128    
129    
130          // Debug output
131    
132    
133          Console.WriteLine(strHashedPassword);
134    
135    
136        }
137    
138    
139      }
140    
141    
142    }
143
144以下几点非常重要:为每个用户生成一个新的  salt  。若两个用户凑巧选择了同一个密码,两个账户的  salted hash  仍然会是不同的。 
145
146源代码显示了新密码和  salt  的创建,当用户试图登录时,如下: 
147    
148    
149    // retrieve salted hash and salt from user database, based on username
150    
151    
152    ...
153    
154    
155     
156    
157    
158    Password pwd = new Password(txtPassword.Text, nSaltFromDatabase);
159    
160    
161     
162    
163    
164    if (pwd.ComputeSaltedHash() == strStoredSaltedHash)
165    
166    
167    {
168    
169    
170       // user is authenticated successfully
171    
172    
173    }
174    
175    
176    else
177    
178    
179    {
180    
181    
182    ...
183
184基本上这和普通的用户名  /  密码实现没什么分别,但即使服务器端的密码数据落入未经授权的第三方手中,数据也更为安全。 
185
186结论 
187
188我们这里演示的类可以加入你自己的  .NET  项目中——直接在  C#  项目中或作为其他编程语言的一个程序集。从现在开始,不安全的密码储存再没有其他借口了。 
189
190下载代码 
191
192点击  这里  开始下载。</passwordlength;i++)>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus