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
Exemple #2
0
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()
Exemple #6
0
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()
Exemple #8
0
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
Exemple #9
0
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()