tl;dr——在使用SQLAlchemy将密码插入MySQL数据库之前,我如何使用Python端库(如PassLib)对密码进行散列
好吧,所以我已经在桌子上敲了一两天的头,试图弄清楚这一点,所以它来了:
我正在使用Pyramid/SQLAlchemy编写一个web应用程序,并尝试与MySQL数据库的Users表接口。
最终,我想做以下事情:
将密码与哈希进行比较:
if user1.password == 'supersecret'
插入新密码:
user2.password = 'supersecret'
我希望能够在密码进入数据库之前使用PassLib对其进行哈希处理,而且我并不喜欢使用内置的MySQL SHA2函数,因为它没有加盐。
然而,为了尝试一下,我确实使用了SQL端函数:
from sqlalchemy import func, TypeDecorator, type_coerce
from sqlalchemy.dialects.mysql import CHAR, VARCHAR, INTEGER
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column
class SHA2Password(TypeDecorator):
"""Applies the SHA2 function to incoming passwords."""
impl = CHAR(64)
def bind_expression(self, bindvalue):
return func.sha2(bindvalue, 256)
class comparator_factory(CHAR.comparator_factory):
def __eq__(self, other):
local_pw = type_coerce(self.expr, CHAR)
return local_pw == func.sha2(other, 256)
class User(Base):
__tablename__ = 'Users'
_id = Column('userID', INTEGER(unsigned=True), primary_key=True)
username = Column(VARCHAR(length=64))
password = Column(SHA2Password(length=64))
def __init__(self, username, password):
self.username = username
self.password = password
这是从示例2中复制的http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DatabaseCrypt
因此,这很有效,并允许我使用内置的MySQL SHA2函数(通过调用func.sha2()
),并完全按照我的意愿进行操作。然而,现在我正试图用Python端的PassLib来替换它。
PassLib提供了两个函数:一个用于创建新的密码哈希,另一个用于验证密码:
from passlib.hash import sha256_crypt
new_password = sha256_crypt.encrypt("supersecret")
sha256_crypt.verify("supersecret", new_password)
我不太清楚如何真正实现这一点。在阅读了所有文档后,我认为它要么是不同形式的TypeDecorator,要么是自定义类型声明,要么是混合值,要么是一个混合属性。我试着遵循这一点,但这对我来说并没有什么意义,建议的代码也没有真正运行。
所以,总结一下我的问题——我如何重载=
和==
运算符,使它们通过适当的哈希函数运行?
PasswordType应该最适合这个问题。它使用passlib。从文档中截取:
以下用法将创建一个密码列,该列将自动将新密码散列为pbkdf2_sha512,但仍将密码与预先存在的md5_crypt散列进行比较。当密码被比较时;数据库中的密码散列将被更新为pbkdf2_sha512。
class Model(Base):
password = sa.Column(PasswordType(
schemes=[
'pbkdf2_sha512',
'md5_crypt'
],
deprecated=['md5_crypt']
))
验证密码就像一样简单
target = Model()
target.password = 'b'
# '$5$rounds=80000$H.............'
target.password == 'b'
# True
据我所知,您想要的是:
- 创建帐户时加密用户的密码。使用你的盐和算法
- 当用户登录时,按照存储传入密码时的方式对其进行散列
- 在数据库请求中使用常规字符串比较来比较这两个哈希
所以,登录代码是这样的:
from passlib.hash import sha256_crypt
passHash = sha256_crypt.encrypt(typed_password)
// call your sqlalchemy code to query the db with this value (below)
// In your SQLAlchemy code assuming "users" is your users table
// and "password" is your password field
s = users.select(and_(users.username == typed_username, users.password == passHash))
rs = s.execute()
rs将是匹配用户的结果集(当然应该是零或一)。
免责声明-我没有测试任何这个
编辑:感谢您指出PassLib每次运行时使用不同的盐。在这种情况下,由于似乎没有一种简单的方法来使用sqlalchemy,您最好的选择是:
s=users.select(users.username == typed_username)
rs = s.execute()
userRow = rs.fetchone()
if (sha256_crypt.verify(userRow.password)):
# you have a match
此外,为了满足您的抽象请求:处理此操作的常见方法是创建一个"安全"实用程序类,用于获取与传递的登录凭据匹配的用户(对象)。
当前设置的问题是,User构造函数有两个不同的操作目标,虽然相关,但不一定是同一回事:验证用户和获取User对象(例如,组中的用户列表)。在这种情况下,构造函数变得不必要地复杂。最好将该逻辑与其他安全或登录相关功能封装在一起,例如通过会话ID或SSO令牌而不是用户名/密码登录用户:
security.loginUser(username, password)
# or security.loginUser(single_sign_on_token), etc. for polymorphic Security
loggedInUser = security.getLoggedInUser()
... later ...
otherUser = User(username) #single job, simple, clean