summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Carrillo <ben@futeisha.org>2013-01-28 09:09:07 +0900
committerBen Carrillo <ben@futeisha.org>2013-01-28 09:09:07 +0900
commit7761b24a526987fad55af130e20417503d2cea51 (patch)
treeabdb5628bb63b3937f4242b7f4a01487d321e33a
initial packaging attempt
-rw-r--r--LICENSE25
-rw-r--r--MANIFEST.in3
-rw-r--r--README.txt33
-rw-r--r--debian/changelog5
-rw-r--r--debian/compat1
-rw-r--r--debian/control35
-rw-r--r--debian/copyright56
-rw-r--r--debian/docs2
-rwxr-xr-xdebian/rules13
-rw-r--r--debian/source/format1
-rw-r--r--debian/watch3
-rwxr-xr-xsetup.py62
-rw-r--r--srp/__init__.py44
-rw-r--r--srp/_ctsrp.py585
-rw-r--r--srp/_pysrp.py364
-rw-r--r--srp/_srp.c1568
-rw-r--r--srp/doc/conf.py216
-rw-r--r--srp/doc/index.rst22
-rw-r--r--srp/doc/srp.rst377
-rw-r--r--srp/test_srp.py268
20 files changed, 3683 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9431bf6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2010, Tom Cocagne
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the Python Software Foundation nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL TOM COCAGNE BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..9f39218
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include *.txt
+recursive-include srp *.py
+recursive-include srp *.rst \ No newline at end of file
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..4902c9f
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,33 @@
+
+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.
+
+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:
+ python setup.py install
+
+Validity & Performance Testing:
+ python setup.py build
+ python test_srp.py
+
+Documentation:
+ cd srp/doc
+ sphinx-build -b html . <desired output directory>
+
+
+** Note: The Sphinx documentation system is easy-installable:
+ easy-install sphinx
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..9a5845f
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+srp (1.0.2-1) unstable; urgency=low
+
+ * Initial release. Closes: 699122
+
+ -- Ben Carrillo <ben@futeisha.org> Mon, 28 Jan 2013 06:37:21 +0900
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..53996fd
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,35 @@
+Source: srp
+Maintainer: Ben Carrillo <ben@futeisha.org>
+Section: python
+Priority: optional
+Build-Depends: python-all-dev (>= 2.6.6-3), debhelper (>= 9)
+Standards-Version: 3.9.4
+Vcs-Hg: https://code.google.com/p/pysrp/
+Vcs-Browser: http://code.google.com/p/pysrp/source/browse/
+Homepage: http://code.google.com/p/pysrp/
+
+Package: python-srp
+Architecture: any
+Depends: ${misc:Depends}, ${python:Depends}, ${shlibs:Depends}
+Description: Secure Remote Password protocol implementation
+ 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 Kereros 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.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..33bc8d5
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,56 @@
+Format: http://dep.debian.net/deps/dep5
+Upstream-Name: srp
+Source: http://pypi.python.org/pypi/srp/
+
+Files: *
+Copyright: 2010, Tom Cocagne
+License: New BSD
+ All rights reserved.
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ .
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the Python Software Foundation nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL TOM COCAGNE BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Files: debian/*
+Copyright: 2013 Ben Carrillo <ben@futeisha.org>
+License: BSD-3-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ .
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..8f9baa1
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,2 @@
+README.txt
+srp/doc/*
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..7c49f0e
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+export DEB_CFLAGS_MAINT_APPEND = -Wl,-z,relro
+
+package=python-srp
+
+%:
+ dh $@ --with python2 --buildsystem=python_distutils
+
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 0000000..e9f6561
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1,3 @@
+version=3
+
+http://pypi.python.org/packages/source/s/srp/srp-(.*)\.tar\.gz
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..a6bbfde
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,62 @@
+#!/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 Kereros 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
+
+'''
+
+ext_modules = [ Extension('srp._srp', ['srp/_srp.c',], libraries = ['ssl',]), ]
+
+setup(name = 'srp',
+ version = '1.0.2',
+ description = 'Secure Remote Password',
+ author = 'Tom Cocagne',
+ author_email = 'tom.cocagne@gmail.com',
+ url = 'http://code.google.com/p/pysrp/',
+ download_url = 'http://pypi.python.org/pypi/srp',
+ long_description = long_description,
+ provides = 'srp',
+ packages = ['srp'],
+ package_data = {'srp' : ['doc/*.rst', 'doc/*.py']},
+ ext_modules = ext_modules,
+ license = "New BSD",
+ platforms = "OS Independent",
+ classifiers = [
+ 'Development Status :: 5 - Production/Stable',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: C',
+ 'Topic :: Security',
+ ],)
diff --git a/srp/__init__.py b/srp/__init__.py
new file mode 100644
index 0000000..808b210
--- /dev/null
+++ b/srp/__init__.py
@@ -0,0 +1,44 @@
+
+_mod = None
+
+try:
+ import srp._srp
+ _mod = srp._srp
+except ImportError:
+ pass
+
+if not _mod:
+ try:
+ import srp._ctsrp
+ _mod = srp._ctsrp
+ except ImportError:
+ 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
+
+
+
+
+
+
+
+
+
diff --git a/srp/_ctsrp.py b/srp/_ctsrp.py
new file mode 100644
index 0000000..6754e83
--- /dev/null
+++ b/srp/_ctsrp.py
@@ -0,0 +1,585 @@
+ # 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 os
+import sys
+import hashlib
+import random
+import ctypes
+import time
+
+
+
+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''',
+'13')
+)
+
+
+
+#N_HEX = "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73"
+#G_HEX = "2"
+#HNxorg = None
+
+dlls = list()
+
+if 'win' in sys.platform:
+ for d in ('libeay32.dll', 'libssl32.dll', 'ssleay32.dll'):
+ try:
+ dlls.append( ctypes.cdll.LoadLibrary(d) )
+ except:
+ pass
+else:
+ 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_init', [ BIGNUM ], None )
+load_func( 'BN_clear', [ BIGNUM ], None )
+
+load_func( 'BN_CTX_new', [] , BN_CTX )
+load_func( 'BN_CTX_init', [ BN_CTX ], None )
+load_func( 'BN_CTX_free', [ BN_CTX ], None )
+
+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 ):
+ 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)
+ h.update( bin1.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 ):
+ up = hash_class('%s:%s' % (username, password )).digest()
+ H_bn_str( hash_class, dest, salt, up )
+
+
+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 ):
+ 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)
+
+ hN = hash_class( bN.raw ).digest()
+ hg = hash_class( bg.raw ).digest()
+
+ return ''.join( chr( ord(hN[i]) ^ ord(hg[i]) ) for i in range(0,len(hN)) )
+
+
+
+def get_ngk( hash_class, ng_type, n_hex, g_hex ):
+ 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)
+
+ return N, g, k
+
+
+
+def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_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 )
+
+ BN_rand(s, 32, -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, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_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")
+ self.A = BN_new()
+ 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.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 )
+ bytes_to_bn( self.A, bytes_A )
+
+ # SRP-6a safety check
+ BN_mod(self.tmp1, self.A, N, self.ctx)
+
+ if BN_is_zero(self.tmp1):
+ self.safety_failed = True
+ else:
+ BN_rand(self.b, 256, -1, 0)
+
+ # 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)
+
+ H_bn_bn(hash_class, self.u, self.A, self.B)
+
+ # S = (A *(v^u)) ^ b
+ BN_mod_exp(self.tmp1, self.v, self.u, N, self.ctx)
+ BN_mul(self.tmp2, self.A, self.tmp1, self.ctx)
+ BN_mod_exp(self.S, self.tmp2, self.b, N, self.ctx)
+
+ self.K = hash_class( bn_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 )
+
+
+ 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_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):
+ if user_M == self.M:
+ self._authenticated = True
+ return self.H_AMK
+
+
+
+
+class User (object):
+ def __init__(self, username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_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")
+ 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.hash_class = hash_class
+ self.N = N
+ self.g = g
+ self.k = k
+
+ BN_rand(self.a, 256, -1, 0)
+
+ 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_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)
+
+ # 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 )
+
diff --git a/srp/_pysrp.py b/srp/_pysrp.py
new file mode 100644
index 0000000..ff907ea
--- /dev/null
+++ b/srp/_pysrp.py
@@ -0,0 +1,364 @@
+ # 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
+
+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 = ord(s[0])
+ for b in ( ord(x) for x in s[1:] ):
+ 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 ''.join(l)
+
+
+def get_random( nbytes ):
+ return bytes_to_long( os.urandom( nbytes ) )
+
+
+def old_H( hash_class, s1, s2 = '', s3=''):
+ if isinstance(s1, (long, int)):
+ s1 = long_to_bytes(s1)
+ if s2 and isinstance(s2, (long, int)):
+ s2 = long_to_bytes(s2)
+ if s3 and isinstance(s3, (long, int)):
+ s3 = long_to_bytes(s3)
+ s = s1 + s2 + s3
+ return long(hash_class(s).hexdigest(), 16)
+
+
+def H( hash_class, *args, **kwargs ):
+ h = hash_class()
+
+ for s in args:
+ if s is not None:
+ h.update( long_to_bytes(s) if isinstance(s, (long, int)) else s )
+
+ return long( h.hexdigest(), 16 )
+
+
+
+#N = 0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73;
+#g = 2;
+#k = H(N,g)
+
+def HNxorg( hash_class, N, g ):
+ hN = hash_class( long_to_bytes(N) ).digest()
+ hg = hash_class( long_to_bytes(g) ).digest()
+
+ return ''.join( chr( ord(hN[i]) ^ ord(hg[i]) ) for i in range(0,len(hN)) )
+
+
+
+def gen_x( hash_class, salt, username, password ):
+ return H( hash_class, salt, H( hash_class, username + ':' + password ) )
+
+
+
+
+def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_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( 4 ) )
+ _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 ):
+ 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, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_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")
+ self.s = bytes_to_long(bytes_s)
+ self.v = bytes_to_long(bytes_v)
+ self.I = username
+ self.K = None
+ self._authenticated = False
+
+ N,g = get_ng( ng_type, n_hex, g_hex )
+ hash_class = _hash_map[ hash_alg ]
+ k = H( hash_class, N, g )
+
+ self.hash_class = hash_class
+ self.N = N
+ self.g = g
+ self.k = k
+
+ self.A = bytes_to_long(bytes_A)
+
+ # SRP-6a safety check
+ self.safety_failed = self.A % N == 0
+
+ if not self.safety_failed:
+
+ self.b = get_random( 32 )
+ self.B = (k*self.v + pow(g, self.b, N)) % N
+ self.u = H(hash_class, self.A, self.B)
+ self.S = pow(self.A*pow(self.v, self.u, N ), self.b, 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 )
+
+
+ def authenticated(self):
+ return self._authenticated
+
+
+ def get_username(self):
+ return self.I
+
+
+ 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):
+ if not self.safety_failed and user_M == self.M:
+ self._authenticated = True
+ return self.H_AMK
+
+
+
+
+class User (object):
+ def __init__(self, username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_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")
+ N,g = get_ng( ng_type, n_hex, g_hex )
+ hash_class = _hash_map[ hash_alg ]
+ k = H( hash_class, N, g )
+
+ self.I = username
+ self.p = password
+ self.a = get_random( 32 )
+ 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.username
+
+
+ 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 )
+
+ # 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
diff --git a/srp/_srp.c b/srp/_srp.c
new file mode 100644
index 0000000..c2341d6
--- /dev/null
+++ b/srp/_srp.c
@@ -0,0 +1,1568 @@
+#include <Python.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <openssl/bn.h>
+#include <openssl/sha.h>
+#include <openssl/crypto.h>
+#include <openssl/rand.h>
+
+/*****************************************************************************/
+/* Begin SRP Header */
+/*****************************************************************************/
+
+struct SRPVerifier;
+struct SRPUser;
+
+typedef enum
+{
+ SRP_NG_1024,
+ SRP_NG_2048,
+ SRP_NG_4096,
+ SRP_NG_8192,
+ SRP_NG_CUSTOM
+} SRP_NGType;
+
+typedef enum
+{
+ SRP_SHA1,
+ SRP_SHA224,
+ SRP_SHA256,
+ SRP_SHA384,
+ SRP_SHA512
+} SRP_HashAlgorithm;
+
+
+/* This library will automatically seed the OpenSSL random number generator
+ * using cryptographically sound random data on Windows & Linux. If this is
+ * undesirable behavior or the host OS does not provide a /dev/urandom file,
+ * this function may be called to seed the random number generator with
+ * alternate data.
+ *
+ * Passing a null pointer to this function will cause this library to skip
+ * seeding the random number generator.
+ *
+ * Notes:
+ * * This function is optional on Windows & Linux.
+ *
+ * * This function is mandatory on all other platforms. Although it
+ * will appear to work on other platforms, this library uses the current
+ * time of day to seed the random number generator. This is well known to
+ * be insecure.
+ *
+ * * When using this function, ensure the provided random data is
+ * cryptographically strong.
+ */
+void srp_random_seed( const unsigned char * random_data, int data_length );
+
+
+/* Out: bytes_s, len_s, bytes_v, len_v
+ *
+ * The caller is responsible for freeing the memory allocated for bytes_s and bytes_v
+ *
+ * The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type.
+ * If provided, they must contain ASCII text of the hexidecimal notation.
+ */
+void srp_create_salted_verification_key( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username,
+ const unsigned char * password, int len_password,
+ const unsigned char ** bytes_s, int * len_s,
+ const unsigned char ** bytes_v, int * len_v,
+ const char * n_hex, const char * g_hex );
+
+
+/* Out: bytes_B, len_B.
+ *
+ * On failure, bytes_B will be set to NULL and len_B will be set to 0
+ *
+ * The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type
+ */
+struct SRPVerifier * srp_verifier_new( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username,
+ const unsigned char * bytes_s, int len_s,
+ const unsigned char * bytes_v, int len_v,
+ const unsigned char * bytes_A, int len_A,
+ const unsigned char ** bytes_B, int * len_B,
+ const char * n_hex, const char * g_hex );
+
+
+void srp_verifier_delete( struct SRPVerifier * ver );
+
+
+int srp_verifier_is_authenticated( struct SRPVerifier * ver );
+
+
+const char * srp_verifier_get_username( struct SRPVerifier * ver );
+
+/* key_length may be null */
+const unsigned char * srp_verifier_get_session_key( struct SRPVerifier * ver, int * key_length );
+
+
+int srp_verifier_get_session_key_length( struct SRPVerifier * ver );
+
+
+/* user_M must be exactly srp_verifier_get_session_key_length() bytes in size */
+void srp_verifier_verify_session( struct SRPVerifier * ver,
+ const unsigned char * user_M,
+ const unsigned char ** bytes_HAMK );
+
+/*******************************************************************************/
+
+/* The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type */
+struct SRPUser * srp_user_new( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username,
+ const unsigned char * bytes_password, int len_password,
+ const char * n_hex, const char * g_hex );
+
+void srp_user_delete( struct SRPUser * usr );
+
+int srp_user_is_authenticated( struct SRPUser * usr);
+
+
+const char * srp_user_get_username( struct SRPUser * usr );
+
+/* key_length may be null */
+const unsigned char * srp_user_get_session_key( struct SRPUser * usr, int * key_length );
+
+int srp_user_get_session_key_length( struct SRPUser * usr );
+
+/* Output: username, bytes_A, len_A */
+void srp_user_start_authentication( struct SRPUser * usr, const char ** username,
+ const unsigned char ** bytes_A, int * len_A );
+
+/* Output: bytes_M, len_M (len_M may be null and will always be
+ * srp_user_get_session_key_length() bytes in size) */
+void srp_user_process_challenge( struct SRPUser * usr,
+ const unsigned char * bytes_s, int len_s,
+ const unsigned char * bytes_B, int len_B,
+ const unsigned char ** bytes_M, int * len_M );
+
+/* bytes_HAMK must be exactly srp_user_get_session_key_length() bytes in size */
+void srp_user_verify_session( struct SRPUser * usr, const unsigned char * bytes_HAMK );
+
+
+/*****************************************************************************/
+/* Begin SRP Library */
+/*****************************************************************************/
+
+
+static int g_initialized = 0;
+
+typedef struct
+{
+ BIGNUM * N;
+ BIGNUM * g;
+} NGConstant;
+
+struct NGHex
+{
+ const char * n_hex;
+ const char * g_hex;
+};
+
+/* All constants here were pulled from Appendix A of RFC 5054 */
+static struct NGHex global_Ng_constants[] = {
+ { /* 1024 */
+ "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",
+ "13"
+ },
+ {0,0} /* null sentinel */
+};
+
+
+static NGConstant * new_ng( SRP_NGType ng_type, const char * n_hex, const char * g_hex )
+{
+ NGConstant * ng = (NGConstant *) malloc( sizeof(NGConstant) );
+ ng->N = BN_new();
+ ng->g = BN_new();
+
+ if ( ng_type != SRP_NG_CUSTOM )
+ {
+ n_hex = global_Ng_constants[ ng_type ].n_hex;
+ g_hex = global_Ng_constants[ ng_type ].g_hex;
+ }
+
+ BN_hex2bn( &ng->N, n_hex );
+ BN_hex2bn( &ng->g, g_hex );
+
+ return ng;
+}
+
+static void delete_ng( NGConstant * ng )
+{
+ BN_free( ng->N );
+ BN_free( ng->g );
+ ng->N = 0;
+ ng->g = 0;
+ free(ng);
+}
+
+
+
+typedef union
+{
+ SHA_CTX sha;
+ SHA256_CTX sha256;
+ SHA512_CTX sha512;
+} HashCTX;
+
+
+struct SRPVerifier
+{
+ SRP_HashAlgorithm hash_alg;
+ NGConstant *ng;
+
+ const char * username;
+ const unsigned char * bytes_B;
+ int authenticated;
+
+ unsigned char M [SHA512_DIGEST_LENGTH];
+ unsigned char H_AMK [SHA512_DIGEST_LENGTH];
+ unsigned char session_key [SHA512_DIGEST_LENGTH];
+};
+
+
+struct SRPUser
+{
+ SRP_HashAlgorithm hash_alg;
+ NGConstant *ng;
+
+ BIGNUM *a;
+ BIGNUM *A;
+ BIGNUM *S;
+
+ const unsigned char * bytes_A;
+ int authenticated;
+
+ const char * username;
+ const unsigned char * password;
+ int password_len;
+
+ unsigned char M [SHA512_DIGEST_LENGTH];
+ unsigned char H_AMK [SHA512_DIGEST_LENGTH];
+ unsigned char session_key [SHA512_DIGEST_LENGTH];
+};
+
+
+static int hash_init( SRP_HashAlgorithm alg, HashCTX *c )
+{
+ switch (alg)
+ {
+ case SRP_SHA1 : return SHA1_Init( &c->sha );
+ case SRP_SHA224: return SHA224_Init( &c->sha256 );
+ case SRP_SHA256: return SHA256_Init( &c->sha256 );
+ case SRP_SHA384: return SHA384_Init( &c->sha512 );
+ case SRP_SHA512: return SHA512_Init( &c->sha512 );
+ default:
+ return -1;
+ };
+}
+static int hash_update( SRP_HashAlgorithm alg, HashCTX *c, const void *data, size_t len )
+{
+ switch (alg)
+ {
+ case SRP_SHA1 : return SHA1_Update( &c->sha, data, len );
+ case SRP_SHA224: return SHA224_Update( &c->sha256, data, len );
+ case SRP_SHA256: return SHA256_Update( &c->sha256, data, len );
+ case SRP_SHA384: return SHA384_Update( &c->sha512, data, len );
+ case SRP_SHA512: return SHA512_Update( &c->sha512, data, len );
+ default:
+ return -1;
+ };
+}
+static int hash_final( SRP_HashAlgorithm alg, HashCTX *c, unsigned char *md )
+{
+ switch (alg)
+ {
+ case SRP_SHA1 : return SHA1_Final( md, &c->sha );
+ case SRP_SHA224: return SHA224_Final( md, &c->sha256 );
+ case SRP_SHA256: return SHA256_Final( md, &c->sha256 );
+ case SRP_SHA384: return SHA384_Final( md, &c->sha512 );
+ case SRP_SHA512: return SHA512_Final( md, &c->sha512 );
+ default:
+ return -1;
+ };
+}
+static unsigned char * hash( SRP_HashAlgorithm alg, const unsigned char *d, size_t n, unsigned char *md )
+{
+ switch (alg)
+ {
+ case SRP_SHA1 : return SHA1( d, n, md );
+ case SRP_SHA224: return SHA224( d, n, md );
+ case SRP_SHA256: return SHA256( d, n, md );
+ case SRP_SHA384: return SHA384( d, n, md );
+ case SRP_SHA512: return SHA512( d, n, md );
+ default:
+ return 0;
+ };
+}
+static int hash_length( SRP_HashAlgorithm alg )
+{
+ switch (alg)
+ {
+ case SRP_SHA1 : return SHA_DIGEST_LENGTH;
+ case SRP_SHA224: return SHA224_DIGEST_LENGTH;
+ case SRP_SHA256: return SHA256_DIGEST_LENGTH;
+ case SRP_SHA384: return SHA384_DIGEST_LENGTH;
+ case SRP_SHA512: return SHA512_DIGEST_LENGTH;
+ default:
+ return -1;
+ };
+}
+
+
+static BIGNUM * H_nn( SRP_HashAlgorithm alg, const BIGNUM * n1, const BIGNUM * n2 )
+{
+ unsigned char buff[ SHA512_DIGEST_LENGTH ];
+ int len_n1 = BN_num_bytes(n1);
+ int len_n2 = BN_num_bytes(n2);
+ int nbytes = len_n1 + len_n2;
+ unsigned char * bin = (unsigned char *) malloc( nbytes );
+ BN_bn2bin(n1, bin);
+ BN_bn2bin(n2, bin + len_n1);
+ hash( alg, bin, nbytes, buff );
+ free(bin);
+ return BN_bin2bn(buff, hash_length(alg), NULL);
+}
+
+static BIGNUM * H_ns( SRP_HashAlgorithm alg, const BIGNUM * n, const unsigned char * bytes, int len_bytes )
+{
+ unsigned char buff[ SHA512_DIGEST_LENGTH ];
+ int len_n = BN_num_bytes(n);
+ int nbytes = len_n + len_bytes;
+ unsigned char * bin = (unsigned char *) malloc( nbytes );
+ BN_bn2bin(n, bin);
+ memcpy( bin + len_n, bytes, len_bytes );
+ hash( alg, bin, nbytes, buff );
+ free(bin);
+ return BN_bin2bn(buff, hash_length(alg), NULL);
+}
+
+static BIGNUM * calculate_x( SRP_HashAlgorithm alg, const BIGNUM * salt, const char * username, const unsigned char * password, int password_len )
+{
+ unsigned char ucp_hash[SHA512_DIGEST_LENGTH];
+ HashCTX ctx;
+
+ hash_init( alg, &ctx );
+
+ hash_update( alg, &ctx, username, strlen(username) );
+ hash_update( alg, &ctx, ":", 1 );
+ hash_update( alg, &ctx, password, password_len );
+
+ hash_final( alg, &ctx, ucp_hash );
+
+ return H_ns( alg, salt, ucp_hash, hash_length(alg) );
+}
+
+static void update_hash_n( SRP_HashAlgorithm alg, HashCTX *ctx, const BIGNUM * n )
+{
+ unsigned long len = BN_num_bytes(n);
+ unsigned char * n_bytes = (unsigned char *) malloc( len );
+ BN_bn2bin(n, n_bytes);
+ hash_update(alg, ctx, n_bytes, len);
+ free(n_bytes);
+}
+
+static void hash_num( SRP_HashAlgorithm alg, const BIGNUM * n, unsigned char * dest )
+{
+ int nbytes = BN_num_bytes(n);
+ unsigned char * bin = (unsigned char *) malloc( nbytes );
+ BN_bn2bin(n, bin);
+ hash( alg, bin, nbytes, dest );
+ free(bin);
+}
+
+static void calculate_M( SRP_HashAlgorithm alg, NGConstant *ng, unsigned char * dest, const char * I, const BIGNUM * s,
+ const BIGNUM * A, const BIGNUM * B, const unsigned char * K )
+{
+ unsigned char H_N[ SHA512_DIGEST_LENGTH ];
+ unsigned char H_g[ SHA512_DIGEST_LENGTH ];
+ unsigned char H_I[ SHA512_DIGEST_LENGTH ];
+ unsigned char H_xor[ SHA512_DIGEST_LENGTH ];
+ HashCTX ctx;
+ int i = 0;
+ int hash_len = hash_length(alg);
+
+ hash_num( alg, ng->N, H_N );
+ hash_num( alg, ng->g, H_g );
+
+ hash(alg, (const unsigned char *)I, strlen(I), H_I);
+
+
+ for (i=0; i < hash_len; i++ )
+ H_xor[i] = H_N[i] ^ H_g[i];
+
+ hash_init( alg, &ctx );
+
+ hash_update( alg, &ctx, H_xor, hash_len );
+ hash_update( alg, &ctx, H_I, hash_len );
+ update_hash_n( alg, &ctx, s );
+ update_hash_n( alg, &ctx, A );
+ update_hash_n( alg, &ctx, B );
+ hash_update( alg, &ctx, K, hash_len );
+
+ hash_final( alg, &ctx, dest );
+}
+
+static void calculate_H_AMK( SRP_HashAlgorithm alg, unsigned char *dest, const BIGNUM * A, const unsigned char * M, const unsigned char * K )
+{
+ HashCTX ctx;
+
+ hash_init( alg, &ctx );
+
+ update_hash_n( alg, &ctx, A );
+ hash_update( alg, &ctx, M, hash_length(alg) );
+ hash_update( alg, &ctx, K, hash_length(alg) );
+
+ hash_final( alg, &ctx, dest );
+}
+
+/* Python module calls random_seed during module initialization */
+#define init_random()
+
+
+/***********************************************************************************************************
+ *
+ * Exported Functions
+ *
+ ***********************************************************************************************************/
+
+void srp_random_seed( const unsigned char * random_data, int data_length )
+{
+ g_initialized = 1;
+
+ if (random_data)
+ RAND_seed( random_data, data_length );
+}
+
+
+void srp_create_salted_verification_key( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username,
+ const unsigned char * password, int len_password,
+ const unsigned char ** bytes_s, int * len_s,
+ const unsigned char ** bytes_v, int * len_v,
+ const char * n_hex, const char * g_hex )
+{
+ BIGNUM * s = BN_new();
+ BIGNUM * v = BN_new();
+ BIGNUM * x = 0;
+ BN_CTX * ctx = BN_CTX_new();
+ NGConstant * ng = new_ng( ng_type, n_hex, g_hex );
+
+ init_random(); /* Only happens once */
+
+ BN_rand(s, 32, -1, 0);
+
+ x = calculate_x( alg, s, username, password, len_password );
+
+ BN_mod_exp(v, ng->g, x, ng->N, ctx);
+
+ *len_s = BN_num_bytes(s);
+ *len_v = BN_num_bytes(v);
+
+ *bytes_s = (const unsigned char *) malloc( *len_s );
+ *bytes_v = (const unsigned char *) malloc( *len_v );
+
+ BN_bn2bin(s, (unsigned char *) *bytes_s);
+ BN_bn2bin(v, (unsigned char *) *bytes_v);
+
+ delete_ng( ng );
+ BN_free(s);
+ BN_free(v);
+ BN_free(x);
+ BN_CTX_free(ctx);
+}
+
+
+
+/* Out: bytes_B, len_B.
+ *
+ * On failure, bytes_B will be set to NULL and len_B will be set to 0
+ */
+struct SRPVerifier * srp_verifier_new( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username,
+ const unsigned char * bytes_s, int len_s,
+ const unsigned char * bytes_v, int len_v,
+ const unsigned char * bytes_A, int len_A,
+ const unsigned char ** bytes_B, int * len_B,
+ const char * n_hex, const char * g_hex )
+{
+ BIGNUM *s = BN_bin2bn(bytes_s, len_s, NULL);
+ BIGNUM *v = BN_bin2bn(bytes_v, len_v, NULL);
+ BIGNUM *A = BN_bin2bn(bytes_A, len_A, NULL);
+ BIGNUM *u = 0;
+ BIGNUM *B = BN_new();
+ BIGNUM *S = BN_new();
+ BIGNUM *b = BN_new();
+ BIGNUM *k = 0;
+ BIGNUM *tmp1 = BN_new();
+ BIGNUM *tmp2 = BN_new();
+ BN_CTX *ctx = BN_CTX_new();
+ int ulen = strlen(username) + 1;
+ NGConstant *ng = new_ng( ng_type, n_hex, g_hex );
+
+ struct SRPVerifier * ver = (struct SRPVerifier *) malloc( sizeof(struct SRPVerifier) );
+
+ init_random(); /* Only happens once */
+
+ ver->username = (char *) malloc( ulen );
+ ver->hash_alg = alg;
+ ver->ng = ng;
+
+ memcpy( (char*)ver->username, username, ulen );
+
+ ver->authenticated = 0;
+
+ /* SRP-6a safety check */
+ BN_mod(tmp1, A, ng->N, ctx);
+ if ( !BN_is_zero(tmp1) )
+ {
+ BN_rand(b, 256, -1, 0);
+
+ k = H_nn(alg, ng->N, ng->g);
+
+ /* B = kv + g^b */
+ BN_mul(tmp1, k, v, ctx);
+ BN_mod_exp(tmp2, ng->g, b, ng->N, ctx);
+ BN_add(B, tmp1, tmp2);
+
+ u = H_nn(alg, A, B);
+
+ /* S = (A *(v^u)) ^ b */
+ BN_mod_exp(tmp1, v, u, ng->N, ctx);
+ BN_mul(tmp2, A, tmp1, ctx);
+ BN_mod_exp(S, tmp2, b, ng->N, ctx);
+
+ hash_num(alg, S, ver->session_key);
+
+ calculate_M( alg, ng, ver->M, username, s, A, B, ver->session_key );
+ calculate_H_AMK( alg, ver->H_AMK, A, ver->M, ver->session_key );
+
+ *len_B = BN_num_bytes(B);
+ *bytes_B = malloc( *len_B );
+
+ BN_bn2bin( B, (unsigned char *) *bytes_B );
+
+ ver->bytes_B = *bytes_B;
+ }
+ else
+ {
+ *len_B = 0;
+ *bytes_B = NULL;
+ }
+
+ BN_free(s);
+ BN_free(v);
+ BN_free(A);
+ if (u) BN_free(u);
+ if (k) BN_free(k);
+ BN_free(B);
+ BN_free(S);
+ BN_free(b);
+ BN_free(tmp1);
+ BN_free(tmp2);
+ BN_CTX_free(ctx);
+
+ return ver;
+}
+
+
+
+
+void srp_verifier_delete( struct SRPVerifier * ver )
+{
+ delete_ng( ver->ng );
+ free( (char *) ver->username );
+ free( (unsigned char *) ver->bytes_B );
+ free( ver );
+}
+
+
+
+int srp_verifier_is_authenticated( struct SRPVerifier * ver )
+{
+ return ver->authenticated;
+}
+
+
+const char * srp_verifier_get_username( struct SRPVerifier * ver )
+{
+ return ver->username;
+}
+
+
+const unsigned char * srp_verifier_get_session_key( struct SRPVerifier * ver, int * key_length )
+{
+ if (key_length)
+ *key_length = hash_length( ver->hash_alg );
+ return ver->session_key;
+}
+
+
+int srp_verifier_get_session_key_length( struct SRPVerifier * ver )
+{
+ return hash_length( ver->hash_alg );
+}
+
+
+/* user_M must be exactly SHA512_DIGEST_LENGTH bytes in size */
+void srp_verifier_verify_session( struct SRPVerifier * ver, const unsigned char * user_M, const unsigned char ** bytes_HAMK )
+{
+ if ( memcmp( ver->M, user_M, hash_length(ver->hash_alg) ) == 0 )
+ {
+ ver->authenticated = 1;
+ *bytes_HAMK = ver->H_AMK;
+ }
+ else
+ *bytes_HAMK = NULL;
+}
+
+/*******************************************************************************/
+
+struct SRPUser * srp_user_new( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username,
+ const unsigned char * bytes_password, int len_password,
+ const char * n_hex, const char * g_hex )
+{
+ struct SRPUser *usr = (struct SRPUser *) malloc( sizeof(struct SRPUser) );
+ int ulen = strlen(username) + 1;
+
+ init_random(); /* Only happens once */
+
+ usr->hash_alg = alg;
+ usr->ng = new_ng( ng_type, n_hex, g_hex );
+
+ usr->a = BN_new();
+ usr->A = BN_new();
+ usr->S = BN_new();
+
+ usr->username = (const char *) malloc(ulen);
+ usr->password = (const unsigned char *) malloc(len_password);
+ usr->password_len = len_password;
+
+ memcpy((char *)usr->username, username, ulen);
+ memcpy((char *)usr->password, bytes_password, len_password);
+
+ usr->authenticated = 0;
+
+ usr->bytes_A = 0;
+
+ return usr;
+}
+
+
+
+void srp_user_delete( struct SRPUser * usr )
+{
+ BN_free( usr->a );
+ BN_free( usr->A );
+ BN_free( usr->S );
+
+ delete_ng( usr->ng );
+
+ free((char *)usr->username);
+ free((char *)usr->password);
+
+ if (usr->bytes_A)
+ free( (char *)usr->bytes_A );
+
+ free( usr );
+}
+
+
+
+int srp_user_is_authenticated( struct SRPUser * usr)
+{
+ return usr->authenticated;
+}
+
+
+const char * srp_user_get_username( struct SRPUser * usr )
+{
+ return usr->username;
+}
+
+
+
+const unsigned char * srp_user_get_session_key( struct SRPUser * usr, int * key_length )
+{
+ if (key_length)
+ *key_length = hash_length( usr->hash_alg );
+ return usr->session_key;
+}
+
+
+int srp_user_get_session_key_length( struct SRPUser * usr )
+{
+ return hash_length( usr->hash_alg );
+}
+
+
+
+/* Output: username, bytes_A, len_A */
+void srp_user_start_authentication( struct SRPUser * usr, const char ** username,
+ const unsigned char ** bytes_A, int * len_A )
+{
+ BN_CTX *ctx = BN_CTX_new();
+
+ BN_rand(usr->a, 256, -1, 0);
+
+ BN_mod_exp(usr->A, usr->ng->g, usr->a, usr->ng->N, ctx);
+
+ BN_CTX_free(ctx);
+
+ *len_A = BN_num_bytes(usr->A);
+ *bytes_A = malloc( *len_A );
+
+ BN_bn2bin( usr->A, (unsigned char *) *bytes_A );
+
+ usr->bytes_A = *bytes_A;
+ *username = usr->username;
+}
+
+
+/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */
+void srp_user_process_challenge( struct SRPUser * usr,
+ const unsigned char * bytes_s, int len_s,
+ const unsigned char * bytes_B, int len_B,
+ const unsigned char ** bytes_M, int * len_M )
+{
+ BIGNUM *s = BN_bin2bn(bytes_s, len_s, NULL);
+ BIGNUM *B = BN_bin2bn(bytes_B, len_B, NULL);
+ BIGNUM *u = 0;
+ BIGNUM *x = 0;
+ BIGNUM *k = 0;
+ BIGNUM *v = BN_new();
+ BIGNUM *tmp1 = BN_new();
+ BIGNUM *tmp2 = BN_new();
+ BIGNUM *tmp3 = BN_new();
+ BN_CTX *ctx = BN_CTX_new();
+
+ u = H_nn(usr->hash_alg, usr->A, B);
+
+ x = calculate_x( usr->hash_alg, s, usr->username, usr->password, usr->password_len );
+
+ k = H_nn(usr->hash_alg, usr->ng->N, usr->ng->g);
+
+ /* SRP-6a safety check */
+ if ( !BN_is_zero(B) && !BN_is_zero(u) )
+ {
+ BN_mod_exp(v, usr->ng->g, x, usr->ng->N, ctx);
+
+ /* S = (B - k*(g^x)) ^ (a + ux) */
+ BN_mul(tmp1, u, x, ctx);
+ BN_add(tmp2, usr->a, tmp1); /* tmp2 = (a + ux) */
+ BN_mod_exp(tmp1, usr->ng->g, x, usr->ng->N, ctx);
+ BN_mul(tmp3, k, tmp1, ctx); /* tmp3 = k*(g^x) */
+ BN_sub(tmp1, B, tmp3); /* tmp1 = (B - K*(g^x)) */
+ BN_mod_exp(usr->S, tmp1, tmp2, usr->ng->N, ctx);
+
+ hash_num(usr->hash_alg, usr->S, usr->session_key);
+
+ calculate_M( usr->hash_alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key );
+ calculate_H_AMK( usr->hash_alg, usr->H_AMK, usr->A, usr->M, usr->session_key );
+
+ *bytes_M = usr->M;
+ if (len_M)
+ *len_M = hash_length( usr->hash_alg );
+ }
+ else
+ {
+ *bytes_M = NULL;
+ if (len_M)
+ *len_M = 0;
+ }
+
+ BN_free(s);
+ BN_free(B);
+ BN_free(u);
+ BN_free(x);
+ BN_free(k);
+ BN_free(v);
+ BN_free(tmp1);
+ BN_free(tmp2);
+ BN_free(tmp3);
+ BN_CTX_free(ctx);
+}
+
+
+void srp_user_verify_session( struct SRPUser * usr, const unsigned char * bytes_HAMK )
+{
+ if ( memcmp( usr->H_AMK, bytes_HAMK, hash_length(usr->hash_alg) ) == 0 )
+ usr->authenticated = 1;
+}
+
+
+/******************************************************************************
+ *
+ * Python Module
+ *
+ *****************************************************************************/
+
+typedef struct
+{
+ PyObject_HEAD
+ struct SRPVerifier * ver;
+ const unsigned char * bytes_B;
+ const unsigned char * bytes_s;
+ int len_B;
+ int len_s;
+}PyVerifier;
+
+
+typedef struct
+{
+ PyObject_HEAD
+ struct SRPUser * usr;
+}PyUser;
+
+
+static void ver_dealloc( PyVerifier * self )
+{
+ if ( self->ver != NULL )
+ srp_verifier_delete( self->ver );
+
+ if ( self->bytes_s != NULL )
+ free( (char *)self->bytes_s );
+
+ self->ob_type->tp_free( (PyObject *) self );
+}
+
+
+static void usr_dealloc( PyUser * self )
+{
+ if ( self->usr != NULL )
+ srp_user_delete( self->usr );
+ self->ob_type->tp_free( (PyObject *) self );
+}
+
+
+static PyObject * ver_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyVerifier *self = (PyVerifier *) type->tp_alloc(type, 0);
+
+ if (!self)
+ return NULL;
+
+ self->ver = NULL;
+ self->bytes_B = NULL;
+ self->bytes_s = NULL;
+ self->len_B = 0;
+ self->len_s = 0;
+
+ return (PyObject *) self;
+}
+
+
+static PyObject * usr_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyUser *self = (PyUser *) type->tp_alloc(type, 0);
+
+ if (!self)
+ return NULL;
+
+ self->usr = NULL;
+
+ return (PyObject *) self;
+}
+
+
+static int ver_init( PyVerifier *self, PyObject *args, PyObject *kwds )
+{
+ const char *username;
+ const unsigned char *bytes_s, *bytes_v, *bytes_A;
+ int len_s, len_v, len_A;
+ int hash_alg = SRP_SHA1;
+ int ng_type = SRP_NG_2048;
+ const char *n_hex = 0;
+ const char *g_hex = 0;
+ static char * kwnames[] = { "username", "bytes_s", "bytes_v", "bytes_A",
+ "hash_alg", "ng_type",
+ "n_hex", "g_hex", NULL };
+
+ if ( self->ver != NULL )
+ {
+ PyErr_SetString(PyExc_TypeError, "Type cannot be re-initialized");
+ return -1;
+ }
+
+ if ( ! PyArg_ParseTupleAndKeywords(args, kwds, "st#t#t#|iiss", kwnames,
+ &username,
+ &bytes_s, &len_s,
+ &bytes_v, &len_v,
+ &bytes_A, &len_A,
+ &hash_alg,
+ &ng_type,
+ &n_hex,
+ &g_hex ) )
+ {
+ return -1;
+ }
+
+ if ( hash_alg < SRP_SHA1 || hash_alg > SRP_SHA512 )
+ {
+ PyErr_SetString(PyExc_ValueError, "Invalid Hash Algorithm");
+ return -1;
+ }
+
+ if ( ng_type < SRP_NG_1024 || ng_type > SRP_NG_CUSTOM )
+ {
+ PyErr_SetString(PyExc_ValueError, "Invalid Prime Number Constant");
+ return -1;
+ }
+
+ if ( ng_type == SRP_NG_CUSTOM && ( !n_hex || !g_hex ) )
+ {
+ PyErr_SetString(PyExc_ValueError, "Both n_hex and g_hex are required when ng_type = NG_CUSTOM");
+ return -1;
+ }
+
+ /* The srp_verifier_new command is computationally intensive. Allowing multiple,
+ * simultaneous calls here will speed things up for multi-cpu machines
+ */
+ Py_BEGIN_ALLOW_THREADS
+ self->ver = srp_verifier_new( (SRP_HashAlgorithm) hash_alg,
+ (SRP_NGType) ng_type,
+ username,
+ bytes_s, len_s,
+ bytes_v, len_v,
+ bytes_A, len_A,
+ &self->bytes_B, &self->len_B,
+ n_hex,
+ g_hex );
+ Py_END_ALLOW_THREADS
+
+ if ( self->bytes_B == NULL )
+ {
+ PyErr_SetString(PyExc_Exception, "SRP-6a safety check violated");
+ return -1;
+ }
+
+ self->bytes_s = malloc( len_s );
+ self->len_s = len_s;
+
+ memcpy( (char *)self->bytes_s, bytes_s, len_s );
+
+ return 0;
+}
+
+
+static int usr_init( PyUser *self, PyObject *args, PyObject *kwds )
+{
+ const char *username = 0;
+ const unsigned char *bytes_password = 0;
+ int len_password = 0;
+ int hash_alg = SRP_SHA1;
+ int ng_type = SRP_NG_2048;
+ const char *n_hex = 0;
+ const char *g_hex = 0;
+ static char * kwnames[] = { "username", "password", "hash_alg",
+ "ng_type", "n_hex", "g_hex", NULL };
+
+
+ if ( self->usr != NULL )
+ {
+ PyErr_SetString(PyExc_TypeError, "Type cannot be re-initialized");
+ return -1;
+ }
+
+ if ( ! PyArg_ParseTupleAndKeywords(args, kwds, "st#|iiss", kwnames,
+ &username,
+ &bytes_password,
+ &len_password,
+ &hash_alg,
+ &ng_type,
+ &n_hex,
+ &g_hex) )
+ {
+ return -1;
+ }
+
+ if ( hash_alg < SRP_SHA1 || hash_alg > SRP_SHA512 )
+ {
+ PyErr_SetString(PyExc_ValueError, "Invalid Hash Algorithm");
+ return -1;
+ }
+
+ if ( ng_type < SRP_NG_1024 || ng_type > SRP_NG_CUSTOM )
+ {
+ PyErr_SetString(PyExc_ValueError, "Invalid Prime Number Constant");
+ return -1;
+ }
+
+ if ( ng_type == SRP_NG_CUSTOM && ( !n_hex || !g_hex ) )
+ {
+ PyErr_SetString(PyExc_ValueError, "Both n_hex and g_hex are required when ng_type = NG_CUSTOM");
+ return -1;
+ }
+
+
+ self->usr = srp_user_new( (SRP_HashAlgorithm) hash_alg,
+ (SRP_NGType) ng_type,
+ username,
+ bytes_password,
+ len_password,
+ n_hex,
+ g_hex );
+
+ return 0;
+}
+
+
+static PyObject * ver_is_authenticated( PyVerifier * self )
+{
+ if ( self->ver == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+ if ( srp_verifier_is_authenticated(self->ver) )
+ Py_RETURN_TRUE;
+ else
+ Py_RETURN_FALSE;
+}
+
+
+static PyObject * usr_is_authenticated( PyUser * self )
+{
+ if ( self->usr == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+ if ( srp_user_is_authenticated(self->usr) )
+ Py_RETURN_TRUE;
+ else
+ Py_RETURN_FALSE;
+}
+
+
+static PyObject * ver_get_username( PyVerifier * self )
+{
+ if ( self->ver == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+
+ return PyString_FromString( srp_verifier_get_username(self->ver) );
+}
+
+
+static PyObject * usr_get_username( PyUser * self )
+{
+ if ( self->usr == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+
+ return PyString_FromString( srp_user_get_username(self->usr) );
+}
+
+
+static PyObject * ver_get_session_key( PyVerifier * self )
+{
+ if ( self->ver == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+ if ( srp_verifier_is_authenticated(self->ver) )
+ {
+ int key_len;
+ const char * u = (const char *)srp_verifier_get_session_key(self->ver, &key_len);
+ return PyString_FromStringAndSize(u, key_len);
+ }
+ else
+ Py_RETURN_NONE;
+}
+
+
+static PyObject * usr_get_session_key( PyUser * self )
+{
+ if ( self->usr == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+ if ( srp_user_is_authenticated(self->usr) )
+ {
+ int key_len;
+ const char * u = (const char *) srp_user_get_session_key(self->usr, &key_len);
+ return PyString_FromStringAndSize(u, key_len);
+ }
+ else
+ Py_RETURN_NONE;
+}
+
+
+static PyObject * ver_get_challenge( PyVerifier * self )
+{
+ if ( self->ver == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+ if ( self->bytes_B == NULL ) {
+ PyErr_SetString(PyExc_Exception, "SRP-6a security check failed");
+ return NULL;
+ }
+
+ return Py_BuildValue("s#s#", self->bytes_s,
+ self->len_s,
+ self->bytes_B,
+ self->len_B);
+}
+
+
+static PyObject * ver_verify_session( PyVerifier * self, PyObject * args )
+{
+ const unsigned char * bytes_M;
+ const unsigned char * bytes_HAMK;
+ int len_M;
+
+ if ( self->ver == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+
+ if ( ! PyArg_ParseTuple(args, "t#", &bytes_M, &len_M) )
+ {
+ return NULL;
+ }
+
+ if ( len_M != srp_verifier_get_session_key_length( self->ver ) )
+ Py_RETURN_NONE;
+
+ srp_verifier_verify_session( self->ver, bytes_M, &bytes_HAMK );
+
+ if ( bytes_HAMK == NULL )
+ Py_RETURN_NONE;
+ else
+ return PyString_FromStringAndSize((const char *) bytes_HAMK,
+ srp_verifier_get_session_key_length( self->ver ));
+}
+
+
+static PyObject * usr_start_authentication( PyUser * self )
+{
+ const char * username;
+ const unsigned char * bytes_A;
+ int len_A;
+
+ if ( self->usr == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+
+ srp_user_start_authentication( self->usr, &username, &bytes_A, &len_A );
+
+ return Py_BuildValue("ss#", username, bytes_A, len_A);
+}
+
+
+static PyObject * usr_process_challenge( PyUser * self, PyObject * args )
+{
+ const unsigned char * bytes_s, *bytes_B;
+ int len_s, len_B, len_M;
+ const unsigned char * bytes_M;
+
+ if ( self->usr == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+
+ if ( ! PyArg_ParseTuple(args, "t#t#", &bytes_s, &len_s, &bytes_B,
+ &len_B) )
+ {
+ return NULL;
+ }
+
+ /* The srp_user_process_challenge command is computationally intensive.
+ * Allowing multiple, simultaneous calls here will speed things up on
+ * multi-cpu machines.
+ */
+ Py_BEGIN_ALLOW_THREADS
+ srp_user_process_challenge( self->usr, bytes_s, len_s, bytes_B, len_B,
+ &bytes_M, &len_M );
+ Py_END_ALLOW_THREADS
+
+ if (bytes_M == NULL)
+ Py_RETURN_NONE;
+ else
+ return PyString_FromStringAndSize((const char *) bytes_M, len_M);
+}
+
+
+static PyObject * usr_verify_session( PyUser * self, PyObject * args )
+{
+ const unsigned char * bytes_HAMK;
+ int len_HAMK;
+
+ if ( self->usr == NULL ) {
+ PyErr_SetString(PyExc_Exception, "Type not initialized");
+ return NULL;
+ }
+
+ if ( ! PyArg_ParseTuple(args, "t#", &bytes_HAMK, &len_HAMK) )
+ {
+ return NULL;
+ }
+
+ if ( len_HAMK == srp_user_get_session_key_length( self->usr ) )
+ srp_user_verify_session( self->usr, bytes_HAMK );
+
+ Py_RETURN_NONE;
+}
+
+
+static PyObject * py_create_salted_verification_key( PyObject *self, PyObject *args, PyObject *kwds )
+{
+ PyObject *ret;
+ const char *username;
+ const unsigned char *bytes_password, *bytes_s, *bytes_v;
+ int len_password, len_s, len_v;
+ int hash_alg = SRP_SHA1;
+ int ng_type = SRP_NG_2048;
+ const char *n_hex = 0;
+ const char *g_hex = 0;
+ static char * kwnames[] = { "username", "password", "hash_alg",
+ "ng_type", "n_hex", "g_hex", NULL };
+
+ if ( ! PyArg_ParseTupleAndKeywords(args, kwds, "st#|iiss", kwnames,
+ &username,
+ &bytes_password,
+ &len_password,
+ &hash_alg,
+ &ng_type,
+ &n_hex,
+ &g_hex) )
+ return NULL;
+
+
+ if ( hash_alg < SRP_SHA1 || hash_alg > SRP_SHA512 )
+ {
+ PyErr_SetString(PyExc_ValueError, "Invalid Hash Algorithm");
+ return NULL;
+ }
+
+ if ( ng_type < SRP_NG_1024 || ng_type > SRP_NG_CUSTOM )
+ {
+ PyErr_SetString(PyExc_ValueError, "Invalid Prime Number Constant");
+ return NULL;
+ }
+
+ if ( ng_type == SRP_NG_CUSTOM && ( !n_hex || !g_hex ) )
+ {
+ PyErr_SetString(PyExc_ValueError, "Both n_hex and g_hex are required when ng_type = NG_CUSTOM");
+ return NULL;
+ }
+
+ srp_create_salted_verification_key( (SRP_HashAlgorithm) hash_alg,
+ (SRP_NGType) ng_type,
+ username, bytes_password, len_password, &bytes_s, &len_s,
+ &bytes_v, &len_v,
+ n_hex,
+ g_hex );
+
+ ret = Py_BuildValue("s#s#", bytes_s, len_s, bytes_v, len_v);
+
+ free((char*)bytes_s);
+ free((char*)bytes_v);
+
+ return ret;
+}
+
+
+/***********************************************************************************/
+static PyMethodDef PyVerifier_methods[] = {
+ {"authenticated", (PyCFunction) ver_is_authenticated, METH_NOARGS,
+ PyDoc_STR("Returns boolean indicating whether the session is "
+ "authenticated or not")
+ },
+ {"get_username", (PyCFunction) ver_get_username, METH_NOARGS,
+ PyDoc_STR("Returns the username the Verifier instance is bound to.")
+ },
+ {"get_session_key", (PyCFunction) ver_get_session_key, METH_NOARGS,
+ PyDoc_STR("Returns the session key for an authenticated session. "
+ "Returns None if the session is not authenticated.")
+ },
+ {"get_challenge", (PyCFunction) ver_get_challenge, METH_NOARGS,
+ PyDoc_STR("Returns: (s,B) or None. The salt & challenge that "
+ "should be sent to the user or None if the SRP-6a "
+ "safety check fails.")
+ },
+ {"verify_session", (PyCFunction) ver_verify_session, METH_VARARGS,
+ PyDoc_STR("Verifies the user based on their reply to "
+ "the challenge")
+ },
+ {NULL} /* Sentinel */
+};
+
+
+static PyMethodDef PyUser_methods[] = {
+ {"authenticated", (PyCFunction) usr_is_authenticated, METH_NOARGS,
+ PyDoc_STR("Returns boolean indicating whether the session is "
+ "authenticated or not")
+ },
+ {"get_username", (PyCFunction) usr_get_username, METH_NOARGS,
+ PyDoc_STR("Returns the username the User instance is bound to.")
+ },
+ {"get_session_key", (PyCFunction) usr_get_session_key, METH_NOARGS,
+ PyDoc_STR("Returns the session key for an authenticated session. "
+ "Returns None if the session is not authenticated.")
+ },
+ {"start_authentication", (PyCFunction) usr_start_authentication,
+ METH_NOARGS,
+ PyDoc_STR("Returns (username,A). The username and initial "
+ "authentication challenge to send to the verifier")
+ },
+ {"process_challenge", (PyCFunction) usr_process_challenge, METH_VARARGS,
+ PyDoc_STR("Returns the reply to send to the server or None if the "
+ "SRP-6a safety check fails")
+ },
+ {"verify_session", (PyCFunction) usr_verify_session, METH_VARARGS,
+ PyDoc_STR("Verifies the server based on its reply to the users "
+ "challenge response")
+ },
+ {NULL} /* Sentinel */
+};
+
+
+static PyMethodDef srp_module_methods[] = {
+ {"create_salted_verification_key", (PyCFunction) py_create_salted_verification_key, METH_VARARGS | METH_KEYWORDS,
+ PyDoc_STR("Returns (s,v): Generates a salt & verifier for the "
+ "given username and password")
+ },
+ {NULL} /* Sentinel */
+};
+
+
+static PyTypeObject PyVerifier_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "srp._srp.Verifier", /*tp_name*/
+ sizeof(PyVerifier), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)ver_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ "SRP-6a verfier", /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ PyVerifier_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ (initproc)ver_init, /*tp_init*/
+ 0, /*tp_alloc*/
+ ver_new, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+};
+
+
+static PyTypeObject PyUser_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "srp._srp.User", /*tp_name*/
+ sizeof(PyUser), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)usr_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ "SRP-6a User", /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ PyUser_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ (initproc)usr_init, /*tp_init*/
+ 0, /*tp_alloc*/
+ usr_new, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+};
+
+
+PyMODINIT_FUNC
+init_srp(void)
+{
+ int init_ok = 0;
+ PyObject *m = NULL;
+ PyObject *os = NULL;
+ PyObject *py_urandom = NULL;
+
+ os = PyImport_ImportModule("os");
+
+ if (os == NULL)
+ return;
+
+ py_urandom = PyObject_GetAttrString(os, "urandom");
+
+ if ( py_urandom && PyCallable_Check(py_urandom) )
+ {
+ PyObject *args = Py_BuildValue("(i)", 32);
+ if ( args )
+ {
+ PyObject *randstr = PyObject_CallObject(py_urandom, args);
+ if ( randstr && PyString_Check(randstr))
+ {
+ char *buff = NULL;
+ Py_ssize_t slen = 0;
+ if (!PyString_AsStringAndSize(randstr, &buff, &slen))
+ {
+ srp_random_seed( (const unsigned char *)buff, slen );
+ init_ok = 1;
+ }
+ }
+ Py_XDECREF(randstr);
+ }
+ Py_XDECREF(args);
+ }
+
+ Py_XDECREF(os);
+ Py_XDECREF(py_urandom);
+
+ if (!init_ok)
+ {
+ PyErr_SetString(PyExc_ImportError, "Initialization failed");
+ return;
+ }
+
+
+ if (PyType_Ready(&PyVerifier_Type) < 0 || PyType_Ready(&PyUser_Type) < 0)
+ return;
+
+ m = Py_InitModule3("srp._srp", srp_module_methods,"SRP-6a implementation");
+
+ if (m == NULL)
+ return;
+
+ Py_INCREF(&PyVerifier_Type);
+ Py_INCREF(&PyUser_Type);
+
+ PyModule_AddObject(m, "Verifier", (PyObject*) &PyVerifier_Type );
+ PyModule_AddObject(m, "User", (PyObject*) &PyUser_Type );
+
+ PyModule_AddIntConstant(m, "NG_1024", SRP_NG_1024);
+ PyModule_AddIntConstant(m, "NG_2048", SRP_NG_2048);
+ PyModule_AddIntConstant(m, "NG_4096", SRP_NG_4096);
+ PyModule_AddIntConstant(m, "NG_8192", SRP_NG_8192);
+ PyModule_AddIntConstant(m, "NG_CUSTOM", SRP_NG_CUSTOM);
+
+
+ PyModule_AddIntConstant(m, "SHA1", SRP_SHA1);
+ PyModule_AddIntConstant(m, "SHA224", SRP_SHA224);
+ PyModule_AddIntConstant(m, "SHA256", SRP_SHA256);
+ PyModule_AddIntConstant(m, "SHA384", SRP_SHA384);
+ PyModule_AddIntConstant(m, "SHA512", SRP_SHA512);
+
+}
diff --git a/srp/doc/conf.py b/srp/doc/conf.py
new file mode 100644
index 0000000..ba75a8c
--- /dev/null
+++ b/srp/doc/conf.py
@@ -0,0 +1,216 @@
+# -*- 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
+# "<project> v<release> 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 <link> 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)
+]
diff --git a/srp/doc/index.rst b/srp/doc/index.rst
new file mode 100644
index 0000000..0c13606
--- /dev/null
+++ b/srp/doc/index.rst
@@ -0,0 +1,22 @@
+.. 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`
+
diff --git a/srp/doc/srp.rst b/srp/doc/srp.rst
new file mode 100644
index 0000000..9cdd967
--- /dev/null
+++ b/srp/doc/srp.rst
@@ -0,0 +1,377 @@
+:mod:`srp` --- Secure Remote Password
+=====================================
+
+.. module:: srp
+ :synopsis: Secure Remote Password
+
+.. moduleauthor:: Tom Cocagne <tom.cocagne@gmail.com>
+
+.. sectionauthor:: Tom Cocagne <tom.cocagne@gmail.com>
+
+
+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)
+
+
+: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[, 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`
+
+ *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 )
+
+ Complete the :class:`Verifier` side of the authentication
+ process. If the authentication succeded the return result,
+ 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
+
+The server replies with the verifier salt and challenge::
+
+ server -> client: s, B = kv + g^b
+
+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)
+ server -> client: H(A, M, K)
+
+SRP 6a requires the two parties to use the following safeguards:
+
+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
+
diff --git a/srp/test_srp.py b/srp/test_srp.py
new file mode 100644
index 0000000..d33fae2
--- /dev/null
+++ b/srp/test_srp.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python
+
+import unittest
+import os.path
+import os
+import sys
+import time
+import thread
+
+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
+
+try:
+ import srp._srp as _srp
+except ImportError:
+ print 'Failed to import srp._srp. Aborting tests'
+ sys.exit(1)
+
+
+test_g_hex = "2"
+test_n_hex = '''\
+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=''):
+ User = u_mod.User
+ Verifier = v_mod.Verifier
+ create_salted_verification_key = g_mod.create_salted_verification_key
+
+ username = 'testuser'
+ password = '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 )
+ uname, A = usr.start_authentication()
+
+ # username, A => server
+ svr = Verifier( uname, _s, _v, A, hash_alg, ng_type, n_hex, g_hex )
+ 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 )
+
+ self.assertTrue( svr.authenticated() and usr.authenticated() )
+
+ def test_pure_python_defaults(self):
+ self.doit( _pysrp, _pysrp, _pysrp )
+
+ def test_ctypes_defaults(self):
+ self.doit( _ctsrp, _ctsrp, _ctsrp )
+
+ def test_c_defaults(self):
+ self.doit( _srp, _srp, _srp )
+
+ def test_mix1(self):
+ self.doit( _pysrp, _ctsrp, _srp )
+
+ def test_mix2(self):
+ self.doit( _pysrp, _srp, _ctsrp )
+
+ def test_mix3(self):
+ self.doit( _ctsrp, _pysrp, _srp )
+
+ def test_mix4(self):
+ self.doit( _ctsrp, _srp, _pysrp )
+
+ def test_mix5(self):
+ self.doit( _srp, _pysrp, _ctsrp )
+
+ def test_mix6(self):
+ self.doit( _srp, _ctsrp, _pysrp )
+
+ def test_hash_SHA512(self):
+ self.doit( _srp, _srp, _srp, hash_alg=srp.SHA512 )
+
+ def test_NG_8192(self):
+ self.doit( _srp, _srp, _srp, ng_type=srp.NG_8192 )
+
+ def test_NG_CUSTOM(self):
+ self.doit( _srp, _srp, _srp, ng_type=srp.NG_CUSTOM, n_hex=test_n_hex, g_hex=test_g_hex )
+
+ def test_all1(self):
+ self.doit( _srp, _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, _srp, hash_alg=srp.SHA224, ng_type=srp.NG_4096 )
+
+ 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())
+
+ usr = _srp.User('test', 'test')
+ self.assertTrue(not usr.authenticated())
+
+
+#-----------------------------------------------------------------------------------
+# 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 = 'testuser'
+password = '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',
+ 'srp._srp' : 'C ' }
+
+ 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 )
+ c_time = param_test( _srp, srp.SHA1, srp.NG_2048 )
+ print ''
+ print 'Performance increases: '
+ print ' ctypes-module : ', py_time/ct_time
+ print ' C-module : ', py_time/c_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 ''
+ print ' |',
+ for ng in ng_types:
+ print ('NG_%d' % prime_map[ng]).rjust(12),
+ print ''
+ print '-'*60
+
+ for hash_alg in hash_types:
+
+ print '%s |' % hash_map[hash_alg],
+ for ng in ng_types:
+ print '{0:>12f}'.format(performance_test(_srp, hash_alg, ng) / 10),
+ 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(_srp, 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)
+
+