跳转到主要内容

SCRAM协议的实现。

项目描述

Scramp

SCRAM身份验证协议的Python实现。Scramp支持以下机制

  • SCRAM-SHA-1
  • SCRAM-SHA-1-PLUS
  • SCRAM-SHA-256
  • SCRAM-SHA-256-PLUS
  • SCRAM-SHA-512
  • SCRAM-SHA-512-PLUS
  • SCRAM-SHA3-512
  • SCRAM-SHA3-512-PLUS

安装

  • 创建虚拟环境: python3 -m venv venv
  • 激活虚拟环境: source venv/bin/activate
  • 安装: pip install scramp

示例

客户端和服务器

以下是一个同时使用客户端和服务器端的示例。这有点牵强,因为通常你会单独使用客户端或服务器。

>>> from scramp import ScramClient, ScramMechanism
>>>
>>> USERNAME = 'user'
>>> PASSWORD = 'pencil'
>>> MECHANISMS = ['SCRAM-SHA-256']
>>>
>>>
>>> # Choose a mechanism for our server
>>> m = ScramMechanism()  # Default is SCRAM-SHA-256
>>>
>>> # On the server side we create the authentication information for each user
>>> # and store it in an authentication database. We'll use a dict:
>>> db = {}
>>>
>>> salt, stored_key, server_key, iteration_count = m.make_auth_info(PASSWORD)
>>>
>>> db[USERNAME] = salt, stored_key, server_key, iteration_count
>>>
>>> # Define your own function for retrieving the authentication information
>>> # from the database given a username
>>>
>>> def auth_fn(username):
...     return db[username]
>>>
>>> # Make the SCRAM server
>>> s = m.make_server(auth_fn)
>>>
>>> # Now set up the client and carry out authentication with the server
>>> c = ScramClient(MECHANISMS, USERNAME, PASSWORD)
>>> cfirst = c.get_client_first()
>>>
>>> s.set_client_first(cfirst)
>>> sfirst = s.get_server_first()
>>>
>>> c.set_server_first(sfirst)
>>> cfinal = c.get_client_final()
>>>
>>> s.set_client_final(cfinal)
>>> sfinal = s.get_server_final()
>>>
>>> c.set_server_final(sfinal)
>>>
>>> # If it all runs through without raising an exception, the authentication
>>> # has succeeded

仅客户端

以下是一个仅使用客户端的示例。客户端随机数被指定以提供一个可重复的示例,但在生产中,你会省略 c_nonce 参数,并让 ScramClient 生成客户端随机数。

>>> from scramp import ScramClient
>>>
>>> USERNAME = 'user'
>>> PASSWORD = 'pencil'
>>> C_NONCE = 'rOprNGfwEbeRWgbNEkqO'
>>> MECHANISMS = ['SCRAM-SHA-256']
>>>
>>> # Normally the c_nonce would be omitted, in which case ScramClient will
>>> # generate the nonce itself.
>>>
>>> c = ScramClient(MECHANISMS, USERNAME, PASSWORD, c_nonce=C_NONCE)
>>>
>>> # Get the client first message and send it to the server
>>> cfirst = c.get_client_first()
>>> print(cfirst)
n,,n=user,r=rOprNGfwEbeRWgbNEkqO
>>>
>>> # Set the first message from the server
>>> c.set_server_first(
...     'r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,'
...     's=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096')
>>>
>>> # Get the client final message and send it to the server
>>> cfinal = c.get_client_final()
>>> print(cfinal)
c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=
>>>
>>> # Set the final message from the server
>>> c.set_server_final('v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=')
>>>
>>> # If it all runs through without raising an exception, the authentication
>>> # has succeeded

仅服务器

以下是一个仅使用服务器的示例。服务器随机数和盐被指定以提供一个可重复的示例,但在生产中,你会省略 s_noncesalt 参数,并让 Scramp 生成它们。

