Giter Site home page Giter Site logo

pysrp's Introduction

pysrp

Tom Cocagne <[email protected]>

pysrp provides a Python implementation of the Secure Remote Password protocol (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

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

import srp

# Consider enabling RFC5054 compatibility for interoperation with non pysrp SRP-6a implementations
#srp.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 . <desired output directory>

Validity & Performance Testing:

   $ python setup.py build
   $ python srp/test_srp.py

pysrp's People

Contributors

alexh-name avatar bgouesbetnetatmo avatar cocagne avatar dae3 avatar ddealmei avatar dragomirecky avatar evilaliv3 avatar fupduck avatar holocronweaver avatar masihyeganeh avatar mkj avatar polendri avatar space55 avatar timmartin avatar tuedel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pysrp's Issues

shared key for symetric encryption

I did not see how could be obtained a large key that was known at the end of exchanges and known only by both sides. We cannot perform symmetric encryption without it. Could you explain that?

public key of verifier not available before obtaining key of user

In some SRP usages like Homekit protocol it is required to generate the verifier's public key before the user public key. Currently it is necessary to create a Verifier object in order to get the verifier public key and the Verifier object can be created only with the user public Key A as parameter.
It would be more flexible (and HomeKit compatible) to require the User public key when calling the Verifier.verify_session() api instead of in the verifier constructor.

Unable to "python srp\test_srp.py"

The error I received:

Traceback (most recent call last):
File "H:\Downloads\pysrp-master\pysrp-master\srp\test_srp.py", line 32, in
import srp._ctsrp as _ctsrp
File "H:\Downloads\pysrp-master\pysrp-master\build\lib\srp_ctsrp.py", line 197, in
load_func( 'BN_new', [], BIGNUM )
File "H:\Downloads\pysrp-master\pysrp-master\build\lib\srp_ctsrp.py", line 194, in load_func
raise ImportError('Unable to load required functions from SSL dlls')
ImportError: Unable to load required functions from SSL dlls

Tried to solve this issue by renaming all _ctsrp to srp._ctsrp in the test_srp.py functions, but given "AttributeError: module 'srp' has no attribute '_ctsrp'"

Does anyone have a solution?

license mismatch

Hi. It seems to be a mismatch between what setup.py reports (new bsd) and the included LICENSE file (MIT).

Also, it would be good to include LICENSE in the MANIFEST.in

Python crashes on MacOS Catalina 10.15.4 due libssl.dylib import

Steps to reproduce

  1. On MacOS Catalina install pysrp with pip install srp
  2. python3 -c 'import srp'

Root cause

dlls.append( ctypes.cdll.LoadLibrary('libssl.dylib') )

Possible solutions

  1. Use openssl provided by Homebrew ln -s /usr/local/opt/[email protected]/lib/libssl.dylib /usr/local/lib/libssl.dylib.
  2. Specify libssl version while importing (e.g. libssl.46.dylib) here:
    dlls.append( ctypes.cdll.LoadLibrary('libssl.dylib') )

Make more 'transactional'

I'm trying to use this library in a Web context... I would prefer if at least the Verifier-side wasn't required to be long-lived.

Here's an example of the unit test rewritten to show what I'm talking about:

import srp

        # The salt and verifier returned from srp.create_salted_verification_key() should be
        # stored on the server.
        username = 'testuser'
        password = 'testpassword'
        salt, vkey = srp.create_salted_verification_key(username, password)

        class AuthenticationFailed (Exception):
            pass

        # ~~~ Begin Authentication ~~~
        usr      = srp.User(username, password)
        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
        svr      = srp.Verifier(uname, salt, vkey, A)
        HAMK     = svr.verify_session( M )
        if HAMK is None:
            raise AuthenticationFailed()

        private_b = srv.get_private_b()
        svr2    = srp.Verifier(uname, salt, vkey, A, private_b=private_b)
        HAMK2   = svr2.verify_session( M )
        if HAMK2 is None:
            raise AuthenticationFailed()

        # Server => Client: HAMK
        usr.verify_session( HAMK )
        usr.verify_session( HAMK2 )

        # At this point the authentication process is complete.

        assert usr.authenticated()
        assert svr.authenticated()
        assert svr2.authenticated()

Notice that srv2 was recreated from srv. This will enable the web back-end to be short-lived while doing two client->server hits.

Calculation of B

It seems there is a difference between the python and C implementation of how B is calculated, notably that in the C version:

            # 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)

the modulo differs from the python version:

    self.B = (k*self.v + pow(g, self.b, N)) % N

Should that be BN_mod_add() instead?

Support custom k value

I'm trying to use this to log into a 3rd party device that has a broken SRP implementation.
They use a different k calculation...
Would it be possible to add a k_hex parameter to User to bypass the standard calculation?

Support Alternate M1 Proof

Alternatively, in a password-only proof the calculation of "K" can be skipped and the shared "S" proven with:

Carol โ†’ Steve: M1 = H(A | B | SCarol). Steve verifies M1.
Steve โ†’ Carol: M2 = H(A | M1 | SSteve). Carol verifies M2.

Wikipedia.

It appears the JavaScript implementations of SRP use this proof. I think they're wrong as I can't find it in the RFC, but it might be nice to have the option for both in Python for interoperability.

process_challenge() - TypeError: Can't convert 'bytes' object to str implicitly

When attempting to use the process_challenge() method it keeps throwing a TypeError (Can't convert 'bytes' object to str implicitly.)

Traced to exception to be thrown from the method:
calculate_x(hash_class, dest, salt, username, password) in the file _ctsrp.py

This method fetches the username and password values as str values, but inside this method they are attempted concatenated with a bytes literal, which in turn causes the TypeError.

Altered method, fixing the TypeError issue

def calculate_x( hash_class, dest, salt, username, password ):
    up = hash_class(username.encode() + six.b(':') + password.encode()).digest()
    H_bn_str( hash_class, dest, salt, up )

Compile error

I couldn't install your package. It has compile errors either in python 2 or 3.
Here is output of pip install srp==1.0.5:

srp/_srp.c:928:11: error: no member named 'ob_type' in 'PyVerifier'
    self->ob_type->tp_free( (PyObject *) self );
    ~~~~  ^
srp/_srp.c:936:11: error: no member named 'ob_type' in 'PyUser'
    self->ob_type->tp_free( (PyObject *) self );
    ~~~~  ^
srp/_srp.c:1166:12: warning: implicit declaration of function 'PyString_FromString' is invalid in C99 [-Wimplicit-function-declaration]
    return PyString_FromString( srp_verifier_get_username(self->ver) );
           ^
srp/_srp.c:1166:12: warning: incompatible integer to pointer conversion returning 'int' from a function with result type 'PyObject *' (aka 'struct _object *') [-Wint-conversion]
    return PyString_FromString( srp_verifier_get_username(self->ver) );
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
srp/_srp.c:1177:12: warning: incompatible integer to pointer conversion returning 'int' from a function with result type 'PyObject *' (aka 'struct _object *') [-Wint-conversion]
    return PyString_FromString( srp_user_get_username(self->usr) );
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
srp/_srp.c:1224:16: warning: implicit declaration of function 'PyString_FromStringAndSize' is invalid in C99 [-Wimplicit-function-declaration]
        return PyString_FromStringAndSize(u, key_len);
               ^
srp/_srp.c:1224:16: warning: incompatible integer to pointer conversion returning 'int' from a function with result type 'PyObject *' (aka 'struct _object *') [-Wint-conversion]
        return PyString_FromStringAndSize(u, key_len);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
srp/_srp.c:1241:16: warning: incompatible integer to pointer conversion returning 'int' from a function with result type 'PyObject *' (aka 'struct _object *') [-Wint-conversion]
        return PyString_FromStringAndSize(u, key_len);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
srp/_srp.c:1290:16: warning: incompatible integer to pointer conversion returning 'int' from a function with result type 'PyObject *' (aka 'struct _object *') [-Wint-conversion]
        return PyString_FromStringAndSize((const char *) bytes_HAMK,
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
srp/_srp.c:1341:16: warning: incompatible integer to pointer conversion returning 'int' from a function with result type 'PyObject *' (aka 'struct _object *') [-Wint-conversion]
        return PyString_FromStringAndSize((const char *) bytes_M, len_M);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
srp/_srp.c:1600:9: error: non-void function 'init_srp' should return a value [-Wreturn-type]
        return;
        ^
srp/_srp.c:1610:29: warning: implicit declaration of function 'PyString_Check' is invalid in C99 [-Wimplicit-function-declaration]
            if ( randstr && PyString_Check(randstr))
                            ^
srp/_srp.c:1614:22: warning: implicit declaration of function 'PyString_AsStringAndSize' is invalid in C99 [-Wimplicit-function-declaration]
                if (!PyString_AsStringAndSize(randstr, &buff, &slen))
                     ^
srp/_srp.c:1631:9: error: non-void function 'init_srp' should return a value [-Wreturn-type]
        return;
        ^
srp/_srp.c:1636:9: error: non-void function 'init_srp' should return a value [-Wreturn-type]
        return;
        ^
srp/_srp.c:1638:9: warning: implicit declaration of function 'Py_InitModule3' is invalid in C99 [-Wimplicit-function-declaration]
    m = Py_InitModule3("srp._srp", srp_module_methods,"SRP-6a implementation");
        ^
srp/_srp.c:1638:7: warning: incompatible integer to pointer conversion assigning to 'PyObject *' (aka 'struct _object *') from 'int' [-Wint-conversion]
    m = Py_InitModule3("srp._srp", srp_module_methods,"SRP-6a implementation");
      ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
srp/_srp.c:1641:9: error: non-void function 'init_srp' should return a value [-Wreturn-type]
        return;
        ^
12 warnings and 6 errors generated.
error: command 'clang' failed with exit status 1

I just looked at your code and it seems to have pure python and cython version.
Is there anyway to make the cython version optional in install time?

It seems that your package is only SRP implementation in python. I need it for my project.

Thanks

Python2 issue

Hello,

Just wanted you to know that on python2 bytes(2) returns the string "2", whereas in python3 it returns b"\x00\x00".

This makes the padding (inside H()) wrong in python2 which results in invalid values.

Discovered this while trying to make sure that pysrp and secure-remote-password are compatible (LinusU/secure-remote-password#13). Been ripping my hair out for the past few hours ๐Ÿ˜‚

AttributeError when using create_salted_verification_key

I used latest version srp:

$ pip install git+https://github.com/cocagne/[email protected]
Collecting git+https://github.com/cocagne/[email protected]
  Cloning https://github.com/cocagne/pysrp.git (to 1.0.8) to /private/var/folders/g9/m3cmg0m10cl80xt362wpsld00000gn/T/pip-sb9zr717-build
Requirement already satisfied: six in /xxxx/xxx/xxx/xxx/env/lib/python3.6/site-packages (from srp==1.0.8)
Installing collected packages: srp
  Found existing installation: srp 1.0.7
    Uninstalling srp-1.0.7:
      Successfully uninstalled srp-1.0.7
  Running setup.py install for srp ... done
Successfully installed srp-1.0.8

Example:

import srp


def main():
    print(srp)
    salt, vkey = srp.create_salted_verification_key('testuser', 'testpassword')
    print(salt, vkey)


if __name__ == '__main__':
    main()

Then I got error:

$ python srp.py
<module 'srp' from '/xxxx/xxx/xxx/xxx/srp.py'>
Traceback (most recent call last):
  File "srp.py", line 11, in <module>
    main()
  File "srp.py", line 6, in main
    salt, vkey = srp.create_salted_verification_key('testuser', 'testpassword')
AttributeError: module 'srp' has no attribute 'create_salted_verification_key'

I tested under Python 3.6.2(OSX) with virtualenv enabled. #

Get a UTF-8 string?

I'm trying to use this library to do SRP for AWS cognito. I need to pass everything to the API as UTF-8 string. I've noticed its all encoded this way:

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 long_to_bytes(n):

Any idea how to make this optional, so I can encode as necessary for the API?

Need padding for compatibility with RFC 5054

The implementation here seems to be incompatible with the specification of SRP in RFC 5054 which requires padding in various places that is not applied here. For example, in section 2.6 on computing the premaster secret, the server k value is defined as k = SHA1(N | PAD(g)) where the PAD function means pad with zeros to match the byte length of N. The code here by contrast just computes k = HASH(N | g) with no padding. It seems like compatibility with TLS-SRP would be a nice feature to have here.

should pysrp check malloc returns?

The C implementation in _srp.c calls malloc and assumes that allocations succeed, which is not guaranteed by the C standard.

Now, there are a couple of possible responses to this observation:

  • perhaps the community standard for Python extensions says that malloc never fails -- I don't know, I have never written a Python module in C.
  • perhaps having the app immediately SEGV is the proper response to malloc failing. Certainly I've never seen a Python application that could deal with allocations failing. I haven't done an exhaustive survey but everywhere I checked, malloc returning NULL results in an immediate NULL pointer dereference, and I think Linux these days is protected against allocating things at address 0.
  • we could switch to a malloc wrapper which explicitly guarantees that failed allocations crash the app with a reasonable error message rather than a potentially obsure SEGV.
  • or, perhaps we do want to handle failed malloc and should modify the ~17 callsites in _srp.c to test for NULL and return errors to their callers.

Thoughts?

Issue when calculating X in pure Python implementation

With certain combinations of username and password, the library does not calculate correctly X due to a wrong conversion between byte array and integer in H ( I | ":" | P ) , which loses the initial 0x00 byte(s), and the successive conversion from integer to byte array when hashing the salt along with the previous hash.

Here's a detailed example of this issue happening when username is apiservice and password 1257c:

  1. Hash username and password.
    H(apiservice:1257c) = 00d108f217cbcb3f960ddd7b776ba781ca8e079f21d454d27afaabaa6272f59c
    The library converts this value to integer, which results in 369332777322568772068616687266314200464576548617341020254200090987014059420.
  2. Hash salt and previous hash.
    The issue comes here, when the library converts again the previous hash, in integer format, to byte array, which results in d108f217cbcb3f960ddd7b776ba781ca8e079f21d454d27afaabaa6272f59c. Note that this byte array is 1 byte less than the original because the first byte was 00. This causes the result hash value to be different than it should, which generates a totally different X.

This only happens in the pure Python implementation, not in the ctypes one.

SyntaxError: invalid syntax with python2.7

with python2.7:

byte-compiling /var/tmp/portage/dev-python/srp-1.0.10/image/_python2.7/usr/lib64/python2.7/site-packages/srp/_pysrp.py to _pysrp.pyc
  File "/usr/lib64/python2.7/site-packages/srp/_pysrp.py", line 176
    def H( hash_class, *args, width=None, **kwargs ):
                                  ^
SyntaxError: invalid syntax

with python3.5:

byte-compiling /var/tmp/portage/dev-python/srp-1.0.10/image/_python3.5/usr/lib64/python3.5/site-packages/srp/_pysrp.py to _pysrp.cpython-35.pyc

SRP updates

Hi,

I'm working on some updates on your library. These are the things I'm working on right now:

  • making the C extension optional (I talked about it on a PR)
  • making the code py2/py3 compatible (I'll probably add tox to manage tests in each python version)

Also, why is the ctypes code optional? It is available in both py2 and py3 (at least in my Arch linux), is there some version or platform which doesn't come with this module? If ctypes is in fact optional, there's another option: compile _pysrp.py with Cython, if available, like mistune does, for example.

I'm yet to benchmark the Cython version against all others, but the advantage of Cython is that you can use just regular Python and still have a performance improvement, so maybe you can get rid of _ctsrp.py and maintain just _srp.c and _pysrp.py; less code duplication, less things to maintain. I'll update this thread later with the benchmarks.

Compatible JS library?

Hi! Sorry if this is offtopic, but have anyone used pysrp with JavaScript?
I'm currently fiddling with Thinbus-srp Spring demo. Managed to make both implementations communicate with each other, using pysrp with Flask as server-side, but can't make them to authenticate to each other (both sides using 2048 N and sha256). All I can figure out so far is:

They have a bit different logic.
In pysrp the dataflow expected during authentication is:

  • Client -> Server: username, A
  • Server -> Client: s, B
  • Client - > Server: M
  • Server - > Client : H(A,M,K)

While in Thinbus:

  • Client -> Server: username
  • Server -> Client: s, B
  • Client - > Server: M, A
  • Server -> Client: H(A,M,K)

Since flask is stateless by default, managed to overcome this issue by re-initializing srp.Verifier() at fourth step with actual A, while passing s,b,v stored from second step
2. Thinbus sending it's A,M,s,v in hex format, so I used binascii.hexlify() to send data to client and binascii.unhexlify() to recieve it back, but it often fails with "Odd padding" when using unhexlify() on v and s values

I've tried to compare both implementations, but I have no much experience with such math at JS side, and for me everything seems ok.
Could someone point me out where to look at? Or does anyone know any JS library compatible with pysrp?

NOT WORK

My script python2 for login router:

def login(r,user,passwd,token):
    __process=srp.User(user,passwd)
    A=__process.start_authentication()[1].encode('hex')

    data = {'CSRFtoken' : token, 'I' : user, 'A' : A}
    verify_srp=json.loads(r.post('https://192.168.1.1/authenticate', data=data, verify=False, allow_redirects=True).text)
    
    s=verify_srp['s'].lower().encode('utf8')
    B=verify_srp['B'].lower().encode('utf8')

    M=__process.process_challenge(s,B).encode('hex')

    data = {'CSRFtoken' : token, 'M' : M}
    verify_srp=json.loads(r.post('https://192.168.1.1/authenticate', data=data, verify=False, allow_r

Response router:

{u'error': {u'msg': u"M didn't match"}}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.