Skip to content

Commit

Permalink
Merge pull request #277 from vladimir-v-diaz/compare-digests-non-vari…
Browse files Browse the repository at this point in the history
…able

Protect Against Timing Attacks When Comparing Digests
  • Loading branch information
vladimir-v-diaz committed May 5, 2015
2 parents fccce96 + 2f49561 commit 311dff2
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 1 deletion.
43 changes: 43 additions & 0 deletions tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import logging
import tempfile
import unittest
import timeit

import tuf
import tuf.log
Expand Down Expand Up @@ -577,6 +578,48 @@ def test_c6_get_compressed_length(self):



def test_digests_are_equal(self):
digest = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'

# Normal case: test for digests that are equal.
self.assertTrue(tuf.util.digests_are_equal(digest, digest))

# Normal case: test for digests that are unequal.
self.assertFalse(tuf.util.digests_are_equal(digest, '0a8df1'))

# Test for invalid arguments.
self.assertRaises(tuf.FormatError, tuf.util.digests_are_equal, 7,
digest)
self.assertRaises(tuf.FormatError, tuf.util.digests_are_equal, digest,
7)

# Test that digests_are_equal() takes the same amount of time to compare
# equal and unequal arguments.
runtime = timeit.timeit('digests_are_equal("ab8df", "ab8df")',
setup='from tuf.util import digests_are_equal',
number=100000)

runtime2 = timeit.timeit('digests_are_equal("ab8df", "1b8df")',
setup='from tuf.util import digests_are_equal',
number=100000)

print('\nruntime1: ' + repr(runtime))
print('runtime2: ' + repr(runtime2))

runtime3 = timeit.timeit('"ab8df" == "ab8df"', number=100000)

runtime4 = timeit.timeit('"ab8df" == "1b8df"', number=1000000)

# The ratio for the 'digest_are_equal' runtimes should be at or near 1.
ratio_digests_are_equal = abs(runtime2 / runtime)

# The ratio for the variable-time runtimes should be (>1) & at or near 10?
ratio_variable_compare = abs(runtime4 / runtime3)

self.assertTrue(ratio_digests_are_equal < ratio_variable_compare)



# Run unit test.
if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion tuf/pycrypto_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ def _decrypt(file_contents, password):
Crypto.Hash.SHA256)
generated_hmac = generated_hmac_object.hexdigest()

if generated_hmac != hmac:
if not tuf.util.digests_are_equal(generated_hmac, hmac):
raise tuf.CryptoError('Decryption failed.')

# The following decryption routine assumes 'ciphertext' was encrypted with
Expand Down
42 changes: 42 additions & 0 deletions tuf/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,3 +950,45 @@ def load_json_file(filepath):

finally:
fileobject.close()



def digests_are_equal(digest1, digest2):
"""
<Purpose>
While protecting against timing attacks, compare the hexadecimal arguments
and determine if they are equal.
<Arguments>
digest1:
The first hexadecimal string value to compare.
digest2:
The second hexadecimal string value to compare.
<Exceptions>
tuf.FormatError: If the arguments are improperly formatted.
<Side Effects>
None.
<Return>
Return True if 'digest1' is equal to 'digest2', False otherwise.
"""

# Ensure the arguments have the appropriate number of objects and object
# types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if there is a mismatch.
tuf.formats.HEX_SCHEMA.check_match(digest1)
tuf.formats.HEX_SCHEMA.check_match(digest2)

if len(digest1) != len(digest2):
return False

are_equal = True

for element in range(len(digest1)):
if digest1[element] != digest2[element]:
are_equal = False

return are_equal

0 comments on commit 311dff2

Please sign in to comment.