scramp
An implementation of the SCRAM protocol.
Description
Scramp
A Python implementation of the SCRAM authentication protocol. Scramp supports the following mechanisms:
- 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
Table Of Contents
<!-- mtoc-start -->- Installation
- Examples
- Standards
- API Docs
- Testing
- OpenSSF Scorecard
- Doing A Release Of Scramp
- Release Notes
- Version 1.4.8, 2026-01-06
- Version 1.4.7, 2026-01-04
- Version 1.4.6, 2025-07-05
- Version 1.4.5, 2024-04-13
- Version 1.4.4, 2022-11-01
- Version 1.4.3, 2022-10-26
- Version 1.4.2, 2022-10-22
- Version 1.4.1, 2021-08-25
- Version 1.4.0, 2021-03-28
- Version 1.3.0, 2021-03-28
- Version 1.2.2, 2021-02-13
- Version 1.2.1, 2021-02-07
- Version 1.2.0, 2020-05-30
- Version 1.1.1, 2020-03-28
- Version 1.1.0, 2019-02-24
- Version 1.0.0, 2019-02-17
- Version 0.0.0, 2019-02-10
Installation
- Create a virtual environment:
python3 -m venv venv - Activate the virtual environment:
source venv/bin/activate - Install:
pip install scramp
Examples
Client and Server
Here's an example using both the client and the server. It's a bit contrived as normally you'd be using either the client or server on its own.
>>> 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
Client only
Here's an example using just the client. The client nonce is specified in order to give
a reproducible example, but in production you'd omit the c_nonce parameter and let
ScramClient generate a client nonce:
>>> 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
Server only
Here's an example using just the server. The server nonce and salt is specified in order
to give a reproducible example, but in production you'd omit the s_nonce and salt
parameters and let Scramp generate them:
>>> 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
Server Error
Here's an example of when setting a message from the client causes an error. The server
nonce and salt is specified in order to give a reproducible example, but in production
you'd omit the s_nonce and salt parameters and let Scramp generate them:
>>> 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
Standards
- RFC 5802 Describes SCRAM.
- RFC 7677 Registers SCRAM-SHA-256 and SCRAM-SHA-256-PLUS.
- draft-melnikov-scram-sha-512-02 Registers SCRAM-SHA-512 and SCRAM-SHA-512-PLUS.
- draft-melnikov-scram-sha3-512 Registers SCRAM-SHA3-512 and SCRAM-SHA3-512-PLUS.
- RFC 5929 Channel Bindings for TLS.
- draft-ietf-kitten-tls-channel-bindings-for-tls13 Defines the
tls-exporterchannel binding, which is not yet supported by Scramp.
API Docs
scramp.MECHANISMS
A tuple of the supported mechanism names.
scramp.ScramClient
ScramClient(mechanisms, username, password, channel_binding=None, c_nonce=None)
Constructor of the scramp.ScramClient class, with the following parameters:
mechanisms- A list or tuple of mechanism names. ScramClient will choose the most secure. Ifcbind_dataisNone, the '-PLUS' variants will be filtered out first. The chosen mechanism is available as the propertymechanism_name.usernamepasswordchannel_binding- Providing a value for this parameter allows channel binding to be used (ie. it lets you use mechanisms ending in '-PLUS'). The value forchannel_bindingis a tuple consisting of the channel binding name and the channel binding data. For example, if