fast_secure_compare 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e3fb2369f25ee854588c61eee84e3e8d83101935
4
- data.tar.gz: 9ce4e51ea075a0356c94a5fbfa2ba73d782a747f
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDM5MWFhMTcwNjJhNjQyZjA4YTc1ODE4NGZlNGQ4ZTU2NjhiY2ZmYw==
5
+ data.tar.gz: !binary |-
6
+ MmY2NjNlYzg4MzFhNWQyZWYyMTU0NzI3M2FlYjg4NWI5NjE4MTc0Zg==
5
7
  SHA512:
6
- metadata.gz: 014835e0a31b4c6c22e776c6641f77a82a41edef1be4048e20f9aeb201e5b2707d16ea52748ebd3affcd4152f66b7268325f295a8cc85567a5398902ecf53979
7
- data.tar.gz: ee5a4427431ecb102938ef55bc6cf8588bae22e71f9e90fe32c5f73f17c5db0bd0334d4165c952ef1f0cef4c21302ab10d17f16edcee84297ac099027baf16c8
8
+ metadata.gz: !binary |-
9
+ YmU3ODRhYTM4Njg4MGIzMmJmMjQ0ZjI1MGI1YjMyYTEyZjVjMGQ3OTMxMTE2
10
+ MjM4ZGJmMmZmNWFlN2E0NjEzOGI0N2YxYjNmNjQ3NjRkYmM0ODQyYzE0ZTA5
11
+ OTc0NjJmMGI0MjJmZjE5MzJmYjAwZjIwODRhYzU0NGQ5ODI5Mzk=
12
+ data.tar.gz: !binary |-
13
+ OTc2MTgwY2NlMzhjZGZhZGNmNzBhOWQ2ZmJmZDYyODE4MjZkMzQ4NTMxYTky
14
+ NWQ1ZmM1MjQzYzM2MmM1Nzc1YjUzN2MzZTU2Njk3YWFiNmM0ZTdlNjNmZjVm
15
+ ZWU1ODY0ZjQzZWY0YmRkZmEwY2Y0NDQ5ZTU4OTRmMTQ4NDFmYTc=
data/README.md CHANGED
@@ -1,5 +1,11 @@
1
+ # FastSecureCompare #
2
+ [![Build Status](https://travis-ci.org/daxtens/fast_secure_compare.svg?branch=master)](https://travis-ci.org/daxtens/fast_secure_compare)
3
+ [![Gem Version](https://badge.fury.io/rb/fast_secure_compare.svg)](http://badge.fury.io/rb/fast_secure_compare)
4
+
1
5
  This gem provides a simple, fast way to do string comparisons that resist timing attacks.
2
6
 
7
+ It also provides an easy way to (marginally) speed up your existing web apps that use Rails or Rack's secure_compare functions (for example, any app that uses Rails built in sessions) by monkey patching the fast C function over the slow pure Ruby one.
8
+
3
9
  # What is a timing attack? #
4
10
  A timing attack is an attack on a system that determines secret information based on how long an operation takes.
5
11
 
@@ -23,17 +29,64 @@ This gem provides a secure comparison function that is:
23
29
 
24
30
  Furthermore, unlike some other secure comparison fuctions, the code does not require that the strings are the same length, and should not leak the length of the string if the string lengths do not match.
25
31
 
26
- If you're interested in seeing how much faster you can make your code by using this gem, check out the demo folder.
32
+ The gem also provides monkeypatches for rack and rails, to make it easier to deploy.
27
33
 
28
34
  # How do I use this gem in my code? #
29
35
 
36
+ That depends on your use case:
37
+
38
+ ## By itself ##
39
+
30
40
  ```ruby
31
41
 
32
- import 'fast_secure_compare'
42
+ require 'fast_secure_compare'
33
43
 
34
44
  FastSecureCompare.compare(my_secret, user_input)
35
45
  ```
36
46
 
47
+ ## In a Rails app ##
48
+ To make all uses of `ActiveSupport::MessageVerifier` (including every time a user's session is verifed!) faster, you need to `require 'fast_secure_compare/rails'` somehow.
49
+
50
+ In, e.g. Redmine, this is as simple as adding the following line to `Gemfile.local`:
51
+
52
+ `gem 'fast_secure_compare', :require => "fast_secure_compare/rails"`
53
+
54
+ Don't forget to run `bundle install`.
55
+
56
+ ## In a Rack app ##
57
+
58
+ Apps using Rack and calling `Rack::Utils.secure_compare` can be sped up with `require 'fast_secure_compare/rack'`.
59
+
60
+ Note that this is not well tested and I'd appreciate feedback.
61
+
62
+ # How much faster is this? #
63
+
64
+ Well, that depends on how you measure it.
65
+
66
+ If you do a synthetic microbenchmark (see `demo/timings.rb`), you'll see something like this when comparing two 40 byte strings (like SHA1 hashes), 1000 times over.
67
+
68
+ The 'early fail' one differs at the first character, while the 'late fail' one differs at the last character.
69
+
70
+ ```
71
+ user system total real
72
+ ==, early fail 0.000000 0.000000 0.000000 ( 0.000191)
73
+ ==, late fail 0.000000 0.000000 0.000000 ( 0.000219)
74
+ Pure Ruby secure_compare, 'early' 0.020000 0.000000 0.020000 ( 0.019503)
75
+ Pure Ruby secure_compare, 'late' 0.020000 0.000000 0.020000 ( 0.019279)
76
+ C-based FastSecureCompare, 'early' 0.000000 0.000000 0.000000 ( 0.000588)
77
+ C-based FastSecureCompare, 'late' 0.000000 0.000000 0.000000 ( 0.000582)
78
+ ```
79
+
80
+ Interpreting the results, the C based one executes 2 orders of magnitude faster than the pure Ruby, and the same order of magnitude as `==`.
81
+
82
+ However, if you benchmark an actual application, things will obviously differ. If you look at the difference per call, on my hardware you'd see about a ((0.02-0.0005)/1000) = 20 microsecond difference, which is probably too small to measure.
83
+
84
+ However, if you are comparing strings that are greater than 40 characters in length, the differences become significantly more pronounced. (This was the original motivation for this gem.)
85
+
37
86
  # Is there anything else I should know? #
38
87
 
39
- The gem is not foolproof. In particular, it can't protect you against anything designed to exploit cache misses or any other more elaborate form of timing attack. However, none of the existing pure Ruby secure_compare functions do either.
88
+ * The gem is not foolproof. In particular, it can't protect you against anything designed to exploit cache misses or any other more elaborate form of timing attack. However, none of the existing pure Ruby secure_compare functions do either.
89
+
90
+ * Putting the argmuents around the wrong way may leak the length of your secret.
91
+
92
+ * Don't use a secret of length 0.
data/Rakefile CHANGED
@@ -1,21 +1,41 @@
1
- # shamelessly ripped off https://github.com/cotag/http-parser/blob/master/Rakefile
2
-
3
1
  require 'rubygems'
4
2
  require 'rake'
5
3
  require 'rspec/core/rake_task'
4
+ require 'rake/extensiontask'
6
5
 
7
6
  task :default => [:compile, :test]
8
7
 
9
- task :compile do
10
- protect = ['secure_compare.c']
11
- Dir["ext/fast_secure_compare/**/*"].each do |file|
12
- begin
13
- next if protect.include? File.basename(file)
14
- FileUtils.rm file
15
- rescue
16
- end
17
- end
18
- system 'cd ext && rake'
8
+ spec = Gem::Specification.new do |s|
9
+ s.name = 'fast_secure_compare'
10
+ s.version = '1.0.0'
11
+ s.date = '2014-08-23'
12
+ s.summary = "A fast, simple way to do constant time string comparisons."
13
+ s.description = "A secure_comparison function implemented in C for blazing speed. Includes monkeypatch for Rails and Rack."
14
+ s.authors = ["Daniel Axtens"]
15
+ s.email = 'daniel@axtens.net'
16
+ s.homepage =
17
+ 'https://github.com/daxtens/fast_secure_compare'
18
+ s.license = 'MIT'
19
+
20
+
21
+ s.add_development_dependency 'rake'
22
+ s.add_development_dependency 'rake-compiler'
23
+ s.add_development_dependency 'rspec'
24
+
25
+ s.files = Dir["{lib}/**/*"] + %w(Rakefile README.md LICENSE)
26
+ s.files += Dir['ext/**/*.c'] + Dir['ext/**/extconf.rb']
27
+ s.test_files = Dir["spec/**/*"]
28
+ s.extra_rdoc_files = ["README.md"]
29
+
30
+ s.extensions = Dir['ext/**/extconf.rb']
31
+ s.require_paths = ["lib"]
32
+ end
33
+
34
+ # add your default gem packing task
35
+ Gem::PackageTask.new(spec) do |pkg|
19
36
  end
20
37
 
38
+ # feed the ExtensionTask with your spec
39
+ Rake::ExtensionTask.new('fast_secure_compare', spec)
40
+
21
41
  RSpec::Core::RakeTask.new(:test)
@@ -0,0 +1,3 @@
1
+ require "mkmf"
2
+
3
+ create_makefile "fast_secure_compare/fast_secure_compare"
@@ -1,15 +1,37 @@
1
- int secure_compare_bytes(const unsigned char * secret, unsigned int secret_len,
2
- const unsigned char * input, unsigned int input_len) {
1
+ #include <ruby.h>
2
+
3
+ VALUE FSC_module = Qnil;
4
+
5
+
6
+ static VALUE
7
+ method_compare(VALUE self, VALUE secret, VALUE input) {
8
+ Check_Type(secret, T_STRING);
9
+ Check_Type(input, T_STRING);
10
+
11
+ // handle 0-length secrets
12
+ if (RSTRING_LEN(secret) == 0 && RSTRING_LEN(input) != 0) {
13
+ return Qfalse;
14
+ }
3
15
 
4
16
  int input_pos;
5
17
  int secret_pos = 0;
18
+ int input_len = RSTRING_LEN(input);
19
+ int secret_len = RSTRING_LEN(secret);
20
+ char * secret_ = RSTRING_PTR(secret);
21
+ char * input_ = RSTRING_PTR(input);
6
22
  int result = secret_len - input_len;
7
23
  // make sure our time isn't dependent on secret_len, and only dependent
8
24
  // on input_len
9
25
  for (input_pos = 0; input_pos < input_len; input_pos++) {
10
- result |= input[input_pos] ^ secret[secret_pos];
26
+ result |= input_[input_pos] ^ secret_[secret_pos];
11
27
  secret_pos = (secret_pos + 1) % secret_len;
12
28
  }
13
29
 
14
- return result;
30
+ return ((result == 0) ? Qtrue : Qfalse);
31
+ }
32
+
33
+ void
34
+ Init_fast_secure_compare(void) {
35
+ FSC_module = rb_define_module("FastSecureCompare");
36
+ rb_define_module_function(FSC_module, "compare", method_compare, 2);
15
37
  }
@@ -0,0 +1,12 @@
1
+ require 'fast_secure_compare'
2
+
3
+ # Monkeypatch Rack
4
+ module Rack
5
+ module Utils
6
+ class<< self
7
+ def secure_compare(a, b)
8
+ FastSecureCompare.compare(a, b)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require 'fast_secure_compare'
2
+
3
+ # Monkeypatch Rails
4
+ module ActiveSupport
5
+ class MessageVerifier
6
+ private
7
+ def secure_compare(a, b)
8
+ FastSecureCompare.compare(a, b)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ require 'fast_secure_compare/rack'
2
+
3
+ describe Rack::Utils, "#secure_compare" do
4
+ it "returns true on equal strings" do
5
+ expect(Rack::Utils.secure_compare("aaa","aaa")).to eq(true)
6
+ end
7
+ it "returns false on an empty string" do
8
+ expect(Rack::Utils.secure_compare("aaa","")).to eq(false)
9
+ end
10
+ it "returns false on different strings of different length" do
11
+ expect(Rack::Utils.secure_compare("aaa","a")).to eq(false)
12
+ expect(Rack::Utils.secure_compare("aaa","aaaaaa")).to eq(false)
13
+ end
14
+ it "returns false on different strings of equal length" do
15
+ expect(Rack::Utils.secure_compare("aaa","bbb")).to eq(false)
16
+ end
17
+ it "returns true on two empty strings." do
18
+ expect(Rack::Utils.secure_compare("","")).to eq(true)
19
+ end
20
+ it "returns false on one empty string, one non-empty string." do
21
+ expect(Rack::Utils.secure_compare("a","")).to eq(false)
22
+ expect(Rack::Utils.secure_compare("","a")).to eq(false)
23
+ end
24
+ end
metadata CHANGED
@@ -1,73 +1,75 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_secure_compare
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Axtens
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-18 00:00:00.000000000 Z
11
+ date: 2014-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ffi-compiler
14
+ name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ! '>='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.0.2
20
- type: :runtime
19
+ version: '0'
20
+ type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.0.2
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: rake-compiler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ! '>='
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
- type: :runtime
34
+ type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ! '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ! '>='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ! '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- description: A secure_comparison function implemented in C for blazing speed.
55
+ description: A secure_comparison function implemented in C for blazing speed. Includes
56
+ monkeypatch for Rails and Rack.
56
57
  email: daniel@axtens.net
57
58
  executables: []
58
59
  extensions:
59
- - ext/Rakefile
60
+ - ext/fast_secure_compare/extconf.rb
60
61
  extra_rdoc_files:
61
62
  - README.md
62
63
  files:
63
- - lib/fast_secure_compare.rb
64
- - Rakefile
65
- - fast_secure_compare.gemspec
66
- - README.md
67
64
  - LICENSE
65
+ - README.md
66
+ - Rakefile
67
+ - ext/fast_secure_compare/extconf.rb
68
68
  - ext/fast_secure_compare/secure_compare.c
69
+ - lib/fast_secure_compare/rack.rb
70
+ - lib/fast_secure_compare/rails.rb
69
71
  - spec/fast_secure_compare_spec.rb
70
- - ext/Rakefile
72
+ - spec/fsc_rack_spec.rb
71
73
  homepage: https://github.com/daxtens/fast_secure_compare
72
74
  licenses:
73
75
  - MIT
@@ -78,20 +80,20 @@ require_paths:
78
80
  - lib
79
81
  required_ruby_version: !ruby/object:Gem::Requirement
80
82
  requirements:
81
- - - '>='
83
+ - - ! '>='
82
84
  - !ruby/object:Gem::Version
83
85
  version: '0'
84
86
  required_rubygems_version: !ruby/object:Gem::Requirement
85
87
  requirements:
86
- - - '>='
88
+ - - ! '>='
87
89
  - !ruby/object:Gem::Version
88
90
  version: '0'
89
91
  requirements: []
90
92
  rubyforge_project:
91
- rubygems_version: 2.0.6
93
+ rubygems_version: 2.2.2
92
94
  signing_key:
93
95
  specification_version: 4
94
96
  summary: A fast, simple way to do constant time string comparisons.
95
97
  test_files:
96
98
  - spec/fast_secure_compare_spec.rb
97
- has_rdoc:
99
+ - spec/fsc_rack_spec.rb
@@ -1,3 +0,0 @@
1
- require 'ffi-compiler/compile_task'
2
-
3
- FFI::Compiler::CompileTask.new('fast_secure_compare')
@@ -1,27 +0,0 @@
1
- Gem::Specification.new do |s|
2
- s.name = 'fast_secure_compare'
3
- s.version = '0.0.3'
4
- s.date = '2014-08-18'
5
- s.summary = "A fast, simple way to do constant time string comparisons."
6
- s.description = "A secure_comparison function implemented in C for blazing speed."
7
- s.authors = ["Daniel Axtens"]
8
- s.email = 'daniel@axtens.net'
9
- s.homepage =
10
- 'https://github.com/daxtens/fast_secure_compare'
11
- s.license = 'MIT'
12
-
13
-
14
- s.add_dependency 'ffi-compiler', '>= 0.0.2'
15
- s.add_dependency 'rake'
16
-
17
- s.add_development_dependency 'rspec'
18
-
19
- s.files = Dir["{lib}/**/*"] + %w(Rakefile fast_secure_compare.gemspec README.md LICENSE)
20
- s.files += ["ext/fast_secure_compare/secure_compare.c"]
21
- s.test_files = Dir["spec/**/*"]
22
- s.extra_rdoc_files = ["README.md"]
23
-
24
- s.extensions << "ext/Rakefile"
25
- s.require_paths = ["lib"]
26
- end
27
-
@@ -1,18 +0,0 @@
1
- require 'ffi'
2
- require 'ffi-compiler/loader'
3
-
4
- module FastSecureCompare
5
- extend FFI::Library
6
- ffi_lib FFI::Compiler::Loader.find('fast_secure_compare')
7
-
8
- attach_function :secure_compare_bytes, [:pointer, :uint, :pointer, :uint], :int
9
-
10
- def self.compare(secret, input)
11
- return false if secret == "" and input != ""
12
- sBuf = FFI::MemoryPointer.new(:char, secret.size)
13
- sBuf.put_bytes(0, secret)
14
- iBuf = FFI::MemoryPointer.new(:char, input.size)
15
- iBuf.put_bytes(0, input)
16
- secure_compare_bytes(secret, secret.size, input, input.size) == 0
17
- end
18
- end