paulstatezny
Phx.gen.auth - Why does hashing the tokens prevent timing attacks?
Regarding the phx.gen.auth generator: https://github.com/aaronrenner/phx_gen_auth
I’m trying to understand why, for many of the user tokens (such as reset_password ones), a hashed version of the token is stored instead of the original token.
The only hint I’ve been able to find is the “original design spec”, which states:
The hashing helps protect against timing attacks.
Can someone explain in clear terms how a timing attack would work, if they weren’t hashed? Would it be something like this?
- Attacker attempts to reset a password using a random token. Observes that the request takes N milliseconds.
- Attacker keeps trying random tokens. Once they find one that worked, it takes a statistically significantly different amount of time.
It sounds like the hashing step perhaps makes the request take an order of magnitude longer, such that it’s much more difficult to be able to tell the difference between valid and invalid tokens.
Thanks to anyone who can enlighten me!
Most Liked
LostKobrakai
A timing attack is basically an attacker trying out requests with different inputs. The attacker then tries to observe differences in how long requests take. Those differences can come into existance by different code paths in the request handling having (non tiny) different computational complexity. E.g. for a login one might fetch the user and only if one is found the login password would be hashed for comparison with the db stored value. If no user was found no hashing would happen. The hashing vs. no hashing path likely take noticeable different times. A timing attack could therefore aggregate a list of emails registered to your service, just by observing response times. Long duration → user likely exists; Short duration → user likely doesn’t exist.
Storing the hashed values in the db for tokens likely (I haven’t looked at the code) means on requests, where the user input includes the token, no amount of hashing needs to happen anymore. A single string comparison values is not computationally complex enough to be observable in timing attacks or at least it’s masked by changing latencies in various places between the computation and the attacker.
tangui
Actually it’s not about trying random tokens, but methodically trying with all letters (a-zA-Z) or whatever the allowed symbols are as the first letter, and measure.
Let’s say the token is "dG90b3RpdGl0dXR1dG91dG91", trying with the following strings:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
baaaaaaaaaaaaaaaaaaaaaaaaaaaaa
caaaaaaaaaaaaaaaaaaaaaaaaaaaaa
daaaaaaaaaaaaaaaaaaaaaaaaaaaaa
eaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
faaaaaaaaaaaaaaaaaaaaaaaaaaaaa
...
Yaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Zaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
you’ll notice that it’s a bit longer for "daaaaaaaaaaaaaaaaaaaaaaaaaaaaa" if string comparison is used, simply because only in this case there’ll be a second comparison (the first letter being equal, the string comparison needs to check for the second letter before returning false).
Then repeat with "daaaaaaa...", "dbaaaaaa...", "dcaaaaaa..."…
Sounds impractical but it is not:
A worst-case scenario for guessing an HMAC would require 20×256×n20 \times 256 \times n20×256×n measurements, where nnn is the number of measurements required to pin down a single byte. So—around 5,000,000 requests. You could do that in less than a week at a barely-perceptible 10 req/s.
Storing hash, you can still guess a hash using this attack but this will be useless because what you need is not the hash, but the original message (or any valid preimage), which mean you need to crack the hashing algorithm.
You can use Plug.Crypto.secure_compare/2 in Elixir to avoid timing attacks (it compares string in constant time) but my guess is that you cannot do it in database, which is why hashes are stored instead.
supersimple
I wrote a little about it: https://til.simplebet.io/posts/ki5natqkn3-mitigating-timing-attacks
Hashing the attempted password is going to result in a quasi-random string that has no perceptible relation to the actual password. Therefore, missing the password by 1 character, vs 10 characters does not result in a calculable difference in timing.
Doing a left-to-right comparison would fail at different times no matter how close or far the attempted password was.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









