def test_blinding(): global key_to_oracle key = key_2048 key_to_oracle = key print("\nTest: blinding(key, signing_oracle=signing_oracle)") for _ in range(10): msg_to_sign = b2i( random_bytes(randint(10, (key.size / 8) - 1)).replace( bytes(b'\n'), bytes(b''))) key.add_plaintext(msg_to_sign) signature = blinding(key, signing_oracle=signing_oracle) assert len(signature) == 1 is_correct = subprocess.check_output([ "python", rsa_oracles_path, "verify", key_to_oracle.identifier, i2h(msg_to_sign), i2h(signature[0]) ]).strip().decode() assert is_correct == 'True' key.clear_texts() print("\nTest: blinding(key, decryption_oracle=decryption_oracle)") for _ in range(10): plaintext = b2i( random_bytes(randint(10, (key.size / 8) - 1)).replace( bytes(b'\n'), bytes(b''))) ciphertext = key.encrypt(plaintext) key.add_ciphertext(ciphertext) plaintext_recovered = blinding(key, decryption_oracle=decryption_oracle) assert len(plaintext_recovered) == 1 assert plaintext_recovered[0] == plaintext key.clear_texts() key_to_oracle = None
def verify(message, signature, key): # message = add_rsa_signature_padding(message, size=key.size, hash_function='sha1') message = b2i(message) signature = b2i(signature) if pow(signature, key.e, key.n) == message: return True return False
def test_parity(): key = key_1024 print("\nTest: parity") plaintext1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") plaintext2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be2") ciphertext1 = h2b( subprocess.check_output([ "python", rsa_oracles_path, "encrypt", key.identifier, b2h(plaintext1) ]).strip().decode()) ciphertext2 = h2b( subprocess.check_output([ "python", rsa_oracles_path, "encrypt", key.identifier, b2h(plaintext2) ]).strip().decode()) key.texts.append({'cipher': b2i(ciphertext1)}) key.texts.append({'cipher': b2i(ciphertext2)}) msgs_recovered = parity(parity_oracle, key.publickey()) assert msgs_recovered[0] == b2i(plaintext1) assert msgs_recovered[1] == b2i(plaintext2) key.clear_texts()
def parity_oracle(ciphertext): key = key_1024 ciphertext = b2i(ciphertext) message = pow(ciphertext, key.d, key.n) if message & 1 == 1: return 1 return 0
def length_extension(old_hash, size, new_message, type='sha1'): """Length extension attack: given hash(secret) and len(secret), compute new_hash and old_padding so that hash(secret+old_padding+new_message) == new_hash Args: old_hash(string): hash of secret value size(int): length of secret (in bytes) new_message(string) type(string): sha1 or md4 Returns: tuple: (new_hash, old_padding+new_message) """ implemented_functions = ['sha1', 'md4'] if type == 'sha1': endian = 'big' hash_function = sha1 elif type == 'md4': endian = 'little' hash_function = md4 else: log.critical_error("Not implemented, type must be one of {}".format( implemented_functions)) return None old_padding = add_md_padding(bytes(b'a' * size), endian=endian)[size:] new_data_size = len(new_message) + len(old_padding) + size new_padding = add_md_padding(bytes(b'a' * new_data_size), endian=endian)[new_data_size:] h = [b2i(x, endian=endian) for x in chunks(old_hash, 4)] return hash_function(new_message, h, new_padding), old_padding + new_message
def pkcs15_padding_oracle(ciphertext, **kwargs): kwargs['pkcs15_padding_oracle_calls'][0] += 1 key = kwargs['oracle_key'] ciphertext = b2i(ciphertext) message = pow(ciphertext, key.d, key.n) if message >> (key.size - 16) == 0x0002: return True return
def oaep_padding_oracle(ciphertext, **kwargs): kwargs['manger_padding_oracle_calls'][0] += 1 key = kwargs['oracle_key'] ciphertext = b2i(ciphertext) message = pow(ciphertext, key.d, key.n) if message >> (key.size - 8) == 0x00: return True return
def test_small_e_msg(): key = key_1024_small_e print("\nTest: small_e_msg") for _ in range(10): plaintext = b2i(random_bytes(10)) ciphertext = key.encrypt(plaintext) key.add_ciphertext(ciphertext) recovered_plaintext = small_e_msg(key) assert len(recovered_plaintext) == 1 assert recovered_plaintext[0] == plaintext key.clear_texts() for _ in range(10): plaintext = b2i(random_bytes(42)) ciphertext = key.encrypt(plaintext) key.add_ciphertext(ciphertext) recovered_plaintext = small_e_msg(key) assert len(recovered_plaintext) == 1 assert recovered_plaintext[0] == plaintext key.clear_texts()
def verify_bleichenbacher_middle(message, signature, key, hash_function='sha1'): """00 01 garbage 00 ANS1 HASH""" hash_msg = getattr(hashlib, hash_function)(message).digest() asn1 = hash_asn1[hash_function] signature = b2i(signature) plain = i2b(key.encrypt(signature), size=1024) try: plain_hash = plain[plain.index(bytes(b'\x00'), 2) + 1:] # have ASN1 HASH except: return False if plain[:2] == bytes(b'\x00\x01') and plain_hash == asn1 + hash_msg: return True return False
def decrypt(self, ciphertext): """Raw decryption Args: ciphertext(int/string) Returns: pow(ciphertext, d, n) """ if not isinstance(ciphertext, Number): try: ciphertext = b2i(ciphertext) except: log.critical_error( "Ciphertext to decrypt must be number or be convertible to number ({})" .format(ciphertext)) return self.pyrsa_key.decrypt(gmpy2.mpz(ciphertext))
def encrypt(self, plaintext): """Raw encryption Args: plaintext(int/string) Returns: pow(plaintext,e,n) """ if not isinstance(plaintext, Number): try: plaintext = b2i(plaintext) except: log.critical_error( "Plaintext to decrypt must be number or be convertible to number ({})" .format(plaintext)) return self.pyrsa_key.encrypt(int(plaintext), 0)[0]
def test_RSAKey(): print("\nTest: RSAKey") key = RSAKey.generate(2048) key2 = RSAKey(key.n, key.e) assert key2.n == key.n key2 = RSAKey(key.n, key.e, d=key.d) assert key2.p == key.p or key2.q == key.p key2 = RSAKey(key.n, key.e, p=key.q) assert key2.d == key.d for _ in range(10): tmp = random_bytes(randint(1, key.size // 8 - 10)) assert key.decrypt(key.encrypt(tmp)) == b2i(tmp)
def add_plaintext(self, plaintext, position=None): """Args: plaintext(int/string) position(int/None) - position in list where to add, None for new """ if not isinstance(plaintext, Number): try: plaintext = b2i(plaintext) except: log.critical_error( "Plaintext to add must be number or be convertible to number ({})" .format(plaintext)) if position is None: self.texts.append({'plain': plaintext}) else: self.texts[position]['plain'] = plaintext
def decrypt(ciphertext, key): ciphertext = b2i(ciphertext) plaintext = pow(ciphertext, key.d, key.n) return i2b(plaintext)
def bleichenbacher_signature_forgery(key, garbage='suffix', hash_function='sha1'): """Bleichenbacher's signature forgery based on bug in verify implementation Args: key(RSAKey): with small e and at least one plaintext garbage(string): middle: 00 01 ff garbage 00 ASN.1 HASH suffix: 00 01 ff 00 ASN.1 HASH garbage hash_function(string) Returns: dict: forged signatures, signatures[no] == signature(key.texts[no]['plain']) update key texts """ hash_asn1 = { 'md5': bytes( b'\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10' ), 'sha1': bytes(b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'), 'sha256': bytes( b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20' ), 'sha384': bytes( b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30' ), 'sha512': bytes( b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40' ) } if garbage not in ['suffix', 'middle']: log.critical_error("Bad garbage position, must be suffix or middle") if hash_function not in list(hash_asn1.keys()): log.critical_error( "Hash function {} not implemented".format(hash_function)) if key.e > 3: log.debug("May not work, because e > 3") signatures = {} if garbage == 'suffix': for text_no in range(len(key.texts)): if 'plain' in key.texts[text_no] and 'cipher' not in key.texts[ text_no]: log.info("Forge for plaintext no {} ({})".format( text_no, key.texts[text_no]['plain'])) hash_callable = getattr(hashlib, hash_function)(i2b( key.texts[text_no] ['plain'])).digest() # hack to call hashlib.hash_function plaintext_prefix = bytes(b'\x00\x01\xff\x00') + hash_asn1[ hash_function] + hash_callable plaintext = plaintext_prefix + bytes( b'\x00' * (key.size // 8 - len(plaintext_prefix))) plaintext = b2i(plaintext) for round_error in range(-5, 5): signature, _ = gmpy2.iroot(plaintext, key.e) signature = int(signature + round_error) test_prefix = i2b(gmpy2.powmod(signature, key.e, key.n), size=key.size)[:len(plaintext_prefix)] if test_prefix == plaintext_prefix: log.info("Got signature: {}".format(signature)) log.debug("signature**e % n == {}".format( i2h(gmpy2.powmod(signature, key.e, key.n), size=key.size))) key.texts[text_no]['cipher'] = signature signatures[text_no] = signature break else: log.error( "Something wrong, can't compute correct signature") return signatures elif garbage == 'middle': for text_no in range(len(key.texts)): if 'plain' in key.texts[text_no] and 'cipher' not in key.texts[ text_no]: log.info("Forge for plaintext no {} ({})".format( text_no, key.texts[text_no]['plain'])) hash_callable = getattr(hashlib, hash_function)(i2b( key.texts[text_no] ['plain'])).digest() # hack to call hashlib.hash_function plaintext_suffix = bytes( b'\x00') + hash_asn1[hash_function] + hash_callable if b2i(plaintext_suffix) & 1 != 1: log.error( "Plaintext suffix is even, can't compute signature") continue # compute suffix signature_suffix = 0b1 for b in range(len(plaintext_suffix) * 8): if (signature_suffix** 3) & (1 << b) != b2i(plaintext_suffix) & (1 << b): signature_suffix |= 1 << b signature_suffix = i2b( signature_suffix)[-len(plaintext_suffix):] # compute prefix while True: plaintext_prefix = bytes(b'\x00\x01\xff') + random_bytes( key.size // 8 - 3) signature_prefix, _ = gmpy2.iroot(b2i(plaintext_prefix), key.e) signature_prefix = i2b( int(signature_prefix), size=key.size)[:-len(signature_suffix)] signature = b2i(signature_prefix + signature_suffix) test_plaintext = i2b(gmpy2.powmod(signature, key.e, key.n), size=key.size) if bytes(b'\x00' ) not in test_plaintext[2:-len(plaintext_suffix)]: if test_plaintext[:3] == plaintext_prefix[:3] and test_plaintext[ -len(plaintext_suffix):] == plaintext_suffix: log.info("Got signature: {}".format(signature)) key.texts[text_no]['cipher'] = signature signatures[text_no] = signature break else: log.error("Something wrong, signature={}," " signature**{}%{} is {}".format( signature, key.e, key.n, [(test_plaintext)])) break return signatures
def compression_function_md4(chunk, state): """MD4 compression function, taken from: https://gist.github.com/tristanwietsma/5937448 Args: chunk(string): len(chunk) == 64 state(list of ints): len(state) == 4 Returns: list of ints: compressed state """ def _f(x, y, z): return x & y | ~x & z def _g(x, y, z): return x & y | x & z | y & z def _h(x, y, z): return x ^ y ^ z def _f1(a, b, c, d, k, s, X): return _left_rotate(a + _f(b, c, d) + X[k], s) def _f2(a, b, c, d, k, s, X): return _left_rotate(a + _g(b, c, d) + X[k] + 0x5a827999, s) def _f3(a, b, c, d, k, s, X): return _left_rotate(a + _h(b, c, d) + X[k] + 0x6ed9eba1, s) state = state[:] x = chunks(chunk, 4) x = [b2i(one_x, endian='little') for one_x in x] a, b, c, d = state a = _f1(a, b, c, d, 0, 3, x) d = _f1(d, a, b, c, 1, 7, x) c = _f1(c, d, a, b, 2, 11, x) b = _f1(b, c, d, a, 3, 19, x) a = _f1(a, b, c, d, 4, 3, x) d = _f1(d, a, b, c, 5, 7, x) c = _f1(c, d, a, b, 6, 11, x) b = _f1(b, c, d, a, 7, 19, x) a = _f1(a, b, c, d, 8, 3, x) d = _f1(d, a, b, c, 9, 7, x) c = _f1(c, d, a, b, 10, 11, x) b = _f1(b, c, d, a, 11, 19, x) a = _f1(a, b, c, d, 12, 3, x) d = _f1(d, a, b, c, 13, 7, x) c = _f1(c, d, a, b, 14, 11, x) b = _f1(b, c, d, a, 15, 19, x) a = _f2(a, b, c, d, 0, 3, x) d = _f2(d, a, b, c, 4, 5, x) c = _f2(c, d, a, b, 8, 9, x) b = _f2(b, c, d, a, 12, 13, x) a = _f2(a, b, c, d, 1, 3, x) d = _f2(d, a, b, c, 5, 5, x) c = _f2(c, d, a, b, 9, 9, x) b = _f2(b, c, d, a, 13, 13, x) a = _f2(a, b, c, d, 2, 3, x) d = _f2(d, a, b, c, 6, 5, x) c = _f2(c, d, a, b, 10, 9, x) b = _f2(b, c, d, a, 14, 13, x) a = _f2(a, b, c, d, 3, 3, x) d = _f2(d, a, b, c, 7, 5, x) c = _f2(c, d, a, b, 11, 9, x) b = _f2(b, c, d, a, 15, 13, x) a = _f3(a, b, c, d, 0, 3, x) d = _f3(d, a, b, c, 8, 9, x) c = _f3(c, d, a, b, 4, 11, x) b = _f3(b, c, d, a, 12, 15, x) a = _f3(a, b, c, d, 2, 3, x) d = _f3(d, a, b, c, 10, 9, x) c = _f3(c, d, a, b, 6, 11, x) b = _f3(b, c, d, a, 14, 15, x) a = _f3(a, b, c, d, 1, 3, x) d = _f3(d, a, b, c, 9, 9, x) c = _f3(c, d, a, b, 5, 11, x) b = _f3(b, c, d, a, 13, 15, x) a = _f3(a, b, c, d, 3, 3, x) d = _f3(d, a, b, c, 11, 9, x) c = _f3(c, d, a, b, 7, 11, x) b = _f3(b, c, d, a, 15, 15, x) state[0] = (state[0] + a) & 0xffffffff state[1] = (state[1] + b) & 0xffffffff state[2] = (state[2] + c) & 0xffffffff state[3] = (state[3] + d) & 0xffffffff return state
def encrypt(plaintext, key): plaintext = b2i(plaintext) ciphertext = pow(plaintext, key.e, key.n) return i2b(ciphertext)
def test_bleichenbacher_signature_forgery(): key = key_1024_small_e print( "\nTest bleichenbacher_signature_forgery(key, garbage='suffix', hash_function='sha1')" ) for _ in range(10): message1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") message2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") key.add_plaintext(b2i(message1)) key.add_plaintext(b2i(message2)) forged_signatures = bleichenbacher_signature_forgery( key, garbage='suffix', hash_function='sha1') assert len(forged_signatures) == 2 verify_signature1 = subprocess.check_output([ "python", rsa_oracles_path, "verify_bleichenbacher_suffix", key.identifier, b2h(message1), i2h(forged_signatures[0]), 'sha1' ]).strip().decode() assert verify_signature1 == 'True' verify_signature2 = subprocess.check_output([ "python", rsa_oracles_path, "verify_bleichenbacher_suffix", key.identifier, b2h(message2), i2h(forged_signatures[1]), 'sha1' ]).strip().decode() assert verify_signature2 == 'True' key.clear_texts() print( "\nTest bleichenbacher_signature_forgery(key, garbage='middle', hash_function='sha1')" ) for _ in range(10): message1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") message2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") key.add_plaintext(b2i(message1)) key.add_plaintext(b2i(message2)) forged_signatures = bleichenbacher_signature_forgery( key, garbage='middle', hash_function='sha1') print(forged_signatures) # first plaintext signed if 0 in forged_signatures: verify_signature1 = subprocess.check_output([ "python", rsa_oracles_path, "verify_bleichenbacher_middle", key.identifier, b2h(message1), i2h(forged_signatures[0]), 'sha1' ]).strip().decode() assert verify_signature1 == 'True' # second plaintext signed if 1 in forged_signatures: verify_signature2 = subprocess.check_output([ "python", rsa_oracles_path, "verify_bleichenbacher_middle", key.identifier, b2h(message2), i2h(forged_signatures[1]), 'sha1' ]).strip().decode() assert verify_signature2 == 'True' key.clear_texts()
def sign(message, key): # message = add_rsa_signature_padding(message, size=key.size, hash_function='sha1') message = b2i(message) signature = pow(message, key.d, key.n) return i2b(signature)