Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't reveal string length to attacker. #28

Merged
merged 7 commits into from May 27, 2016
Merged

Don't reveal string length to attacker. #28

merged 7 commits into from May 27, 2016

Conversation

eugene-eeo
Copy link

Instead of terminating at the shortest string, terminate at the longest string.

@coveralls
Copy link

coveralls commented May 22, 2016

Coverage Status

Coverage increased (+0.2%) to 93.162% when pulling 2f79700 on eugene-eeo:master into a7073fe on pyotp:master.

@coveralls
Copy link

coveralls commented May 22, 2016

Coverage Status

Coverage increased (+1.9%) to 94.783% when pulling 7ccb519 on eugene-eeo:master into a7073fe on pyotp:master.

@coveralls
Copy link

coveralls commented May 22, 2016

Coverage Status

Coverage increased (+0.2%) to 93.162% when pulling fa4a70f on eugene-eeo:master into a7073fe on pyotp:master.

for c1, c2 in zip(s1, s2):
for c1, c2 in izip_longest(s1, s2):
if c1 is None or c2 is None:
pass

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line looks odd - pass will fallthrough to the next line, and fail when xoring None. e.g:

>>> None ^ 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for ^: 'NoneType' and 'int'

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. Will change pass to continue.

@nathforge
Copy link

Nice - created a Gist to test the code with continue, the new method is much better!

Secret length: 6

Original:
  6 0.289390087128 <-- 2x slower, our secret is obviously 6 characters
  11 0.108039617538
  1 0.0941004753113
  2 0.0926082134247
  4 0.0918550491333
  7 0.090735912323
  8 0.0895481109619
  5 0.0887336730957
  10 0.0881381034851
  9 0.0855760574341
  3 0.0826115608215

eugene-eeo: secret length isn't obvious
  10 0.353785276413
  11 0.353744983673
  9 0.343442201614
  8 0.337969779968
  7 0.329211235046
  6 0.309950113297
  5 0.294120550156
  1 0.28773188591
  4 0.278280258179
  3 0.267837047577
  2 0.246340036392

@coveralls
Copy link

coveralls commented May 22, 2016

Coverage Status

Coverage increased (+0.2%) to 93.162% when pulling 5abb503 on eugene-eeo:master into a7073fe on pyotp:master.

@eugene-eeo
Copy link
Author

There is also a version which makes it do almost as much "work" when the smaller string runs out of characters:

def strings_equal(s1, s2):
    differences = 0
    for c1, c2 in izip_longest(s1, s2):
        if c1 is None or c2 is None:
            differences |= ord('a') ^ ord('b')
        else:
            differences |= ord(c1) ^ ord(c2)
    return differences == 0

But I'm not sure if it's just over-engineering.

@coveralls
Copy link

coveralls commented May 22, 2016

Coverage Status

Coverage increased (+0.2%) to 93.162% when pulling a9c3de2 on eugene-eeo:master into a7073fe on pyotp:master.

@kislyuk
Copy link
Member

kislyuk commented May 22, 2016

Yes, my guess is that's unnecessary. Nathan's benchmark shows the improvement clearly, LGTM

@kislyuk
Copy link
Member

kislyuk commented May 22, 2016

For reference, this is the stdlib implementation: http://bugs.python.org/issue19259.

@eugene-eeo
Copy link
Author

eugene-eeo commented May 22, 2016

@kislyuk The standard library version and the one proposed is largely equivalent IMHO.

@coveralls
Copy link

coveralls commented May 23, 2016

Coverage Status

Coverage decreased (-0.5%) to 92.373% when pulling db32982 on eugene-eeo:master into a7073fe on pyotp:master.

@coveralls
Copy link

coveralls commented May 23, 2016

Coverage Status

Coverage increased (+5.4%) to 98.319% when pulling 4346321 on eugene-eeo:master into a7073fe on pyotp:master.

@kislyuk kislyuk merged commit af3c8d1 into pyauth:master May 27, 2016
@kislyuk kislyuk mentioned this pull request Aug 30, 2016
psivesely pushed a commit to freedomofpress/securedrop that referenced this pull request Aug 31, 2016
Normally, we're hesistant to issue an update for dependencies when we've already
entered the release candidate(s) stage of the release process. In this case, the
changes I'm adding are all minor bug fixes that I've reviewed. Two of the fixes
were labeled as security issues, however, they don't really affect us as
explained below.

* Werkzeug
  * A bug that allowed XSS attacks on the debug page has been fixed (we
    don't run Flask in debug mode in production) -
    pallets/werkzeug#1001
  * Invalid Content-Type makes for parsing throw ValueError exception (the fix
    returns an invalid request 400 Bad Request page instead of an internal
    server error when the content-type field of a HTTP request is bad--such as
    ' ' or ',') - pallets/werkzeug#995
  * Raise BadRequestKeyError instead of IndexError in MultiDict when calling
    __getitem__ on a key with an empty associated list of values (Flask returns
    forms and query strings as MultiDicts. This is just better error-handling,
    no real bug being fixed here.) -
    pallets/werkzeug#979

* pytop
  * The string comparison function now no longer leaks string length (shouldn't
    affect SD because the length of our TOTP codes are already known) -
    pyauth/pyotp#28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants