Merge pull request #277 from vladimir-v-diaz/compare-digests-non-variable

Protect Against Timing Attacks When Comparing Digests
This commit is contained in:
Vladimir Diaz 2015-05-04 23:50:45 -04:00
commit 311dff2f6f
3 changed files with 86 additions and 1 deletions

View file

@ -32,6 +32,7 @@
import logging
import tempfile
import unittest
import timeit
import tuf
import tuf.log
@ -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()

View file

@ -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

View file

@ -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