在设计一个系统的时候,肯定都有会有用户身份认证的问题,一般对用户校验的时候,都是对用户存在数据库总的密码哈希值进行判断,从而避免密码泄露和反向解密,那么在Python 开发中,我们可以引入bcrypt 或 Passlib 对系统用户密码进行哈希和验证处理,以及介绍使用其他类库实现常规加解密处理操作。本篇随笔主要介绍bcrypt 和 Passlib 它们之间的差异,以及在实际使用中的一些代码供参考。

1、
bcrypt

Passlib的介绍

bcrypt

Passlib
都是用于密码哈希和验证的 Python 库,但它们有一些显著的区别:

  • bcrypt
    :


    • bcrypt
      是一个专门用于实现
      bcrypt
      哈希算法的库。它相对简单,专注于单一功能,即对密码进行
      bcrypt
      哈希处理和验证。
    • 适合只需要
      bcrypt
      哈希算法的场景。
    • 提供的 API 简单直接,功能较少。
  • Passlib
    :


    • Passlib
      是一个更高级的密码哈希库,它支持多种哈希算法(如
      bcrypt

      PBKDF2

      Argon2
      等),并且提供了更丰富的功能。
    • 适合需要支持多种密码哈希算法和策略的场景。
    • 提供的
      CryptContext
      类可以方便地管理和迁移多个哈希算法。还提供了密码哈希的自动升级机制,以及对旧算法的弃用处理。

当你确定只需要使用
bcrypt
算法,并且不需要额外的复杂功能时,
bcrypt
是一个合适的选择。它适合简单的项目,或者在需要直接控制
salt
等参数的情况下使用。

Passlib 适合复杂的项目,尤其是需要支持多个哈希算法或需要迁移哈希算法的场景。适合需要长期维护的项目,因为它提供了更多的配置和安全功能。

bcrypt
:
灵活性较低,因为它只支持
bcrypt
算法。
没有多种哈希算法选择或密码策略管理功能。使用简单,代码更直观。如果你只需要
bcrypt
算法,
bcrypt
库可能更容易上手。

Passlib
:
提供了很高的灵活性和扩展性。可以根据需要切换和配置不同的哈希算法,管理复杂的密码策略。
通过
CryptContext
,可以轻松管理不同算法之间的过渡。功能强大但相对复杂,需要更深入的学习和理解。但它的高层 API 设计得很友好,一旦熟悉,可以简化很多常见任务。
CryptContext
是其中一个用于管理多个哈希算法和密码哈希策略的类。

示例代码对比:

bcrypt
使用示例:

importbcrypt

password
= b"supersecretpassword"hashed=bcrypt.hashpw(password, bcrypt.gensalt())#验证密码 ifbcrypt.checkpw(password, hashed):print("Password matches!")else:print("Password does not match.")

Passlib
使用示例:

from passlib.context importCryptContext#创建一个 CryptContext 对象
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")#哈希密码
password = "my_secret_password"hashed_password=pwd_context.hash(password)print("Hashed password:", hashed_password)#验证密码
is_correct =pwd_context.verify(password, hashed_password)ifis_correct:print("密码正确")else:print("密码错误")

定义了一个
CryptContext
对象,用于管理密码哈希算法。
schemes=["bcrypt"]
表示你要使用
bcrypt
算法,而
deprecated="auto"
表示自动管理过时的哈希方案。

使用
pwd_context.hash()
方法对密码进行哈希处理。每次生成的哈希值都是唯一的,即使是相同的密码也会生成不同的哈希值。

使用
pwd_context.verify()
方法可以验证给定的密码与存储的哈希值是否匹配。

你还可以在创建
CryptContext
对象时传递更多参数来定制密码哈希行为,这种方法可以增强密码存储的安全性。例如:

pwd_context =CryptContext(
schemes
=["bcrypt"],
bcrypt__rounds
=12 #bcrypt 的哈希轮数,默认为 12 )

2、使用指定的salt进行加密


Passlib
中,
bcrypt
算法默认会自动生成一个随机的
salt
,这也是
bcrypt
的一种安全特性。如果你想使用指定的
salt
进行加密,需要注意的是,
Passlib
并不直接支持通过指定
salt
来进行哈希处理,因为这可能会降低安全性。

不过,如果你确实需要使用指定的
salt
进行哈希处理,你可以使用以下的方式:

  1. 手动拼接
    salt
    和密码

    :可以手动拼接
    salt
    和密码,然后对结果进行哈希处理。但这种方法仅适用于了解风险并确保安全措施的场景。

  2. 使用
    bcrypt

    :直接使用
    bcrypt
    库进行处理,它允许你传递一个指定的
    salt
    。不过,注意这会有一定的安全风险。

1) 使用
bcrypt
库指定
salt

如果你确实需要指定
salt
,可以使用
bcrypt
库。

importbcrypt#指定的 salt(必须为 16 字节,前缀为 b"$2b$")
salt = bcrypt.gensalt(rounds=12)  #或者使用自定义的 16 字节 salt
print(f"Generated salt: {salt}")#要加密的密码
password = "my_secret_password"

#使用指定的 salt 进行加密
hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)print(f"Hashed password: {hashed_password}")

2) 手动拼接
salt
和密码

如果你使用
Passlib
,并想使用指定的
salt
,可以手动拼接
salt
和密码,然后对这个组合结果进行哈希处理。这个方式一般不建议使用,因为它破坏了
bcrypt
的安全设计原则。

from passlib.context importCryptContext#创建一个 CryptContext 对象
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")#自定义 salt
custom_salt = "my_custom_salt"

#拼接 salt 和密码
password = "my_secret_password"password_with_salt= custom_salt +password#对拼接后的字符串进行哈希处理
hashed_password =pwd_context.hash(password_with_salt)print("Hashed password with custom salt:", hashed_password)

注意事项

  • 使用固定的
    salt
    会降低密码哈希的安全性,因为相同的
    salt
    和相同的密码会生成相同的哈希值。
  • bcrypt
    的设计初衷是让每次生成的
    salt
    都不同,以此提高安全性。
  • 如果你需要在特定的场景下使用固定的
    salt
    ,一定要确保你的系统有足够的其他安全措施。

同一密码,每次获得的hash值都会不同,那么有些人会问,如果通过pwd_context.hash获得的hash值,下一次能够对比正确吗?

回答是的,使用
pwd_context.hash()
生成的哈希值可以在后续对比中正确匹配,即使每次生成的哈希值看起来不同。
Passlib

bcrypt
的设计确保了这一点。

  • 自动生成的
    salt

    :每次你使用
    pwd_context.hash()
    生成一个新的哈希值时,
    bcrypt
    都会自动生成一个随机的
    salt
    并将其嵌入到生成的哈希值中。因此,即使对同一个密码进行多次哈希,每次生成的哈希值也会不同。

  • 验证过程
    :在验证过程中,
    pwd_context.verify()
    会自动从存储的哈希值中提取
    salt
    并重新计算哈希,然后将其与提供的哈希值进行比较。这意味着,即使哈希值不同,验证仍然能够成功匹配。

即使你每次运行
pwd_context.hash(password)
得到的哈希值不同(因为
salt
不同),
pwd_context.verify(password, hashed_password)
仍然会返回
True
,表示密码验证成功。

3、加密和解密处理

Passlib
主要用于密码哈希处理,并不支持加密和解密操作。如果你需要对字符串进行加密和解密,或者使用非对称加密,你需要使用其他库,例如
cryptography

PyCryptodome

1)
对称加密和解密

对于对称加密,你可以使用
cryptography
库中的
Fernet
,它是基于 AES 算法的加密方案。

安装
cryptography

pip install cryptography

对称加密和解密示例

from cryptography.fernet importFernet#生成密钥(注意:密钥需要安全存储)
key =Fernet.generate_key()
cipher
=Fernet(key)#加密 message = "This is a secret message"encrypted_message=cipher.encrypt(message.encode())print("Encrypted:", encrypted_message)#解密 decrypted_message =cipher.decrypt(encrypted_message).decode()print("Decrypted:", decrypted_message)

2)
非对称加密和解密

对于非对称加密,你可以使用
cryptography
库中的
RSA
算法。通常,非对称加密用于加密较短的信息或加密对称密钥。

非对称加密和解密示例

from cryptography.hazmat.primitives.asymmetric importrsa, paddingfrom cryptography.hazmat.primitives importserialization, hashes#生成私钥和公钥
private_key =rsa.generate_private_key(
public_exponent
=65537,
key_size
=2048,
)

public_key
=private_key.public_key()#加密 message = b"This is a secret message"encrypted_message=public_key.encrypt(
message,
padding.OAEP(
mgf
=padding.MGF1(algorithm=hashes.SHA256()),
algorithm
=hashes.SHA256(),
label
=None
)
)
print("Encrypted:", encrypted_message)#解密 decrypted_message =private_key.decrypt(
encrypted_message,
padding.OAEP(
mgf
=padding.MGF1(algorithm=hashes.SHA256()),
algorithm
=hashes.SHA256(),
label
=None
)
)
print("Decrypted:", decrypted_message.decode())

3)保存和加载密钥

保存私钥
:

private_pem =private_key.private_bytes(
encoding
=serialization.Encoding.PEM,
format
=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm
=serialization.NoEncryption()
)
with open(
'private_key.pem', 'wb') as f:
f.write(private_pem)

加载私钥
:

with open('private_key.pem', 'rb') as f:
private_key
=serialization.load_pem_private_key(
f.read(),
password
=None,
)

保存公钥
:

public_pem =public_key.public_bytes(
encoding
=serialization.Encoding.PEM,
format
=serialization.PublicFormat.SubjectPublicKeyInfo
)
with open(
'public_key.pem', 'wb') as f:
f.write(public_pem)

加载公钥
:

with open('public_key.pem', 'rb') as f:
public_key
= serialization.load_pem_public_key(f.read())

我们在开发过程总,可以根据需求选择合适的加密方式和库,并妥善管理密钥。

标签: none

添加新评论