Consider this common example used to demonstrate timing attacks:
async def sign_in(username, password):
user = await get_user_from_db(username)
if user is None:
return False # early return :(
password_hash = slow_hash(password)
return verify(password_hash, user.password_hash)
The usual suggestion is to do the same thing on all execution branches. For example, something like this:
async def sign_in(username, password):
user = await get_user_from_db(username)
if user is None:
actual_password_hash = "foo"
else:
actual_password_hash = user.password_hash
password_hash = slow_hash(password)
res = verify(password_hash, actual_password_hash)
return res and user is not None
But I wonder if the following strategy is also useful against timing attacks (not considering other types of side-channel attacks), while not wasting computing resources:
async def sign_in(username, password):
# Longer than what `sign_in_impl` takes normally
fixed_duration = ...
_, sign_in_result = await asyncio.gather(delay(fixed_duration), sign_in_impl)
return sign_in_result
# Awaits a certain amount of time
async def delay(duration):
...
# This takes variable time
async def sign_in_impl(username, password):
user = await get_user_from_db(username)
if user is None:
return False # early return :(
password_hash = slow_hash(password)
return verify(password_hash, user.password_hash)
Go to Source
Author: Zizheng Tai