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:
Why might the last digit not be correct in this timing attack?
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:
I suspect that the timing measurements might not be accurate enough, especially for the last digit.
I've considered increasing the sleep time, but I'm not sure if that's the best approach.
Expected vs. Actual Results:
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