TL;DR
Yes, timing attacks on string comparison are possible and have been demonstrated in proof-of-concept (PoC) code. They exploit the fact that comparing strings character by character takes longer when there’s a mismatch early on. This can leak information about secret strings like passwords or API keys. Mitigation involves using constant-time comparison functions where available, and avoiding direct string comparisons of sensitive data.
Understanding Timing Attacks
A timing attack measures the time it takes to execute an operation. In this case, we’re looking at how long a program takes to compare two strings. If the comparison stops as soon as it finds a difference, the execution time will vary depending on where that difference occurs. A clever attacker can use these tiny differences in timing to guess characters of a secret string.
Proof-of-Concept: Python Example
The following Python example demonstrates a simple timing attack vulnerability. It’s not sophisticated, but illustrates the core principle.
import time
def vulnerable_compare(secret, user_input):
if len(secret) != len(user_input):
return False
for i in range(len(secret)):
if secret[i] != user_input[i]:
return False
return True
secret = "password123"
time.sleep(0.1) # Allow time for system to settle
start_time = time.time()
vulnerable_compare(secret, "passworx123")
end_time = time.time()
print(f"Comparison time with mismatch: {end_time - start_time}")
time.sleep(0.1)
start_time = time.time()
vulnerable_compare(secret, "password123")
end_time = time.time()
print(f"Comparison time with match: {end_time - start_time}")
You’ll notice the comparison takes longer when there’s a mismatch because it iterates further before returning False.
Steps to Protect Against Timing Attacks
- Use Constant-Time Comparison Functions: Many programming languages and cyber security libraries provide functions designed to take the same amount of time regardless of input.
- Python (secrets module): The
secrets.compare_digest()function is specifically for constant-time comparisons.import secrets secret = "password123" user_input = "password123" if secrets.compare_digest(secret, user_input): print("Passwords match!") else: print("Passwords do not match.") - C/C++: Use functions like
memcmp()with caution (see step 4). Some implementations are constant-time, others aren’t. - Java:
MessageDigest.isEqual()can be used for comparing byte arrays in a constant time manner.
==, !=).- This doesn’t eliminate all risks but significantly reduces the attack surface.
memcmp() (C/C++): While often used, not all implementations of memcmp() are constant-time. Check your compiler documentation and platform specifics.
- Some compilers optimise
memcmp()which can introduce timing variations.
Further Considerations
- Side-Channel Attacks: Timing attacks are a type of side-channel attack. Other side channels include power consumption, electromagnetic radiation, and acoustic emissions.
- Compiler Optimizations: Compilers can sometimes introduce timing variations even in seemingly safe code. Be aware of this possibility.