Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

There might be a chance of timing attacks against such "security". A secure system should never depend on obscurity.


Node.js example:

    return new Buffer(sig).toString('base64') == signature;
PHP example:

    return hash_hmac("sha512", $string_to_verify, $shared_secret) == $signature;
Yeah, like fdomig said: timing attacks. The Python example included a mitigation. PHP includes hash_equals() and a constant time comparison in Javascript isn't difficult to write.

Possibly also, the Ruby example:

    return OpenSSL::HMAC.digest('sha512', shared_secret, string_to_verify) == signature
(I don't write Ruby so I don't know if this is overloadable somewhere.)


> (I don't write Ruby so I don't know if this is overloadable somewhere.)

Ruby allows overriding the == operator[1], but OpenSSL::HMAC.digest returns an instance of String[2], rather than returning a special subclass of string or some other kind of special HMAC-representing class overloading ==.

[1] http://docs.ruby-lang.org/en/2.2.0/syntax/methods_rdoc.html#...

[2] http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL...


To be pedantic, in Ruby, the fact that an object is of class String is not sufficient evidence that a method has not been overloaded (they can be overloaded via the objects eigenclass), though you're probably right.


Even if it did (and it doesn't, I checked), you're probably still better off doing it explicitly since it's easier to audit, less likely to differ across implementations, and less error prone (e.g. swap the order of the comparison and you're back to plain old String#==).


Yes, that Ruby version is also insecure.


Okay, so only the Python version was acceptable. :(

(And maybe Golang?)


The Golang example looks fine but I didn't read it carefully and do not vouch for it.


> The Python example included a mitigation.

Unnecessarily so too, Python provides hmac.compare_digests since Python 3.3 and it was backported to Python 2.7.7, which was released almost a year ago.


For Ruby you should be using ActiveSupport::SecurityUtils.secure_compare or similar.

In theory openssl could return a String-like object with an overridden ==, but it doesn't.


All crypto libraries should provide and developers should use constant time comparisons for exactly this purpose. A good example from the same page is the Go crypto/hmac package includes mac.Equal to check for equality without introducing timing weaknesses.


It's also pretty sad that they didn't use a constant time comparison function. Whether it exists in the target language standard library or not, it's easy enough to write one.


Wanted to add that as of 3.3, Python has a nice constant-time compare built in: https://docs.python.org/3/library/hmac.html#hmac.compare_dig...



It looks like they're including and checking a timestamp with the payload. Is this not sufficient?


No. A timing attack is used to determine the shared key that creates the HMAC. Because == and === (depending on the language) use memcmp(), the time of the comparison varies. EG:

36D5F4EA999342FED17D7488CB260FC92926 36D5F4EA999342FED18D7488CB260FC92926

The code would compare only up to the difference and return, indicating through the time spent analyzing the HMAC how many characters are the same. The attacker can then work the HMAC like they would a combination lock, till they reproduce the key used.

That's why a constant time comparison is so important: it leaks far less information.


This is a forgery attack, not a key-recovery attack.


I am not a cryptographer. I don't quite understand the distinction you are drawing with so little information. Care to explain what I misunderstood?


Given a message M and a secret key K, we have:

    MAC(K, M) -> T
To validate a pair (M, T), we verify:

   T = MAC(K, M)
Ideally, the execution time in this verification is independent of T. But many languages use string-comparison algorithms that exit immediately on failure. If Eve can detect this difference with high granularity, she has an oracle telling her how many leading bytes of a guessed tag T' are valid:

    O(M, T') -> n
She can use this to recover the first byte of a valid tag for M:

    for k in [0, 255]:
        if O(M, k || 0000...) > 0:
            return k
She can extend this to recover the second byte, the third, and so on. Eventually, Eve will recover T such that MAC(K, M) = T. In other words, Eve is able to forge an authentication tag T for an arbitrary message M.

What she won't do is recover K. So while she can forge tags for arbitrary messages, each forgery will require a fresh, online interaction with the verifying party. She cannot work backwards from (M, T) to K.


To put it another way: You (slowly and painstakingly) defeat the authentication without having to brute force, e.g. 2^128, possible keys.

However, the key is still unknown to you. It just doesn't matter, because you can forge messages without it.


Thank you for the in-depth explanation!


Thanks for the explanation.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: