Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # The simplest digest function from the article
- # No salt, no frills
- def getDigest(password):
- return hashlib.sha256(password).hexdigest()
- # I have a dictionary file with 38600 words
- # pw_digest will be the SHA-256 hash of an arbitrarily picked dictionary word
- pw_digest = getDigest('???')
- # This will be our model of the server
- # It hashes the supplied password, then compares the hash against the
- # expected hash using '=='
- def verify_password(pw):
- time_used = 0
- # This is our '=='
- # We'll use the hex digest here, like in the example function
- digests_equal = False
- for pw_char, correct_char in zip(getDigest(pw), pw_digest):
- time_used += 1
- if pw_char != correct_char:
- break
- else:
- digests_equal = True
- # Oops, we have side channel leak!
- print 'Used', time_used, 'ms to verify password'
- return digests_equal
- # Example output:
- # sha256('foo') => 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae
- verify_password('foo')
- # Used 1 ms to verify password
- # False
- # Okay, so how many calls to verify_password do we need to discover the correct password?
- # Let's find out!
- # We'll start off with some nonsense words, the contents doesn't matter as long as we know the hash
- # sha256('000016') => 054cb08ba55a5a5f8885578644125c1862ab996db04c4cf523b3848bca85579a
- verify_password('000016')
- # Used 1 ms to verify password
- # False
- # '0...' is out
- # sha256('000014') => 1f5dbbbc4af3816b8ebdafe6d3c458cab237e586622900de5347c63be1184e6c
- verify_password('000014')
- # Used 1 ms to verify password
- # False
- # (Snipped 3 attempts)
- # sha256('000025') => 6fb53cf99da69b910aa03ea5a4748a00189ca724d6989e99005968f7c39ea948
- verify_password('000025')
- # Used 2 ms to verify password
- # False
- # All right, looks like our hash starts with '6'
- # Let's move on to the next digit
- # sha256('000083') => 60bdf9c8fe2df773120ba841d57a6dc95b8b5ac512cc2800a2e12f65ba40e0af
- verify_password('000083')
- # Used 2 ms to verify password
- # False
- # (Snipped 10 attempts)
- # sha256('000586') => 6b0a43a32299596db396b968527881dff409c3ea32ff7190498f827f66a6f3e4
- verify_password('000586')
- # Used 3 ms to verify password
- # False
- # '6b...'
- # (Snipped 3 attempts)
- # sha256('003496') => 6b311f8c360053cf3cc7856e1c3542936ab48b26764950ea48a017544be8b11e
- verify_password('003496')
- # Used 4 ms to verify password
- # False
- # '6b3...'
- # All right, so we're doing pretty good here.
- # We could keep going, but we're already down to five words out of 38600
- # Here's our potential matches: ['biannual', 'cackling', 'dismal', 'grabber', 'mated']
- # I'll just try them in order:
- # sha256('biannual') => 6b3a622042d40e23a50c068772325d0c789fa97e50c583edf8419ca2dc10ee00
- verify_password('biannual')
- # Used 64 ms to verify password
- # True
- # Oh, look, there it was.
- # So how many login attempts did we make?
- # All counted, 23.
- # Now in the real world that would be multiplied by some number in order to get
- # a statistically significant timing sample, but still, that's not bad.
- # Keep in mind, if we had run a dictionary attack directly to the verify
- # function, we would have required on average 19300 calls.
- # Bet that would have set off a few alarms!
Advertisement
Add Comment
Please, Sign In to add comment