pax_global_header00006660000000000000000000000064137270264270014524gustar00rootroot0000000000000052 comment=56916f87c6b49ff90b5e97a65e22c0c9581744d8 idlesign-srptools-019adc7/000077500000000000000000000000001372702642700156175ustar00rootroot00000000000000idlesign-srptools-019adc7/.coveragerc000066400000000000000000000000601372702642700177340ustar00rootroot00000000000000[run] source = srptools/ omit = srptools/cli.py idlesign-srptools-019adc7/.gitignore000066400000000000000000000001221372702642700176020ustar00rootroot00000000000000.project .pydevproject .idea .tox __pycache__ *.pyc *.pyo *.egg-info docs/_build/ idlesign-srptools-019adc7/.travis.yml000066400000000000000000000003351372702642700177310ustar00rootroot00000000000000dist: xenial language: python sudo: false python: - 3.7 - 3.5 - 3.6 install: - pip install pytest coverage coveralls six script: - coverage run --source=srptools setup.py test after_success: - coveralls idlesign-srptools-019adc7/AUTHORS000066400000000000000000000002751372702642700166730ustar00rootroot00000000000000srptools authors ================ Created by Igor `idle sign` Starikov. Contributors ------------ Bouke Haarsma Michael Zimmermann idlesign-srptools-019adc7/CHANGELOG000066400000000000000000000011251372702642700170300ustar00rootroot00000000000000srptools changelog ================== v1.0.1 [2020-09-12] ------------------- ! Dropped QA for Python 2. * Now context.get_common_password_hash handles inner hashes with leading zeros correctly. v1.0.0 ------ ! Dropped QA for Python 3.3 and 3.4. * No functional changes. Celebrating 1.0.0. v0.2.0 ------ * Fixed unable to install into envs with 'six' unavailable + Added 'preset' CLI option * Improved compatibility with other implementations. v0.1.1 ------ * Hex strings are now even-length. * Fixed shareable hashed values with leading zeros issue. v0.1.0 ------ + Basic functionality.idlesign-srptools-019adc7/INSTALL000066400000000000000000000007011372702642700166460ustar00rootroot00000000000000srptools installation ===================== Python ``pip`` package is required to install ``srptools``. From sources ------------ Use the following command line to install ``srptools`` from sources directory (containing setup.py): pip install . or python setup.py install From PyPI --------- Alternatively you can install ``srptools`` from PyPI: pip install srptools Use `-U` flag for upgrade: pip install -U srptools idlesign-srptools-019adc7/LICENSE000066400000000000000000000027341372702642700166320ustar00rootroot00000000000000Copyright (c) 2017-2019, Igor `idle sign` Starikov All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the srptools nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. idlesign-srptools-019adc7/MANIFEST.in000066400000000000000000000004611372702642700173560ustar00rootroot00000000000000include AUTHORS include CHANGELOG include INSTALL include LICENSE include README.rst include docs/Makefile recursive-include docs *.rst recursive-include docs *.py recursive-include bin * recursive-include tests * recursive-exclude * __pycache__ recursive-exclude * *.py[co] recursive-exclude * empty idlesign-srptools-019adc7/README.rst000066400000000000000000000125541372702642700173150ustar00rootroot00000000000000srptools ======== https://github.com/idlesign/srptools .. image:: https://idlesign.github.io/lbc/py2-lbc.svg :target: https://idlesign.github.io/lbc/ :alt: LBC Python 2 ---- |release| |stats| |lic| |ci| |coverage| |health| .. |release| image:: https://img.shields.io/pypi/v/srptools.svg :target: https://pypi.python.org/pypi/srptools .. |stats| image:: https://img.shields.io/pypi/dm/srptools.svg :target: https://pypi.python.org/pypi/srptools .. |lic| image:: https://img.shields.io/pypi/l/srptools.svg :target: https://pypi.python.org/pypi/srptools .. |ci| image:: https://img.shields.io/travis/idlesign/srptools/master.svg :target: https://travis-ci.org/idlesign/srptools .. |coverage| image:: https://img.shields.io/coveralls/idlesign/srptools/master.svg :target: https://coveralls.io/r/idlesign/srptools .. |health| image:: https://landscape.io/github/idlesign/srptools/master/landscape.svg?style=flat :target: https://landscape.io/github/idlesign/srptools/master Description ----------- *Tools to implement Secure Remote Password (SRP) authentication* SRP is a secure password-based authentication and key-exchange protocol - a password-authenticated key agreement protocol (PAKE). This package contains protocol implementation for Python 2 and 3. You may import it into you applications and use its API or you may use ``srptools`` command-line utility (CLI): CLI usage --------- Command-line utility requires ``click`` package to be installed. Basic scenario: .. code-block:: > srptools get_user_data_triplet > srptools server get_private_and_public > srptools client get_private_and_public > srptools client get_session_data > srptools server get_session_data Help is available: .. code-block:: > srptools --help API usage --------- Preliminary step. Agree on communication details: .. code-block:: python from srptools import SRPContext context = SRPContext('alice', 'password123') username, password_verifier, salt = context.get_user_data_triplet() prime = context.prime gen = context.generator Simplified workflow: .. code-block:: python from srptools import SRPContext, SRPServerSession, SRPClientSession # Receive username from client and generate server public. server_session = SRPServerSession(SRPContext(username, prime=prime, generator=gen), password_verifier) server_public = server_session.public # Receive server public and salt and process them. client_session = SRPClientSession(SRPContext('alice', 'password123', prime=prime, generator=gen)) client_session.process(server_public, salt) # Generate client public and session key. client_public = client_session.public # Process client public and compare session keys. server_session.process(client_public, salt) assert server_session.key == client_session.key Extended workflow .. code-block:: python from srptools import SRPContext, SRPServerSession, SRPClientSession # Receive username from client and generate server public. server_session = SRPServerSession(SRPContext(username, prime=prime, generator=gen), password_verifier) server_public = server_session.public # Receive server public and salt and process them. client_session = SRPClientSession(SRPContext('alice', 'password123', prime=prime, generator=gen)) client_session.process(server_public, salt) # Generate client public and session key proof. client_public = client_session.public client_session_key_proof = client_session.key_proof # Process client public and verify session key proof. server_session.process(client_public, salt) assert server_session.verify_proof(client_session_key_proof) # Generate session key proof hash. server_session_key_proof_hash = client_session.key_proof_hash # Verify session key proof hash received from server. assert client_session.verify_proof(server_session_key_proof_hash) Usage hints ----------- * ``srptools.constants`` contains basic constants which can be used with ``SRPContext`` for server and client to agree upon communication details. * ``.process()`` methods of session classes may raise ``SRPException`` in certain circumstances. Auth process on such occasions must be stopped. * ``.private`` attribute of session classes may be used to restore sessions: .. code-block:: python server_private = server_session.private # Restore session on new request. server_session = SRPServerSession(context, password_verifier, private=server_private) * ``SRPContext`` is rather flexible, so you can implement some custom server/client session logic with its help. * Basic values are represented as hex strings but base64 encoded values are also supported: .. code-block:: python server_public = server_session.public_b64 # Receive server public and salt and process them. client_session = SRPClientSession(SRPContext('alice', 'password123', prime=prime, generator=gen)) client_session.process(server_public, salt, base64=True) # Use srptools.hex_from_b64() to represent base64 value as hex. server_public_hex = hex_from_b64(server_public) Links ----- * RFC 2945 - The SRP Authentication and Key Exchange System https://tools.ietf.org/html/rfc2945 * RFC 5054 - Using the Secure Remote Password (SRP) Protocol for TLS Authentication https://tools.ietf.org/html/rfc5054 idlesign-srptools-019adc7/setup.cfg000066400000000000000000000001351372702642700174370ustar00rootroot00000000000000[aliases] release = clean --all sdist bdist_wheel upload test = pytest [wheel] universal = 1idlesign-srptools-019adc7/setup.py000066400000000000000000000037411372702642700173360ustar00rootroot00000000000000import os import io import re import sys from setuptools import setup, find_packages PATH_BASE = os.path.dirname(__file__) PYTEST_RUNNER = ['pytest-runner'] if 'test' in sys.argv else [] def read(fpath): return io.open(fpath).read() def get_version(): """Reads version number. This workaround is required since __init__ is an entry point exposing stuff from other modules, which may use dependencies unavailable in current environment, which in turn will prevent this application from install. """ contents = read(os.path.join(PATH_BASE, 'srptools', '__init__.py')) version = re.search('VERSION = \(([^)]+)\)', contents) version = version.group(1).replace(', ', '.').strip() return version setup( name='srptools', version=get_version(), url='https://github.com/idlesign/srptools', description='Tools to implement Secure Remote Password (SRP) authentication', long_description=read(os.path.join(PATH_BASE, 'README.rst')), license='BSD 3-Clause License', author='Igor `idle sign` Starikov', author_email='idlesign@yandex.ru', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=['six'], setup_requires=[] + PYTEST_RUNNER, extras_require={'cli': ['click>=2.0']}, tests_require=['pytest'], entry_points={'console_scripts': ['srptools = srptools.cli:main']}, test_suite='tests', classifiers=[ # As in https://pypi.python.org/pypi?:action=list_classifiers 'Development Status :: 5 - Production/Stable', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'License :: OSI Approved :: BSD License' ], ) idlesign-srptools-019adc7/srptools/000077500000000000000000000000001372702642700175045ustar00rootroot00000000000000idlesign-srptools-019adc7/srptools/__init__.py000066400000000000000000000003041372702642700216120ustar00rootroot00000000000000from .context import SRPContext from .client import SRPClientSession from .server import SRPServerSession from .exceptions import SRPException from .utils import hex_from_b64 VERSION = (1, 0, 1)idlesign-srptools-019adc7/srptools/cli.py000066400000000000000000000107031372702642700206260ustar00rootroot00000000000000from __future__ import division from collections import OrderedDict import click from srptools import VERSION, SRPContext, SRPServerSession, SRPClientSession, hex_from_b64 from srptools.constants import * PRESETS = OrderedDict([ ('1024', (PRIME_1024, PRIME_1024_GEN)), ('1536', (PRIME_1536, PRIME_1536_GEN)), ('2048', (PRIME_2048, PRIME_2048_GEN)), ('3072', (PRIME_3072, PRIME_3072_GEN)), ('4096', (PRIME_4096, PRIME_4096_GEN)), ('6144', (PRIME_6144, PRIME_6144_GEN)), ]) @click.group() @click.version_option(version='.'.join(map(str, VERSION))) def base(): """srptools command line utility. Tools to implement Secure Remote Password (SRP) authentication. Basic scenario: > srptools get_user_data_triplet > srptools server get_private_and_public > srptools client get_private_and_public > srptools client get_session_data > srptools server get_session_data """ def common_options(func): """Commonly used command options.""" def parse_preset(ctx, param, value): return PRESETS.get(value, (None, None)) def parse_private(ctx, param, value): return hex_from_b64(value) if value else None func = click.option('--private', default=None, help='Private.', callback=parse_private)(func) func = click.option( '--preset', default=None, help='Preset ID defining prime and generator pair.', type=click.Choice(PRESETS.keys()), callback=parse_preset )(func) return func @base.group() def server(): """Server session related commands.""" @base.group() def client(): """Client session related commands.""" @server.command() @click.argument('username') @click.argument('password_verifier') @common_options def get_private_and_public(username, password_verifier, private, preset): """Print out server public and private.""" session = SRPServerSession( SRPContext(username, prime=preset[0], generator=preset[1]), hex_from_b64(password_verifier), private=private) click.secho('Server private: %s' % session.private_b64) click.secho('Server public: %s' % session.public_b64) @server.command() @click.argument('username') @click.argument('password_verifier') @click.argument('salt') @click.argument('client_public') @common_options def get_session_data( username, password_verifier, salt, client_public, private, preset): """Print out server session data.""" session = SRPServerSession( SRPContext(username, prime=preset[0], generator=preset[1]), hex_from_b64(password_verifier), private=private) session.process(client_public, salt, base64=True) click.secho('Server session key: %s' % session.key_b64) click.secho('Server session key proof: %s' % session.key_proof_b64) click.secho('Server session key hash: %s' % session.key_proof_hash_b64) @client.command() @click.argument('username') @click.argument('password') @common_options def get_private_and_public(ctx, username, password, private, preset): """Print out server public and private.""" session = SRPClientSession( SRPContext(username, password, prime=preset[0], generator=preset[1]), private=private) click.secho('Client private: %s' % session.private_b64) click.secho('Client public: %s' % session.public_b64) @client.command() @click.argument('username') @click.argument('password') @click.argument('salt') @click.argument('server_public') @common_options def get_session_data(ctx, username, password, salt, server_public, private, preset): """Print out client session data.""" session = SRPClientSession( SRPContext(username, password, prime=preset[0], generator=preset[1]), private=private) session.process(server_public, salt, base64=True) click.secho('Client session key: %s' % session.key_b64) click.secho('Client session key proof: %s' % session.key_proof_b64) click.secho('Client session key hash: %s' % session.key_proof_hash_b64) @base.command() @click.argument('username') @click.argument('password') def get_user_data_triplet(username, password): """Print out user data triplet: username, password verifier, salt.""" context = SRPContext(username, password) username, password_verifier, salt = context.get_user_data_triplet(base64=True) click.secho('Username: %s' % username) click.secho('Password verifier: %s' % password_verifier) click.secho('Salt: %s' % salt) def main(): """ CLI entry point """ base(obj={}) idlesign-srptools-019adc7/srptools/client.py000066400000000000000000000024631372702642700213410ustar00rootroot00000000000000from __future__ import unicode_literals from .common import SRPSessionBase if False: # pragma: no cover from .context import SRPContext class SRPClientSession(SRPSessionBase): role = 'client' def __init__(self, srp_context, private=None): """ :param SRPContext srp_context: :param st|unicode private: """ super(SRPClientSession, self).__init__(srp_context, private) self._password_hash = None if not private: self._this_private = srp_context.generate_client_private() self._client_public = srp_context.get_client_public(self._this_private) def init_base(self, salt): super(SRPClientSession, self).init_base(salt) self._password_hash = self._context.get_common_password_hash(self._salt) def init_session_key(self): super(SRPClientSession, self).init_session_key() premaster_secret = self._context.get_client_premaster_secret( self._password_hash, self._server_public, self._this_private, self._common_secret) self._key = self._context.get_common_session_key(premaster_secret) def verify_proof(self, key_proof, base64=False): super(SRPClientSession, self).verify_proof(key_proof) return self._value_decode(key_proof, base64) == self.key_proof_hash idlesign-srptools-019adc7/srptools/common.py000066400000000000000000000073401372702642700213520ustar00rootroot00000000000000from __future__ import unicode_literals from binascii import unhexlify from .utils import hex_from, int_from_hex, hex_from_b64, value_encode, b64_from from .exceptions import SRPException if False: # pragma: no cover from .context import SRPContext class SRPSessionBase(object): """Base session class for server and client.""" role = None def __init__(self, srp_context, private=None): """ :param SRPContext srp_context: :param str|unicode private: """ self._context = srp_context self._salt = None # type: bytes self._common_secret = None # type: int self._key = None # type: bytes self._key_proof = None # type: bytes self._key_proof_hash = None # type: bytes self._server_public = None # type: int self._client_public = None # type: int self._this_private = None # type: int if private: self._this_private = int_from_hex(private) # type: int @property def _this_public(self): return getattr(self, '_%s_public' % self.role) def _other_public(self, val): other = ('server' if self.role == 'client' else 'client') setattr(self, '_%s_public' % other, val) _other_public = property(None, _other_public) @property def private(self): return hex_from(self._this_private) @property def private_b64(self): return b64_from(self._this_private) @property def public(self): return hex_from(self._this_public) @property def public_b64(self): return b64_from(self._this_public) @property def key(self): return hex_from(self._key) @property def key_b64(self): return b64_from(self._key) @property def key_proof(self): return hex_from(self._key_proof) @property def key_proof_b64(self): return b64_from(self._key_proof) @property def key_proof_hash(self): return hex_from(self._key_proof_hash) @property def key_proof_hash_b64(self): return b64_from(self._key_proof_hash) @classmethod def _value_decode(cls, value, base64=False): """Decodes value into hex optionally from base64""" return hex_from_b64(value) if base64 else value def process(self, other_public, salt, base64=False): salt = self._value_decode(salt, base64) other_public = self._value_decode(other_public, base64) self.init_base(salt) self.init_common_secret(other_public) self.init_session_key() self.init_session_key_proof() key = value_encode(self._key, base64) key_proof = value_encode(self._key_proof, base64) key_proof_hash = value_encode(self._key_proof_hash, base64) return key, key_proof, key_proof_hash def init_base(self, salt): salt = unhexlify(salt) self._salt = salt def init_session_key(self): """""" def verify_proof(self, key_prove, base64=False): """""" def init_common_secret(self, other_public): other_public = int_from_hex(other_public) if other_public % self._context._prime == 0: # A % N is zero | B % N is zero raise SRPException('Wrong public provided for %s.' % self.__class__.__name__) self._other_public = other_public self._common_secret = self._context.get_common_secret(self._server_public, self._client_public) def init_session_key_proof(self): proof = self._context.get_common_session_key_proof( self._key, self._salt, self._server_public, self._client_public) self._key_proof = proof self._key_proof_hash = self._context.get_common_session_key_proof_hash(self._key, proof, self._client_public) idlesign-srptools-019adc7/srptools/constants.py000066400000000000000000000115631372702642700221000ustar00rootroot00000000000000from __future__ import unicode_literals import hashlib HASH_SHA_1 = hashlib.sha1 HASH_SHA_256 = hashlib.sha256 PRIME_1024_GEN = '2' PRIME_1024 = '''\ EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF74\ 96EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6\ CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4\ 976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3''' PRIME_1536_GEN = '2' PRIME_1536 = '''\ 9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F55\ 6E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D0\ 8134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E\ 2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE\ 837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93\ 499A234DCF76E3FED135F9BB''' PRIME_2048_GEN = '2' PRIME_2048 = '''\ AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CB\ B4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0\ CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740A\ DBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481\ F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDB\ F52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C382\ 71AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F\ 9E4AFF73 ''' PRIME_3072_GEN = '5' PRIME_3072 = '''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA6\ 3B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245\ E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F2411\ 7C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F\ 83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08\ CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9\ DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7\ ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D8760273\ 3EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB31\ 43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF''' PRIME_4096_GEN = '5' PRIME_4096 = '''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA6\ 3B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245\ E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F2411\ 7C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F\ 83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08\ CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9\ DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7\ ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D8760273\ 3EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB31\ 43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C32718\ 6AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6\ 287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD76\ 2170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199\ FFFFFFFFFFFFFFFF''' PRIME_6144_GEN = '5' PRIME_6144 = '''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA6\ 3B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245\ E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F2411\ 7C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F\ 83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08\ CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9\ DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7\ ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D8760273\ 3EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB31\ 43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C32718\ 6AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6\ 287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD76\ 2170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492\ 36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F\ 413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B\ DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15\ D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3\ 23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED2\ 0F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C\ DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E\ 6DCC4024FFFFFFFFFFFFFFFF''' idlesign-srptools-019adc7/srptools/context.py000066400000000000000000000171751372702642700215550ustar00rootroot00000000000000from __future__ import unicode_literals from random import SystemRandom as random from six import integer_types, PY3 from .utils import int_from_hex, int_to_bytes, hex_from, value_encode, b64_from from .constants import PRIME_1024, PRIME_1024_GEN, HASH_SHA_1 from .exceptions import SRPException class SRPContext(object): """ * The SRP Authentication and Key Exchange System https://tools.ietf.org/html/rfc2945 * Using the Secure Remote Password (SRP) Protocol for TLS Authentication https://tools.ietf.org/html/rfc5054 """ def __init__( self, username, password=None, prime=None, generator=None, hash_func=None, multiplier=None, bits_random=1024, bits_salt=64): """ :param str|unicode username: User name :param str|unicode password: User _password :param str|unicode|None prime: Prime hex string . Default: PRIME_1024 :param str|unicode|None generator: Generator hex string. Default: PRIME_1024_GEN :param str|unicode hash_func: Function to calculate hash. Default: HASH_SHA_1 :param str|unicode multiplier: Multiplier hex string. If not given will be calculated automatically using _prime and _gen. :param int bits_random: Random value bits. Default: 1024 :param int bits_salt: Salt value bits. Default: 64 """ self._hash_func = hash_func or HASH_SHA_1 # H self._user = username # I self._password = password # p self._gen = int_from_hex(generator or PRIME_1024_GEN) # g self._prime = int_from_hex(prime or PRIME_1024) # N self._mult = ( # k = H(N | PAD(g)) int_from_hex(multiplier) if multiplier else self.hash(self._prime, self.pad(self._gen))) self._bits_salt = bits_salt self._bits_random = bits_random @property def generator(self): return hex_from(self._gen) @property def generator_b64(self): return b64_from(self._gen) @property def prime(self): return hex_from(self._prime) @property def prime_b64(self): return b64_from(self._prime) def pad(self, val): """ :param val: :rtype: bytes """ padding = len(int_to_bytes(self._prime)) padded = int_to_bytes(val).rjust(padding, b'\x00') return padded def hash(self, *args, **kwargs): """ :param args: :param kwargs: joiner - string to join values (args) as_bytes - bool to return hash bytes instead of default int :rtype: int|bytes """ joiner = kwargs.get('joiner', '').encode('utf-8') as_bytes = kwargs.get('as_bytes', False) def conv(arg): if isinstance(arg, integer_types): arg = int_to_bytes(arg) if PY3: if isinstance(arg, str): arg = arg.encode('utf-8') return arg return str(arg) digest = joiner.join(map(conv, args)) hash_obj = self._hash_func(digest) if as_bytes: return hash_obj.digest() return int_from_hex(hash_obj.hexdigest()) def generate_random(self, bits_len=None): """Generates a random value. :param int bits_len: :rtype: int """ bits_len = bits_len or self._bits_random return random().getrandbits(bits_len) def generate_salt(self): """s = random :rtype: int """ return self.generate_random(self._bits_salt) def get_common_secret(self, server_public, client_public): """u = H(PAD(A) | PAD(B)) :param int server_public: :param int client_public: :rtype: int """ return self.hash(self.pad(client_public), self.pad(server_public)) def get_client_premaster_secret(self, password_hash, server_public, client_private, common_secret): """S = (B - (k * g^x)) ^ (a + (u * x)) % N :param int server_public: :param int password_hash: :param int client_private: :param int common_secret: :rtype: int """ password_verifier = self.get_common_password_verifier(password_hash) return pow( (server_public - (self._mult * password_verifier)), (client_private + (common_secret * password_hash)), self._prime) def get_common_session_key(self, premaster_secret): """K = H(S) :param int premaster_secret: :rtype: bytes """ return self.hash(premaster_secret, as_bytes=True) def get_server_premaster_secret(self, password_verifier, server_private, client_public, common_secret): """S = (A * v^u) ^ b % N :param int password_verifier: :param int server_private: :param int client_public: :param int common_secret: :rtype: int """ return pow((client_public * pow(password_verifier, common_secret, self._prime)), server_private, self._prime) def generate_client_private(self): """a = random() :rtype: int """ return self.generate_random() def generate_server_private(self): """b = random() :rtype: int """ return self.generate_random() def get_client_public(self, client_private): """A = g^a % N :param int client_private: :rtype: int """ return pow(self._gen, client_private, self._prime) def get_server_public(self, password_verifier, server_private): """B = (k*v + g^b) % N :param int password_verifier: :param int server_private: :rtype: int """ return ((self._mult * password_verifier) + pow(self._gen, server_private, self._prime)) % self._prime def get_common_password_hash(self, salt): """x = H(s | H(I | ":" | P)) :param int salt: :rtype: int """ password = self._password if password is None: raise SRPException('User password should be in context for this scenario.') return self.hash(salt, self.hash(self._user, password, joiner=':', as_bytes=True)) def get_common_password_verifier(self, password_hash): """v = g^x % N :param int password_hash: :rtype: int """ return pow(self._gen, password_hash, self._prime) def get_common_session_key_proof(self, session_key, salt, server_public, client_public): """M = H(H(N) XOR H(g) | H(U) | s | A | B | K) :param bytes session_key: :param int salt: :param int server_public: :param int client_public: :rtype: bytes """ h = self.hash prove = h( h(self._prime) ^ h(self._gen), h(self._user), salt, client_public, server_public, session_key, as_bytes=True ) return prove def get_common_session_key_proof_hash(self, session_key, session_key_proof, client_public): """H(A | M | K) :param bytes session_key: :param bytes session_key_proof: :param int client_public: :rtype: bytes """ return self.hash(client_public, session_key_proof, session_key, as_bytes=True) def get_user_data_triplet(self, base64=False): """( <_user>, <_password verifier>, ) :param base64: :rtype: tuple """ salt = self.generate_salt() verifier = self.get_common_password_verifier(self.get_common_password_hash(salt)) verifier = value_encode(verifier, base64) salt = value_encode(salt, base64) return self._user, verifier, salt idlesign-srptools-019adc7/srptools/exceptions.py000066400000000000000000000001621372702642700222360ustar00rootroot00000000000000from __future__ import unicode_literals class SRPException(Exception): """Base srptools exception class.""" idlesign-srptools-019adc7/srptools/server.py000066400000000000000000000024421372702642700213660ustar00rootroot00000000000000from __future__ import unicode_literals from .common import SRPSessionBase from .utils import int_from_hex if False: # pragma: no cover from .context import SRPContext class SRPServerSession(SRPSessionBase): role = 'server' def __init__(self, srp_context, password_verifier, private=None): """ :param SRPContext srp_context: :param st|unicode password_verifier: :param st|unicode private: """ super(SRPServerSession, self).__init__(srp_context, private) self._password_verifier = int_from_hex(password_verifier) if not private: self._this_private = srp_context.generate_server_private() self._server_public = srp_context.get_server_public(self._password_verifier, self._this_private) def init_session_key(self): super(SRPServerSession, self).init_session_key() premaster_secret = self._context.get_server_premaster_secret( self._password_verifier, self._this_private, self._client_public, self._common_secret) self._key = self._context.get_common_session_key(premaster_secret) def verify_proof(self, key_proof, base64=False): super(SRPServerSession, self).verify_proof(key_proof) return self._value_decode(key_proof, base64) == self.key_proof idlesign-srptools-019adc7/srptools/utils.py000066400000000000000000000026511372702642700212220ustar00rootroot00000000000000from __future__ import unicode_literals from binascii import unhexlify, hexlify from base64 import b64encode, b64decode from six import integer_types def value_encode(val, base64=False): """Encodes int into hex or base64.""" return b64_from(val) if base64 else hex_from(val) def hex_from_b64(val): """Returns hex string representation for base64 encoded value. :param str val: :rtype: bytes|str """ return hex_from(b64decode(val)) def hex_from(val): """Returns hex string representation for a given value. :param bytes|str|unicode|int|long val: :rtype: bytes|str """ if isinstance(val, integer_types): hex_str = '%x' % val if len(hex_str) % 2: hex_str = '0' + hex_str return hex_str return hexlify(val) def int_from_hex(hexstr): """Returns int/long representation for a given hex string. :param bytes|str|unicode hexstr: :rtype: int|long """ return int(hexstr, 16) def int_to_bytes(val): """Returns bytes representation for a given int/long. :param int|long val: :rtype: bytes|str """ hex_str = hex_from(val) return unhexlify(hex_str) def b64_from(val): """Returns base64 encoded bytes for a given int/long/bytes value. :param int|long|bytes val: :rtype: bytes|str """ if isinstance(val, integer_types): val = int_to_bytes(val) return b64encode(val).decode('ascii') idlesign-srptools-019adc7/tests/000077500000000000000000000000001372702642700167615ustar00rootroot00000000000000idlesign-srptools-019adc7/tests/test_client_server.py000066400000000000000000000103601372702642700232360ustar00rootroot00000000000000from __future__ import unicode_literals import pytest from srptools import SRPContext, SRPClientSession, SRPServerSession, SRPException from srptools.utils import int_from_hex, value_encode def test_extended(): # Preliminary steps. context = SRPContext('alice', 'password123') # Generate basic user auth data usually stored on server. username, password_verifier, salt = context.get_user_data_triplet() # And gather basic numbers for client and server to agree upon. prime = context.prime gen = context.generator salt_b64 = value_encode(int_from_hex(salt), base64=True) # Actual negotiation # Receive username from client and generate server public. server_session = SRPServerSession(SRPContext(username, prime=prime, generator=gen), password_verifier) server_public = server_session.public server_public_b64 = server_session.public_b64 server_private = server_session.private assert server_session.private_b64 # Receive server public and salt and process them. client_session = SRPClientSession(SRPContext(username, 'password123', prime=prime, generator=gen)) client_session.process(server_public, salt) # Generate client public and session key proof. client_public = client_session.public client_public_b64 = client_session.public_b64 client_session_key_proof = client_session.key_proof client_private = client_session.private assert client_session.private_b64 # Process client public and verify session key proof. server_session.process(client_public, salt) assert server_session.verify_proof(client_session_key_proof) # Generate session key proof hash. server_session_key_proof_hash = client_session.key_proof_hash # Verify session key proff hash received from server. assert client_session.verify_proof(server_session_key_proof_hash) assert client_session.key_b64 assert client_session.key_proof_b64 assert client_session.key_proof_hash_b64 # Restore sessions from privates. server_session = SRPServerSession( SRPContext(username, prime=prime, generator=gen), password_verifier, private=server_private) client_session = SRPClientSession( SRPContext(username, 'password123', prime=prime, generator=gen), private=client_private) skey_cl, skey_proof_cl, skey_prove_hash_cl = client_session.process(server_public, salt) skey_srv, skey_proof_srv, skey_prove_hash_srv = server_session.process(client_public, salt) assert skey_cl == skey_srv assert skey_proof_cl == skey_proof_srv # Base 64 test skey_cl, skey_proof_cl, skey_prove_hash_cl = client_session.process(server_public_b64, salt_b64, base64=True) skey_srv, skey_proof_srv, skey_prove_hash_srv = server_session.process(client_public_b64, salt_b64, base64=True) assert skey_cl == skey_srv assert skey_proof_cl == skey_proof_srv def test_simple(): # Agree on communication details. context = SRPContext('alice', 'password123') username, password_verifier, salt = context.get_user_data_triplet() prime = context.prime gen = context.generator # Receive username from client and generate server public. server_session = SRPServerSession(SRPContext(username, prime=prime, generator=gen), password_verifier) server_public = server_session.public # Receive server public and salt and process them. client_session = SRPClientSession(SRPContext(username, 'password123', prime=prime, generator=gen)) client_session.process(server_public, salt) # Generate client public and session key. client_public = client_session.public client_session_key = client_session.key # Process client public and compare session keys. server_session.process(client_public, salt) server_session_key = server_session.key assert server_session_key == client_session_key def test_raises(): server_session = SRPServerSession(SRPContext('1', '2'), '1') server_session._context._prime = 1 # to trigger error with pytest.raises(SRPException): server_session.init_common_secret('1') client_session = SRPClientSession(SRPContext('1', '2')) client_session._context._prime = 1 # to trigger error with pytest.raises(SRPException): client_session.init_common_secret('1') idlesign-srptools-019adc7/tests/test_context.py000066400000000000000000000167021372702642700220640ustar00rootroot00000000000000from __future__ import unicode_literals import pytest from srptools import SRPContext, SRPException from srptools.utils import int_from_hex, hex_from def test_context(): def to_hex_u(val): return hex_from(val).upper() static_salt = int_from_hex('BEB25379D1A8581EB5A727673A2441EE') static_client_private = int_from_hex('60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393') static_server_private = int_from_hex('E487CB59D31AC550471E81F00F6928E01DDA08E974A004F49E61F5D105284D20') context = SRPContext( 'alice', 'password123', multiplier='7556AA045AEF2CDD07ABAF0F665C3E818913186F', ) assert context.prime_b64 assert context.generator_b64 password_hash = context.get_common_password_hash(static_salt) assert to_hex_u(password_hash) == '94B7555AABE9127CC58CCF4993DB6CF84D16C124' password_verifier = context.get_common_password_verifier(password_hash) assert to_hex_u(password_verifier) == ( '7E273DE8696FFC4F4E337D05B4B375BEB0DDE1569E8FA00A9886D8129BADA1F1822223CA1A605B530E379BA4729FDC59' 'F105B4787E5186F5C671085A1447B52A48CF1970B4FB6F8400BBF4CEBFBB168152E08AB5EA53D15C1AFF87B2B9DA6E04' 'E058AD51CC72BFC9033B564E26480D78E955A5E29E7AB245DB2BE315E2099AFB') client_public = context.get_client_public(static_client_private) assert to_hex_u(client_public) == ( '61D5E490F6F1B79547B0704C436F523DD0E560F0C64115BB72557EC44352E8903211C04692272D8B2D1A5358A2CF1B6E' '0BFCF99F921530EC8E39356179EAE45E42BA92AEACED825171E1E8B9AF6D9C03E1327F44BE087EF06530E69F66615261' 'EEF54073CA11CF5858F0EDFDFE15EFEAB349EF5D76988A3672FAC47B0769447B') server_public = context.get_server_public(password_verifier, static_server_private) assert to_hex_u(server_public) == ( 'BD0C61512C692C0CB6D041FA01BB152D4916A1E77AF46AE105393011BAF38964DC46A0670DD125B95A981652236F99D9' 'B681CBF87837EC996C6DA04453728610D0C6DDB58B318885D7D82C7F8DEB75CE7BD4FBAA37089E6F9C6059F388838E7A' '00030B331EB76840910440B1B27AAEAEEB4012B7D7665238A8E3FB004B117B58') common_secret = context.get_common_secret(server_public, client_public) assert to_hex_u(common_secret) == 'CE38B9593487DA98554ED47D70A7AE5F462EF019' expected_premaster_secret = ( 'B0DC82BABCF30674AE450C0287745E7990A3381F63B387AAF271A10D233861E359B48220F7C4693C9AE12B0A6F67809F' '0876E2D013800D6C41BB59B6D5979B5C00A172B4A2A5903A0BDCAF8A709585EB2AFAFA8F3499B200210DCC1F10EB3394' '3CD67FC88A2F39A4BE5BEC4EC0A3212DC346D7E474B29EDE8A469FFECA686E5A') expected_session_key = b'017EEFA1CEFC5C2E626E21598987F31E0F1B11BB' server_premaster_secret = context.get_server_premaster_secret( password_verifier, static_server_private, client_public, common_secret) assert to_hex_u(server_premaster_secret) == expected_premaster_secret client_premaster_secret = context.get_client_premaster_secret( password_hash, server_public, static_client_private, common_secret) assert to_hex_u(client_premaster_secret) == expected_premaster_secret server_session_key = context.get_common_session_key(server_premaster_secret) assert to_hex_u(server_session_key) == expected_session_key client_session_key = context.get_common_session_key(client_premaster_secret) assert to_hex_u(client_session_key) == expected_session_key client_session_key_prove = context.get_common_session_key_proof( client_session_key, static_salt, server_public, client_public) assert to_hex_u(client_session_key_prove) == b'3F3BC67169EA71302599CF1B0F5D408B7B65D347' server_session_key_prove = context.get_common_session_key_proof_hash( server_session_key, client_session_key_prove, client_public) assert to_hex_u(server_session_key_prove) == b'9CAB3C575A11DE37D3AC1421A9F009236A48EB55' def test_context_raises(): context = SRPContext('alice') with pytest.raises(SRPException): context.get_common_password_hash(123) def test_byte_hashes(): static_salt = int_from_hex('99e50c9ad1bd2856') static_client_private = int_from_hex('2b557313c052bb0e24a3c7462e8f436769a54e8d325da794004cefab83ac8b71') static_server_private = int_from_hex( '57e997761d2aeb4c8dbfed9fde120c0ec730af1237e296f58649a6b3193ff21b36f5cfaed3049ee0051e5378f666f13d' '0c7c91040940a77a3ff1a461666c41e9aca3bd4747d74036e34941578553eb56d369638f796707425d0294809e81363f' 'ac90af29c7fde1ae142f8c280e3c2e17f9c4d68f644de5406aac7d378b812a34') context = SRPContext('bouke', 'test') password_hash = context.get_common_password_hash(static_salt) password_verifier = context.get_common_password_verifier(password_hash) assert hex_from(password_verifier) == ( '52e3ee0cde007d2e7cee87acca1c041999b528e56dec925112d30a63d8e814231c2cd3bac9ae40220c44d63029912f1f' '7dda878e938ab5bfe7b87b854bb8385020d765054d07424eb5749fcd90344dbc0372432f6db25ae12cca4584ea72270c' 'a61d831540b10919a31fde1b7b9e1cc7110429d8bbde1a6fe005896697b91436') client_public = context.get_client_public(static_client_private) assert hex_from(client_public) == ( 'e18b11cddbfa709020fa2c67344a20e6704dba3e5ca6c4ca864b94ff5442965c80dfa751a9404feb2234fcd02d7f179d' 'ca4e308d76af173ec4eacc13a8daf0237bf19d4ac0ae9a4db885fdb46d5107caea8f71a8db39eda96d594e216c632a0d' '9720d84e8abb82b3dfa67fad099e1c67b13081bb564b2369c6db5f10358680b2') server_public = context.get_server_public(password_verifier, static_server_private) assert hex_from(server_public) == ( '0b3cc73f40a5fbdee992995dc26bfc43558803689798731fd303cdf18fdecbb5544f5caf960910f1b9449c772032be38' '2b22d8763104781793553977bfdbd7cd3b05af0bf00deee22d76b477275e3294713711e3fe97f34724f9580bf2c055e7' '8ae138664dfecaa2fe353768e30c3cc395541a929dc2af6a66e118ca937cffe8') common_secret = context.get_common_secret(server_public, client_public) assert hex_from(common_secret) == 'cb709a3c8a6767fda651ad6543436e4da2c85268' expected_premaster_secret = ( '8c0ade0a5cc22507230bf092348a518fe9c29f1cbeb7a1a089ac070da5f5f7d540377fa30703164823017f421cc71237' '2cc2093228fc6b05a4c77f05216c7c911fbdc2ed63f48a1ecec9da8a1edda3c810c724d8c45f83acd48a6c05f33d36b4' '0ebca6db6f34a3f8e69289f7e49ef3492265d18488d447fb232b56306cb39a3a') server_premaster_secret = context.get_server_premaster_secret( password_verifier, static_server_private, client_public, common_secret) assert hex_from(server_premaster_secret) == expected_premaster_secret client_premaster_secret = context.get_client_premaster_secret( password_hash, server_public, static_client_private, common_secret) assert hex_from(client_premaster_secret) == expected_premaster_secret expected_session_key = b'86a5aff58ae7eca772b05bbb629f5b1c51677b14' server_session_key = context.get_common_session_key(server_premaster_secret) assert hex_from(server_session_key) == expected_session_key client_session_key = context.get_common_session_key(client_premaster_secret) assert hex_from(client_session_key) == expected_session_key client_session_key_prove = context.get_common_session_key_proof( client_session_key, static_salt, server_public, client_public) assert hex_from(client_session_key_prove) == b'001961fb3aa5c24c437df55a18a41cabce3d57b4' server_session_key_prove = context.get_common_session_key_proof_hash( server_session_key, client_session_key_prove, client_public) assert hex_from(server_session_key_prove) == b'f0a6d49e5037f34b770a8e2de9ec5e3c0880953b' idlesign-srptools-019adc7/tox.ini000066400000000000000000000003421372702642700171310ustar00rootroot00000000000000# See http://tox.readthedocs.org/en/latest/examples.html for samples. [tox] envlist = py{35,36,37} skip_missing_interpreters = True install_command = pip install {opts} {packages} [testenv] commands = python setup.py test