Web 开发中的盐、哈希和安全性
Salt, hashes and security in web development
Udacity.com 有一门名为 Web 开发的课程。
讲师向我们介绍了 cookie、如何签署 cookie、什么是盐以及我们必须始终在数据库中保留密码的哈希值。然后他给了我们作业,然后他发布了一个解决方案。
只有在这里我们才看到了全貌:它们应该如何协同工作。
下面是 Python 和 Google App Engine 的代码。但是,请不要害怕。该代码似乎像伪代码一样可读。 而且问题是笼统的,不是关于这个特殊技术的。
import hashlib
import hmac
secret = 'iamsosecret'
def make_secure_val(val):
return '%s|%s' % (val, hmac.new(secret, val).hexdigest())
def check_secure_val(secure_val):
val = secure_val.split('|')[0]
if secure_val == make_secure_val(val):
return val
def make_salt(length = 5):
return ''.join(random.choice(letters) for x in xrange(length))
def make_pw_hash(name, pw, salt = None):
if not salt:
salt = make_salt()
h = hashlib.sha256(name + pw + salt).hexdigest()
return '%s,%s' % (salt, h)
def valid_pw(name, password, h):
salt = h.split(',')[0]
return h == make_pw_hash(name, password, salt)
class User(db.Model):
name = db.StringProperty(required = True)
pw_hash = db.StringProperty(required = True)
email = db.StringProperty()
@classmethod
def register(cls, name, pw, email = None):
pw_hash = make_pw_hash(name, pw)
return User(parent = users_key(),
name = name,
pw_hash = pw_hash,
email = email)
@classmethod
def login(cls, name, pw):
u = cls.by_name(name)
if u and valid_pw(name, pw, u.pw_hash):
return u
class BlogHandler(webapp2.RequestHandler):
def set_secure_cookie(self, name, val):
cookie_val = make_secure_val(val)
self.response.headers.add_header(
'Set-Cookie',
'%s=%s; Path=/' % (name, cookie_val))
def read_secure_cookie(self, name):
cookie_val = self.request.cookies.get(name)
return cookie_val and check_secure_val(cookie_val)
def login(self, user):
self.set_secure_cookie('user_id', str(user.key().id()))
def logout(self):
self.response.headers.add_header('Set-Cookie', 'user_id=; Path=/')
def initialize(self, *a, **kw):
webapp2.RequestHandler.initialize(self, *a, **kw)
uid = self.read_secure_cookie('user_id')
self.user = uid and User.by_id(int(uid))
让我们仔细检查一下我们这里有什么。
- 函数make_secure_val产生如下内容:
'keyword|6f21001bbf8bfbd8c04b9d537df1e314'
- 函数make_secure_val用于函数set_secure_cookie。因此,它用于登录。
- 函数 make_secure_val 使用密钥,但不使用盐。
- 函数make_pw_hash(name, pw, salt = None) 生成如下内容:'some_salt,c984cade696390b71ff914293c53767502ea542bfd0e7240a051e7ead2c60077'
- 函数make_pw_hash 准备一个哈希密码,该密码将保存在数据库中。用户在表单中输入用户名和密码,然后表单中的数据成为此 make_pw_hash 函数的参数。
- 函数 make_pw_hash 使用盐但不使用密钥。
- 函数 make_pw_hash 由于某些原因将用户名混入哈希。
好吧,我在这里什么都不懂。我会说这一切都不安全。
问题:
为什么老师做饼干不用盐?
他为什么不用秘钥做hash把密码存入数据库?
为什么他混合了用户名来做哈希来把密码存入数据库?
我们可以看到两种生成哈希的方法:一种用于 cookie,另一种用于数据库。他们为什么分开?统一起来不是更实用:总是使用密钥和盐。更少的代码。并且安全性好。但是他分开了。为什么?
最重要的第一点:这种存储密码的方法不安全,不应使用或教授。问题是,SHA-256 的单次传递速度太快 (3 Giga SHA-256 per second),因此很容易被暴力破解。我们需要一个在 BCrypt、PBKDF2 或 SCrypt 中可用的成本因素。
对您的问题的一些回答:
- 加盐弱密码和强令牌是两种不同的东西。 short/weak 密码需要加盐和密钥拉伸,但对于强令牌可以省略。这要求参数
val
是一个强随机标记(最少 20 个字符 a..z、A..Z、0..9)。这不能通过显示的代码来判断,但应该是 set_secure_cookie()
. 文档的一部分
- 可以将服务器端机密添加到密码哈希中,但重要的是要了解您要缓解什么以及如何做。如果您有兴趣,请查看我的 tutorial 关于安全密码散列的内容。
- 混淆用户名不是散列密码的常用方法,它不会提高安全性但会阻止更改用户名。
- 见第一点。
Udacity.com 有一门名为 Web 开发的课程。 讲师向我们介绍了 cookie、如何签署 cookie、什么是盐以及我们必须始终在数据库中保留密码的哈希值。然后他给了我们作业,然后他发布了一个解决方案。 只有在这里我们才看到了全貌:它们应该如何协同工作。
下面是 Python 和 Google App Engine 的代码。但是,请不要害怕。该代码似乎像伪代码一样可读。 而且问题是笼统的,不是关于这个特殊技术的。
import hashlib
import hmac
secret = 'iamsosecret'
def make_secure_val(val):
return '%s|%s' % (val, hmac.new(secret, val).hexdigest())
def check_secure_val(secure_val):
val = secure_val.split('|')[0]
if secure_val == make_secure_val(val):
return val
def make_salt(length = 5):
return ''.join(random.choice(letters) for x in xrange(length))
def make_pw_hash(name, pw, salt = None):
if not salt:
salt = make_salt()
h = hashlib.sha256(name + pw + salt).hexdigest()
return '%s,%s' % (salt, h)
def valid_pw(name, password, h):
salt = h.split(',')[0]
return h == make_pw_hash(name, password, salt)
class User(db.Model):
name = db.StringProperty(required = True)
pw_hash = db.StringProperty(required = True)
email = db.StringProperty()
@classmethod
def register(cls, name, pw, email = None):
pw_hash = make_pw_hash(name, pw)
return User(parent = users_key(),
name = name,
pw_hash = pw_hash,
email = email)
@classmethod
def login(cls, name, pw):
u = cls.by_name(name)
if u and valid_pw(name, pw, u.pw_hash):
return u
class BlogHandler(webapp2.RequestHandler):
def set_secure_cookie(self, name, val):
cookie_val = make_secure_val(val)
self.response.headers.add_header(
'Set-Cookie',
'%s=%s; Path=/' % (name, cookie_val))
def read_secure_cookie(self, name):
cookie_val = self.request.cookies.get(name)
return cookie_val and check_secure_val(cookie_val)
def login(self, user):
self.set_secure_cookie('user_id', str(user.key().id()))
def logout(self):
self.response.headers.add_header('Set-Cookie', 'user_id=; Path=/')
def initialize(self, *a, **kw):
webapp2.RequestHandler.initialize(self, *a, **kw)
uid = self.read_secure_cookie('user_id')
self.user = uid and User.by_id(int(uid))
让我们仔细检查一下我们这里有什么。
- 函数make_secure_val产生如下内容: 'keyword|6f21001bbf8bfbd8c04b9d537df1e314'
- 函数make_secure_val用于函数set_secure_cookie。因此,它用于登录。
- 函数 make_secure_val 使用密钥,但不使用盐。
- 函数make_pw_hash(name, pw, salt = None) 生成如下内容:'some_salt,c984cade696390b71ff914293c53767502ea542bfd0e7240a051e7ead2c60077'
- 函数make_pw_hash 准备一个哈希密码,该密码将保存在数据库中。用户在表单中输入用户名和密码,然后表单中的数据成为此 make_pw_hash 函数的参数。
- 函数 make_pw_hash 使用盐但不使用密钥。
- 函数 make_pw_hash 由于某些原因将用户名混入哈希。
好吧,我在这里什么都不懂。我会说这一切都不安全。
问题:
为什么老师做饼干不用盐?
他为什么不用秘钥做hash把密码存入数据库?
为什么他混合了用户名来做哈希来把密码存入数据库?
我们可以看到两种生成哈希的方法:一种用于 cookie,另一种用于数据库。他们为什么分开?统一起来不是更实用:总是使用密钥和盐。更少的代码。并且安全性好。但是他分开了。为什么?
最重要的第一点:这种存储密码的方法不安全,不应使用或教授。问题是,SHA-256 的单次传递速度太快 (3 Giga SHA-256 per second),因此很容易被暴力破解。我们需要一个在 BCrypt、PBKDF2 或 SCrypt 中可用的成本因素。
对您的问题的一些回答:
- 加盐弱密码和强令牌是两种不同的东西。 short/weak 密码需要加盐和密钥拉伸,但对于强令牌可以省略。这要求参数
val
是一个强随机标记(最少 20 个字符 a..z、A..Z、0..9)。这不能通过显示的代码来判断,但应该是set_secure_cookie()
. 文档的一部分
- 可以将服务器端机密添加到密码哈希中,但重要的是要了解您要缓解什么以及如何做。如果您有兴趣,请查看我的 tutorial 关于安全密码散列的内容。
- 混淆用户名不是散列密码的常用方法,它不会提高安全性但会阻止更改用户名。
- 见第一点。