Repository: cocagne/pysrp

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


Discussion:

Patch:
diff --git a/srp/_ctsrp.py b/srp/_ctsrp.py
index 7794bf1..9436728 100644
--- a/srp/_ctsrp.py
+++ b/srp/_ctsrp.py
@@ -353,7 +353,7 @@ def get_ngk( hash_class, ng_type, n_hex, g_hex, ctx ):
 
 
 
-def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, salt_len=4 ):
+def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, salt_len=4, k_hex=None ):
     if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None):
         raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM")
     s    = BN_new()
@@ -386,7 +386,7 @@ def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=N
 
 
 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, bytes_b=None):
+    def __init__(self,  username, bytes_s, bytes_v, bytes_A, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_b=None, k_hex=None):
         if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None):
             raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM")
         if bytes_b and len(bytes_b) != 32:
@@ -411,6 +411,8 @@ class Verifier (object):
 
         hash_class = _hash_map[ hash_alg ]
         N,g,k      = get_ngk( hash_class, ng_type, n_hex, g_hex, self.ctx )
+        if k_hex is not None:
+            BN_hex2bn(k, k_hex)
 
         self.hash_class = hash_class
         self.N          = N
@@ -503,7 +505,7 @@ class Verifier (object):
 
 
 class User (object):
-    def __init__(self, username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_a=None, bytes_A=None):
+    def __init__(self, username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_a=None, bytes_A=None, k_hex=None):
         if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None):
             raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM")
         if bytes_a and len(bytes_a) != 32:
@@ -529,6 +531,8 @@ class User (object):
 
         hash_class = _hash_map[ hash_alg ]
         N,g,k      = get_ngk( hash_class, ng_type, n_hex, g_hex, self.ctx )
+        if k_hex is not None:
+            BN_hex2bn(k, k_hex)
 
         self.hash_class = hash_class
         self.N          = N
diff --git a/srp/_pysrp.py b/srp/_pysrp.py
index 6b773f1..d59f100 100644
--- a/srp/_pysrp.py
+++ b/srp/_pysrp.py
@@ -180,7 +180,7 @@ def old_H( hash_class, s1, s2 = '', s3=''):
 
 def H( hash_class, *args, **kwargs ):
     width = kwargs.get('width', None)
-    
+
     h = hash_class()
 
     for s in args:
@@ -221,7 +221,7 @@ def gen_x( hash_class, salt, username, password ):
 
 
 
-def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, salt_len=4 ):
+def create_salted_verification_key( username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, salt_len=4, k_hex=None ):
     if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None):
         raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM")
     hash_class = _hash_map[ hash_alg ]
@@ -257,7 +257,7 @@ def calculate_H_AMK( hash_class, A, M, K ):
 
 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, bytes_b=None):
+    def __init__(self, username, bytes_s, bytes_v, bytes_A, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_b=None, k_hex=None):
         if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None):
             raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM")
         if bytes_b and len(bytes_b) != 32:
@@ -270,7 +270,10 @@ class Verifier (object):
 
         N,g        = get_ng( ng_type, n_hex, g_hex )
         hash_class = _hash_map[ hash_alg ]
-        k          = H( hash_class, N, g, width=len(long_to_bytes(N)) )
+        if k_hex is None:
+            k          = H( hash_class, N, g, width=len(long_to_bytes(N)) )
+        else:
+            k          = int(k_hex, 16)
 
         self.hash_class = hash_class
         self.N          = N
@@ -328,14 +331,17 @@ class Verifier (object):
 
 
 class User (object):
-    def __init__(self, username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_a=None, bytes_A=None):
+    def __init__(self, username, password, hash_alg=SHA1, ng_type=NG_2048, n_hex=None, g_hex=None, bytes_a=None, bytes_A=None, k_hex=None):
         if ng_type == NG_CUSTOM and (n_hex is None or g_hex is None):
             raise ValueError("Both n_hex and g_hex are required when ng_type = NG_CUSTOM")
         if bytes_a and len(bytes_a) != 32:
             raise ValueError("32 bytes required for bytes_a")
         N,g        = get_ng( ng_type, n_hex, g_hex )
         hash_class = _hash_map[ hash_alg ]
-        k          = H( hash_class, N, g, width=len(long_to_bytes(N)) )
+        if k_hex is None:
+            k          = H( hash_class, N, g, width=len(long_to_bytes(N)) )
+        else:
+            k          = int(k_hex, 16)
 
         self.I     = username
         self.p     = password


Test patch:
diff --git a/srp/test_srp.py b/srp/test_srp.py
index 3f1af68..b49aa78 100644
--- a/srp/test_srp.py
+++ b/srp/test_srp.py
@@ -45,7 +45,7 @@ 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=''):
+    def doit(self, u_mod, v_mod, g_mod, hash_alg=srp.SHA1, ng_type=srp.NG_2048, n_hex='', g_hex='', k_hex=None):
         User                           = u_mod.User
         Verifier                       = v_mod.Verifier
         create_salted_verification_key = g_mod.create_salted_verification_key
@@ -55,19 +55,19 @@ class SRPTests( unittest.TestCase ):
 
         _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 )
+        usr      = User( username, password, hash_alg, ng_type, n_hex, g_hex, None, None, k_hex )
         bytes_a  = usr.get_ephemeral_secret()
         uname, A = usr.start_authentication()
 
         # Make sure a recreated User does all the same appropriate things
-        usr2     = User( username, password, hash_alg, ng_type, n_hex, g_hex, bytes_a )
+        usr2     = User( username, password, hash_alg, ng_type, n_hex, g_hex, bytes_a, None, k_hex )
         self.assertEqual(bytes_a, usr2.get_ephemeral_secret())
         uname2, A2 = usr2.start_authentication()
         self.assertEqual(uname, uname2)
         self.assertEqual(A, A2)
 
         # username, A => server
-        svr      = Verifier( uname, _s, _v, A, hash_alg, ng_type, n_hex, g_hex )
+        svr      = Verifier( uname, _s, _v, A, hash_alg, ng_type, n_hex, g_hex, None, k_hex )
         bytes_b  = svr.get_ephemeral_secret()
         s,B      = svr.get_challenge()
 
@@ -80,7 +80,7 @@ class SRPTests( unittest.TestCase ):
         HAMK     = svr.verify_session( M )
 
         # Make sure that a recreated Verifier will authenticate appropriately
-        svr2     = Verifier( uname, _s, _v, A, hash_alg, ng_type, n_hex, g_hex, bytes_b )
+        svr2     = Verifier( uname, _s, _v, A, hash_alg, ng_type, n_hex, g_hex, bytes_b, k_hex )
         self.assertEqual(bytes_b, svr2.get_ephemeral_secret())
         HAMK2    = svr2.verify_session( M )
         self.assertEqual(HAMK, HAMK2)
@@ -165,6 +165,8 @@ class SRPTests( unittest.TestCase ):
         usr = _ctsrp.User('test', 'test')
         self.assertTrue(not usr.authenticated())
 
+    def test_custom_k(self):
+        self.doit( _ctsrp, _pysrp, _ctsrp, k_hex=six.b('5'))
 
 #-----------------------------------------------------------------------------------
 # Performance Testing