我有以下代码片段:
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
pwd_context.hash(password)
这是描述here。
我不明白的是,如果它一直返回相同的散列密码而不考虑另一个 secret_key 例如散列密码值,这怎么能保证安全?
您假设它一直返回相同的散列密码而不考虑另一个“秘密”(好吧,这不是真正的秘密)是错误的;如果你运行pwd_context.hash
多次,你会看到这个:
>>> from passlib.context import CryptContext
>>>
>>> pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
>>> pwd_context.hash("test")
'$2b$12$0qdOrAMoK7dgySjmNbyRpOggbk.IM2vffMh8rFoITorRKabyFiElC'
>>> pwd_context.hash("test")
'$2b$12$gqaNzwTmjAQbGW/08zs4guq1xWD/g7JkWtKqE2BWo6nU1TyP37Feq'
如您所见,这两个哈希值并不相同——即使给出相同的密码也是如此。那么究竟发生了什么?
当您不提供hash
明确的盐(您正在谈论的秘密“密钥”)时,将由passlib
. 值得指出的是,散列与加密不同,因此没有可谈的密钥。相反,您会看到salt
提到的,这是一个明文值,用于确保两次散列的相同密码会产生不同的结果(因为您实际上是在进行散列salt + password
)。
那么为什么我们会得到两个不同的值呢?这salt
是实际 bcrypt 值的前 22 个字符。这些字段由$
-2b
表示 bcrypt 分隔,12
表示 12 轮,下一个字符串是为密码存储的实际结果值(salt+resulting bcrypt hash)。该字符串的前 22 个字符是哈希。
如果你给 bcrypt 一个盐而不是让它生成一个盐,你可以看到这一点(我们将在这里忽略关于小填充的可能警告,但为了展示这个概念):
>>> pwd_context.hash("test", salt="a"*22)
'$2b$12$aaaaaaaaaaaaaaaaaaaaaOm/4kNFO.mb908CDiMw1TgDxyZeDSwum'
^-------------------^
如果我们明确给出相同的哈希值,结果应该是相同的(以及稍后验证密码的方式):
>>> pwd_context.hash("test", salt="a"*22)
'$2b$12$aaaaaaaaaaaaaaaaaaaaaOm/4kNFO.mb908CDiMw1TgDxyZeDSwum'
>>> pwd_context.hash("test", salt="a"*22)
'$2b$12$aaaaaaaaaaaaaaaaaaaaaOm/4kNFO.mb908CDiMw1TgDxyZeDSwum'
前面的哈希也是如此:
>>> pwd_context.hash("test")
'$2b$12$gqaNzwTmjAQbGW/08zs4guq1xWD/g7JkWtKqE2BWo6nU1TyP37Feq'
^-------------------^
这是实际生成的盐,然后与它一起test
用于创建实际的散列:
>>> pwd_context.hash("test")
'$2b$12$gqaNzwTmjAQbGW/08zs4guq1xWD/g7JkWtKqE2BWo6nU1TyP37Feq'
^------------------------------^
那么,当它对每个人都清晰可见时,我们为什么要使用这种盐呢?不可能只扫描已知哈希的哈希列表 - 因为test
您的列表中的值与test
您要比较的列表中的值不同(因为不同的盐),您必须实际测试猜测的密码及其盐,并通过散列算法运行它们。bcrypt
被明确设计为使该过程需要时间,因此您将花费更长的时间来尝试破解密码,而不仅仅是扫描 2 亿个密码的列表并在数据库中搜索已知的哈希值。
那么当计算机变得更快时你会怎么做?您增加12
参数 - 这rounds
- 这会增加散列算法的运行时间,希望更安全地保持更长时间(您可以试验rounds
参数 to passlib.hash
)。