From d4c4d2e1bdff972f58a04ecbdc522d2f7d9a64c2 Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Sun, 8 Oct 2017 00:36:18 +0200 Subject: [PATCH 01/10] Using pow(x, y, modp) where applicable Additionally minor reformatting. Closes #1 --- noise/crypto.py | 50 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/noise/crypto.py b/noise/crypto.py index 39cca26..c47f522 100644 --- a/noise/crypto.py +++ b/noise/crypto.py @@ -61,8 +61,8 @@ class X448(object): d = (x3 - z3) % P da = (d * a) % P cb = (c * b) % P - x3 = (((da + cb) % P) ** 2) % P - z3 = (x1 * (((da - cb) % P) ** 2) % P) % P + x3 = pow((da + cb) % P, 2, P) + z3 = (x1 * pow((da - cb) % P, 2, P)) % P x2 = (aa * bb) % P z2 = (e * ((aa + (A24 * e) % P) % P)) % P @@ -82,31 +82,29 @@ class X448(object): # Self-test # Test vectors taken from RFC 7748 section 5.2 and 6.2 -scalar1 = bytes.fromhex('203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b437' - '1282dd2c8d5be3095f') -u1 = bytes.fromhex('0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfb' - 'de71ce8d157db') -assert X448.mul(scalar1, u1) == bytes.fromhex('884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df3' - '4321d62077e63633c575c1c954514e99da7c179d') +scalar1 = bytes.fromhex( + '203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f') +u1 = bytes.fromhex( + '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db') +assert X448.mul(scalar1, u1) == bytes.fromhex( + '884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d') -scalar2 = bytes.fromhex('3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f5' - '4b29259a4f1c600ad3') -u2 = bytes.fromhex('06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa' - '7a7fb4ef8a086') -assert X448.mul(scalar2, u2) == bytes.fromhex('ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc6' - '6a01b0779d98223961111e21766282f73dd96b6f') +scalar2 = bytes.fromhex( + '3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3') +u2 = bytes.fromhex( + '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086') +assert X448.mul(scalar2, u2) == bytes.fromhex( + 'ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f') -alice_priv = bytes.fromhex('9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f12' - '7ab1d9ac2d8c0a598726b') -alice_pub = bytes.fromhex('9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da8812' - '0d53177f80e532c41fa0') -bob_priv = bytes.fromhex('1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d' - '1b0366f10b65173992d') -bob_pub = bytes.fromhex('3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3d' - 'ae07bdc1c67bf33609') +alice_priv = bytes.fromhex( + '9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b') +alice_pub = bytes.fromhex( + '9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0') +bob_priv = bytes.fromhex( + '1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d') +bob_pub = bytes.fromhex( + '3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609') assert alice_pub == X448.mul_5(alice_priv) assert bob_pub == X448.mul_5(bob_priv) -assert X448.mul(alice_priv, bob_pub) == X448.mul(bob_priv, alice_pub) == bytes.fromhex('07fff4181ac6cc95ec1c16a94a0f74d' - '12da232ce40a77552281d282bb60c0b' - '56fd2464c335543936521c24403085d' - '59a449a5037514a879d') +assert X448.mul(alice_priv, bob_pub) == X448.mul(bob_priv, alice_pub) == bytes.fromhex( + '07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d') From 7e51c3a6f7a38e49fcb2125c1e8330452598d54e Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Sun, 8 Oct 2017 00:54:54 +0200 Subject: [PATCH 02/10] Switched from lists to tuples for const data type Closes #2 --- noise/patterns.py | 2 +- tests/test_vectors.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/noise/patterns.py b/noise/patterns.py index 978766b..cca285a 100644 --- a/noise/patterns.py +++ b/noise/patterns.py @@ -60,7 +60,7 @@ class Pattern(object): def get_required_keypairs(self, initiator: bool) -> list: required = [] if initiator: - if self.name[0] in ['K', 'X', 'I']: + if self.name[0] in ('K', 'X', 'I'): required.append('s') if self.one_way or self.name[1] == 'K': required.append('rs') diff --git a/tests/test_vectors.py b/tests/test_vectors.py index 990cf68..2eece8c 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -15,10 +15,10 @@ vector_files = [ # As in test vectors specification (https://github.com/noiseprotocol/noise_wiki/wiki/Test-vectors) # We use this to cast read strings into bytes -byte_fields = ['protocol_name'] -hexbyte_fields = ['init_prologue', 'init_static', 'init_ephemeral', 'init_remote_static', 'resp_static', - 'resp_prologue', 'resp_ephemeral', 'resp_remote_static', 'handshake_hash'] -list_fields = ['init_psks', 'resp_psks'] +byte_field = 'protocol_name' +hexbyte_fields = ('init_prologue', 'init_static', 'init_ephemeral', 'init_remote_static', 'resp_static', + 'resp_prologue', 'resp_ephemeral', 'resp_remote_static', 'handshake_hash') +list_fields = ('init_psks', 'resp_psks') dict_field = 'messages' @@ -31,7 +31,7 @@ def _prepare_test_vectors(): for vector in vectors_list: for key, value in vector.copy().items(): - if key in byte_fields: + if key == byte_field: vector[key] = value.encode() if key in hexbyte_fields: vector[key] = bytes.fromhex(value) From ae894c6be9962c0eca0014467866a0314d1d3b5a Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Sun, 8 Oct 2017 01:35:41 +0200 Subject: [PATCH 03/10] Docs generation setup First shot at documentation. Just to check if they work with ReadTheDocs. Also, added dev_requirements.txt with packages required for development of package. Refs #8 --- dev_requirements.txt | 3 + docs/Makefile | 20 +++++ docs/conf.py | 175 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 31 ++++++++ 4 files changed, 229 insertions(+) create mode 100644 dev_requirements.txt create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst diff --git a/dev_requirements.txt b/dev_requirements.txt new file mode 100644 index 0000000..00dd848 --- /dev/null +++ b/dev_requirements.txt @@ -0,0 +1,3 @@ +pytest>=3.2.0 +sphinx>=1.6.4 +sphinx-autobuild>=0.7.1 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..36de6b0 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = noiseprotocol +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..1d2bbeb --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# noiseprotocol documentation build configuration file, created by +# sphinx-quickstart on Sun Oct 8 01:15:26 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'noiseprotocol' +copyright = '2017, Piotr Lizonczyk' +author = 'Piotr Lizonczyk' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.2' +# The full version, including alpha/beta/rc tags. +release = '0.2.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + 'donate.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'noiseprotocoldoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'noiseprotocol.tex', 'noiseprotocol Documentation', + 'Piotr Lizonczyk', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'noiseprotocol', 'noiseprotocol Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'noiseprotocol', 'noiseprotocol Documentation', + author, 'noiseprotocol', 'One line description of project.', + 'Miscellaneous'), +] + + + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..6ca97c5 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,31 @@ +.. noiseprotocol documentation master file, created by + sphinx-quickstart on Sun Oct 8 01:15:26 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to noiseprotocol's documentation! +========================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + +Documentation for the Code +************************** + +.. automodule:: noise.builder + :members: + +.. automodule:: noise.functions + :members: + +.. automodule:: noise.state + :members: + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` From 81a2f7e845211f7621a466d513fea8bbceab58fb Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Sun, 8 Oct 2017 01:50:13 +0200 Subject: [PATCH 04/10] Use Read the Docs theme + clean up some default unnecessary options from sphinx conf.py Refs #8 --- dev_requirements.txt | 1 + docs/conf.py | 73 ++------------------------------------------ 2 files changed, 4 insertions(+), 70 deletions(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index 00dd848..b893bd7 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,3 +1,4 @@ pytest>=3.2.0 sphinx>=1.6.4 sphinx-autobuild>=0.7.1 +sphinx_rtd_theme>=0.2.4 diff --git a/docs/conf.py b/docs/conf.py index 1d2bbeb..bfc8e94 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -86,7 +86,9 @@ todo_include_todos = True # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +import sphinx_rtd_theme +html_theme = "sphinx_rtd_theme" +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -99,77 +101,8 @@ html_theme = 'alabaster' # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', - ] -} - - # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'noiseprotocoldoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'noiseprotocol.tex', 'noiseprotocol Documentation', - 'Piotr Lizonczyk', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'noiseprotocol', 'noiseprotocol Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'noiseprotocol', 'noiseprotocol Documentation', - author, 'noiseprotocol', 'One line description of project.', - 'Miscellaneous'), -] - - - From 3bfb39883e63b5fe54eca0f98bae94c16f41a872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Lizo=C5=84czyk?= <3533647+plizonczyk@users.noreply.github.com> Date: Wed, 11 Oct 2017 18:58:07 +0200 Subject: [PATCH 05/10] Cryptography version update (#10) * 2.1 version of pyca/cryptography got released. Version bumped * Python 3.5 support reintroduced, as we are now using BLAKE from Cryptography, not hashlib * Removed hashlib references, uncommented Cryptography references. * Enum fix for python 3.5 compat Closes #9 --- .travis.yml | 4 +-- CHANGELOG.rst | 19 +++++++++++++ README.md | 4 +-- noise/builder.py | 10 +++---- noise/functions.py | 66 ++++++++++++++++------------------------------ requirements.txt | 2 +- setup.py | 4 +-- 7 files changed, 52 insertions(+), 57 deletions(-) create mode 100644 CHANGELOG.rst diff --git a/.travis.yml b/.travis.yml index 41e4cb7..a77f25a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ language: python notifications: email: false python: -# - "3.5" + - "3.5" - "3.6" -# - "3.5-dev" # 3.5 development branch + - "3.5-dev" # 3.5 development branch - "3.6-dev" # 3.6 development branch # - "3.7-dev" # 3.7 development branch # command to install dependencies diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..9e337aa --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,19 @@ +Changelog +========= + +.. _v0-2-0: + +0.2.0 - `trunk` +~~~~~~~~~~~~~~~~ + +.. note:: This version is not yet released and is under active development. + +* Cryptography requirement updated to the newest version (2.1) - **Python 3.5** is supported again. + + +.. _v0-1-0: + +0.1.1 - 2017-09-12 +~~~~~~~~~~~~~~~~~~ + +Initial release. \ No newline at end of file diff --git a/README.md b/README.md index 3d649b1..fc92d6e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This package shall not be used (yet) for production purposes. There was little t Use common sense while using - until this package becomes stable. ## Installation and prerequisites -For now, only Python 3.6 is supported. +For now, only Python 3.5+ is supported. Install via pip: ``` @@ -82,11 +82,9 @@ pytest ### Todo-list for the project: - [ ] fallback patterns support -- [ ] documentation on Read the Docs and more extensive readme - [ ] scripts for keypair generation (+ console entry points) - [ ] "echo" (noise-c like) example - [ ] extensive logging -- [ ] bringing back Python 3.5 support and supporting Python 3.7 (dependent on Cryptography package updates) - [ ] move away from custom ed448 implementation - [ ] implement countermeasures for side-channel attacks - [ ] **get peer review of the code** diff --git a/noise/builder.py b/noise/builder.py index 4bc853e..fc6ca61 100644 --- a/noise/builder.py +++ b/noise/builder.py @@ -1,4 +1,4 @@ -from enum import Enum, auto +from enum import Enum from typing import Union, List from cryptography.exceptions import InvalidTag @@ -9,10 +9,10 @@ from .noise_protocol import NoiseProtocol class Keypair(Enum): - STATIC = auto() - REMOTE_STATIC = auto() - EPHEMERAL = auto() - REMOTE_EPHEMERAL = auto() + STATIC = 1 + REMOTE_STATIC = 2 + EPHEMERAL = 3 + REMOTE_EPHEMERAL = 4 _keypairs = {Keypair.STATIC: 's', Keypair.REMOTE_STATIC: 'rs', diff --git a/noise/functions.py b/noise/functions.py index 0b86d31..3f5c44b 100644 --- a/noise/functions.py +++ b/noise/functions.py @@ -1,15 +1,13 @@ import abc import warnings -from functools import partial # Turn back on when Cryptography gets fixed -import hashlib -import hmac +from functools import partial import os from cryptography.hazmat.backends import default_backend -# from cryptography.hazmat.primitives import hashes # Turn back on when Cryptography gets fixed +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305 -# from cryptography.hazmat.primitives.hmac import HMAC # Turn back on when Cryptography gets fixed +from cryptography.hazmat.primitives.hmac import HMAC from noise.constants import MAX_NONCE from noise.exceptions import NoiseValueError from .crypto import X448 @@ -103,60 +101,44 @@ class Hash(object): self.hashlen = 32 self.blocklen = 64 self.hash = self._hash_sha256 - # self.fn = hashes.SHA256 # Turn back on when Cryptography gets fixed - self.fn = 'SHA256' + self.fn = hashes.SHA256 elif method == 'SHA512': self.hashlen = 64 self.blocklen = 128 self.hash = self._hash_sha512 - # self.fn = hashes.SHA512 # Turn back on when Cryptography gets fixed - self.fn = 'SHA512' + self.fn = hashes.SHA512 elif method == 'BLAKE2s': self.hashlen = 32 self.blocklen = 64 self.hash = self._hash_blake2s - # self.fn = partial(hashes.BLAKE2s, digest_size=self.hashlen) # Turn back on when Cryptography gets fixed - self.fn = 'blake2s' + self.fn = partial(hashes.BLAKE2s, digest_size=self.hashlen) elif method == 'BLAKE2b': self.hashlen = 64 self.blocklen = 128 self.hash = self._hash_blake2b - # self.fn = partial(hashes.BLAKE2b, digest_size=self.hashlen) # Turn back on when Cryptography gets fixed - self.fn = 'blake2b' + self.fn = partial(hashes.BLAKE2b, digest_size=self.hashlen) else: raise NotImplementedError('Hash method: {}'.format(method)) def _hash_sha256(self, data): - return hashlib.sha256(data).digest() + digest = hashes.Hash(hashes.SHA256(), backend) + digest.update(data) + return digest.finalize() def _hash_sha512(self, data): - return hashlib.sha512(data).digest() + digest = hashes.Hash(hashes.SHA512(), backend) + digest.update(data) + return digest.finalize() def _hash_blake2s(self, data): - return hashlib.blake2s(data).digest() + digest = hashes.Hash(hashes.BLAKE2s(digest_size=self.hashlen), backend) + digest.update(data) + return digest.finalize() def _hash_blake2b(self, data): - return hashlib.blake2b(data).digest() - - # def _hash_sha256(self, data): # Turn back on when Cryptography gets fixed - # digest = hashes.Hash(hashes.SHA256(), backend) - # digest.update(data) - # return digest.finalize() - # - # def _hash_sha512(self, data): # Turn back on when Cryptography gets fixed - # digest = hashes.Hash(hashes.SHA512(), backend) - # digest.update(data) - # return digest.finalize() - # - # def _hash_blake2s(self, data): # Turn back on when Cryptography gets fixed - # digest = hashes.Hash(hashes.BLAKE2s(digest_size=self.hashlen), backend) - # digest.update(data) - # return digest.finalize() - # - # def _hash_blake2b(self, data): # Turn back on when Cryptography gets fixed - # digest = hashes.Hash(hashes.BLAKE2b(digest_size=self.hashlen), backend) - # digest.update(data) - # return digest.finalize() + digest = hashes.Hash(hashes.BLAKE2b(digest_size=self.hashlen), backend) + digest.update(data) + return digest.finalize() class _KeyPair(object): @@ -244,15 +226,11 @@ keypair_map = { } -# def hmac_hash(key, data, algorithm): # Turn back on when Cryptography gets fixed -# # Applies HMAC using the HASH() function. -# hmac = HMAC(key=key, algorithm=algorithm(), backend=backend) -# hmac.update(data=data) -# return hmac.finalize() - def hmac_hash(key, data, algorithm): # Applies HMAC using the HASH() function. - return hmac.new(key, data, algorithm).digest() + hmac = HMAC(key=key, algorithm=algorithm(), backend=backend) + hmac.update(data=data) + return hmac.finalize() def hkdf(chaining_key, input_key_material, num_outputs, hmac_hash_fn): diff --git a/requirements.txt b/requirements.txt index aa49793..f540345 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -cryptography==2.0.3 +cryptography==2.1.0 diff --git a/setup.py b/setup.py index 2f1aaa8..6f11631 100644 --- a/setup.py +++ b/setup.py @@ -26,12 +26,12 @@ setup( 'Topic :: Security :: Cryptography', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', - # 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', # 'Programming Language :: Python :: 3.7', ], keywords='cryptography noiseprotocol noise security', packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']), install_requires=['cryptography==2.0.3'], - python_requires='~=3.6', + python_requires='~=3.5,~=3.6', ) From d636c506d3116c30c05f6b89a25e5a94dabf2db1 Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Wed, 11 Oct 2017 19:00:35 +0200 Subject: [PATCH 06/10] Changelog and setup.py update --- CHANGELOG.rst | 2 ++ setup.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9e337aa..f3f26d1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ Changelog .. note:: This version is not yet released and is under active development. * Cryptography requirement updated to the newest version (2.1) - **Python 3.5** is supported again. +* Adding sphinx documentation for Read the Docs publication. +* Minor fixes for better performance. .. _v0-1-0: diff --git a/setup.py b/setup.py index 6f11631..44876bf 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ except (IOError, ImportError): setup( name='noiseprotocol', - version='0.1.1', + version='0.2.0', description='Implementation of Noise Protocol Framework', long_description=long_description, url='https://github.com/plizonczyk/noiseprotocol', @@ -32,6 +32,6 @@ setup( ], keywords='cryptography noiseprotocol noise security', packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']), - install_requires=['cryptography==2.0.3'], + install_requires=['cryptography==2.1.0'], python_requires='~=3.5,~=3.6', ) From 2bac81d05c86616b7d444a4dbc7bb07bef401c0b Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Sat, 14 Oct 2017 17:05:54 +0200 Subject: [PATCH 07/10] Do not reinitialise cipher class every time Now CipherState holds instance of Cipher wrapper and manages initialization of underlying cipher method with keys. Closes #6 --- noise/functions.py | 23 ++++++++++------------- noise/state.py | 14 ++++++++++---- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/noise/functions.py b/noise/functions.py index 3f5c44b..4294778 100644 --- a/noise/functions.py +++ b/noise/functions.py @@ -65,28 +65,22 @@ class Cipher(object): self.rekey = self._default_rekey else: raise NotImplementedError('Cipher method: {}'.format(method)) + self.cipher = None def _aesgcm_encrypt(self, k, n, ad, plaintext): - # Might be expensive to initialise AESGCM with the same key every time. The key should be (as per spec) kept in - # CipherState, but we may as well hold an initialised AESGCM and manage reinitialisation on CipherState.rekey - cipher = self._cipher(k) - return cipher.encrypt(nonce=self._aesgcm_nonce(n), data=plaintext, associated_data=ad) + return self.cipher.encrypt(nonce=self._aesgcm_nonce(n), data=plaintext, associated_data=ad) def _aesgcm_decrypt(self, k, n, ad, ciphertext): - cipher = self._cipher(k) - return cipher.decrypt(nonce=self._aesgcm_nonce(n), data=ciphertext, associated_data=ad) + return self.cipher.decrypt(nonce=self._aesgcm_nonce(n), data=ciphertext, associated_data=ad) def _aesgcm_nonce(self, n): return b'\x00\x00\x00\x00' + n.to_bytes(length=8, byteorder='big') def _chacha20_encrypt(self, k, n, ad, plaintext): - # Same comment as with AESGCM - cipher = self._cipher(k) - return cipher.encrypt(nonce=self._chacha20_nonce(n), data=plaintext, associated_data=ad) + return self.cipher.encrypt(nonce=self._chacha20_nonce(n), data=plaintext, associated_data=ad) def _chacha20_decrypt(self, k, n, ad, ciphertext): - cipher = self._cipher(k) - return cipher.decrypt(nonce=self._chacha20_nonce(n), data=ciphertext, associated_data=ad) + return self.cipher.decrypt(nonce=self._chacha20_nonce(n), data=ciphertext, associated_data=ad) def _chacha20_nonce(self, n): return b'\x00\x00\x00\x00' + n.to_bytes(length=8, byteorder='little') @@ -94,6 +88,9 @@ class Cipher(object): def _default_rekey(self, k): return self.encrypt(k, MAX_NONCE, b'', b'\x00' * 32)[:32] + def initialize(self, key): + self.cipher = self._cipher(key) + class Hash(object): def __init__(self, method): @@ -209,8 +206,8 @@ dh_map = { } cipher_map = { - 'AESGCM': Cipher('AESGCM'), - 'ChaChaPoly': Cipher('ChaCha20') + 'AESGCM': partial(Cipher, 'AESGCM'), + 'ChaChaPoly': partial(Cipher, 'ChaCha20') } hash_map = { diff --git a/noise/state.py b/noise/state.py index 52317cd..a006d40 100644 --- a/noise/state.py +++ b/noise/state.py @@ -9,11 +9,14 @@ class CipherState(object): Implemented as per Noise Protocol specification (rev 32) - paragraph 5.1. The initialize_key() function takes additional required argument - noise_protocol. + + This class holds an instance of Cipher wrapper. It manages initialisation of underlying cipher function + with appropriate key in initialize_key() and rekey() methods. """ def __init__(self, noise_protocol): self.k = Empty() self.n = None - self.noise_protocol = noise_protocol + self.cipher = noise_protocol.cipher_fn() def initialize_key(self, key): """ @@ -21,6 +24,8 @@ class CipherState(object): """ self.k = key self.n = 0 + if self.has_key(): + self.cipher.initialize(key) def has_key(self): """ @@ -41,7 +46,7 @@ class CipherState(object): if not self.has_key(): return plaintext - ciphertext = self.noise_protocol.cipher_fn.encrypt(self.k, self.n, ad, plaintext) + ciphertext = self.cipher.encrypt(self.k, self.n, ad, plaintext) self.n = self.n + 1 return ciphertext @@ -59,12 +64,13 @@ class CipherState(object): if not self.has_key(): return ciphertext - plaintext = self.noise_protocol.cipher_fn.decrypt(self.k, self.n, ad, ciphertext) + plaintext = self.cipher.decrypt(self.k, self.n, ad, ciphertext) self.n = self.n + 1 return plaintext def rekey(self): - self.k = self.noise_protocol.cipher_fn.rekey(self.k) + self.k = self.cipher.rekey(self.k) + self.cipher.initialize(self.k) class SymmetricState(object): From 59c11248ff85a7de140bbb275ab40d3e1bc3a6c5 Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Sat, 14 Oct 2017 17:46:56 +0200 Subject: [PATCH 08/10] Ensure compatibility with revision 33 * Allowed '/' in protocol name * Added SymmetricState.GetHandshakeHash() * Added CipherState.SetNonce() * Unittests to ensure that each of above is working Closes #3 --- CHANGELOG.rst | 3 ++- README.md | 1 + noise/noise_protocol.py | 2 +- noise/state.py | 6 ++++++ requirements.txt | 2 +- setup.py | 2 +- tests/test_rev33_compat.py | 26 ++++++++++++++++++++++++++ 7 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 tests/test_rev33_compat.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f3f26d1..66a31ff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,8 @@ Changelog .. note:: This version is not yet released and is under active development. -* Cryptography requirement updated to the newest version (2.1) - **Python 3.5** is supported again. +* Compatible with revision 33 (doesn't break compatibility with revision 32). +* Cryptography requirement updated to the newest version (2.1.1) - **Python 3.5** is supported again. * Adding sphinx documentation for Read the Docs publication. * Minor fixes for better performance. diff --git a/README.md b/README.md index fc92d6e..06307e2 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ noiseprotocol [![PyPI](https://img.shields.io/pypi/v/noiseprotocol.svg)](https://pypi.python.org/pypi/noiseprotocol) This repository contains source code of **noiseprotocol** - a Python 3 implementation of [Noise Protocol Framework](http://www.noiseprotocol.org/). +Compatible with revisions 32 and 33. ### Warning This package shall not be used (yet) for production purposes. There was little to none peer review done so far. diff --git a/noise/noise_protocol.py b/noise/noise_protocol.py index 7491f60..e067c00 100644 --- a/noise/noise_protocol.py +++ b/noise/noise_protocol.py @@ -102,7 +102,7 @@ class NoiseProtocol(object): self.cipher_state_decrypt = None else: self.cipher_state_encrypt = None - self.handshake_hash = self.symmetric_state.h + self.handshake_hash = self.symmetric_state.get_handshake_hash() del self.handshake_state del self.symmetric_state del self.cipher_state_handshake diff --git a/noise/state.py b/noise/state.py index a006d40..4bffad4 100644 --- a/noise/state.py +++ b/noise/state.py @@ -33,6 +33,9 @@ class CipherState(object): """ return not isinstance(self.k, Empty) + def set_nonce(self, nonce): + self.n = nonce + def encrypt_with_ad(self, ad: bytes, plaintext: bytes) -> bytes: """ If k is non-empty returns ENCRYPT(k, n++, ad, plaintext). Otherwise returns plaintext. @@ -148,6 +151,9 @@ class SymmetricState(object): # Calls InitializeKey(temp_k). self.cipher_state.initialize_key(temp_k) + def get_handshake_hash(self): + return self.h + def encrypt_and_hash(self, plaintext: bytes) -> bytes: """ Sets ciphertext = EncryptWithAd(h, plaintext), calls MixHash(ciphertext), and returns ciphertext. Note that if diff --git a/requirements.txt b/requirements.txt index f540345..64d75b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -cryptography==2.1.0 +cryptography==2.1.1 diff --git a/setup.py b/setup.py index 44876bf..297fbaa 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,6 @@ setup( ], keywords='cryptography noiseprotocol noise security', packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']), - install_requires=['cryptography==2.1.0'], + install_requires=['cryptography==2.1.1'], python_requires='~=3.5,~=3.6', ) diff --git a/tests/test_rev33_compat.py b/tests/test_rev33_compat.py new file mode 100644 index 0000000..13eeb21 --- /dev/null +++ b/tests/test_rev33_compat.py @@ -0,0 +1,26 @@ +from noise.noise_protocol import NoiseProtocol +from noise.state import CipherState, SymmetricState + + +class TestRevision33Compatibility(object): + def test_noise_protocol_accepts_slash(self): + class FakeSHA3_256(): + fn = None + + noise_name = b"Noise_NN_25519_AESGCM_SHA3/256" + modified_class = NoiseProtocol + modified_class.methods['hash']['SHA3/256'] = FakeSHA3_256 # Add callable to hash functions mapping + modified_class(noise_name) + + def test_cipher_state_set_nonce(self): + noise_protocol = NoiseProtocol(b"Noise_NN_25519_AESGCM_SHA256") + cipher_state = CipherState(noise_protocol) + cipher_state.initialize_key(b'\x00'*32) + assert cipher_state.n == 0 + cipher_state.set_nonce(42) + assert cipher_state.n == 42 + + def test_symmetric_state_get_handshake_hash(self): + symmetric_state = SymmetricState() + symmetric_state.h = 42 + assert symmetric_state.get_handshake_hash() == 42 From f5e892fc96ad9cc6fb5f163b843bbe857576867c Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Sat, 14 Oct 2017 18:07:40 +0200 Subject: [PATCH 09/10] Update documentation generation Added information about documentation in README Closes #8 --- README.md | 5 +++++ docs/index.rst | 7 +------ noise/state.py | 20 ++++++++++++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 06307e2..9be2b23 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ noiseprotocol ============= [![Build Status](https://travis-ci.org/plizonczyk/noiseprotocol.svg?branch=master)](https://travis-ci.org/plizonczyk/noiseprotocol) [![PyPI](https://img.shields.io/pypi/v/noiseprotocol.svg)](https://pypi.python.org/pypi/noiseprotocol) +[![Documentation Status](https://readthedocs.org/projects/noiseprotocol/badge/)](http://noiseprotocol.readthedocs.io/) This repository contains source code of **noiseprotocol** - a Python 3 implementation of [Noise Protocol Framework](http://www.noiseprotocol.org/). Compatible with revisions 32 and 33. @@ -10,6 +11,10 @@ Compatible with revisions 32 and 33. This package shall not be used (yet) for production purposes. There was little to none peer review done so far. Use common sense while using - until this package becomes stable. +## Documentation +Available on [Read the Docs](https://noiseprotocol.readthedocs.io). For now it provides basic documentation on +HandshakeState, CipherState and SymmetricState. Refer to the rest of the README below for more information. + ## Installation and prerequisites For now, only Python 3.5+ is supported. diff --git a/docs/index.rst b/docs/index.rst index 6ca97c5..7cec576 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,15 +14,10 @@ Welcome to noiseprotocol's documentation! Documentation for the Code ************************** -.. automodule:: noise.builder - :members: - -.. automodule:: noise.functions - :members: - .. automodule:: noise.state :members: + Indices and tables ================== diff --git a/noise/state.py b/noise/state.py index 4bffad4..63e53b6 100644 --- a/noise/state.py +++ b/noise/state.py @@ -6,7 +6,7 @@ from .constants import Empty, TOKEN_E, TOKEN_S, TOKEN_EE, TOKEN_ES, TOKEN_SE, TO class CipherState(object): """ - Implemented as per Noise Protocol specification (rev 32) - paragraph 5.1. + Implemented as per Noise Protocol specification - paragraph 5.1. The initialize_key() function takes additional required argument - noise_protocol. @@ -20,6 +20,7 @@ class CipherState(object): def initialize_key(self, key): """ + :param key: Key to set within CipherState """ self.k = key @@ -29,6 +30,7 @@ class CipherState(object): def has_key(self): """ + :return: True if self.k is not an instance of Empty """ return not isinstance(self.k, Empty) @@ -39,6 +41,7 @@ class CipherState(object): def encrypt_with_ad(self, ad: bytes, plaintext: bytes) -> bytes: """ If k is non-empty returns ENCRYPT(k, n++, ad, plaintext). Otherwise returns plaintext. + :param ad: bytes sequence :param plaintext: bytes sequence :return: ciphertext bytes sequence @@ -57,6 +60,7 @@ class CipherState(object): """ If k is non-empty returns DECRYPT(k, n++, ad, ciphertext). Otherwise returns ciphertext. If an authentication failure occurs in DECRYPT() then n is not incremented and an error is signaled to the caller. + :param ad: bytes sequence :param ciphertext: bytes sequence :return: plaintext bytes sequence @@ -78,7 +82,7 @@ class CipherState(object): class SymmetricState(object): """ - Implemented as per Noise Protocol specification (rev 32) - paragraph 5.2. + Implemented as per Noise Protocol specification - paragraph 5.2. The initialize_symmetric function takes different required argument - noise_protocol, which contains protocol_name. """ @@ -95,6 +99,7 @@ class SymmetricState(object): protocol name and crypto functions Comments below are mostly copied from specification. + :param noise_protocol: a valid NoiseProtocol instance :return: initialised SymmetricState instance """ @@ -121,6 +126,7 @@ class SymmetricState(object): def mix_key(self, input_key_material: bytes): """ + :param input_key_material: :return: """ @@ -136,6 +142,7 @@ class SymmetricState(object): def mix_hash(self, data: bytes): """ Sets h = HASH(h + data). + :param data: bytes sequence """ self.h = self.noise_protocol.hash_fn.hash(self.h + data) @@ -158,6 +165,7 @@ class SymmetricState(object): """ Sets ciphertext = EncryptWithAd(h, plaintext), calls MixHash(ciphertext), and returns ciphertext. Note that if k is empty, the EncryptWithAd() call will set ciphertext equal to plaintext. + :param plaintext: bytes sequence :return: ciphertext bytes sequence """ @@ -169,6 +177,7 @@ class SymmetricState(object): """ Sets plaintext = DecryptWithAd(h, ciphertext), calls MixHash(ciphertext), and returns plaintext. Note that if k is empty, the DecryptWithAd() call will set plaintext equal to ciphertext. + :param ciphertext: bytes sequence :return: plaintext bytes sequence """ @@ -179,6 +188,7 @@ class SymmetricState(object): def split(self): """ Returns a pair of CipherState objects for encrypting/decrypting transport messages. + :return: tuple (CipherState, CipherState) """ # Sets temp_k1, temp_k2 = HKDF(ck, b'', 2). @@ -209,7 +219,7 @@ class SymmetricState(object): class HandshakeState(object): """ - Implemented as per Noise Protocol specification (rev 32) - paragraph 5.3. + Implemented as per Noise Protocol specification - paragraph 5.3. The initialize() function takes different required argument - noise_protocol, which contains handshake_pattern. """ @@ -235,7 +245,7 @@ class HandshakeState(object): :param noise_protocol: a valid NoiseProtocol instance :param initiator: boolean indicating the initiator or responder role :param prologue: byte sequence which may be zero-length, or which may contain context information that both - parties want to confirm is identical + parties want to confirm is identical :param s: local static key pair :param e: local ephemeral key pair :param rs: remote party’s static public key @@ -282,6 +292,7 @@ class HandshakeState(object): def write_message(self, payload: Union[bytes, bytearray], message_buffer: bytearray): """ Comments below are mostly copied from specification. + :param payload: byte sequence which may be zero-length :param message_buffer: buffer-like object :return: None or result of SymmetricState.split() - tuple (CipherState, CipherState) @@ -340,6 +351,7 @@ class HandshakeState(object): def read_message(self, message: Union[bytes, bytearray], payload_buffer: bytearray): """ Comments below are mostly copied from specification. + :param message: byte sequence containing a Noise handshake message :param payload_buffer: buffer-like object :return: None or result of SymmetricState.split() - tuple (CipherState, CipherState) From 2aa9166f9d1a2a8e058ba5bc37bdd214f67326e1 Mon Sep 17 00:00:00 2001 From: Piotr Lizonczyk Date: Mon, 30 Oct 2017 21:37:12 +0100 Subject: [PATCH 10/10] Improved README with responder example. Closes #11 --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9be2b23..b1612ed 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,16 @@ NoiseBuilder class provides highest level of abstraction for the package. You ca through this class' interfaces. An example for setting up NoiseBuilder could look like this: ```python +import socket + from noise.builder import NoiseBuilder +sock = socket.socket() +sock.connect(('localhost', 2000)) + # Create instance of NoiseBuilder, set up to use NN handshake pattern, Curve25519 for # elliptic curve keypair, ChaCha20Poly1305 as cipher function and SHA256 for hashing. -proto = NoiseBuilder.from_name('Noise_NN_25519_ChaChaPoly_SHA256') +proto = NoiseBuilder.from_name(b'Noise_NN_25519_ChaChaPoly_SHA256') # Set role in this connection as initiator proto.set_as_initiator() @@ -47,20 +52,62 @@ proto.start_handshake() message = proto.write_message() # Send the message to the responder - you may simply use sockets or any other way # to exchange bytes between communicating parties. -# For clarity - we omit socket creation in this example. -sock.send(message) +sock.sendall(message) # Receive the message from the responder -received = sock.recv() +received = sock.recv(2048) # Feed the received message into noise payload = proto.read_message(received) # As of now, the handshake should be finished (as we are using NN pattern). # Any further calls to write_message or read_message would raise NoiseHandshakeError exception. # We can use encrypt/decrypt methods of NoiseBuilder now for encryption and decryption of messages. -encrypted_message = proto.encrypt('This is an example payload') +encrypted_message = proto.encrypt(b'This is an example payload') +sock.sendall(encrypted_message) -ciphertext = sock.recv() +ciphertext = sock.recv(2048) plaintext = proto.decrypt(ciphertext) +print(plaintext) +``` + +The example above covers the connection from the initiator's ("client") point of view. The snippet below is an example of responder's code ("server") using a socket connection to send and receive ciphertext. + +```python +import socket +from itertools import cycle + +from noise.builder import NoiseBuilder + +if __name__ == '__main__': + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('localhost', 2000)) + s.listen(1) + + conn, addr = s.accept() + print('Accepted connection from', addr) + + noise = NoiseBuilder.from_name(b'Noise_NN_25519_ChaChaPoly_SHA256') + noise.set_as_responder() + noise.start_handshake() + + # Perform handshake. Break when finished + for action in cycle(['receive', 'send']): + if noise.handshake_finished: + break + elif action == 'send': + ciphertext = noise.write_message() + conn.sendall(ciphertext) + elif action == 'receive': + data = conn.recv(2048) + plaintext = noise.read_message(data) + + # Endless loop "echoing" received data + while True: + data = conn.recv(2048) + if not data: + break + received = noise.decrypt(data) + conn.sendall(noise.encrypt(received)) ``` #### Wireguard integration example