>>> from scramp import ScramMechanism
>>>
>>> USERNAME = 'user'
>>> PASSWORD = 'pencil'
>>> S_NONCE = '%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0'
>>> SALT = b'[m\x99h\x9d\x125\x8e\xec\xa0K\x14\x126\xfa\x81'
>>>
>>> db = {}
>>>
>>> m = ScramMechanism()
>>>
>>> salt, stored_key, server_key, iteration_count = m.make_auth_info(
...     PASSWORD, salt=SALT)
>>>
>>> db[USERNAME] = salt, stored_key, server_key, iteration_count
>>>
>>> # Define your own function for getting a password given a username
>>> def auth_fn(username):
...     return db[username]
>>>
>>> # Normally the s_nonce parameter would be omitted, in which case the
>>> # server will generate the nonce itself.
>>>
>>> s = m.make_server(auth_fn, s_nonce=S_NONCE)
>>>
>>> # Set the first message from the client
>>> s.set_client_first('n,,n=user,r=rOprNGfwEbeRWgbNEkqO')
>>>
>>> # Get the first server message, and send it to the client
>>> sfirst = s.get_server_first()
>>> print(sfirst)
r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096
>>>
>>> # Set the final message from the client
>>> s.set_client_final(
...     'c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,'
...     'p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=')
>>>
>>> # Get the final server message and send it to the client
>>> sfinal = s.get_server_final()
>>> print(sfinal)
v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=
>>>
>>> # If it all runs through without raising an exception, the authentication
>>> # has succeeded

仅服务器与passlib

以下是一个仅使用服务器和使用passlib哈希库的示例。服务器nonce和salt被指定以便给出可复现的示例,但在生产环境中,您将省略s_noncesalt参数,并让Scramp生成它们

>>> from scramp import ScramMechanism
>>> from passlib.hash import scram
>>>
>>> USERNAME = 'user'
>>> PASSWORD = 'pencil'
>>> S_NONCE = '%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0'
>>> SALT = b'[m\x99h\x9d\x125\x8e\xec\xa0K\x14\x126\xfa\x81'
>>> ITERATION_COUNT = 4096
>>>
>>> db = {}
>>> hash = scram.using(salt=SALT, rounds=ITERATION_COUNT).hash(PASSWORD)
>>>
>>> salt, iteration_count, digest = scram.extract_digest_info(hash, 'sha-256')
>>> 
>>> stored_key, server_key = m.make_stored_server_keys(digest)
>>>
>>> db[USERNAME] = salt, stored_key, server_key, iteration_count
>>>
>>> # Define your own function for getting a password given a username
>>> def auth_fn(username):
...     return db[username]
>>>
>>> # Normally the s_nonce parameter would be omitted, in which case the
>>> # server will generate the nonce itself.
>>>
>>> m = ScramMechanism()
>>> s = m.make_server(auth_fn, s_nonce=S_NONCE)
>>>
>>> # Set the first message from the client
>>> s.set_client_first('n,,n=user,r=rOprNGfwEbeRWgbNEkqO')
>>>
>>> # Get the first server message, and send it to the client
>>> sfirst = s.get_server_first()
>>> print(sfirst)
r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096
>>>
>>> # Set the final message from the client
>>> s.set_client_final(
...     'c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,'
...     'p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=')
>>>
>>> # Get the final server message and send it to the client
>>> sfinal = s.get_server_final()
>>> print(sfinal)
v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=
>>>
>>> # If it all runs through without raising an exception, the authentication
>>> # has succeeded

服务器错误

以下是一个从客户端设置消息导致错误的情况的示例。服务器nonce和salt被指定以便给出可复现的示例,但在生产环境中,您将省略s_noncesalt参数,并让Scramp生成它们

>>> from scramp import ScramException, ScramMechanism
>>>
>>> USERNAME = 'user'
>>> PASSWORD = 'pencil'
>>> S_NONCE = '%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0'
>>> SALT = b'[m\x99h\x9d\x125\x8e\xec\xa0K\x14\x126\xfa\x81'
>>>
>>> db = {}
>>>
>>> m = ScramMechanism()
>>>
>>> salt, stored_key, server_key, iteration_count = m.make_auth_info(
...     PASSWORD, salt=SALT)
>>>
>>> db[USERNAME] = salt, stored_key, server_key, iteration_count
>>>
>>> # Define your own function for getting a password given a username
>>> def auth_fn(username):
...     return db[username]
>>>
>>> # Normally the s_nonce parameter would be omitted, in which case the
>>> # server will generate the nonce itself.
>>>
>>> s = m.make_server(auth_fn, s_nonce=S_NONCE)
>>>
>>> try:
...     # Set the first message from the client
...     s.set_client_first('p=tls-unique,,n=user,r=rOprNGfwEbeRWgbNEkqO')
... except ScramException as e:
...     print(e)
...     # Get the final server message and send it to the client
...     sfinal = s.get_server_final()
...     print(sfinal)
Received GS2 flag 'p' which indicates that the client requires channel binding, but the server does not: channel-binding-not-supported
e=channel-binding-not-supported

