From fda5217028d2e5167d5c99000a1341238e8847c9 Mon Sep 17 00:00:00 2001 From: arrtchiu Date: Tue, 29 Jul 2014 11:56:48 +0800 Subject: [PATCH] add timing safe string compare method --- string.c | 36 ++++++++++++++++++++++++++++++++++++ test/ruby/test_string.rb | 7 +++++++ 2 files changed, 43 insertions(+) diff --git a/string.c b/string.c index 9f06837..5c2852f 100644 --- a/string.c +++ b/string.c @@ -2491,6 +2491,41 @@ rb_str_eql(VALUE str1, VALUE str2) } /* + * call-seq: + * str.tsafe_eql?(other) -> true or false + * + * Compares each byte of +str+ against +other+, similarly to String#eql?, but + * performs the comparison in constant-time. + * + * This method is timing-safe if both strings are of equal length. + */ + +static VALUE +rb_str_tsafe_eql(VALUE str1, VALUE str2) +{ + long len, idx; + char result; + const char *buf1, *buf2; + + str2 = StringValue(str2); + len = RSTRING_LEN(str1); + + if (RSTRING_LEN(str2) != len) return Qfalse; + + buf1 = RSTRING_PTR(str1); + buf2 = RSTRING_PTR(str2); + + result = 0; + for (idx = 0; idx < len; idx++) { + result |= buf1[idx] ^ buf2[idx]; + } + + if (result == 0) return Qtrue; + + return Qfalse; +} + +/* * call-seq: * string <=> other_string -> -1, 0, +1 or nil * @@ -8761,6 +8796,7 @@ Init_String(void) rb_define_method(rb_cString, "==", rb_str_equal, 1); rb_define_method(rb_cString, "===", rb_str_equal, 1); rb_define_method(rb_cString, "eql?", rb_str_eql, 1); + rb_define_method(rb_cString, "tsafe_eql?", rb_str_tsafe_eql, 1); rb_define_method(rb_cString, "hash", rb_str_hash_m, 0); rb_define_method(rb_cString, "casecmp", rb_str_casecmp, 1); rb_define_method(rb_cString, "+", rb_str_plus, 1); diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index e8decc0..384230d 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -304,6 +304,13 @@ class TestString < Test::Unit::TestCase casetest(S("CaT"), S('cAt'), true) # find these in the case. end + def test_TIMING_SAFE_EQUAL # 'tsafe_eql?' + assert_equal(true, S("foo").tsafe_eql?(S("foo"))) + assert_equal(false, S("foo").tsafe_eql?(S("foO"))) + assert_equal(true, S("f\x00oo").tsafe_eql?(S("f\x00oo"))) + assert_equal(false, S("f\x00oo").tsafe_eql?(S("f\x00oO"))) + end + def test_capitalize assert_equal(S("Hello"), S("hello").capitalize) assert_equal(S("Hello"), S("hELLO").capitalize) -- 1.8.5.2 (Apple Git-48)