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_nonce
和 salt
参数,并让 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_nonce
和salt
参数,并让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_nonce
和salt
参数,并让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
标准
- RFC 5802描述了SCRAM。
- RFC 7677注册了SCRAM-SHA-256和SCRAM-SHA-256-PLUS。
- draft-melnikov-scram-sha-512-02注册了SCRAM-SHA-512和SCRAM-SHA-512-PLUS。
- draft-melnikov-scram-sha3-512注册了SCRAM-SHA3-512和SCRAM-SHA3-512-PLUS。
- RFC 5929 TLS的通道绑定。
- draft-ietf-kitten-tls-channel-bindings-for-tls13定义了
tls-exporter
通道绑定,该绑定目前在Scramp中尚不支持。
API文档
scramp.MECHANISMS
支持机制名称的元组。
scramp.ScramClient
ScramClient(mechanisms, username, password, channel_binding=None, c_nonce=None)
scramp.ScramClient
类的构造函数,以下参数
mechanisms
- 机制名称的列表或元组。ScramClient将选择最安全的机制。如果cbind_data
为None
,则首先过滤出'-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_key
和server_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
- 安装
tox
:pip install tox
- 运行
tox
:tox
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
- 切换到使用 MIT-0 许可证 https://choosealicense.com/licenses/mit-0/
- 当创建 ScramClient 时,允许非
-PLUS
变体,即使提供了channel_binding
参数。之前这会引发异常。
版本 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
项目详情
下载文件
下载适合您平台的项目。如果您不确定选择哪一个,请了解更多关于 安装包 的信息。
源发行版
构建发行版
scramp-1.4.5.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | be3fbe774ca577a7a658117dca014e5d254d158cecae3dd60332dfe33ce6d78e |
|
MD5 | f843217a4c0a85c01a4ceec970c3aa6e |
|
BLAKE2b-256 | f9fa8f1b99c3f875f334ac782e173ec03c35c246ec7a94fc5dd85153bc1d8285 |
scramp-1.4.5-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 50e37c464fc67f37994e35bee4151e3d8f9320e9c204fca83a5d313c121bbbe7 |
|
MD5 | 95ff4349358113345ce47c476f371b04 |
|
BLAKE2b-256 | d99f8b2f2749ccfbe4fcef08650896ac47ed919ff25b7ac57b7a1ae7da16c8c3 |