W tym zadaniu zapoznajemy się z protokołem Secure Remote Password. Protokół dział wykorzystując ideę podobną do tej zgodnie z którą działa protokół Diffielgo-Hellmana i pozwala w bezpieczny sposób uwierzytelniać się np. na serwerze.
W związku z tym, że samo zadanie polega na implementacji poszczególnych kroków podanych przez autorów, wklejam od razu swoją implementację poniżej:
def server36(config):
# Generate salt as random integer
# Generate string xH=SHA256(salt|password)
# Convert xH to integer x somehow (put 0x on hexdigest)
# Generate v=g**x % N
# Save everything but x, xH
SALT = random.randrange(1, config['N'])
data_to_hash = str(SALT)+config['P']
xH = hashlib.sha256(data_to_hash.encode())
x = int(xH.hexdigest(), 16)
v = power_mod(config['g'], x, config['N'])
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_ma:
socket_ma.bind((config['host'], config['port']))
socket_ma.listen()
conn, addr = socket_ma.accept()
with conn:
print("Connected: ", addr)
data = wait_and_recv_data(conn, 4*1024)
print("Received: ", data)
b = random.randrange(1, config['N'])
B = config['k']*v + power_mod(config['g'], b, config['N'])
json_data = json.loads(data)
A = json_data['A']
# Send salt, B=kv + g**b % N
resp = json.dumps({
'salt': SALT,
'B': B
}).encode()
conn.sendall(resp)
sleep(1)
# Compute string uH = SHA256(A|B), u = integer of uH
# Generate S = (A * v**u) ** b % N
# Generate K = SHA256(S)
uH = hashlib.sha256((str(A)+str(B)).encode())
u = int(uH.hexdigest(),16)
S = power_mod(A*power_mod(v, u, config['N']), b, config['N'])
K = hashlib.sha256(str(S).encode())
# print('SRV::S: ', S)
# print('SRV::K: ', K.hexdigest())
data = wait_and_recv_data(conn, 4*1024)
hmac_obj = hmac.new(K.digest(), str(SALT).encode(), hashlib.sha256)
valid_hmac = hmac_obj.digest()
if (valid_hmac == data):
conn.sendall(b'OK')
else:
conn.sendall(b'FAIL')
def s5challenge36():
print("Challenge 36")
config = {
'N': 0xffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff,
'g': 2,
'k': 3,
'I': '[email protected]',
'P': 'superpassword',
'host': '127.0.0.1',
'port': 40038
}
server_handle = threading.Thread(target=server36, args=(config,))
server_handle.start()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_a:
socket_a.connect((config["host"], config["port"]))
# Send I, A=g**a % N (a la Diffie Hellman)
a = random.randrange(1, config["N"])
A = power_mod(config["g"], a, config["N"])
session_init_data = json.dumps(
{
"I": config['I'],
"A": A
}
).encode()
socket_a.sendall(session_init_data)
resp = wait_and_recv_data(socket_a, 4096)
print("Client received: ", resp)
json_data = json.loads(resp)
# Compute string uH = SHA256(A|B), u = integer of uH
B = json_data['B']
SALT = json_data['salt']
uH = hashlib.sha256((str(A)+str(B)).encode())
u = int(uH.hexdigest(),16)
# Generate string xH=SHA256(salt|password)
# Convert xH to integer x somehow (put 0x on hexdigest)
# Generate S = (B - k * g**x)**(a + u * x) % N
# Generate K = SHA256(S)
data_to_hash = str(SALT)+config['P']
xH = hashlib.sha256(data_to_hash.encode())
x = int(xH.hexdigest(), 16)
S = power_mod(B - config['k'] * power_mod(config['g'], x, config['N']), a+u*x, config['N'])
K = hashlib.sha256(str(S).encode())
# print("CLIENT::S: ", S)
# print("CLIENT::K: ", K.hexdigest())
hmac_obj = hmac.new(K.digest(), str(SALT).encode(), hashlib.sha256)
socket_a.sendall(hmac_obj.digest())
resp = wait_and_recv_data(socket_a, 4096)
print("CLIENT::Srv response: ", resp)
server_handle.join()
Powyższa implementacja prezentuje ideę działania protokołu SRP natomiast zawiera pewną istotną zmianę. W tym wypadku zarówno serwer jak i klient znają zarówno hasło jak i „sól” (losowa wartość dodawana do hasła podczas obliczania funkcji skrótu). W normalnych implementacjach klient podczas tworzenia konta na serwerze przesyła na serwer swój login, sól oraz wartość v (weryfikator). Następnym razem kiedy użytkownik chce się zalogować, na serwer przesyłany jest login a serwer znajduje przypisane do danego loginu sól i weryfikator i dalej proces przebiega już podobnie jak w powyższej implementacji. Polecam zapoznać się z ciekawą prezentacją na ten temat:
Kod dostępny w repozytorium: https://gitlab.com/akoltys/cryptopals.