Secure Remote Password Protocol
Secure Remote Password Protocol (SRP)¶
- Used in Apple's iCloud Key Vault and MikroTik Authentication
- Does not store the password in plaintext
Security¶
SRP is vulnerable to pre-computation attacks, due to the fact that it hands over the user’s “salt” to any attacker who can start an SRP session. This means I can ask a server for your salt, and build a dictionary of potential password hashes even before the server is compromised.
Example¶
https://margin.re/blog/mikrotik-authentication-revealed.aspx#fn1
User Registration:
from cryptopals_lib import *
import os, hashlib
import hmac
def generate_key_pair(prime=((1 << 1024) - 1093337), g=7):
#generate a 1024 bit prime number for the Mod
#prime_mod = generate_probable_prime(1024)
prime_mod = prime
#Genreate Secret Exponent
secret_exp = secure_rand_between(2, prime_mod-2)
generator = g
#Generate the Transmit key A = G^x % P
transmit_key = pow(generator, secret_exp, prime_mod)
return {"generator":generator, "prime_mod":prime_mod, "transmit_key": transmit_key}, {"generator":generator, "prime_mod":prime_mod, "secret_exp": secret_exp}
class SRPServer():
def __init__(self, k, hash_obj=hashlib.sha256):
self.salt_length = 16
self.k = k
self.hash_obj = hash_obj
self.database = {}
self.auth_database = {}
#Generate server Keys
server_public_key, server_private_key = generate_key_pair()
def register_user(self, username, salt, verifier):
if username not in self.database:
self.database[username] = {"salt": salt, "verifier": verifier}
else:
print("Error: Already in Database")
def authenticate_init(self, username, client_public_key):
if username not in self.database:
print(f"Error: {username} is not registed")
return
else:
salt, verifier = self.database[username]["salt"], self.database[username]["verifier"]
#B = (k * v + pow(g, b, N)) % N
session_public_key, session_private_key = generate_key_pair()
tmp_server_key = ((k * verifier) + session_public_key["transmit_key"]) % prime
self.auth_database[username] = {"client_public_key": client_public_key, "tmp_server_key": tmp_server_key, "session_private_key": session_private_key}
return salt, tmp_server_key
def authenticate_verify(self, username, client_proof):
if username not in self.database:
print(f"Error: {username} is not registed")
return
else:
#Pull data from database
salt, verifier = self.database[username]["salt"], self.database[username]["verifier"]
client_public_key, tmp_server_key, session_private_key = self.auth_database[username]["client_public_key"], self.auth_database[username]["tmp_server_key"], self.auth_database[username]["session_private_key"]
#Calculate scrambling paramater
u = hash_obj(int_to_bytes(client_public_key) + b":" + int_to_bytes(tmp_server_key)).digest()
#Compute Session Keys
tmp = pow(verifier, bytes_to_int(u), prime)
session_key_s = pow(client_public_key * tmp, session_private_key["secret_exp"], prime)
session_key_k = hash_obj(int_to_bytes(session_key_s)).digest()
print(f"Server: session_key_k: {session_key_k.hex()}")
#Verify client_proof
#M_c = H(H(N) ^ H(g), H(I), s, A, B, K_c)
tmp = fixedlen_xor(hash_obj(int_to_bytes(prime)).digest(), hash_obj(int_to_bytes(g)).digest())
client_proof_verify = hash_obj(tmp + b":" + hash_obj(username).digest() + b":" + salt + b":"
+ int_to_bytes(client_public_key) + b":" + int_to_bytes(tmp_server_key) + b":" + session_key_k).digest()
print(f"client_proof: {client_proof.hex()}")
print(f"client_proof_verify: {client_proof_verify.hex()}")
if client_proof == client_proof_verify:
#Compute M_s
M_s = hash_obj(int_to_bytes(client_public_key) + b":" + client_proof + b":" + session_key_k).digest()
#print(f"Server: M_s: {M_s.hex()}")
return M_s
else:
print("Error: Client Verification is invalid")
return
def generate_salt(self):
return os.urandom(self.salt_length)
#Globals
username = b"admin"
password = b"Password123"
k = 3
hash_obj = hashlib.sha256
prime=((1 << 1024) - 1093337)
g=7
if __name__ == '__main__':
#Exchange Public Keys
server = SRPServer(k)
#### Register User
print(f"Register User: {username}")
salt = os.urandom(16)
x = hash_obj(salt + b":" + username + b":" + password).digest()
verifier = pow(g, bytes_to_int(x), prime)
server.register_user(username, salt, verifier)
print()
#### Authenticate User
print(f"Authenticate User: {username}")
#Generate Temp Key
client_public_key, client_private_key = generate_key_pair()
#print(f"client_public_key: {client_public_key}")
salt, tmp_server_key = server.authenticate_init(username, client_public_key["transmit_key"])
#Calculate scrambling paramater
u = hash_obj(int_to_bytes(client_public_key["transmit_key"]) + b":" + int_to_bytes(tmp_server_key)).digest()
#Compute the Session Key
x = bytes_to_int(hash_obj(salt + b":" + username + b":" + password).digest())
session_key_s = pow(tmp_server_key - k * pow(g, x, prime), client_private_key["secret_exp"] + bytes_to_int(u) * x, prime)
session_key_k = hash_obj(int_to_bytes(session_key_s)).digest()
#print(f"session_key_s: {session_key_s}")
print(f"Client: session_key_k: {session_key_k.hex()}")
#H(H(N) ^ H(g), H(I), s, A, B, K_c)
tmp = fixedlen_xor(hash_obj(int_to_bytes(prime)).digest(), hash_obj(int_to_bytes(g)).digest())
client_proof = hash_obj(tmp + b":" + hash_obj(username).digest() + b":" + salt + b":"
+ int_to_bytes(client_public_key["transmit_key"]) + b":" + int_to_bytes(tmp_server_key) + b":" + session_key_k).digest()
#Send Client Verification
server_proof = server.authenticate_verify(username, client_proof)
#Verify Server Verification
#H(A, M_c, K_s)
server_proof_verify = hash_obj(int_to_bytes(client_public_key["transmit_key"]) + b":" + client_proof + b":" + session_key_k ).digest()
print(f"server_proof: {server_proof.hex()}")
print(f"server_proof_verify: {server_proof_verify.hex()}")
#Verify Server Key