Faster constant-time string comparison in Ruby
To make things fast while keeping them simple, I've reimplemented the function in C, and made it available as a gem.
The source is on GitHub (https://github.com/daxtens/fast_secure_compare), but the crux of it is the following very simple C routine.
int secure_compare_bytes(const unsigned char * secret, unsigned int secret_len,
const unsigned char * input, unsigned int input_len) {
int input_pos;
int secret_pos = 0;
int result = secret_len - input_len;
// make sure our time isn't dependent on secret_len, and only dependent
// on input_len
for (input_pos = 0; input_pos < input_len; input_pos++) {
result |= input[input_pos] ^ secret[secret_pos];
secret_pos = (secret_pos + 1) % secret_len;
}
return result;
}
```
There's also a bit of FFI glue to get it to talk to Ruby.
It's much faster than the original pure Ruby, and is somewhat faster (and much simpler) than hashing. I have edited out rehearsals for brevity. This is on a 2008 MacBook. You can replicate this with timing.rb
in the demo directory.
==== Long text ====
user system total real
==, early fail 0.000000 0.000000 0.000000 ( 0.000028)
==, late fail 0.000000 0.000000 0.000000 ( 0.000710)
Pure Ruby secure_compare, 'early' 1.730000 0.040000 1.770000 ( 1.777258)
Pure Ruby secure_compare, 'late' 1.730000 0.050000 1.780000 ( 1.774144)
C-based FastSecureCompare, 'early' 0.040000 0.000000 0.040000 ( 0.047612)
C-based FastSecureCompare, 'late' 0.040000 0.000000 0.040000 ( 0.045767)
SHA512-then-==, 'early' 0.050000 0.000000 0.050000 ( 0.048569)
SHA512-then-==, 'late' 0.050000 0.000000 0.050000 ( 0.046100)
==== Short text ====
user system total real
==, early fail 0.000000 0.000000 0.000000 ( 0.000028)
==, late fail 0.000000 0.000000 0.000000 ( 0.000031)
Pure Ruby secure_compare, 'early' 0.010000 0.000000 0.010000 ( 0.010552)
Pure Ruby secure_compare, 'late' 0.010000 0.000000 0.010000 ( 0.010805)
C-based FastSecureCompare, 'early' 0.000000 0.000000 0.000000 ( 0.000556)
C-based FastSecureCompare, 'late' 0.000000 0.000000 0.000000 ( 0.000516)
SHA512-then-==, 'early' 0.000000 0.000000 0.000000 ( 0.000780)
SHA512-then-==, 'late' 0.000000 0.000000 0.000000 ( 0.000812)