pythonalgorithmperformancesecuritytiming-attack

Why is the last digit incorrect in my timing attack password cracker using Python?


I'm working on a timing attack to crack a password using Python. My goal is to measure the time it takes to check each digit of the password and use this information to deduce the correct digits. The code mostly works, but I often find that the last digit is not correct. Here is the code I have so far:

import time
real_password = "7090"

def check_password(password):  # Don't change
    if len(password) != len(real_password):
        return False

    for x, y in zip(password, real_password):
        time.sleep(0.1)  # Simulates the wait time of the safe's mechanism

        if int(x) != int(y):
            return False
    return True

def crack_password():
    password = ["0"] * len(real_password)

    for i in range(len(real_password)):
        max_time = 0
        correct_digit = 0

        for digit in range(10):
            password[i] = str(digit)
            start_time = time.perf_counter()
            check_password("".join(password))
            end_time = time.perf_counter()
            elapsed_time = end_time - start_time

            # Compare the elapsed time to find the maximum

            if elapsed_time > max_time:
                max_time = elapsed_time
                correct_digit = digit        

        # Set the found correct digit
        password[i] = str(correct_digit)

    return "".join(password)


print(crack_password())

QUESTIONS:

  1. Why might the last digit not be correct in this timing attack?

  2. How can I improve the accuracy of my timing measurements to ensure the correct password is found?

Any help or suggestions would be greatly appreciated. Thanks!

Attempts and Research:

Expected vs. Actual Results:


Solution

  • The timing you will get for checking the very last digit will not offer much direction, because the number of times that sleep has been executed will be equal whether or not you provided the correct digit. The loop will start all iterations in either case.

    But there is another difference you can use for the last digit: the returned value! It is quite obvious once you see it, but you should exit immediately once you get a True as returned value:

                if check_password("".join(password)):
                    return "".join(password)
    

    And as suggested, it would be more reliable for your timing approach if you would perform the join before starting the timing:

                s = "".join(password)
                start_time = time.perf_counter()
                if check_password(s):
                    return s