From 03b4b9708f375db46ee214b219f709d08ed6eeb0 Mon Sep 17 00:00:00 2001 From: Bart de Water Date: Thu, 8 Oct 2020 22:24:18 -0400 Subject: [PATCH] Use faster OpenSSL secure_compare if available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comparing a 16 byte string: openssl_secure_compare: 9397508.4 i/s rack_secure_compare: 515938.0 i/s - 18.21x (± 0.00) slower --- CHANGELOG.md | 1 + lib/rack/utils.rb | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e11183c4..dbdb5cad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file. For info on - `Rack::Request#[]` and `#[]=` now warn even in non-verbose mode. ([#1277](https://github.com/rack/rack/issues/1277), [@jeremyevans](https://github.com/jeremyevans)) - Decrease default allowed parameter recursion level from 100 to 32. ([#1640](https://github.com/rack/rack/issues/1640), [@jeremyevans](https://github.com/jeremyevans)) - Attempting to parse a multipart response with an empty body now raises Rack::Multipart::EmptyContentError. ([#1603](https://github.com/rack/rack/issues/1603), [@jeremyevans](https://github.com/jeremyevans)) +- `Rack::Utils.secure_compare` uses OpenSSL's faster implementation if available. ([#1711](https://github.com/rack/rack/pull/1711), [@bdewater](https://github.com/bdewater)) ### Fixed diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index 7de7a9dff..2b61298ee 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -374,14 +374,22 @@ def get_byte_ranges(http_range, size) # that have already been processed by HMAC. This should not be used # on variable length plaintext strings because it could leak length info # via timing attacks. - def secure_compare(a, b) - return false unless a.bytesize == b.bytesize + if defined?(OpenSSL.fixed_length_secure_compare) + def secure_compare(a, b) + return false unless a.bytesize == b.bytesize - l = a.unpack("C*") + OpenSSL.fixed_length_secure_compare(a, b) + end + else + def secure_compare(a, b) + return false unless a.bytesize == b.bytesize - r, i = 0, -1 - b.each_byte { |v| r |= v ^ l[i += 1] } - r == 0 + l = a.unpack("C*") + + r, i = 0, -1 + b.each_byte { |v| r |= v ^ l[i += 1] } + r == 0 + end end # Context allows the use of a compatible middleware at different points