Skip to content

Commit 0faa750

Browse files
bdewaterioquatix
authored andcommitted
Rename OpenSSL.secure_compare to fixed_length_secure_compare
In 1ade643 the Rails-like secure_compare naming was adopted and in original pull request introducing this functionality debate around timing of hash functions followed. This made me realize why Rails' default of hashing the values to protect users from making mistakes is a good idea.
1 parent 0c927a4 commit 0faa750

File tree

2 files changed

+26
-38
lines changed

2 files changed

+26
-38
lines changed

ext/openssl/ossl.c

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -606,29 +606,17 @@ static void Init_ossl_locks(void)
606606

607607
/*
608608
* call-seq:
609-
* OpenSSL.secure_compare(string, string) -> boolean
609+
* OpenSSL.fixed_length_secure_compare(string, string) -> boolean
610610
*
611-
* Constant time memory comparison. Inputs must be of equal length, otherwise
612-
* an error is raised since timing attacks could leak the length of a
613-
* secret.
611+
* Constant time memory comparison for fixed length strings, such as results
612+
* of HMAC calculations.
614613
*
615-
* Returns +true+ if the strings are identical, +false+ otherwise.
616-
*
617-
* For securely comparing user input, it's recommended to use hashing and
618-
* regularly compare after to prevent an unlikely false positive due to a
619-
* collision.
620-
*
621-
* user_input = "..."
622-
* secret = "..."
623-
* hashed_input = OpenSSL::Digest::SHA256.digest(user_input)
624-
* hashed_secret = OpenSSL::Digest::SHA256.digest(secret)
625-
* OpenSSL.secure_compare(hashed_input, hashed_secret) && user_input == secret
626-
*
627-
* Be aware that timing attacks against the hash functions may reveal the
628-
* length of the secret.
614+
* Returns +true+ if the strings are identical, +false+ if they are of the same
615+
* length but not identical. If the length is different, +ArgumentError+ is
616+
* raised.
629617
*/
630618
static VALUE
631-
ossl_crypto_secure_compare(VALUE dummy, VALUE str1, VALUE str2)
619+
ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2)
632620
{
633621
const unsigned char *p1 = (const unsigned char *)StringValuePtr(str1);
634622
const unsigned char *p2 = (const unsigned char *)StringValuePtr(str2);
@@ -1166,7 +1154,7 @@ Init_openssl(void)
11661154
*/
11671155
mOSSL = rb_define_module("OpenSSL");
11681156
rb_global_variable(&mOSSL);
1169-
rb_define_singleton_method(mOSSL, "secure_compare", ossl_crypto_secure_compare, 2);
1157+
rb_define_singleton_method(mOSSL, "fixed_length_secure_compare", ossl_crypto_fixed_length_secure_compare, 2);
11701158

11711159
/*
11721160
* OpenSSL ruby extension version

test/test_ossl.rb

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,28 @@
66
if defined?(OpenSSL)
77

88
class OpenSSL::OSSL < OpenSSL::SSLTestCase
9-
def test_secure_compare
10-
assert_raises(ArgumentError) { OpenSSL.secure_compare("aaa", "a") }
11-
assert_raises(ArgumentError) { OpenSSL.secure_compare("aaa", "aa") }
9+
def test_fixed_length_secure_compare
10+
assert_raises(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "a") }
11+
assert_raises(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aa") }
1212

13-
assert OpenSSL.secure_compare("aaa", "aaa")
14-
assert OpenSSL.secure_compare(
13+
assert OpenSSL.fixed_length_secure_compare("aaa", "aaa")
14+
assert OpenSSL.fixed_length_secure_compare(
1515
OpenSSL::Digest::SHA256.digest("aaa"), OpenSSL::Digest::SHA256.digest("aaa")
1616
)
1717

18-
assert_raises(ArgumentError) { OpenSSL.secure_compare("aaa", "aaaa") }
19-
refute OpenSSL.secure_compare("aaa", "baa")
20-
refute OpenSSL.secure_compare("aaa", "aba")
21-
refute OpenSSL.secure_compare("aaa", "aab")
22-
assert_raises(ArgumentError) { OpenSSL.secure_compare("aaa", "aaab") }
23-
assert_raises(ArgumentError) { OpenSSL.secure_compare("aaa", "b") }
24-
assert_raises(ArgumentError) { OpenSSL.secure_compare("aaa", "bb") }
25-
refute OpenSSL.secure_compare("aaa", "bbb")
26-
assert_raises(ArgumentError) { OpenSSL.secure_compare("aaa", "bbbb") }
18+
assert_raises(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaaa") }
19+
refute OpenSSL.fixed_length_secure_compare("aaa", "baa")
20+
refute OpenSSL.fixed_length_secure_compare("aaa", "aba")
21+
refute OpenSSL.fixed_length_secure_compare("aaa", "aab")
22+
assert_raises(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaab") }
23+
assert_raises(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "b") }
24+
assert_raises(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bb") }
25+
refute OpenSSL.fixed_length_secure_compare("aaa", "bbb")
26+
assert_raises(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bbbb") }
2727
end
2828

2929
def test_memcmp_timing
30-
# Ensure using secure_compare takes almost exactly the same amount of time to compare two different strings.
30+
# Ensure using fixed_length_secure_compare takes almost exactly the same amount of time to compare two different strings.
3131
# Regular string comparison will short-circuit on the first non-matching character, failing this test.
3232
# NOTE: this test may be susceptible to noise if the system running the tests is otherwise under load.
3333
a = "x" * 512_000
@@ -36,9 +36,9 @@ def test_memcmp_timing
3636
a = "#{a}x"
3737

3838
n = 10_000
39-
a_b_time = Benchmark.measure { n.times { OpenSSL.secure_compare(a, b) } }.real
40-
a_c_time = Benchmark.measure { n.times { OpenSSL.secure_compare(a, c) } }.real
41-
assert_in_delta(a_b_time, a_c_time, 1, "secure_compare timing test failed")
39+
a_b_time = Benchmark.measure { n.times { OpenSSL.fixed_length_secure_compare(a, b) } }.real
40+
a_c_time = Benchmark.measure { n.times { OpenSSL.fixed_length_secure_compare(a, c) } }.real
41+
assert_in_delta(a_b_time, a_c_time, 1, "fixed_length_secure_compare timing test failed")
4242
end
4343
end
4444

0 commit comments

Comments
 (0)