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 +13 -5
- data/README.md +56 -3
- data/Rakefile +32 -12
- data/ext/fast_secure_compare/extconf.rb +3 -0
- data/ext/fast_secure_compare/secure_compare.c +26 -4
- data/lib/fast_secure_compare/rack.rb +12 -0
- data/lib/fast_secure_compare/rails.rb +11 -0
- data/spec/fsc_rack_spec.rb +24 -0
- metadata +27 -25
- data/ext/Rakefile +0 -3
- data/fast_secure_compare.gemspec +0 -27
- data/lib/fast_secure_compare.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MDM5MWFhMTcwNjJhNjQyZjA4YTc1ODE4NGZlNGQ4ZTU2NjhiY2ZmYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MmY2NjNlYzg4MzFhNWQyZWYyMTU0NzI3M2FlYjg4NWI5NjE4MTc0Zg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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)
|
@@ -1,15 +1,37 @@
|
|
1
|
-
|
2
|
-
|
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 |=
|
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,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
|
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-
|
11
|
+
date: 2014-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: rake
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - '>='
|
17
|
+
- - ! '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0
|
20
|
-
type: :
|
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
|
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: :
|
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/
|
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
|
-
-
|
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.
|
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
|
-
|
99
|
+
- spec/fsc_rack_spec.rb
|
data/ext/Rakefile
DELETED
data/fast_secure_compare.gemspec
DELETED
@@ -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
|
-
|
data/lib/fast_secure_compare.rb
DELETED
@@ -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
|