标准

API文档

scramp.MECHANISMS

支持机制名称的元组。

scramp.ScramClient

ScramClient(mechanisms, username, password, channel_binding=None, c_nonce=None)

scramp.ScramClient类的构造函数,以下参数

  • mechanisms - 机制名称的列表或元组。ScramClient将选择最安全的机制。如果cbind_dataNone,则首先过滤出'-PLUS'变体。所选机制作为mechanism_name属性可用。
  • 用户名
  • 密码
  • channel_binding - 为此参数提供值允许使用通道绑定(即,它允许您使用以'-PLUS'结尾的机制)。channel_binding的值为包含通道绑定名称和通道绑定数据的元组。例如,如果通道绑定名称是tls-unique,则channel_binding参数将是('tls-unique', data),其中data是通过调用SSLSocket.get_channel_binding()获得的。可以使用便利函数scramp.make_channel_binding()来创建通道绑定元组。
  • c_nonce - 客户端nonce。在测试/调试时有时需要设置此值,但在生产环境中应省略,在这种情况下,ScramClient将生成客户端nonce。

ScramClient对象具有以下方法和属性

  • get_client_first() - 获取客户端第一个消息。
  • set_server_first(message) - 设置来自服务器的第一个消息。
  • get_client_final() - 获取最终客户端消息。
  • set_server_final(message) - 设置来自服务器的最终消息。
  • mechanism_name - 从构造函数中给出的列表中选择的机制。

scramp.ScramMechanism

ScramMechanism(mechanism='SCRAM-SHA-256')

ScramMechanism类的构造函数,以下参数

  • mechanism - 要使用的SCRAM机制。

ScramMechanism对象具有以下方法和属性

  • make_auth_info(password, iteration_count=None, salt=None) - 返回元组(salt, stored_key, server_key, iteration_count),该元组存储在服务器端的认证数据库中。它具有以下参数
    • password - 作为str的用户密码。
    • iteration_count - 作为int的轮数。如果为None,则使用与机制关联的最小轮数。
    • - 在测试/调试时设置此二进制参数有时很有用,但在生产中应省略此参数,此时将生成盐。
  • make_server(auth_fn, channel_binding=None, s_nonce=None) - 返回一个 ScramServer 对象。它接受以下参数
    • auth_fn 这是一个由程序员提供的函数,它有一个参数,即用户名(类型为 str),并返回元组 (salt, stored_key, server_key, iteration_count)。其中 stored_keyserver_key 是二进制类型,而 iteration_count 是一个 int
    • channel_binding - 为此参数提供值允许使用通道绑定(即,它允许您使用以 -PLUS 结尾的机制)。channel_binding 的值是一个由通道绑定名称和通道绑定数据组成的元组。例如,如果通道绑定名称是 'tls-unique',则 channel_binding 参数将是 ('tls-unique', data),其中 data 是通过调用 SSLSocket.get_channel_binding() 获取的。便利函数 scramp.make_channel_binding() 可以用来创建通道绑定元组。如果提供了 channel_binding 且机制不是 -PLUS 变体,则服务器将与客户端协商使用客户端支持的 -PLUS 变体,否则将使用不带通道绑定的机制。
    • s_nonce - 服务器 nonce 作为 str。在测试/调试时设置此参数有时很有用,但在生产中应省略此参数,此时 ScramServer 将生成服务器 nonce。
  • make_stored_server_keys(salted_password) - 根据给定的盐化密码返回 (stored_key, server_key) 字节对象元组。如果您想使用 Scramp 提供之外的单独哈希实现,这将很有用。它接受以下参数
    • salted_password - 代表哈希密码的二进制对象。
  • iteration_count - 此机制推荐的最低迭代次数。

