6

tl;dr -- 在使用 SQLAlchemy 将密码插入 MySQL 数据库之前,如何使用 Python 端库(如 PassLib)对密码进行哈希处理?

好吧,所以我一直在我的桌子上敲了一两天试图弄清楚这一点,所以它是这样的:

我正在使用 Pyramid/SQLAlchemy 编写一个 Web 应用程序,并且我正在尝试与我的 MySQL 数据库的用户表交互。

最终,我想做以下事情:

将密码与哈希进行比较:

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

这是从http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DatabaseCrypt的示例 2 复制而来的

这样就可以了,并允许我使用内置的 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 的不同形式,要么是自定义类型声明,要么是混合值,要么是混合属性。我尝试遵循这个,但这对我来说并没有真正意义,也没有实际运行建议的代码。

所以,总结一下我的问题——我如何重载=and==运算符,以便它们通过适当的散列函数运行?

4

2 回答 2

6

来自sqlalchemy-utils的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
于 2015-09-28T14:43:23.917 回答
3

据我了解,您想要的是:

  1. 创建帐户时加密用户的密码。使用你的盐和算法
  2. 当用户登录时,以与存储密码时相同的方式对传入密码进行哈希处理
  3. 在您的数据库请求中使用常规字符串比较来比较两个哈希值

所以,登录代码是这样的:

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
于 2013-02-20T04:35:31.693 回答