Dissononce is a python implementation for Noise Protocol Framework. A main goal of this project is to provide a simple, easy to read and understand practical reference for Noise enthusiasts, implementers and users. Therefore this project attempts to stick to the following guidelines:
- Syntax that resembles as closely as possible definitions and pseudo code mentioned in Noise Specs.
- As minimal python "magic" as possible (explicit is better than implicit).
- Code that is simple, easy to read, follow and understand.
- Flexibility to easily adopt future changes to Noise specifications.
- Deviations from Noise Specs (additions, opinionated specs and API changes..etc) are isolated from original implementation/API and are optional to use.
- Deviations from Noise Specs do not influence adjustments to original implementation/API that conflict with Noise Specs.
dissononce version: 0.34.3
noise revision: 34
released: 2019-04-24
requires:
- python>=2.5,<=3.7
- cryptography>=2.5
uses:
- transitions==0.6.9
From source:
python setup.py install
Using Pip:
pip install dissononce
Each set of Crypto functions (DH, Cipher, Hash) is enclosed inside an own base class, where an implementation subclasses that base class to implement the methods.
- DH-functions base class:
dissononce.dh.dh.DH
- Cipher-functions base class:
dissononce.cipher.cipher.Cipher
- Hash-functions base class:
dissononce.hash.hash.Hash
Example instantiating objects for X25519 DH
, AESGCM Cipher
and SHA256 Hash
:
from dissononce.cipher.aesgcm import AESGCMCipher
from dissononce.dh.x25519.x25519 import X25519DH
from dissononce.hash.sha256 import SHA256Hash
cipher = AESGCMCipher()
dh = X25519DH()
hash = SHA256Hash()
Implementations for each set of crypto functions are organized according to their support level:
- stable: officially covered in Noise spec, accessible under:
- experimental: recognized by Noise community, not officially covered in Noise spec or endorsed by it, found under:
- dangerous: a monkey might have written those, just don't use:
See Appendices for available Crypto functions.
HandshakeState
, SymmetricState
and CipherState
should ideally be constructed in a composition-manner,
where Crypto-functions dependencies are also to be instantiated before passing them to their dependants.
- A
CipherState
requires aCipher
object - A
SymmetricState
requires aCipherState
and aHash
object. - A
HandshakeState
requires aSymmetricState
and aDH
object.
from dissononce.processing.impl.handshakestate import HandshakeState
from dissononce.processing.impl.symmetricstate import SymmetricState
from dissononce.processing.impl.cipherstate import CipherState
from dissononce.cipher.chachapoly import ChaChaPolyCipher
from dissononce.dh.x448.x448 import X448DH
from dissononce.hash.sha512 import SHA512Hash
handshakestate = HandshakeState(
SymmetricState(
CipherState(
ChaChaPolyCipher()
),
SHA512Hash()
),
X448DH()
)
See Extras for alternative methods of construction.
The HandshakePattern
class allows authoring of patterns using a simple syntax, similar to how patterns are
described in Noise spec.
- message_patterns is a tuple/list of tuples of token(s).
- initiator_pre_messages is a tuple of tokens
- responder_pre_message_pattern is a tuple of tokens
from dissononce.processing.handshakepatterns.handshakepattern import HandshakePattern
k1k1 = HandshakePattern(
name='K1K1',
initiator_pre_messages=('s',),
responder_pre_message_pattern=('s',),
message_patterns=(
('e',),
('e', 'ee', 'es'),
('se',)
)
)
print(k1k1)
K1K1:
-> s
<- s
...
-> e
<- e, ee, es
-> se
See Appendices for already defined Handshake Patterns.
A Modifier
accepts a HandshakePattern
and creates a new one with a modified name, and a modified set of
message and premessage patterns
Fallback
from dissononce.processing.modifiers.fallback import FallbackPatternModifier
from dissononce.processing.handshakepatterns.interactive.XX import XXHandshakePattern
xx = XXHandshakePattern()
xx_fallback = FallbackPatternModifier().modify(xx)
print(xx_fallback)
XXfallback:
-> e
...
<- e, ee, s, es
-> s, se
PSK
from dissononce.processing.modifiers.psk import PSKPatternModifier
from dissononce.processing.handshakepatterns.interactive.NN import NNHandshakePattern
nn = NNHandshakePattern()
nn_psk0 = PSKPatternModifier(0).modify(nn)
nn_psk02 = PSKPatternModifier(2).modify(nn_psk0)
print(nn_psk02)
NNpsk0+psk2:
-> psk, e
<- e, ee, psk
As usual, the modified HandshakePattern
is used to (re)initialize a HandshakeState
:
handshakestate.initialize(
handshake_pattern=nn_psk02,
initiator=True,
prologue=b'',
psks=(psk0, psk2)
)
Classes and functions that are not part of Noise Protocol specification but are part of this implementation are referred to as "Extras" or "Deviations". Examples for Extras are helpers, classes that simplify usage of the library, wrappers that enforce some rules or design patterns, or crypto functions that are not part of Noise Spec. Extras should be decoupled as much as possible from the base spec implementation and never referenced from there.
As an alternative to directly instantiating the Crypto-functions objects, they could also be created by name using a factory designated to each type of Crypto-functions:
from dissononce.extras.meta.hash.factory import HashFactory
from dissononce.extras.meta.dh.factory import DHFactory
from dissononce.extras.meta.cipher.factory import CipherFactory
cipher = CipherFactory().get_cipher('AESGCM')
hash = HashFactory().get_hash('SHA256')
dh = DHFactory().get_dh('25519')
Note that creating by name supports stable/official algorithms only at the moment.
A Noise Protocol, that is:
- DH, Cipher, Hash instance
- CipherState instance
- SymmetricState instance
- HandshakeState instance
- HandshakePattern
can be created by name. Use NoiseProtocolFactory
to get a a NoiseProtocol
instance which encloses
instances of DH
, Cipher
, Hash
, HandshakePattern
, and exposes methods for creating
CipherState
, SymmetricState
, and HandshakeState
.
from dissononce.extras.meta.protocol.factory import NoiseProtocolFactory
protocol = NoiseProtocolFactory().get_noise_protocol('Noise_XX_25519_AESGCM_SHA256')
handshakestate = protocol.create_handshakestate()
Note that creating by name supports stable/official algorithms only at the moment.
from dissononce.extras.processing.handshakestate_guarded import GuardedHandshakeState
guarded = GuardedHandshakeState(handshakestate)
guarded.read_message(b'', bytearray())
> AssertionError: Cannot read_message while in initialize phase.
GuardedHandshakeState
wraps an existing HandshakeState
to enforce a correct flow of the handshake process.
This includes making sure the HandshakeState
is initialized before usage, and that the flow order of
write_message
and read_message
invocations match the HandshakePattern
being used. A violation will
result in an AssertionError getting raised.
from dissononce.extras.processing.handshakestate_switchable import SwitchableHandshakeState
from dissononce.processing.handshakepatterns.interactive.XX import XXHandshakePattern
from dissononce.processing.modifiers.fallback import FallbackPatternModifier
from dissononce.extras.meta.protocol.factory import NoiseProtocolFactory
protocol = NoiseProtocolFactory().get_noise_protocol('Noise_IK_25519_AESGCM_SHA256')
switchable = SwitchableHandshakeState(protocol.create_handshakestate())
## Begin IK, then fallback to XX if necessary using:
switchable.switch(
handshake_pattern=FallbackPatternModifier().modify(XXHandshakePattern()),
initiator=True,
prologue=b''
)
SwitchableHandshakeState
facilitates transforming an ongoing Handshake into using a different pattern. Given
the newHandshakePattern
, it analyses the required initiator and responder pre-messages, and maintains them
across the transformation for use in the new Handshake. This is typically used for example when doing
a IK
handshake then switching to XXfallback
where re
is to be used as a initiator pre-message.
Inside examples directory there are examples for some Noise protocols carrying out a handshake and transporting some messages for demonstration.
Vectors used for testing are found under test/vectors. The data is of JSON type, and is formatted according to Noise Test Vectors Specification. At the moment there are 2 Test Vectors files:
- Tests Vectors from cacophony under tests/vectors/cacophony.txt
- Test Vectors from snow under tests/vectors/snow.txt
Enable debug-level logging for a detailed insight of a handshake process. The debug output syntax and formatting is intended to be as close as possible to the language used in Noise specs. This might be useful for when using dissononce as a reference implementation where one wants to understand what's going on internally and to easily relate to Noise specs.
>>> import dissononce, logging
>>> dissononce.logger.setLevel(logging.DEBUG)
>>> handshakestate.initialize(XXHandshakePattern(), True, b'', X448DH().generate_keypair())
I dissononce.processing.impl.handshakestate - Derived Noise Protocol name Noise_XX_448_ChaChaPoly_SHA512
XX:
-> e
<- e, ee, s, es
-> s, se
>>> handshakestate.write_message(b'',bytearray())
I dissononce.processing.impl.handshakestate - WriteMessage(payload, message_buffer)
D dissononce.processing.impl.handshakestate - Processing token 'e'
D dissononce.processing.impl.handshakestate - e=GENERATE_KEYPAIR()
D dissononce.processing.impl.handshakestate - message_buffer.append(e.public_key)
D dissononce.processing.impl.handshakestate - MixHash(e.public_key)
D dissononce.processing.impl.handshakestate - buffer.append(EncryptAndHash(payload))
Stable:
Stable:
Stable:
Interactive:
Oneway:
Deferred: