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))
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)
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))
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))
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)
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