分享
 
 
 

存储密码——要做对

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

存储密码——要做对!

作者: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<PasswordLength;i++)

{

chars[i] = _allowedChars[(int)randomBytes[i] % allowedCharCount];

}

return new string(chars);

}

这里的原理和文章Generating a secure password (德文) 中的ASP解决方法是类似的,不过我们在这里添加了些特别之处:我们用加密的安全随机数来从“数组”_allowedChars中挑选密码的字符。类RNGCryptoServiceProvider在文章Unbreakable Encryption Using One Time Pads (英文) 中讨论过。

这样,我们生成了可用作用户初始密码的真正随机密码——现在只需要一个salt了。

我们创建一个salt

public static int CreateRandomSalt()

{

Byte[] _saltBytes = new Byte[4];

RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

rng.GetBytes(_saltBytes);

return ((((int)_saltBytes[0]) << 24) + (((int)_saltBytes[1]) << 16) +

(((int)_saltBytes[2]) << 8) + ((int)_saltBytes[3]));

}

创建了一个四字节的salt(一个整数,以方便在数据库表中的存储)。这个salt和生成的密码组成了计算salted hash的基础。

计算Salted Hash

对Salted Hash的计算是操作两个由构造器设置的成员变量的一个实例方法。

public class Password

{

private string _password;

private int _salt;

public Password(string strPassword, int nSalt)

{

_password = strPassword;

_salt = nSalt;

}

这样,方法ComputeSaltedHash只返回salted hash并不接受任何参数。计算哈希值是利用有名的SHAI算法。

public string ComputeSaltedHash()

{

// Create Byte array of password string

ASCIIEncoding encoder = new ASCIIEncoding();

Byte[] _secretBytes = encoder.GetBytes(_password);

// Create a new salt

Byte[] _saltBytes = new Byte[4];

_saltBytes[0] = (byte)(_salt >> 24);

_saltBytes[1] = (byte)(_salt >> 16);

_saltBytes[2] = (byte)(_salt >> 8);

_saltBytes[3] = (byte)(_salt);

// append the two arrays

Byte[] toHash = new Byte[_secretBytes.Length + _saltBytes.Length];

Array.Copy(_secretBytes, 0, toHash, 0, _secretBytes.Length);

Array.Copy(_saltBytes, 0, toHash, _secretBytes.Length, _saltBytes.Length);

SHA1 sha1 = SHA1.Create();

Byte[] computedHash = sha1.ComputeHash(toHash);

return encoder.GetString(computedHash);

}

现在我们有了所有需要的函数,接着来用这个类。

日常使用的类Password

我创建了一个小例子来演示一个新密码的创建、一个新salt和最终的salted hash。

using System;

using DotNetGermanUtils;

namespace HashPassword

{

class TestApplication

{

[STAThread]

static void Main(string[] args)

{

// Generate a new random password string

string myPassword = Password.CreateRandomPassword(8);

// Debug output

Console.WriteLine(myPassword);

// Generate a new random salt

int mySalt = Password.CreateRandomSalt();

// Initialize the Password class with the password and salt

Password pwd = new Password(myPassword, mySalt);

// Compute the salted hash

// NOTE: you store the salt and the salted hash in the datbase

string strHashedPassword = pwd.ComputeSaltedHash();

// Debug output

Console.WriteLine(strHashedPassword);

}

}

}

以下几点非常重要:为每个用户生成一个新的salt。若两个用户凑巧选择了同一个密码,两个账户的salted hash仍然会是不同的。

源代码显示了新密码和salt的创建,当用户试图登录时,如下:

// retrieve salted hash and salt from user database, based on username

...

Password pwd = new Password(txtPassword.Text, nSaltFromDatabase);

if (pwd.ComputeSaltedHash() == strStoredSaltedHash)

{

// user is authenticated successfully

}

else

{

...

基本上这和普通的用户名/密码实现没什么分别,但即使服务器端的密码数据落入未经授权的第三方手中,数据也更为安全。

结论

我们这里演示的类可以加入你自己的.NET项目中——直接在C#项目中或作为其他编程语言的一个程序集。从现在开始,不安全的密码储存再没有其他借口了。

下载代码

点击这里开始下载。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有