scramp.ScramServer

ScramServer 对象具有以下方法

  • set_client_first(message) - 设置客户端的第一个消息。
  • get_server_first() - 获取服务器第一个消息。
  • set_client_final(message) - 设置客户端的最终消息。
  • get_server_final() - 获取服务器的最终消息。

scramp.make_channel_binding(name, ssl_socket)

这是一个辅助函数,当给定通道绑定名称和 SSL 套接字时,它创建一个 channel_binding 元组。参数如下

  • name - 通道绑定名称,如 'tls-unique' 或 'tls-server-end-point'。
  • ssl_socket - ssl.SSLSocket 的实例。

测试

  • 激活虚拟环境: source venv/bin/activate
  • 安装 toxpip install tox
  • 运行 toxtox

OpenSSF Scorecard

运行 OpenSSF Scorecard 可能是有益的

sudo docker run -e GITHUB_AUTH_TOKEN=<auth_token> gcr.io/openssf/scorecard:stable --repo=github.com/tlocke/scramp

发布 Scramp

运行 tox 以确保所有测试通过,然后更新发布说明,然后执行

  • git tag -a x.y.z -m "version x.y.z"
  • rm -r dist
  • python -m build
  • twine upload dist/*

发布说明

版本 1.4.5,2024-04-13

  • 删除对 Python 3.7 的支持,这意味着我们不再依赖于 importlib-metadata
  • 构建系统、文档和自动化测试的各种更改。

版本 1.4.4,2022-11-01

  • 强化对消息的解析,以确保如果消息格式不正确,则会引发 ScramException

版本 1.4.3,2022-10-26

  • 如果客户端支持通道绑定,但认为服务器不支持,则客户端现在会发送 gs2-cbind-flag 为 'y'。

版本 1.4.2,2022-10-22

版本 1.4.1,2021-08-25

  • 使用 make_channel_binding() 创建 tls-server-end-point 通道绑定时,支持使用 sha512 算法的证书。

版本 1.4.0,2021-03-28

  • 如果客户端收到来自服务器的错误,将引发异常。

版本 1.3.0,2021-03-28

  • 按照规范,服务器错误现在通过 server_final 消息发送给客户端,但仍然像以前一样抛出异常。

版本 1.2.2,2021-02-13

  • 修复了生成 AuthMessage 的错误。当使用通道绑定时,这是不正确的。因此,现在 Scramp 支持通道绑定。

版本 1.2.1,2021-02-07

  • 添加对通道绑定的支持。
  • 添加对 SCRAM-SHA-512 和 SCRAM-SHA3-512 以及它们的通道绑定变体的支持。

版本 1.2.0,2020-05-30

  • 这是服务器端的一个向后不兼容的更改,客户端将像以前一样工作。这个更改的目的是使认证数据库成为可能。也就是说,认证信息可以存储起来,并在需要时检索以进行用户认证。
  • 此外,现在在服务器端可以使用第三方哈希库,如 passlib 作为哈希实现。

版本 1.1.1,2020-03-28

  • 将 README 和 LICENCE 添加到发行版中。

版本 1.1.0,2019-02-24

  • 添加对 SCRAM-SHA-1 机制的支持。

版本 1.0.0,2019-02-17

  • 实现了服务器端以及客户端。

版本 0.0.0,2019-02-10

  • pg8000 复制了 SCRAM 实现。想法是将其作为一个通用的 SCRAM 实现。感谢我阅读了 Scrampy 项目,以帮助这个项目。还感谢从其中复制了 saslprep 函数的 passlib 项目。

项目详情


下载文件

下载适合您平台的项目。如果您不确定选择哪一个,请了解更多关于 安装包 的信息。

源发行版

scramp-1.4.5.tar.gz (16.2 kB 查看哈希值)

上传时间

构建发行版

scramp-1.4.5-py3-none-any.whl (12.8 kB 查看哈希值)

上传时间 Python 3

由以下机构支持