def test_hmac(password, salt, u, client_public_key, secret_key, base, real_hmac, prime = NIST_PRIME): """ Takes in all variables used by the server in simple SRP and calculates the hmac. Tests if the calculated hmac matches the true hmac. Args: password (bytes): user password salt (int): salt used in SRP. Should be random u (int): u used in SRP. Should be random client_public_key (int): clients public key secret_key (int): servers secret key base (int): base used in diffie hellman returns: bool: True if simple srp claculation matches hmac given, false otherwise """ salt = bso.int_to_bytes(salt) x = int(sha256(salt + password).hexdigest(), 16) v = modexp(base, x, prime) S = modexp(client_public_key * modexp(v, u, prime), secret_key, prime) hmac_key = sha256(bso.int_to_bytes(S)).digest() calc_hmac = hmac(hmac_key, salt, lambda val:sha256(val).digest(), 64, 32) if real_hmac == calc_hmac: return True return False
def verify_hmac(self, recv_hmac): """ Final SRP verification. Computes the hmac which depends depends on the salt and password but also on a Diffie Hellmen shared key. Equates with the hamc submitted by the client Args: recv_hmac (bytes): hmac computed byt he client Returns: string, int: 'OK', 200 if the verification is successful 'Nope', 400 if the verification is unsuccessful """ S = modexp(self.client_public_key * modexp(self.v, self.u, self.prime), self.secret_key, self.prime) hmac_key = sha256(bso.int_to_bytes(S)).digest() salt_bytes = bso.int_to_bytes(self.salt) calc_hmac = hmac(hmac_key, salt_bytes, lambda val: sha256(val).digest(), 64, 32) if recv_hmac == calc_hmac: return 'OK', 200 return 'Nope', 400
def calculate_hmac(self): """Calculates the hmac and returns it. If the password is correct, it it should match the hmac calculated byt the server. returns: bytes: hmac depending on password, salt and server and client keys """ u = sha256( bso.int_to_bytes(self.public_key) + bso.int_to_bytes(self.server_public_key)).hexdigest() u = int(u, 16) #conver salt to bytes for hashing salt_bytes = bso.int_to_bytes(self.salt) password_exp = sha256(salt_bytes + self.user_password).hexdigest() password_exp = int(password_exp, 16) #Calculating the key for hmac S_base = (self.server_public_key - self.k * modexp(self.base, password_exp, self.prime)) % self.prime S_exponent = (self.secret_key + u * password_exp) % self.prime S = modexp(S_base, S_exponent, self.prime) hmac_key = sha256(bso.int_to_bytes(S)).digest() calc_hmac = hmac(hmac_key, salt_bytes, lambda val: sha256(val).digest(), 64, 32) return calc_hmac
def verify(self, message, public_key, r, s): s_inv = nt.invmod(s, self.q) msg_hash = int(self.hash(message).hexdigest(), 16) exp1 = msg_hash * s_inv % self.q exp2 = r * s_inv % self.q v = ((nt.modexp(self.g, exp1, self.p) * nt.modexp(public_key, exp2, self.p)) % self.p) % self.q return v == r
def parity_oracle_attack(ciphertext, e, modulus, parity_oracle): """Cracks an rsa ciphertext using a parity oracle Args: cipehertext (int): encrypted plaintext for decryption e (int): public exponent used in rsa moduls (int): Modulus used by rsa encryption parity_oracle (function): fucntion which decrypts the ciphertext and returns true if the ciphertext is even, false otherwise returns: int: decrypted ciphertext """ lower_bound = 0 upper_bound = modulus while lower_bound != upper_bound: ciphertext = (nt.modexp(2, e, modulus) * ciphertext) difference = upper_bound - lower_bound if difference % 2 == 1: difference += 1 if parity_oracle(ciphertext): upper_bound = upper_bound - difference // 2 else: lower_bound = lower_bound + difference // 2 #Uncomment for hollywood style hacking #print(upper_bound) return upper_bound
def step2c(self): a, b = self.M[0] r = ceil_division(2 * (b * self.s - 2 * self.B), self.mod) for _ in range(0, 1000): #We expect to find a value every 3 iterations of r. 1000 should be #overkill test_s = ceil_division(2 * self.B + r * self.mod, b) upper_bound = ceil_division(3 * self.B + r * self.mod, a) while test_s < upper_bound: if self.oracle( (self.c * nt.modexp(test_s, self.e, self.mod)) % self.mod): self.s = test_s return test_s += 1 r += 1 raise Exception('Error, r,s pair not found in step 2c')
def gen_public_key(self): if self.secret_key == None: raise Exception( 'Need to set secret key before calculating public key') self.public_key = numbers.modexp(self.base, self.secret_key, self.prime) return self.public_key
def gen_shared_key(self, public_key): if self.secret_key == None: raise Exception( 'Need to set secret key before calculating shared key') self.shared_key = numbers.modexp(public_key, self.secret_key, self.prime) return self.shared_key
def send_dh_public_key(self): """Calculates and returns the SRP public key and user email Returns: bytes, int: a tuple of user email (bytes) and the clients secret key (int) """ self.public_key = modexp(self.base, self.secret_key, self.prime) return self.user_email, self.public_key
def step2b(self): s_start = self.s + 1 while not self.oracle( (self.c * nt.modexp(s_start, self.e, self.mod)) % self.mod): s_start += 1 self.s = s_start
def decrypt_to_bytes_and_check_padding(self, ciphertext): #Decrypt a la RSA plaintext = nt.modexp(ciphertext, self.d, self.n) plaintext = plaintext.to_bytes(bso.byte_len(self.n), 'big') #Check the padding before returning the decryption if self.check_padding_from_bytes(plaintext): return plaintext else: raise Exception('Invalid padding')
def send_dh_public_key(self): """Calculates and returns the SRP public key Returns: salt (int): random integer below the agreed upon prime public_key (int): public key generated by the server for SRP """ #key calculaton self.public_key = (self.k * self.v + modexp(self.base, self.secret_key, self.prime)) % self.prime return self.salt, self.public_key
def send_dh_public_key(self): """Calculates and returns the SRP public key Returns: salt (int): random integer below the agreed upon prime public_key (int): public key generated by the server for SRP int: used in SRP calculation """ #key calculaton self.public_key = modexp(self.base, self.secret_key, self.prime) self.u = secrets.randbelow(2**129) return self.salt, self.public_key, self.u
def sign_message(self, message): r = s = 0 k = secrets.randbelow(self.q) r = nt.modexp(self.g, k, self.p) % self.q k_inv = nt.invmod(k, self.q) msg_hash = int(self.hash(message).hexdigest(), 16) s = (k_inv * (msg_hash + self.secret_key * r)) % self.q return self.public_key, r, s
def main(): oracle = RSAOracle() client = rsa.RSAClient() client.recv_public_key(*oracle.send_public_key()) message = b'A secret message' #Client encrypts the message message_int = int(bso.bytes_to_hex(message), 16) ciphertext = client.encrypt(message_int) #Client sends ciphertext which gets decrypted assert message == bso.hex_to_bytes(hex(oracle.decrypt(ciphertext))[2:]) #Attacker intercepts the ciphertext and tries to get the plaintext from #the oracle. This fails because the oracle only decrypts each plaintext once try: successfully_decrypted = message == bso.hex_to_bytes( hex(oracle.decrypt(ciphertext))[2:]) except: successfully_decrypted = False assert successfully_decrypted == False #instead the attacker can get the decryption of an alternate ciphertext and # convert it to the original message. This attack uses the fat that exponentiation # is a homomorphism e, n = oracle.send_public_key() #S can be any value S = 2 altered_ciphertext = nt.modexp(S, e, n) * ciphertext % n altered_message = oracle.decrypt(altered_ciphertext) #altered message = S * message new_message = altered_message * nt.invmod(S, n) % n assert message == bso.hex_to_bytes(hex(new_message)[2:])
def main(): server = ChallengeDSAUser(SHA1) client = ChallengeDSAUser(SHA1) #malicious g parameter server.g = client.g = 0 server.key_generation() client.key_generation() message = b'henlo' signature = server.sign_message(message) assert client.verify(message, *signature) assert signature[0] == 0 assert signature[1] == 0 #since g = 0, any signature will have r = 0 and secret key = 0 # but worse still, any signature with r = 0 will verify any message new_message = secrets.token_bytes(21) assert client.verify(new_message, *signature) #new malicious g value server.g = client.g = server.p + 1 server.key_generation() client.key_generation() signature = server.sign_message(message) assert signature[0] == signature[1] == 1 assert client.verify(message, *signature) #we can construct a magic signature for a given public key and any message #choose a random public key public_key = secrets.randbelow(server.p) z = 3 #a magic number (not actually magic in this case, any z will do) r = nt.modexp(public_key, z, server.p) % server.q s = (r * nt.invmod(z, server.q)) % server.q assert client.verify(new_message, public_key, r, s)
def _password_computation(self, user_password): """Computes a hash of the users password for storing and later computations Args: user_password (bytes): users password Returns: hashed version of the password which is used in SRP """ #Salt needs to be converted to bytes for use in the hash salt_bytes = bso.int_to_bytes(self.salt) #password is padded with salt (a random int) and hased to be used as #an exponent. This same calculation is also done by the client. password_exponent = sha256(salt_bytes + user_password).hexdigest() password_exponent = int(password_exponent, 16) return modexp(self.base, password_exponent, self.prime)
def test_nonce(self, nonce, message, r, s): """Tests is a nonce gives the desired signature for a message""" message_int = int(self.hash(message).hexdigest(), 16) test_r = nt.modexp(self.g, nonce, self.p) % self.q #Don't test s if r does not match if test_r == r: secret_key = self.secret_key_from_nonce(message, nonce, r, s) nonce_inv = nt.invmod(nonce, self.q) test_s = (nonce_inv * (message_int + secret_key * r)) % self.q if test_s == s: return secret_key return 0
def key_generation(self): self.secret_key = secrets.randbelow(self.q) self.public_key = nt.modexp(self.g, self.secret_key, self.p)
def decrypt(self, ciphertext): return nt.modexp(ciphertext, self.d, self.n)
def encrypt(self, plaintext): return nt.modexp(plaintext, self.e, self.n)