Пример #1
0
def parity_oracle_attack(ciphertext, rsa_parity_oracle, holliwood=False):
    """Decrypts the given ciphertext using just the parity method of the oracle. Here a detailed explanation:
    http://secgroup.dais.unive.it/wp-content/uploads/2012/11/Practical-Padding-Oracle-Attacks-on-RSA.html
    """

    # Compute the encryption of 2, which will be our ciphertext multiplier
    multiplier = pow(2, rsa_parity_oracle.e, rsa_parity_oracle.n)

    # Initialize lower and upper bound.
    # I need to use Decimal because it allows me to set the precision for the floating point
    # numbers, which we will need when doing the binary search divisions.
    lower_bound = Decimal(0)
    upper_bound = Decimal(rsa_parity_oracle.n)

    # Compute the number of iterations that we have to do
    k = int(ceil(log(rsa_parity_oracle.n, 2)))

    # Set the precision of the floating point number to be enough
    getcontext().prec = k

    # Binary search for the correct plaintext
    for _ in range(k):
        ciphertext = (ciphertext * multiplier) % rsa_parity_oracle.n

        if rsa_parity_oracle.is_parity_odd(ciphertext):
            lower_bound = (lower_bound + upper_bound) / 2
        else:
            upper_bound = (lower_bound + upper_bound) / 2

        # If the user wants to see the message being decrypted at every iteration, print the current upper_bound
        if holliwood is True:
            print(int_to_bytes(int(upper_bound)))

    # Return the binary version of the upper_bound (converted from Decimal to int)
    return int_to_bytes(int(upper_bound))
Пример #2
0
def forge_signature(message, key_length):
    """Forges a valid RSA signature for the given message using the Bleichenbacher's e=3 RSA Attack."""

    # Prepare the block which will look like PKCS1.5 standard format to the vulnerable server
    block = b'\x00\x01\xff\x00' + ASN1_SHA1 + unhexlify(sha1(message))
    garbage = (((key_length + 7) // 8) - len(block)) * b'\x00'
    block += garbage

    # Get the int version of the block and find its cube root (emulating the signing process)
    pre_encryption = int.from_bytes(block, byteorder='big')
    forged_sig = find_cube_root(pre_encryption)

    # Convert the signature to bytes and return it
    return int_to_bytes(forged_sig)
Пример #3
0
    def verify(self, encrypted_signature, message):

        # Decrypt the given encrypted signature
        signature = b'\x00' + int_to_bytes(self.encrypt(encrypted_signature))

        # Verify that the signature contains a block in PKCS1.5 standard format (vulnerable implementation)
        r = re.compile(b'\x00\x01\xff+?\x00.{15}(.{20})', re.DOTALL)
        m = r.match(signature)
        if not m:
            return False

        # Take the hash part of the signature and compare with the server-computed hash
        hashed = m.group(1)
        return hashed == unhexlify(sha1(message))
Пример #4
0
def rsa_broadcast_attack(ciphertexts):
    """Uses the Chinese Remainder Theorem (CRT) to break e=3 RSA given three ciphertexts of the same plaintext.
    This attack could be easily coded to work also when a different number of ciphertexts is provided.
    Check here for reference: https://crypto.stanford.edu/pbc/notes/numbertheory/crt.html
    """
    c0, c1, c2 = ciphertexts[0][0], ciphertexts[1][0], ciphertexts[2][0]
    n0, n1, n2 = ciphertexts[0][1], ciphertexts[1][1], ciphertexts[2][1]
    m0, m1, m2 = n1 * n2, n0 * n2, n0 * n1

    t0 = (c0 * m0 * mod_inv(m0, n0))
    t1 = (c1 * m1 * mod_inv(m1, n1))
    t2 = (c2 * m2 * mod_inv(m2, n2))
    c = (t0 + t1 + t2) % (n0 * n1 * n2)

    return int_to_bytes(find_cube_root(c))
Пример #5
0
def unpadded_message_recovery(ciphertext, rsa_server):
    """Performs the unpadded message recovery attack on the rsa_server which does not use padding."""

    # Let N and E be the public modulus and exponent respectively
    e, n = rsa_server.get_public_key()

    # Let S be a random number > 1 mod N
    while True:
        s = randint(2, n - 1)
        if s % n > 1:
            break

    # Create a new forged ciphertext
    new_ciphertext = (pow(s, e, n) * ciphertext) % n

    # Decipher it and convert the deciphered string to an int
    new_plaintext = rsa_server.decrypt(new_ciphertext)
    int_plaintext = int.from_bytes(new_plaintext, byteorder='big')

    # Recover the original plaintext as int, remembering to be careful about division in cyclic groups
    r = (int_plaintext * mod_inv(s, n)) % n

    # Convert it back to bytes and return it
    return int_to_bytes(r)
Пример #6
0
def pkcs_1_5_padding_oracle_attack(ciphertext,
                                   rsa_padding_oracle,
                                   key_byte_length,
                                   c_is_pkcs_conforming=True):
    """Implements the PKCS 1.5 padding oracle attack described by Bleichenbacher in CRYPTO '98."""

    # For convenience, let:
    B = 2**(8 * (key_byte_length - 2))
    n, e = rsa_padding_oracle.n, rsa_padding_oracle.e

    # Set the starting values
    c_0 = ciphertext
    M = [(2 * B, 3 * B - 1)]
    i = 1

    # If c is not already PKCS 1.5 conforming, perform an additional step
    if not c_is_pkcs_conforming:

        # Step 1: Blinding
        while True:
            s = randint(0, n - 1)
            c_0 = (ciphertext * pow(s, e, n)) % n
            if rsa_padding_oracle.is_padding_correct(c_0):
                break

    # Find the decrypted message through several iterations
    while True:

        # Step 2.a: Starting the search
        if i == 1:
            s = ceil(rsa_padding_oracle.n, 3 * B)
            while True:

                c = (c_0 * pow(s, e, n)) % n
                if rsa_padding_oracle.is_padding_correct(c):
                    break

                s += 1

        # Step 2.b: Searching with more than one interval left
        elif len(M) >= 2:
            while True:
                s += 1
                c = (c_0 * pow(s, e, n)) % n

                if rsa_padding_oracle.is_padding_correct(c):
                    break

        # Step 2.c: Searching with one interval left
        elif len(M) == 1:
            a, b = M[0]

            # Check if the interval contains the solution
            if a == b:

                # And if it does, return it as bytes
                return b'\x00' + int_to_bytes(a)

            r = ceil(2 * (b * s - 2 * B), n)
            s = ceil(2 * B + r * n, b)

            while True:
                c = (c_0 * pow(s, e, n)) % n
                if rsa_padding_oracle.is_padding_correct(c):
                    break

                s += 1
                if s > (3 * B + r * n) // a:
                    r += 1
                    s = ceil((2 * B + r * n), b)

        # Step 3: Narrowing the set of solutions
        M_new = []

        for a, b in M:
            min_r = ceil(a * s - 3 * B + 1, n)
            max_r = (b * s - 2 * B) // n

            for r in range(min_r, max_r + 1):
                l = max(a, ceil(2 * B + r * n, s))
                u = min(b, (3 * B - 1 + r * n) // s)

                if l > u:
                    raise Exception('Unexpected error: l > u in step 3')

                append_and_merge(M_new, l, u)

        if len(M_new) == 0:
            raise Exception('Unexpected error: there are 0 intervals.')

        M = M_new
        i += 1