././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1667161682.1851544 srp-1.0.20/0000755000175000017500000000000014327557122011452 5ustar00rakisrakis././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160702.0 srp-1.0.20/LICENSE0000644000175000017500000000206614327555176012472 0ustar00rakisrakisThe MIT License (MIT) Copyright (c) 2012 Tom Cocagne Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160702.0 srp-1.0.20/MANIFEST.in0000644000175000017500000000012414327555176013214 0ustar00rakisrakisinclude LICENSE include *.txt recursive-include srp *.py recursive-include srp *.rst././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1667161682.1851544 srp-1.0.20/PKG-INFO0000644000175000017500000000360114327557122012547 0ustar00rakisrakisMetadata-Version: 2.1 Name: srp Version: 1.0.20 Summary: Secure Remote Password Home-page: https://github.com/cocagne/pysrp Download-URL: http://pypi.python.org/pypi/srp Author: Tom Cocagne Author-email: tom.cocagne@gmail.com License: MIT Platform: OS Independent Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python Classifier: Topic :: Security Provides: srp License-File: LICENSE This package provides an implementation of the Secure Remote Password protocol (SRP). SRP is a cryptographically strong authentication protocol for password-based, mutual authentication over an insecure network connection. Unlike other common challenge-response autentication protocols, such as Kerberos and SSL, SRP does not rely on an external infrastructure of trusted key servers or certificate management. Instead, SRP server applications use verification keys derived from each user's password to determine the authenticity of a network connection. SRP provides mutual-authentication in that successful authentication requires both sides of the connection to have knowledge of the user's password. If the client side lacks the user's password or the server side lacks the proper verification key, the authentication will fail. Unlike SSL, SRP does not directly encrypt all data flowing through the authenticated connection. However, successful authentication does result in a cryptographically strong shared key that can be used for symmetric-key encryption. For a full description of the pysrp package and the SRP protocol, please refer to the `srp module documentation`_. .. _`srp module documentation`: http://packages.python.org/srp ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160702.0 srp-1.0.20/README.md0000644000175000017500000000750614327555176012750 0ustar00rakisrakispysrp ===== Tom Cocagne <tom.cocagne@gmail.com> pysrp provides a Python implementation of the [Secure Remote Password protocol](http://srp.stanford.edu/) (SRP). SRP Overview ------------ SRP is a cryptographically strong authentication protocol for password-based, mutual authentication over an insecure network connection. Unlike other common challenge-response autentication protocols, such as Kerberos and SSL, SRP does not rely on an external infrastructure of trusted key servers or certificate management. Instead, SRP server applications use verification keys derived from each user's password to determine the authenticity of a network connection. SRP provides mutual-authentication in that successful authentication requires both sides of the connection to have knowledge of the user's password. If the client side lacks the user's password or the server side lacks the proper verification key, the authentication will fail. Unlike SSL, SRP does not directly encrypt all data flowing through the authenticated connection. However, successful authentication does result in a cryptographically strong shared key that can be used for symmetric-key encryption. For a full description of the pysrp package and the SRP protocol, please refer to the [pysrp documentation](http://pythonhosted.org/srp/) Note: RFC5054 now provides the de-facto standard for the hashing algorithm used for interoperable SRP implementations. When using pysrp to interact with another SRP implementation, use the srp.rfc5054_enable() method to enable RFC5054 compatibility. Otherwise a pysrp-specific default implementation will be used. Usage Example ------------- ```python import srp # Consider enabling RFC5054 compatibility for interoperation with non pysrp SRP-6a implementations #pysrp.rfc5054_enable() # The salt and verifier returned from srp.create_salted_verification_key() should be # stored on the server. salt, vkey = srp.create_salted_verification_key( 'testuser', 'testpassword' ) class AuthenticationFailed (Exception): pass # ~~~ Begin Authentication ~~~ usr = srp.User( 'testuser', 'testpassword' ) uname, A = usr.start_authentication() # The authentication process can fail at each step from this # point on. To comply with the SRP protocol, the authentication # process should be aborted on the first failure. # Client => Server: username, A svr = srp.Verifier( uname, salt, vkey, A ) s,B = svr.get_challenge() if s is None or B is None: raise AuthenticationFailed() # Server => Client: s, B M = usr.process_challenge( s, B ) if M is None: raise AuthenticationFailed() # Client => Server: M HAMK = svr.verify_session( M ) if HAMK is None: raise AuthenticationFailed() # Server => Client: HAMK usr.verify_session( HAMK ) # At this point the authentication process is complete. assert usr.authenticated() assert svr.authenticated() ``` Installation ------------ ``` $ pip install srp ``` Implementation -------------- It consists of 3 modules: A pure Python implementation, A ctypes + OpenSSL implementation, and a C extension module. The ctypes & extension modules are approximately 10-20x faster than the pure Python implementation and can take advantage of multiple CPUs. The extension module will be used if available, otherwise the library will fall back to the ctypes implementation followed by the pure Python implementation. Note: The test_srp.py script prints the performance timings for each combination of hash algorithm and prime number size. This may be of use in deciding which pair of parameters to use in the unlikely event that the defaults are unacceptable. Installation from source: ``` $ python setup.py install ``` Documentation: ``` $ cd srp/doc $ sphinx-build -b html . ``` Validity & Performance Testing: ``` $ python setup.py build $ python srp/test_srp.py ``` ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1667161682.1851544 srp-1.0.20/setup.cfg0000644000175000017500000000004614327557122013273 0ustar00rakisrakis[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667161013.0 srp-1.0.20/setup.py0000755000175000017500000000453214327555665013205 0ustar00rakisrakis#!/usr/bin/env python from distutils.core import setup from distutils.extension import Extension long_description = ''' This package provides an implementation of the Secure Remote Password protocol (SRP). SRP is a cryptographically strong authentication protocol for password-based, mutual authentication over an insecure network connection. Unlike other common challenge-response autentication protocols, such as Kerberos and SSL, SRP does not rely on an external infrastructure of trusted key servers or certificate management. Instead, SRP server applications use verification keys derived from each user's password to determine the authenticity of a network connection. SRP provides mutual-authentication in that successful authentication requires both sides of the connection to have knowledge of the user's password. If the client side lacks the user's password or the server side lacks the proper verification key, the authentication will fail. Unlike SSL, SRP does not directly encrypt all data flowing through the authenticated connection. However, successful authentication does result in a cryptographically strong shared key that can be used for symmetric-key encryption. For a full description of the pysrp package and the SRP protocol, please refer to the `srp module documentation`_. .. _`srp module documentation`: http://packages.python.org/srp ''' setup(name = 'srp', version = '1.0.20', description = 'Secure Remote Password', author = 'Tom Cocagne', author_email = 'tom.cocagne@gmail.com', url = 'https://github.com/cocagne/pysrp', download_url = 'http://pypi.python.org/pypi/srp', long_description = long_description, provides = ['srp'], install_requires = ['six'], packages = ['srp'], package_data = {'srp' : ['doc/*.rst', 'doc/*.py']}, license = "MIT", platforms = "OS Independent", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python', 'Topic :: Security', ],) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1667161682.1851544 srp-1.0.20/srp/0000755000175000017500000000000014327557122012256 5ustar00rakisrakis././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160702.0 srp-1.0.20/srp/__init__.py0000644000175000017500000000121514327555176014375 0ustar00rakisrakis _mod = None try: import srp._ctsrp _mod = srp._ctsrp except (ImportError, OSError): pass if not _mod: import srp._pysrp _mod = srp._pysrp User = _mod.User Verifier = _mod.Verifier create_salted_verification_key = _mod.create_salted_verification_key SHA1 = _mod.SHA1 SHA224 = _mod.SHA224 SHA256 = _mod.SHA256 SHA384 = _mod.SHA384 SHA512 = _mod.SHA512 NG_1024 = _mod.NG_1024 NG_2048 = _mod.NG_2048 NG_4096 = _mod.NG_4096 NG_8192 = _mod.NG_8192 NG_CUSTOM = _mod.NG_CUSTOM rfc5054_enable = _mod.rfc5054_enable no_username_in_x = _mod.no_username_in_x ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160987.0 srp-1.0.20/srp/_ctsrp.py0000644000175000017500000005010314327555633014126 0ustar00rakisrakis # N A large safe prime (N = 2q+1, where q is prime) # All arithmetic is done modulo N. # g A generator modulo N # k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) # s User's salt # I Username # p Cleartext Password # H() One-way hash function # ^ (Modular) Exponentiation # u Random scrambling parameter # a,b Secret ephemeral values # A,B Public ephemeral values # x Private key (derived from p and s) # v Password verifier from __future__ import division import os import sys import hashlib import random import ctypes import time import six _rfc5054_compat = False _no_username_in_x = False def rfc5054_enable(enable=True): global _rfc5054_compat _rfc5054_compat = enable def no_username_in_x(enable=True): global _no_username_in_x _no_username_in_x = enable SHA1 = 0 SHA224 = 1 SHA256 = 2 SHA384 = 3 SHA512 = 4 NG_1024 = 0 NG_2048 = 1 NG_4096 = 2 NG_8192 = 3 NG_CUSTOM = 4 _hash_map = { SHA1 : hashlib.sha1, SHA224 : hashlib.sha224, SHA256 : hashlib.sha256, SHA384 : hashlib.sha384, SHA512 : hashlib.sha512 } _ng_const = ( # 1024-bit (six.b('''\ EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496\ EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8E\ F4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA\ 9AFD5138FE8376435B9FC61D2FC0EB06E3'''), six.b("2")), # 2048 (six.b('''\ AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4\ A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60\ 95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF\ 747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907\ 8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861\ 60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB\ FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73'''), six.b("2")), # 4096 (six.b('''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ 8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ 302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ 49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ 670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ 180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ 3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ 1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ 99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ 04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ 233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199\ FFFFFFFFFFFFFFFF'''), six.b("5")), # 8192 (six.b('''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ 8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ 302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ 49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ 670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ 180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ 3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ 1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ 99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ 04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ 233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492\ 36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406\ AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918\ DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151\ 2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03\ F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F\ BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA\ CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B\ B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632\ 387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E\ 6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA\ 3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C\ 5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9\ 22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886\ 2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6\ 6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5\ 0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268\ 359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6\ FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71\ 60C980DD98EDD3DFFFFFFFFFFFFFFFFF'''), six.b('13')) ) #N_HEX = "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73" #G_HEX = "2" #HNxorg = None dlls = list() platform = sys.platform if platform == 'darwin': dlls.append( ctypes.cdll.LoadLibrary('libssl.32.dylib') ) elif 'win' in platform: for d in ('libeay32.dll', 'libssl32.dll', 'ssleay32.dll'): try: dlls.append( ctypes.cdll.LoadLibrary(d) ) except: pass else: try: dlls.append( ctypes.cdll.LoadLibrary('libssl.so.1.1.0') ) except OSError: dlls.append( ctypes.cdll.LoadLibrary('libssl.so') ) class BIGNUM_Struct (ctypes.Structure): _fields_ = [ ("d", ctypes.c_void_p), ("top", ctypes.c_int), ("dmax", ctypes.c_int), ("neg", ctypes.c_int), ("flags", ctypes.c_int) ] class BN_CTX_Struct (ctypes.Structure): _fields_ = [ ("_", ctypes.c_byte) ] BIGNUM = ctypes.POINTER( BIGNUM_Struct ) BN_CTX = ctypes.POINTER( BN_CTX_Struct ) def load_func( name, args, returns = ctypes.c_int): d = sys.modules[ __name__ ].__dict__ f = None for dll in dlls: try: f = getattr(dll, name) f.argtypes = args f.restype = returns d[ name ] = f return except: pass raise ImportError('Unable to load required functions from SSL dlls') load_func( 'BN_new', [], BIGNUM ) load_func( 'BN_free', [ BIGNUM ], None ) load_func( 'BN_clear', [ BIGNUM ], None ) load_func( 'BN_CTX_new', [] , BN_CTX ) load_func( 'BN_CTX_free', [ BN_CTX ], None ) load_func( 'BN_set_flags', [ BIGNUM, ctypes.c_int ], None ) BN_FLG_CONSTTIME = 0x04 load_func( 'BN_cmp', [ BIGNUM, BIGNUM ], ctypes.c_int ) load_func( 'BN_num_bits', [ BIGNUM ], ctypes.c_int ) load_func( 'BN_add', [ BIGNUM, BIGNUM, BIGNUM ] ) load_func( 'BN_sub', [ BIGNUM, BIGNUM, BIGNUM ] ) load_func( 'BN_mul', [ BIGNUM, BIGNUM, BIGNUM, BN_CTX ] ) load_func( 'BN_div', [ BIGNUM, BIGNUM, BIGNUM, BIGNUM, BN_CTX ] ) load_func( 'BN_mod_exp', [ BIGNUM, BIGNUM, BIGNUM, BIGNUM, BN_CTX ] ) load_func( 'BN_rand', [ BIGNUM, ctypes.c_int, ctypes.c_int, ctypes.c_int ] ) load_func( 'BN_bn2bin', [ BIGNUM, ctypes.c_char_p ] ) load_func( 'BN_bin2bn', [ ctypes.c_char_p, ctypes.c_int, BIGNUM ], BIGNUM ) load_func( 'BN_hex2bn', [ ctypes.POINTER(BIGNUM), ctypes.c_char_p ] ) load_func( 'BN_bn2hex', [ BIGNUM ], ctypes.c_char_p ) load_func( 'CRYPTO_free', [ ctypes.c_char_p ] ) load_func( 'RAND_seed', [ ctypes.c_char_p, ctypes.c_int ] ) def BN_num_bytes(a): return ((BN_num_bits(a)+7)//8) def BN_mod(rem,m,d,ctx): return BN_div(None, rem, m, d, ctx) def BN_is_zero( n ): return n[0].top == 0 def bn_to_bytes( n ): b = ctypes.create_string_buffer( BN_num_bytes(n) ) BN_bn2bin(n, b) return b.raw def bytes_to_bn( dest_bn, bytes ): BN_bin2bn(bytes, len(bytes), dest_bn) def H_str( hash_class, dest_bn, s ): d = hash_class(s).digest() buff = ctypes.create_string_buffer( s ) BN_bin2bn(d, len(d), dest) def H_bn( hash_class, dest, n ): bin = ctypes.create_string_buffer( BN_num_bytes(n) ) BN_bn2bin(n, bin) d = hash_class( bin.raw ).digest() BN_bin2bn(d, len(d), dest) def H_bn_bn( hash_class, dest, n1, n2, width ): h = hash_class() bin1 = ctypes.create_string_buffer( BN_num_bytes(n1) ) bin2 = ctypes.create_string_buffer( BN_num_bytes(n2) ) BN_bn2bin(n1, bin1) BN_bn2bin(n2, bin2) if _rfc5054_compat: h.update(bytes(width - len(bin1.raw))) h.update( bin1.raw ) if _rfc5054_compat: h.update(bytes(width - len(bin2.raw))) h.update( bin2.raw ) d = h.digest() BN_bin2bn(d, len(d), dest) def H_bn_str( hash_class, dest, n, s ): h = hash_class() bin = ctypes.create_string_buffer( BN_num_bytes(n) ) BN_bn2bin(n, bin) h.update( bin.raw ) h.update( s ) d = h.digest() BN_bin2bn(d, len(d), dest) def calculate_x( hash_class, dest, salt, username, password ): username = username.encode() if hasattr(username, 'encode') else username password = password.encode() if hasattr(password, 'encode') else password if _no_username_in_x: username = six.b('') up = hash_class(username + six.b(':') + password).digest() H_bn_str( hash_class, dest, salt, up ) BN_set_flags(dest, BN_FLG_CONSTTIME) def update_hash( ctx, n ): buff = ctypes.create_string_buffer( BN_num_bytes(n) ) BN_bn2bin(n, buff) ctx.update( buff.raw ) def calculate_M( hash_class, N, g, I, s, A, B, K ): I = I.encode() if hasattr(I, 'encode') else I h = hash_class() h.update( HNxorg( hash_class, N, g ) ) h.update( hash_class(I).digest() ) update_hash( h, s ) update_hash( h, A ) update_hash( h, B ) h.update( K ) return h.digest() def calculate_H_AMK( hash_class, A, M, K ): h = hash_class() update_hash( h, A ) h.update( M ) h.update( K ) return h.digest() def HNxorg( hash_class, N, g ): bN = ctypes.create_string_buffer( BN_num_bytes(N) ) bg = ctypes.create_string_buffer( BN_num_bytes(g) ) BN_bn2bin(N, bN) BN_bn2bin(g, bg) padding = len(bN) - len(bg) if _rfc5054_compat else 0 hN = hash_class( bN.raw ).digest() hg = hash_class( b''.join([ b'\0'*padding, bg.raw ]) ).digest() return six.b( ''.join( chr( six.indexbytes(hN, i) ^ six.indexbytes(hg, i) ) for i in range(0,len(hN)) ) ) def get_ngk( hash_class, ng_type, n_hex, g_hex, ctx ): if ng_type < NG_CUSTOM: n_hex, g_hex = _ng_const[ ng_type ] N = BN_new() g = BN_new() k = BN_new() BN_hex2bn( N, n_hex ) BN_hex2bn( g, g_hex ) H_bn_bn(hash_class, k, N, g, width=BN_num_bytes(N)) if _rfc5054_compat: BN_mod(k, k, N, ctx) return N, g, k def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, salt_len=4, k_hex=None ): if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None): raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM") s = BN_new() v = BN_new() x = BN_new() ctx = BN_CTX_new() hash_class = _hash_map[ hash_alg ] N,g,k = get_ngk( hash_class, ng_type, n_hex, g_hex, ctx ) BN_rand(s, salt_len * 8, -1, 0); calculate_x( hash_class, x, s, username, password ) BN_mod_exp(v, g, x, N, ctx) salt = bn_to_bytes( s ) verifier = bn_to_bytes( v ) BN_free(s) BN_free(v) BN_free(x) BN_free(N) BN_free(g) BN_free(k) BN_CTX_free(ctx) return salt, verifier class Verifier (object): def __init__(self, username, bytes_s, bytes_v, bytes_A=None, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_b=None, k_hex=None): if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None): raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM") if bytes_b and len(bytes_b) != 32: raise ValueError("32 bytes required for bytes_b") self.B = BN_new() self.K = None self.S = BN_new() self.u = BN_new() self.b = BN_new() self.s = BN_new() self.v = BN_new() self.tmp1 = BN_new() self.tmp2 = BN_new() self.ctx = BN_CTX_new() self.I = username self.M = None self.H_AMK = None self._authenticated = False self.safety_failed = False hash_class = _hash_map[ hash_alg ] N,g,k = get_ngk( hash_class, ng_type, n_hex, g_hex, self.ctx ) if k_hex is not None: BN_hex2bn(k, k_hex) self.hash_class = hash_class self.N = N self.g = g self.k = k bytes_to_bn( self.s, bytes_s ) bytes_to_bn( self.v, bytes_v ) if bytes_A: self._set_A(bytes_A) if not self.safety_failed: if bytes_b: bytes_to_bn( self.b, bytes_b ) else: BN_rand(self.b, 256, 0, 0) BN_set_flags(self.b, BN_FLG_CONSTTIME) # B = kv + g^b BN_mul(self.tmp1, k, self.v, self.ctx) BN_mod_exp(self.tmp2, g, self.b, N, self.ctx) BN_add(self.B, self.tmp1, self.tmp2) BN_mod(self.B, self.B, N, self.ctx) def __del__(self): if not hasattr(self, 'A'): return # __init__ threw exception. no clean up required BN_free(self.A) BN_free(self.B) BN_free(self.S) BN_free(self.u) BN_free(self.b) BN_free(self.s) BN_free(self.v) BN_free(self.N) BN_free(self.g) BN_free(self.k) BN_free(self.tmp1) BN_free(self.tmp2) BN_CTX_free(self.ctx) def authenticated(self): return self._authenticated def get_username(self): return self.I def get_ephemeral_secret(self): return bn_to_bytes(self.b) def get_session_key(self): return self.K if self._authenticated else None # returns (bytes_s, bytes_B) on success, (None,None) if SRP-6a safety check fails def get_challenge(self): if self.safety_failed: return None, None else: return (bn_to_bytes(self.s), bn_to_bytes(self.B)) def verify_session(self, user_M, bytes_A=None): if bytes_A: self._set_A(bytes_A) if not hasattr(self, 'A'): raise ValueError("bytes_A must be provided through Verifier constructor or verify_session parameter.") if not self.safety_failed: self._derive_H_AMK() if user_M == self.M: self._authenticated = True return self.H_AMK def _set_A(self, bytes_A): self.A = BN_new() bytes_to_bn( self.A, bytes_A ) # SRP-6a safety check BN_mod(self.tmp1, self.A, self.N, self.ctx) if BN_is_zero(self.tmp1): self.safety_failed = True def _derive_H_AMK(self): H_bn_bn(self.hash_class, self.u, self.A, self.B, width=BN_num_bytes(self.N)) # S = (A *(v^u)) ^ b BN_mod_exp(self.tmp1, self.v, self.u, self.N, self.ctx) BN_mul(self.tmp2, self.A, self.tmp1, self.ctx) BN_mod_exp(self.S, self.tmp2, self.b, self.N, self.ctx) self.K = self.hash_class( bn_to_bytes(self.S) ).digest() self.M = calculate_M( self.hash_class, self.N, self.g, self.I, self.s, self.A, self.B, self.K ) self.H_AMK = calculate_H_AMK( self.hash_class, self.A, self.M, self.K ) class User (object): def __init__(self, username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_a=None, bytes_A=None, k_hex=None): if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None): raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM") if bytes_a and len(bytes_a) != 32: raise ValueError("32 bytes required for bytes_a") self.username = username self.password = password self.a = BN_new() self.A = BN_new() self.B = BN_new() self.s = BN_new() self.S = BN_new() self.u = BN_new() self.x = BN_new() self.v = BN_new() self.tmp1 = BN_new() self.tmp2 = BN_new() self.tmp3 = BN_new() self.ctx = BN_CTX_new() self.M = None self.K = None self.H_AMK = None self._authenticated = False hash_class = _hash_map[ hash_alg ] N,g,k = get_ngk( hash_class, ng_type, n_hex, g_hex, self.ctx ) if k_hex is not None: BN_hex2bn(k, k_hex) self.hash_class = hash_class self.N = N self.g = g self.k = k if bytes_a: bytes_to_bn( self.a, bytes_a ) else: BN_rand(self.a, 256, 0, 0) if bytes_A: bytes_to_bn( self.A, bytes_A ) else: BN_set_flags(self.a, BN_FLG_CONSTTIME) BN_mod_exp(self.A, g, self.a, N, self.ctx) def __del__(self): if not hasattr(self, 'a'): return # __init__ threw exception. no clean up required BN_free(self.a) BN_free(self.A) BN_free(self.B) BN_free(self.s) BN_free(self.S) BN_free(self.u) BN_free(self.x) BN_free(self.v) BN_free(self.N) BN_free(self.g) BN_free(self.k) BN_free(self.tmp1) BN_free(self.tmp2) BN_free(self.tmp3) BN_CTX_free(self.ctx) def authenticated(self): return self._authenticated def get_username(self): return self.username def get_ephemeral_secret(self): return bn_to_bytes(self.a) def get_session_key(self): return self.K if self._authenticated else None def start_authentication(self): return (self.username, bn_to_bytes(self.A)) # Returns M or None if SRP-6a safety check is violated def process_challenge(self, bytes_s, bytes_B): hash_class = self.hash_class N = self.N g = self.g k = self.k bytes_to_bn( self.s, bytes_s ) bytes_to_bn( self.B, bytes_B ) # SRP-6a safety check if BN_is_zero(self.B): return None H_bn_bn(hash_class, self.u, self.A, self.B, width=BN_num_bytes(N)) # SRP-6a safety check if BN_is_zero(self.u): return None calculate_x( hash_class, self.x, self.s, self.username, self.password ) BN_mod_exp(self.v, g, self.x, N, self.ctx) # S = (B - k*(g^x)) ^ (a + ux) BN_mul(self.tmp1, self.u, self.x, self.ctx) BN_add(self.tmp2, self.a, self.tmp1) # tmp2 = (a + ux) BN_mod_exp(self.tmp1, g, self.x, N, self.ctx) BN_mul(self.tmp3, k, self.tmp1, self.ctx) # tmp3 = k*(g^x) BN_sub(self.tmp1, self.B, self.tmp3) # tmp1 = (B - K*(g^x)) BN_mod_exp(self.S, self.tmp1, self.tmp2, N, self.ctx) self.K = hash_class( bn_to_bytes(self.S) ).digest() self.M = calculate_M( hash_class, N, g, self.username, self.s, self.A, self.B, self.K ) self.H_AMK = calculate_H_AMK( hash_class, self.A, self.M, self.K ) return self.M def verify_session(self, host_HAMK): if self.H_AMK == host_HAMK: self._authenticated = True #--------------------------------------------------------- # Init # RAND_seed( os.urandom(32), 32 ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160987.0 srp-1.0.20/srp/_pysrp.py0000644000175000017500000003431314327555633014155 0ustar00rakisrakis # N A large safe prime (N = 2q+1, where q is prime) # All arithmetic is done modulo N. # g A generator modulo N # k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) # s User's salt # I Username # p Cleartext Password # H() One-way hash function # ^ (Modular) Exponentiation # u Random scrambling parameter # a,b Secret ephemeral values # A,B Public ephemeral values # x Private key (derived from p and s) # v Password verifier import hashlib import os import binascii import six _rfc5054_compat = False _no_username_in_x = False def rfc5054_enable(enable=True): global _rfc5054_compat _rfc5054_compat = enable def no_username_in_x(enable=True): global _no_username_in_x _no_username_in_x = enable SHA1 = 0 SHA224 = 1 SHA256 = 2 SHA384 = 3 SHA512 = 4 NG_1024 = 0 NG_2048 = 1 NG_4096 = 2 NG_8192 = 3 NG_CUSTOM = 4 _hash_map = { SHA1 : hashlib.sha1, SHA224 : hashlib.sha224, SHA256 : hashlib.sha256, SHA384 : hashlib.sha384, SHA512 : hashlib.sha512 } _ng_const = ( # 1024-bit ('''\ EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496\ EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8E\ F4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA\ 9AFD5138FE8376435B9FC61D2FC0EB06E3''', "2"), # 2048 ('''\ AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4\ A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60\ 95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF\ 747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907\ 8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861\ 60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB\ FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73''', "2"), # 4096 ('''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ 8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ 302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ 49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ 670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ 180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ 3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ 1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ 99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ 04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ 233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199\ FFFFFFFFFFFFFFFF''', "5"), # 8192 ('''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ 8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ 302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ 49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ 670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ 180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ 3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ 1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ 99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ 04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ 233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492\ 36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406\ AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918\ DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151\ 2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03\ F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F\ BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA\ CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B\ B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632\ 387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E\ 6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA\ 3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C\ 5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9\ 22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886\ 2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6\ 6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5\ 0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268\ 359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6\ FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71\ 60C980DD98EDD3DFFFFFFFFFFFFFFFFF''', '0x13') ) def get_ng( ng_type, n_hex, g_hex ): if ng_type < NG_CUSTOM: n_hex, g_hex = _ng_const[ ng_type ] return int(n_hex,16), int(g_hex,16) def bytes_to_long(s): n = 0 for b in six.iterbytes(s): n = (n << 8) | b return n def long_to_bytes(n): l = list() x = 0 off = 0 while x != n: b = (n >> off) & 0xFF l.append( chr(b) ) x = x | (b << off) off += 8 l.reverse() return six.b(''.join(l)) def get_random( nbytes ): return bytes_to_long( os.urandom( nbytes ) ) def get_random_of_length( nbytes ): offset = (nbytes*8) - 1 return get_random( nbytes ) | (1 << offset) def old_H( hash_class, s1, s2 = '', s3=''): if isinstance(s1, six.integer_types): s1 = long_to_bytes(s1) if s2 and isinstance(s2, six.integer_types): s2 = long_to_bytes(s2) if s3 and isinstance(s3, six.integer_types): s3 = long_to_bytes(s3) s = s1 + s2 + s3 return long(hash_class(s).hexdigest(), 16) def H( hash_class, *args, **kwargs ): width = kwargs.get('width', None) h = hash_class() for s in args: if s is not None: data = long_to_bytes(s) if isinstance(s, six.integer_types) else s if width is not None and _rfc5054_compat: h.update( bytes(width - len(data))) h.update( data ) return int( h.hexdigest(), 16 ) #N = 0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73; #g = 2; #k = H(N,g) def HNxorg( hash_class, N, g ): bin_N = long_to_bytes(N) bin_g = long_to_bytes(g) padding = len(bin_N) - len(bin_g) if _rfc5054_compat else 0 hN = hash_class( bin_N ).digest() hg = hash_class( b''.join( [b'\0'*padding, bin_g] ) ).digest() return six.b( ''.join( chr( six.indexbytes(hN, i) ^ six.indexbytes(hg, i) ) for i in range(0,len(hN)) ) ) def gen_x( hash_class, salt, username, password ): username = username.encode() if hasattr(username, 'encode') else username password = password.encode() if hasattr(password, 'encode') else password if _no_username_in_x: username = six.b('') return H( hash_class, salt, H( hash_class, username + six.b(':') + password ) ) def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, salt_len=4, k_hex=None ): if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None): raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM") hash_class = _hash_map[ hash_alg ] N,g = get_ng( ng_type, n_hex, g_hex ) _s = long_to_bytes( get_random( salt_len ) ) _v = long_to_bytes( pow(g, gen_x( hash_class, _s, username, password ), N) ) return _s, _v def calculate_M( hash_class, N, g, I, s, A, B, K ): I = I.encode() if hasattr(I, 'encode') else I h = hash_class() h.update( HNxorg( hash_class, N, g ) ) h.update( hash_class(I).digest() ) h.update( long_to_bytes(s) ) h.update( long_to_bytes(A) ) h.update( long_to_bytes(B) ) h.update( K ) return h.digest() def calculate_H_AMK( hash_class, A, M, K ): h = hash_class() h.update( long_to_bytes(A) ) h.update( M ) h.update( K ) return h.digest() class Verifier (object): def __init__(self, username, bytes_s, bytes_v, bytes_A=None, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_b=None, k_hex=None): if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None): raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM") if bytes_b and len(bytes_b) != 32: raise ValueError("32 bytes required for bytes_b") self.s = bytes_to_long(bytes_s) self.v = bytes_to_long(bytes_v) self.I = username self.K = None self._authenticated = False self.safety_failed = False N,g = get_ng( ng_type, n_hex, g_hex ) hash_class = _hash_map[ hash_alg ] if k_hex is None: k = H( hash_class, N, g, width=len(long_to_bytes(N)) ) else: k = int(k_hex, 16) self.hash_class = hash_class self.N = N self.g = g self.k = k if bytes_A: self._set_A(bytes_A) if not self.safety_failed: if bytes_b: self.b = bytes_to_long(bytes_b) else: self.b = get_random_of_length( 32 ) self.B = (k*self.v + pow(g, self.b, N)) % N def authenticated(self): return self._authenticated def get_username(self): return self.I def get_ephemeral_secret(self): return long_to_bytes(self.b) def get_session_key(self): return self.K if self._authenticated else None # returns (bytes_s, bytes_B) on success, (None,None) if SRP-6a safety check fails def get_challenge(self): if self.safety_failed: return None,None else: return (long_to_bytes(self.s), long_to_bytes(self.B)) # returns H_AMK on success, None on failure def verify_session(self, user_M, bytes_A=None): if bytes_A: self._set_A(bytes_A) if not hasattr(self, 'A'): raise ValueError("bytes_A must be provided through Verifier constructor or verify_session parameter.") if not self.safety_failed: self._derive_H_AMK() if user_M == self.M: self._authenticated = True return self.H_AMK def _set_A(self, bytes_A): self.A = bytes_to_long(bytes_A) # SRP-6a safety check self.safety_failed = self.A % self.N == 0 def _derive_H_AMK(self): self.u = H(self.hash_class, self.A, self.B, width=len(long_to_bytes(self.N))) self.S = pow(self.A*pow(self.v, self.u, self.N ), self.b, self.N) self.K = self.hash_class( long_to_bytes(self.S) ).digest() self.M = calculate_M( self.hash_class, self.N, self.g, self.I, self.s, self.A, self.B, self.K ) self.H_AMK = calculate_H_AMK( self.hash_class, self.A, self.M, self.K ) class User (object): def __init__(self, username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_a=None, bytes_A=None, k_hex=None): if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None): raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM") if bytes_a and len(bytes_a) != 32: raise ValueError("32 bytes required for bytes_a") N,g = get_ng( ng_type, n_hex, g_hex ) hash_class = _hash_map[ hash_alg ] if k_hex is None: k = H( hash_class, N, g, width=len(long_to_bytes(N)) ) else: k = int(k_hex, 16) self.I = username self.p = password if bytes_a: self.a = bytes_to_long(bytes_a) else: self.a = get_random_of_length( 32 ) if bytes_A: self.A = bytes_to_long(bytes_A) else: self.A = pow(g, self.a, N) self.v = None self.M = None self.K = None self.H_AMK = None self._authenticated = False self.hash_class = hash_class self.N = N self.g = g self.k = k def authenticated(self): return self._authenticated def get_username(self): return self.I def get_ephemeral_secret(self): return long_to_bytes(self.a) def get_session_key(self): return self.K if self._authenticated else None def start_authentication(self): return (self.I, long_to_bytes(self.A)) # Returns M or None if SRP-6a safety check is violated def process_challenge(self, bytes_s, bytes_B): self.s = bytes_to_long( bytes_s ) self.B = bytes_to_long( bytes_B ) N = self.N g = self.g k = self.k hash_class = self.hash_class # SRP-6a safety check if (self.B % N) == 0: return None self.u = H( hash_class, self.A, self.B, width=len(long_to_bytes(N)) ) # SRP-6a safety check if self.u == 0: return None self.x = gen_x( hash_class, self.s, self.I, self.p ) self.v = pow(g, self.x, N) self.S = pow((self.B - k*self.v), (self.a + self.u*self.x), N) self.K = hash_class( long_to_bytes(self.S) ).digest() self.M = calculate_M( hash_class, N, g, self.I, self.s, self.A, self.B, self.K ) self.H_AMK = calculate_H_AMK(hash_class, self.A, self.M, self.K) return self.M def verify_session(self, host_HAMK): if self.H_AMK == host_HAMK: self._authenticated = True ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1667161682.1851544 srp-1.0.20/srp/doc/0000755000175000017500000000000014327557122013023 5ustar00rakisrakis././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160702.0 srp-1.0.20/srp/doc/conf.py0000644000175000017500000001563514327555176014343 0ustar00rakisrakis# -*- coding: utf-8 -*- # # Secure Remote Password documentation build configuration file, created by # sphinx-quickstart on Fri Mar 25 10:20:52 2011. # # 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. import sys, os # 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. #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 = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Secure Remote Password' copyright = u'2011, Tom Cocagne' # 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 = '1.0' # The full version, including alpha/beta/rc tags. release = '1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- 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 = 'default' # 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 themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # 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'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'SecureRemotePassworddoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'SecureRemotePassword.tex', u'Secure Remote Password Documentation', u'Tom Cocagne', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'secureremotepassword', u'Secure Remote Password Documentation', [u'Tom Cocagne'], 1) ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160702.0 srp-1.0.20/srp/doc/index.rst0000644000175000017500000000074114327555176014675 0ustar00rakisrakis.. Secure Remote Password documentation master file, created by sphinx-quickstart on Fri Mar 25 10:20:52 2011. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Secure Remote Password's documentation! ================================================== Contents: .. toctree:: :maxdepth: 2 srp.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160987.0 srp-1.0.20/srp/doc/srp.rst0000644000175000017500000003374114327555633014376 0ustar00rakisrakis:mod:`srp` --- Secure Remote Password ===================================== .. module:: srp :synopsis: Secure Remote Password .. moduleauthor:: Tom Cocagne .. sectionauthor:: Tom Cocagne The Secure Remote Password protocol (SRP) is a cryptographically strong authentication protocol for password-based, mutual authentication over an insecure network connection. Successful SRP authentication requires both sides of the connection to have knowledge of the user's password. In addition to password verification, the SRP protocol also performs a secure key exchange during the authentication process. This key may be used to protect network traffic via symmetric key encryption. SRP offers security and deployment advantages over other challenge-response protocols, such as Kerberos and SSL, in that it does not require trusted key servers or certificate infrastructures. Instead, small verification keys derived from each user's password are stored and used by each SRP server application. SRP provides a near-ideal solution for many applications requiring simple and secure password authentication that does not rely on an external infrastructure. Another favorable aspect of the SRP protocol is that compromized verification keys are of little value to an attacker. Possesion of a verification key does not allow a user to be impersonated and it cannot be used to obtain the users password except by way of a computationally infeasible dictionary attack. A compromized key would, however, allow an attacker to impersonate the server side of an SRP authenticated connection. Consequently, care should be taken to prevent unauthorized access to verification keys for applications in which the client side relies on the server being genuine. Usage ----- SRP usage begins with *create_salted_verification_key()*. This function creates a salted verification key from the user's password. The resulting salt and key are stored by the server application and will be used during the authentication process. The authentication process occurs as an exchange of messages between the clent and the server. The :ref:`example` below provides a simple demonstration of the protocol. A comprehensive description of the SRP protocol is contained in the :ref:`protocol-description` section. The *User* & *Verifier* constructors, as well as the *create_salted_verification_key()* function, accept optional arguments to specify which hashing algorithm and prime number arguments should be used during the authentication process. These options may be used to tune the security/performance tradeoff for an application. Generally speaking, specifying arguments with a higher number of bits will result in a greater level of security. However, it will come at the cost of increased computation time. The default values of SHA1 hashes and 2048 bit prime numbers strike a good balance between performance and security. These values should be sufficient for most applications. Regardless of which values are used, the parameters passed to the *User* and *Verifier* constructors must exactly match those passed to *create_salted_verification_key()* .. _constants: Constants --------- .. table:: Hashing Algorithm Constants ============== ============== Hash Algorithm Number of Bits ============== ============== SHA1 160 SHA224 224 SHA256 256 SHA384 384 SHA512 512 ============== ============== .. note:: Larger hashing algorithms will result in larger session keys. .. table:: Prime Number Constants ================= ============== Prime Number Size Number of Bits ================= ============== NG_1024 1024 NG_2048 2048 NG_4096 4096 NG_8192 8192 NG_CUSTOM User Supplied ================= ============== .. note:: If NG_CUSTOM is used, the 'n_hex' and 'g_hex' parameters are required. These parameters must be ASCII text containing hexidecimal notation of the prime number 'n_hex' and the corresponding generator number 'g_hex'. Appendix A of RFC 5054 contains several large prime number, generator pairs that may be used with NG_CUSTOM. Functions --------- .. function:: create_salted_verification_key ( username, password[, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None] ) *username* Name of the user *password* Plaintext user password *hash_alg*, *ng_type*, *n_hex*, *g_hex* Refer to the :ref:`constants` section. Generate a salted verification key for the given username and password and return the tuple: (salt_bytes, verification_key_bytes) .. function:: rfc5054_enable( enable=True ) *enable* True if compatibility with RFC5054 is required, False otherwise. For backward compatibility, pysrp by default does not conform to RFC5054. If you need compatibility with RFC5054, just call this function before using pysrp. :class:`Verifier` Objects ------------------------- A :class:`Verifier` object is used to verify the identity of a remote user. .. note:: The standard SRP 6 protocol allows only one password attempt per connection. .. class:: Verifier( username, bytes_s, bytes_v[, bytes_A=None, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None] ) *username* Name of the remote user being authenticated. *bytes_s* Salt generated by :func:`create_salted_verification_key`. *bytes_v* Verification Key generated by :func:`create_salted_verification_key`. *bytes_A* Challenge from the remote user. Generated by :meth:`User.start_authentication`. Useful when user generates A before verifier generates B. If following RFC5054 section 2.2 where B is generated prior to A, then A can instead later be provided to Verifier via :func:`Verifier.verify_session`. *hash_alg*, *ng_type*, *n_hex*, *g_hex* Refer to the :ref:`constants` section. .. method:: Verifier.authenticated() Return True if the authentication succeeded. False otherwise. .. method:: Verifier.get_username() Return the name of the user this :class:`Verifier` object is for. .. method:: Verifier.get_session_key() Return the session key for an authenticated user or None if the authentication failed or has not yet completed. .. method:: Verifier.get_challenge() Return (bytes_s, bytes_B) on success or (None, None) if authentication has failed. .. method:: Verifier.verify_session( user_M[, user_A=None] ) Complete the :class:`Verifier` side of the authentication process. If A was generated after B as in RFC5054 Section 2.2, then bytes_A can be provided here. If the authentication succeeded, bytes_H_AMK should be returned to the remote user. On failure, this method returns None. :class:`User` Objects ------------------------- A :class:`User` object is used to prove a user's identity to a remote :class:`Verifier` and verifiy that the remote :class:`Verifier` knows the verification key associated with the user's password. .. class:: User( username, password[, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None] ) *username* Name of the user being authenticated. *password* Password for the user. *hash_alg*, *ng_type*, *n_hex*, *g_hex* Refer to the :ref:`constants` section. .. method:: User.authenticated() Return True if authentication succeeded. False otherwise. .. method:: User.get_username() Return the username passed to the constructor. .. method:: User.get_session_key() Return the session key if authentication succeeded or None if the authentication failed or has not yet completed. .. method:: User.start_authentication() Return (username, bytes_A). These should be passed to the constructor of the remote :class:`Verifer` .. method:: User.process_challenge( bytes_s, bytes_B ) Processe the challenge returned by :meth:`Verifier.get_challenge` on success this method returns bytes_M that should be sent to :meth:`Verifier.verify_session` if authentication failed, it returns None. .. method:: User.verify_session( bytes_H_AMK ) Complete the :class:`User` side of the authentication process. By verifying the *bytes_H_AMK* value returned by :meth:`Verifier.verify_session`. If the authentication succeded :meth:`authenticated` will return True .. _example: Example ------- Simple Usage Example:: import srp # The salt and verifier returned from srp.create_salted_verification_key() should be # stored on the server. salt, vkey = srp.create_salted_verification_key( 'testuser', 'testpassword' ) class AuthenticationFailed (Exception): pass # ~~~ Begin Authentication ~~~ usr = srp.User( 'testuser', 'testpassword' ) uname, A = usr.start_authentication() # The authentication process can fail at each step from this # point on. To comply with the SRP protocol, the authentication # process should be aborted on the first failure. # Client => Server: username, A svr = srp.Verifier( uname, salt, vkey, A ) s,B = svr.get_challenge() if s is None or B is None: raise AuthenticationFailed() # Server => Client: s, B M = usr.process_challenge( s, B ) if M is None: raise AuthenticationFailed() # Client => Server: M HAMK = svr.verify_session( M ) if HAMK is None: raise AuthenticationFailed() # Server => Client: HAMK usr.verify_session( HAMK ) # At this point the authentication process is complete. assert usr.authenticated() assert svr.authenticated() Implementation Notes -------------------- This implementation of SRP consists of both a pure-python module and a C-based implementation that is approximately 10x faster. By default, the C-implementation will be used if it is available. An additional benefit of the C implementation is that it can take advantage of of multiple CPUs. For cases in which the number of connections per second is an issue, using a small pool of threads to perform the authentication steps on multi-core systems will yield a substantial performance increase. .. _protocol-description: SRP 6a Protocol Description --------------------------- The original SRP protocol, known as SRP-3, is defined in RFC 2945. This implementation, however, uses SRP-6a which is a slight improvement over SRP-3. The authoritative definition for the SRP-6a protocol is available at http://srp.stanford.edu. An additional resource is RFC 5054 which covers the integration of SRP into TLS. This RFC is the source of hashing strategy and the predefined N and g constants used in this implementation. The following is a complete description of the SRP-6a protocol as implemented by this library. Note that the ^ symbol indicates exponentiaion and the | symbol indicates concatenation. .. rubric:: Primary Variables used in SRP 6a ========= ================================================================= Variables Description ========= ================================================================= N A large, safe prime (N = 2q+1, where q is a Sophie Germain prime) All arithmetic is performed in the field of integers modulo N g A generator modulo N s Small salt for the verification key I Username p Cleartext password H() One-way hash function a,b Secret, random values K Session key ========= ================================================================= .. rubric:: Derived Values used in SRP 6a ====================================== ==================================== Derived Values Description ====================================== ==================================== k = H(N,g) Multiplier Parameter A = g^a Public ephemeral value B = kv + g^b Public ephemeral value x = H(s, H( I | ':' | p )) Private key (as defined by RFC 5054) v = g^x Password verifier u = H(A,B) Random scrambling parameter M = H(H(N) xor H(g), H(I), s, A, B, K) Session key verifier ====================================== ==================================== .. rubric:: Protocol Description The server stores the password verifier *v*. Authentication begins with a message from the client:: client -> server: I, [A = g^a] where public ephemeral key *A* may be provided at this point or later as part of verification. The server replies with the verifier salt and challenge:: server -> client: s, B = kv + g^b, [N, g] where *N* and *g* may be provided by the server or hardcoded in the user client. At this point, both the client and server calculate the shared session key:: client & server: u = H(A,B) :: server: K = H( (Av^u) ^ b ) :: client: x = H( s, H( I + ':' + p ) ) client: K = H( (B - kg^x) ^ (a + ux) ) Now both parties have a shared, strong session key *K*. To complete authentication they need to prove to each other that their keys match:: client -> server: M = H(H(N) xor H(g), H(I), s, A, B, K), [A = g^a] server -> client: H(A, M, K) Note that *A* is only provided here if it was not provided during the initial authentication request. SRP 6a requires the two parties to use the following safeguards (all of which this library automatically checks): 1. The client will abort if it recieves B == 0 (mod N) or u == 0 2. The server will abort if it detects A == 0 (mod N) 3. The client must show its proof of K first. If the server detects that this proof is incorrect it must abort without showing its own proof of K Additionally, if the server provides N and g to the user, the user should verify the safe prime bit length is as expected, that its highest bit is 1 to ensure it is large, and that it is indeed a safe prime with the expected generator. These N and g checks are not currently provided by this library. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667160987.0 srp-1.0.20/srp/test_srp.py0000644000175000017500000002401214327555633014477 0ustar00rakisrakis#!/usr/bin/env python import unittest import os.path import os import sys import time from six.moves import _thread import six this_dir = os.path.dirname( os.path.abspath(__file__) ) build_dir = os.path.join( os.path.dirname(this_dir), 'build' ) if not os.path.exists( build_dir ): print('Please run "python setup.py build" prior to running tests') sys.exit(1) plat_dirs = [ d for d in os.listdir('build') if d.startswith('lib') ] if not len(plat_dirs) == 1: print('Unexpected build result... aborting') plat_dir = os.path.join( build_dir, plat_dirs[0] ) sys.path.insert(0, os.path.join('build', plat_dir) ) import srp import srp._pysrp as _pysrp import srp._ctsrp as _ctsrp test_g_hex = six.b("2") test_n_hex = six.b('''\ AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4\ A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60\ 95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF\ 747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907\ 8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861\ 60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB\ FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73''') class SRPTests( unittest.TestCase ): def doit(self, u_mod, v_mod, g_mod, hash_alg=srp.SHA1, ng_type=srp.NG_2048, n_hex='', g_hex='', k_hex=None): User = u_mod.User Verifier = v_mod.Verifier create_salted_verification_key = g_mod.create_salted_verification_key username = six.b('testuser') password = six.b('testpassword') _s, _v = create_salted_verification_key( username, password, hash_alg, ng_type, n_hex, g_hex ) usr = User( username, password, hash_alg, ng_type, n_hex, g_hex, None, None, k_hex ) bytes_a = usr.get_ephemeral_secret() uname, A = usr.start_authentication() # Make sure a recreated User does all the same appropriate things usr2 = User( username, password, hash_alg, ng_type, n_hex, g_hex, bytes_a, None, k_hex ) self.assertEqual(bytes_a, usr2.get_ephemeral_secret()) uname2, A2 = usr2.start_authentication() self.assertEqual(uname, uname2) self.assertEqual(A, A2) # username, A => server svr = Verifier( uname, _s, _v, A, hash_alg, ng_type, n_hex, g_hex, None, k_hex ) bytes_b = svr.get_ephemeral_secret() s,B = svr.get_challenge() # s,B => client M = usr.process_challenge( s, B ) M2 = usr2.process_challenge( s, B ) self.assertEqual(M, M2) # M => server HAMK = svr.verify_session( M ) # Make sure that a recreated Verifier will authenticate appropriately svr2 = Verifier( uname, _s, _v, A, hash_alg, ng_type, n_hex, g_hex, bytes_b, k_hex ) self.assertEqual(bytes_b, svr2.get_ephemeral_secret()) HAMK2 = svr2.verify_session( M ) self.assertEqual(HAMK, HAMK2) # Make sure Verifier generating B before receiving A will not change authentication. svr3 = Verifier ( uname, _s, _v, None, hash_alg, ng_type, n_hex, g_hex, bytes_b, k_hex ) self.assertEqual(bytes_b, svr3.get_ephemeral_secret()) HAMK3 = svr3.verify_session( M, A ) self.assertEqual(HAMK, HAMK3) # HAMK => client usr.verify_session( HAMK ) usr2.verify_session( HAMK ) self.assertTrue( svr.authenticated() ) self.assertTrue( svr2.authenticated() ) self.assertTrue( svr3.authenticated() ) self.assertTrue( usr.authenticated() ) self.assertTrue( svr2.authenticated() ) def test_pure_python_defaults(self): self.doit( _pysrp, _pysrp, _pysrp ) def test_ctypes_defaults(self): self.doit( _ctsrp, _ctsrp, _ctsrp ) def test_mix1(self): self.doit( _pysrp, _ctsrp, _ctsrp ) def test_mix2(self): self.doit( _pysrp, _pysrp, _ctsrp ) def test_mix3(self): self.doit( _ctsrp, _pysrp, _pysrp ) def test_mix4(self): self.doit( _ctsrp, _ctsrp, _pysrp ) def test_hash_SHA512(self): self.doit( _ctsrp, _ctsrp, _ctsrp, hash_alg=srp.SHA512 ) def test_NG_8192(self): self.doit( _ctsrp, _ctsrp, _ctsrp, ng_type=srp.NG_8192 ) def test_NG_CUSTOM(self): self.doit( _ctsrp, _ctsrp, _ctsrp, ng_type=srp.NG_CUSTOM, n_hex=test_n_hex, g_hex=test_g_hex ) def test_all1(self): self.doit( _ctsrp, _pysrp, _ctsrp, hash_alg=srp.SHA256, ng_type=srp.NG_CUSTOM, n_hex=test_n_hex, g_hex=test_g_hex ) def test_all2(self): self.doit( _ctsrp, _pysrp, _ctsrp, hash_alg=srp.SHA224, ng_type=srp.NG_4096 ) def test_random_of_length(self): """ Verify that the Python implementation guarantees byte length by setting most significant bit to 1 """ for x in range(10): val = _pysrp.get_random_of_length(32) self.assertTrue(val >> 255 == 1) def test_ephemeral_length(self): """ Verify that all implementations require 32 bytes for ephemeral values """ random31 = _pysrp.long_to_bytes(_pysrp.get_random_of_length(31)) random33 = _pysrp.long_to_bytes(_pysrp.get_random_of_length(33)) def verf_len(mod, val): with self.assertRaises(ValueError) as ctx: mod.User('uname', 'pwd', bytes_a=val) self.assertIn('bytes_a', str(ctx.exception) ) with self.assertRaises(ValueError) as ctx: mod.Verifier('uname', random31, random31, random31, bytes_b=val) self.assertIn('bytes_b', str(ctx.exception) ) for mod in [_ctsrp, _pysrp]: for val in [random31, random33]: verf_len(mod, val) def test_authenticated_on_init(self): usr = _pysrp.User('test', 'test') self.assertTrue(not usr.authenticated()) usr = _ctsrp.User('test', 'test') self.assertTrue(not usr.authenticated()) def test_custom_k(self): self.doit( _ctsrp, _pysrp, _ctsrp, k_hex=six.b('5')) def test_verifier_requires_A(self): randbytes = _pysrp.long_to_bytes(_pysrp.get_random_of_length(32)) for mod in [_ctsrp, _pysrp]: svr = mod.Verifier('uname', randbytes, randbytes) with self.assertRaises(ValueError) as ctx: svr.verify_session(randbytes) self.assertIn('bytes_A', str(ctx.exception)) #----------------------------------------------------------------------------------- # Performance Testing # hash_map = { 0 : 'SHA1 ', 1 : 'SHA224', 2 : 'SHA256', 3 : 'SHA384', 4 : 'SHA512' } prime_map = { 0 : 1024, 1 : 2048, 2 : 4096, 3 : 8192 } username = six.b('testuser') password = six.b('testpassword') NLEFT = 0 def do_auth( mod, hash_alg, ng_type, _s, _v ): usr = mod.User( username, password, hash_alg, ng_type) uname, A = usr.start_authentication() # username, A => server svr = mod.Verifier( uname, _s, _v, A, hash_alg, ng_type) s,B = svr.get_challenge() # s,B => client M = usr.process_challenge( s, B ) # M => server HAMK = svr.verify_session( M ) # HAMK => client usr.verify_session( HAMK ) if not svr.authenticated() or not usr.authenticated(): raise Exception('Authentication failed!') def performance_test( mod, hash_alg, ng_type, niter=10, nthreads=1 ): global NLEFT _s, _v = srp.create_salted_verification_key( username, password, hash_alg, ng_type ) NLEFT = niter def test_thread(): global NLEFT while NLEFT > 0: do_auth( mod, hash_alg, ng_type, _s, _v ) NLEFT -= 1 start = time.time() while nthreads > 1: _thread.start_new_thread( test_thread, () ) nthreads -= 1 test_thread() duration = time.time() - start return duration def get_param_str( mod, hash_alg, ng_type ): m = { 'srp._pysrp' : 'Python', 'srp._ctsrp' : 'ctypes' } cfg = '%s, %s, %d:' % (m[mod.__name__], hash_map[hash_alg], prime_map[ng_type]) return cfg def param_test( mod, hash_alg, ng_type, niter=10 ): duration = performance_test( mod, hash_alg, ng_type, niter ) cfg = get_param_str( mod, hash_alg, ng_type ) print(' ', cfg.ljust(20), '%.6f' % (duration/niter)) return duration/niter def print_default_timings(): print('*'*60) print('Default Parameter Timings:') py_time = param_test( _pysrp, srp.SHA1, srp.NG_2048 ) ct_time = param_test( _ctsrp, srp.SHA1, srp.NG_2048 ) print('') print('Performance increases: ') print(' ctypes-module : ', py_time/ct_time) def print_performance_table(): ng_types = [ srp.NG_1024, srp.NG_2048, srp.NG_4096, srp.NG_8192 ] hash_types = [ srp.SHA1, srp.SHA224, srp.SHA256, srp.SHA384, srp.SHA512 ] print('*'*60) print('Hash Algorithm vs Prime Number performance table') print('') six.print_(' |', end=' ') for ng in ng_types: six.print_(('NG_%d' % prime_map[ng]).rjust(12), end=' ') print('') print('-'*60) for hash_alg in hash_types: six.print_('%s |' % hash_map[hash_alg], end=' ') for ng in ng_types: six.print_('{0:>12f}'.format(performance_test(_ctsrp, hash_alg, ng) / 10), end=' ') print('') def print_thread_performance(): print('*'*60) print('Thread Performance Test:') niter = 100 for nthreads in range(1,11): print(' Thread Count {0:>2}: {1:8f}'.format(nthreads, performance_test(_ctsrp, srp.SHA1, srp.NG_2048, niter, nthreads)/niter)) print('*'*60) print('*') print('* Testing Implementation') print('*') suite = unittest.TestLoader().loadTestsFromTestCase(SRPTests) unittest.TextTestRunner(verbosity=1).run(suite) print('*'*60) print('*') print('* Performance Testing') print('*') print_thread_performance() print_performance_table() print_default_timings() #--------------------------------------------------------------- # Pause briefly to ensure no background threads are still executing time.sleep(0.1) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1667161682.1851544 srp-1.0.20/srp.egg-info/0000755000175000017500000000000014327557122013750 5ustar00rakisrakis././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667161682.0 srp-1.0.20/srp.egg-info/PKG-INFO0000644000175000017500000000360114327557122015045 0ustar00rakisrakisMetadata-Version: 2.1 Name: srp Version: 1.0.20 Summary: Secure Remote Password Home-page: https://github.com/cocagne/pysrp Download-URL: http://pypi.python.org/pypi/srp Author: Tom Cocagne Author-email: tom.cocagne@gmail.com License: MIT Platform: OS Independent Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python Classifier: Topic :: Security Provides: srp License-File: LICENSE This package provides an implementation of the Secure Remote Password protocol (SRP). SRP is a cryptographically strong authentication protocol for password-based, mutual authentication over an insecure network connection. Unlike other common challenge-response autentication protocols, such as Kerberos and SSL, SRP does not rely on an external infrastructure of trusted key servers or certificate management. Instead, SRP server applications use verification keys derived from each user's password to determine the authenticity of a network connection. SRP provides mutual-authentication in that successful authentication requires both sides of the connection to have knowledge of the user's password. If the client side lacks the user's password or the server side lacks the proper verification key, the authentication will fail. Unlike SSL, SRP does not directly encrypt all data flowing through the authenticated connection. However, successful authentication does result in a cryptographically strong shared key that can be used for symmetric-key encryption. For a full description of the pysrp package and the SRP protocol, please refer to the `srp module documentation`_. .. _`srp module documentation`: http://packages.python.org/srp ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667161682.0 srp-1.0.20/srp.egg-info/SOURCES.txt0000644000175000017500000000043214327557122015633 0ustar00rakisrakisLICENSE MANIFEST.in README.md setup.py srp/__init__.py srp/_ctsrp.py srp/_pysrp.py srp/test_srp.py srp.egg-info/PKG-INFO srp.egg-info/SOURCES.txt srp.egg-info/dependency_links.txt srp.egg-info/requires.txt srp.egg-info/top_level.txt srp/doc/conf.py srp/doc/index.rst srp/doc/srp.rst././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667161682.0 srp-1.0.20/srp.egg-info/dependency_links.txt0000644000175000017500000000000114327557122020016 0ustar00rakisrakis ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667161682.0 srp-1.0.20/srp.egg-info/requires.txt0000644000175000017500000000000414327557122016342 0ustar00rakisrakissix ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667161682.0 srp-1.0.20/srp.egg-info/top_level.txt0000644000175000017500000000000414327557122016474 0ustar00rakisrakissrp