From 4b05840f6ac40111ce7fe44828a09b6fc6a2834a Mon Sep 17 00:00:00 2001 From: Brian Mitchell Date: Tue, 8 Sep 2009 11:38:15 -0400 Subject: [PATCH] Avoid constant timing in exchange for random timing on the failure case. --- .../lib/active_support/message_verifier.rb | 60 ++++++-------------- 1 files changed, 18 insertions(+), 42 deletions(-) diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 8d14423..301d73f 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -1,77 +1,53 @@ module ActiveSupport # MessageVerifier makes it easy to generate and verify messages which are signed # to prevent tampering. - # + # # This is useful for cases like remember-me tokens and auto-unsubscribe links where the # session store isn't suitable or available. # # Remember Me: # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now]) - # + # # In the authentication filter: # # id, time = @verifier.verify(cookies[:remember_me]) # if time < Time.now # self.current_user = User.find(id) # end - # + # class MessageVerifier class InvalidSignature < StandardError; end - + def initialize(secret, digest = 'SHA1') @secret = secret @digest = digest end - + def verify(signed_message) data, digest = signed_message.split("--") - if secure_compare(digest, generate_digest(data)) + if digest == generate_digest(data) Marshal.load(ActiveSupport::Base64.decode64(data)) else + secure_timing raise InvalidSignature end end - + def generate(value) data = ActiveSupport::Base64.encode64s(Marshal.dump(value)) "#{data}--#{generate_digest(data)}" end - - private - if "foo".respond_to?(:force_encoding) - # constant-time comparison algorithm to prevent timing attacks - def secure_compare(a, b) - a = a.force_encoding(Encoding::BINARY) - b = b.force_encoding(Encoding::BINARY) - if a.length == b.length - result = 0 - for i in 0..(a.length - 1) - result |= a[i].ord ^ b[i].ord - end - result == 0 - else - false - end - end - else - # For 1.8 - def secure_compare(a, b) - if a.length == b.length - result = 0 - for i in 0..(a.length - 1) - result |= a[i] ^ b[i] - end - result == 0 - else - false - end - end - end + private + + def secure_timing + sleep(rand / 200) + end + + def generate_digest(data) + require 'openssl' unless defined?(OpenSSL) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), @secret, data) + end - def generate_digest(data) - require 'openssl' unless defined?(OpenSSL) - OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), @secret, data) - end end end -- 1.6.4.2.264.g79b4f