secure_equals 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +27 -0
- data/lib/secure_equals.rb +23 -0
- data/secure_equals.gemspec +12 -0
- data/test/timing_attack.rb +80 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f9883242630ffc2552eb1859d0a0b869f218237b
|
4
|
+
data.tar.gz: 0222f297a7683a1927b92241e7fde4de2c9618f4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0ba9b81d792b35adcabbe6163debddf1f8107b11c62b5c12d60cba6d4feab8577ae08a15aaea9bfe692712a5fce9d1d8ad84ecb5c2888a616646381ffdad3700
|
7
|
+
data.tar.gz: e66e06d162ac9d9983533330ea9d9e77d873bbbc4f9e63144dedfd5d196eaf02720a3c7dce13dceacd78618a8a48424731caac22959995ede2ccc63ffc60c9e7
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Secure equals provides a constant time equality method for ruby strings.
|
2
|
+
|
3
|
+
By avoiding any branches that depend on the similarity of the two strings,
|
4
|
+
we make it significantly harder for an attacker to discover properties of
|
5
|
+
the secret by observing the time it takes to run the comparison.
|
6
|
+
|
7
|
+
You should use this for checking checksums or shared secrets in your code.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
You can either `gem install secure_equals` or add `gem 'secure_equals'` to your Gemfile and then run `bundle install`
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
SecureEquals.equal? checksum, @params[:checksum]
|
17
|
+
```
|
18
|
+
|
19
|
+
## TODO
|
20
|
+
|
21
|
+
I was unable to implement a successful timing attack against ruby's string == on my Mac Book Pro; so while the code here looks promising, I've not actually been able to verify that it actually solves the problem.
|
22
|
+
|
23
|
+
We should try running the attack on 16- and 32- byte sized chunks instead of the character-by-character mode, as I suspect the problem is memcmp(). Alternatively there might be a way to get higher resolution timings, or to use some stats that I don't know about, to make it work.
|
24
|
+
|
25
|
+
## BUGS
|
26
|
+
|
27
|
+
I'm pretty sure this must already exist somewhere...
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SecureEquals
|
2
|
+
# Provides an equality method on strings that is not vulnerable to timing attacks.
|
3
|
+
#
|
4
|
+
# This prevents attackers from being able to guess the answer byte-by-byte using
|
5
|
+
# tiny differences in response time.
|
6
|
+
#
|
7
|
+
# @param [String] The secret string
|
8
|
+
# @param [String] The string provided by the user
|
9
|
+
# @return [Boolean] Are the strings the same?
|
10
|
+
#
|
11
|
+
def self.equal?(mine, theirs)
|
12
|
+
mine = mine.to_str
|
13
|
+
theirs = theirs.to_str
|
14
|
+
return false unless mine.length == theirs.length
|
15
|
+
|
16
|
+
difference = 0
|
17
|
+
0.upto(mine.length - 1) do |i|
|
18
|
+
difference |= (mine[i].ord ^ theirs[i].ord)
|
19
|
+
end
|
20
|
+
|
21
|
+
difference == 0
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "secure_equals"
|
3
|
+
s.version = "0.1"
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.author = "Conrad Irwin"
|
6
|
+
s.email = "conrad.irwin@gmail.com"
|
7
|
+
s.homepage = "http://github.com/Conradirwin/secure_equals"
|
8
|
+
s.summary = "Provides constant time equality for ruby strings"
|
9
|
+
s.description = "Constant time equality (also known as time insensitive equality) lets you compare user-provided strings with secrets in a way that does not leak data about those secrets."
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.require_path = "lib"
|
12
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative '../lib/secure_equals'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'hitimes'
|
4
|
+
GC.disable
|
5
|
+
|
6
|
+
class Box
|
7
|
+
def initialize
|
8
|
+
@secret = '0123456789abcdeffedcba0987654321'
|
9
|
+
end
|
10
|
+
|
11
|
+
# The expected score of a random guess is 0.5.
|
12
|
+
# The expected score of the correct answer is 1.
|
13
|
+
#
|
14
|
+
# If you are able to get scores consistently higher
|
15
|
+
# than 0.5 using only the guess method, then you
|
16
|
+
# have a successful timing attack.
|
17
|
+
def score(str)
|
18
|
+
score = 0.0
|
19
|
+
mine = @secret.each_byte.map{ |x| x.to_s(2) }.join
|
20
|
+
theirs = str.each_byte.map{ |x| x.to_s(2) }.join
|
21
|
+
|
22
|
+
mine.each_byte.zip(theirs.each_byte) do |a,b|
|
23
|
+
score += 1 if a == b
|
24
|
+
end
|
25
|
+
(score / mine.length)
|
26
|
+
end
|
27
|
+
|
28
|
+
class Weak < Box
|
29
|
+
def guess(str)
|
30
|
+
0.upto(@secret.length - 1) do |i|
|
31
|
+
return false unless @secret[i] == str[i]
|
32
|
+
end
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Standard < Box
|
38
|
+
def guess(str)
|
39
|
+
@secret == str
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Secure < Box
|
44
|
+
def guess(str)
|
45
|
+
SecureEquals.same? @secret, str
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def brute_force(box, trials)
|
51
|
+
scores = []
|
52
|
+
1.times do
|
53
|
+
guess = '0' * 32
|
54
|
+
(0..32).each do |pos|
|
55
|
+
max = 0
|
56
|
+
result = nil
|
57
|
+
this_time = guess.dup
|
58
|
+
'abcdef0123456789'.each_char do |letter|
|
59
|
+
this_time[pos] = letter
|
60
|
+
time = Hitimes::Interval.measure do
|
61
|
+
trials.times{ box.guess this_time }
|
62
|
+
end
|
63
|
+
if time > max
|
64
|
+
max = time
|
65
|
+
result = letter
|
66
|
+
end
|
67
|
+
end
|
68
|
+
guess[pos] = result
|
69
|
+
end
|
70
|
+
scores << box.score(guess)
|
71
|
+
end
|
72
|
+
|
73
|
+
puts "average: #{scores.inject(&:+) / scores.size}"
|
74
|
+
end
|
75
|
+
|
76
|
+
10.times do
|
77
|
+
brute_force Box::Weak.new, 1000
|
78
|
+
brute_force Box::Standard.new, 100000
|
79
|
+
brute_force Box::Secure.new, 1000000
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: secure_equals
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Conrad Irwin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-11 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Constant time equality (also known as time insensitive equality) lets
|
14
|
+
you compare user-provided strings with secrets in a way that does not leak data
|
15
|
+
about those secrets.
|
16
|
+
email: conrad.irwin@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- lib/secure_equals.rb
|
23
|
+
- secure_equals.gemspec
|
24
|
+
- test/timing_attack.rb
|
25
|
+
homepage: http://github.com/Conradirwin/secure_equals
|
26
|
+
licenses: []
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.0.3
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Provides constant time equality for ruby strings
|
48
|
+
test_files: []
|
49
|
+
has_rdoc:
|