ruby-srp 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ notifications:
2
+ email: false
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "lib"
5
+ t.test_files = FileList['test/*test.rb']
6
+ t.verbose = true
7
+ end
8
+
9
+ task :default => :test
data/Readme.md CHANGED
@@ -3,3 +3,5 @@
3
3
  ## Ruby-SRP
4
4
 
5
5
  Secure remote password for ruby.
6
+
7
+ [![Build Status](https://secure.travis-ci.org/leapcode/ruby_srp.png?branch=master)](http://travis-ci.org/leapcode/ruby_srp) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/leapcode/ruby_srp)
data/lib/ruby-srp.rb CHANGED
@@ -10,6 +10,8 @@ $:.unshift File.dirname(__FILE__)
10
10
  module SRP
11
11
  autoload :Client, 'srp/client'
12
12
  autoload :Authentication, 'srp/authentication'
13
+ autoload :Util, 'srp/util'
14
+ autoload :Session, 'srp/session'
13
15
  class WrongPassword < StandardError
14
16
  end
15
17
  end
data/lib/srp/client.rb CHANGED
@@ -5,46 +5,37 @@ module SRP
5
5
 
6
6
  include Util
7
7
 
8
- attr_reader :salt, :verifier
8
+ attr_reader :salt, :verifier, :username
9
9
 
10
- def initialize(username, password)
10
+ def initialize(username, password, salt = nil)
11
11
  @username = username
12
12
  @password = password
13
- @salt = "5d3055e0acd3ddcfc15".hex # bigrand(10).hex
14
- @multiplier = multiplier # let's cache it
13
+ @salt = salt || bigrand(4).hex
15
14
  calculate_verifier
16
15
  end
17
16
 
18
- def authenticate(server, username, password)
19
- x = calculate_x(username, password, salt)
20
- a = bigrand(32).hex
21
- aa = modpow(GENERATOR, a, PRIME_N) # A = g^a (mod N)
22
- bb = server.handshake(username, aa)
23
- u = calculate_u(aa, bb, PRIME_N)
24
- client_s = calculate_client_s(x, a, bb, u)
25
- server.validate(calculate_m(aa, bb, client_s))
17
+ def authenticate(server)
18
+ @session = SRP::Session.new(self)
19
+ @session.handshake(server)
20
+ @session.validate(server)
21
+ end
22
+
23
+ def private_key
24
+ @private_key ||= calculate_private_key
26
25
  end
27
26
 
28
27
  protected
28
+
29
29
  def calculate_verifier
30
- x = calculate_x(@username, @password, @salt)
31
- @verifier = modpow(GENERATOR, x, PRIME_N)
32
- @verifier
30
+ @verifier ||= modpow(GENERATOR, private_key)
33
31
  end
34
32
 
35
- def calculate_x(username, password, salt)
36
- shex = '%x' % [salt]
37
- spad = "" # if shex.length.odd? then '0' else '' end
38
- sha256_str(spad + shex + sha256_str([username, password].join(':'))).hex
33
+ def calculate_private_key
34
+ shex = '%x' % [@salt]
35
+ inner = sha256_str([@username, @password].join(':'))
36
+ sha256_hex(shex, inner).hex
39
37
  end
40
38
 
41
- def calculate_client_s(x, a, bb, u)
42
- base = bb
43
- base += PRIME_N * @multiplier
44
- base -= modpow(GENERATOR, x, PRIME_N) * @multiplier
45
- base = base % PRIME_N
46
- modpow(base, x * u + a, PRIME_N)
47
- end
48
39
  end
49
40
  end
50
41
 
@@ -0,0 +1,87 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/util')
2
+
3
+ module SRP
4
+ class Session
5
+ include Util
6
+ attr_accessor :user, :aa, :bb
7
+
8
+ def initialize(user, aa=nil)
9
+ @user = user
10
+ aa ? initialize_server(aa) : initialize_client
11
+ end
12
+
13
+ # client -> server: I, A = g^a
14
+ def handshake(server)
15
+ @bb = server.handshake(user.username, aa)
16
+ @u = calculate_u
17
+ end
18
+
19
+ # client -> server: M = H(H(N) xor H(g), H(I), s, A, B, K)
20
+ def validate(server)
21
+ server.validate(calculate_m(client_secret))
22
+ end
23
+
24
+ def authenticate!(m)
25
+ authenticate(m) || raise(SRP::WrongPassword)
26
+ end
27
+
28
+ def authenticate(m)
29
+ if(m == calculate_m(server_secret))
30
+ return calculate_m2
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ # only seed b for testing purposes.
37
+ def initialize_server(aa, b = nil)
38
+ @aa = aa
39
+ @b = b || bigrand(32).hex
40
+ # B = g^b + k v (mod N)
41
+ @bb = (modpow(GENERATOR, @b) + multiplier * @user.verifier) % BIG_PRIME_N
42
+ @u = calculate_u
43
+ end
44
+
45
+ def initialize_client
46
+ @a = bigrand(32).hex
47
+ @aa = modpow(GENERATOR, @a) # A = g^a (mod N)
48
+ end
49
+
50
+ # client: K = H( (B - kg^x) ^ (a + ux) )
51
+ def client_secret
52
+ base = @bb
53
+ # base += BIG_PRIME_N * @multiplier
54
+ base -= modpow(GENERATOR, @user.private_key) * multiplier
55
+ base = base % BIG_PRIME_N
56
+ modpow(base, @user.private_key * @u + @a)
57
+ end
58
+
59
+ # server: K = H( (Av^u) ^ b )
60
+ # do not cache this - it's secret and someone might store the
61
+ # session in a CookieStore
62
+ def server_secret
63
+ base = (modpow(@user.verifier, @u) * @aa) % BIG_PRIME_N
64
+ modpow(base, @b)
65
+ end
66
+
67
+ # this is outdated - SRP 6a uses
68
+ # M = H(H(N) xor H(g), H(I), s, A, B, K)
69
+ def calculate_m(secret)
70
+ @k = sha256_int(secret).hex
71
+ n_xor_g_long = hn_xor_hg.bytes.map{|b| "%02x" % b.ord}.join.hex
72
+ username_hash = sha256_str(@user.username).hex
73
+ @m = sha256_int(n_xor_g_long, username_hash, @user.salt, @aa, @bb, @k).hex
74
+ end
75
+
76
+ def calculate_m2
77
+ sha256_int(@aa, @m, @k).hex
78
+ end
79
+
80
+ def calculate_u
81
+ sha256_int(@aa, @bb).hex
82
+ end
83
+ end
84
+ end
85
+
86
+
87
+
data/lib/srp/util.rb CHANGED
@@ -10,7 +10,7 @@ module SRP
10
10
  115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3
11
11
  EOS
12
12
 
13
- BIG_PRIME_N = <<-EOS # 1024 bits modulus (N)
13
+ BIG_PRIME_N = <<-EOS.split.join.hex # 1024 bits modulus (N)
14
14
  eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c25657
15
15
  6d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089da
16
16
  d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5
@@ -19,8 +19,12 @@ d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5
19
19
  EOS
20
20
  GENERATOR = 2 # g
21
21
 
22
+ def hn_xor_hg
23
+ byte_xor_hex(sha256_int(BIG_PRIME_N), sha256_int(GENERATOR))
24
+ end
25
+
22
26
  # a^n (mod m)
23
- def modpow(a, n, m)
27
+ def modpow(a, n, m = BIG_PRIME_N)
24
28
  r = 1
25
29
  while true
26
30
  r = r * a % m if n[0] == 1
@@ -30,8 +34,15 @@ d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5
30
34
  end
31
35
  end
32
36
 
33
- def sha256_hex(h)
34
- Digest::SHA2.hexdigest([h].pack('H*'))
37
+ # Hashes the (long) int args
38
+ def sha256_int(*args)
39
+ sha256_hex(*args.map{|a| "%02x" % a})
40
+ end
41
+
42
+ # Hashes the hex args
43
+ def sha256_hex(*args)
44
+ h = args.join('')
45
+ sha256_str([h].pack('H*'))
35
46
  end
36
47
 
37
48
  def sha256_str(s)
@@ -43,34 +54,26 @@ d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5
43
54
  end
44
55
 
45
56
  def multiplier
46
- return "c46d46600d87fef149bd79b81119842f3c20241fda67d06ef412d8f6d9479c58".hex % PRIME_N
47
57
  @k ||= calculate_multiplier
48
58
  end
49
59
 
50
60
  protected
51
61
 
52
62
  def calculate_multiplier
53
- n = PRIME_N
54
- g = GENERATOR
55
- nhex = '%x' % [n]
56
- nlen = nhex.length + (nhex.length.odd? ? 1 : 0 )
57
- ghex = '%x' % [g]
58
- hashin = '0' * (nlen - nhex.length) + nhex \
59
- + '0' * (nlen - ghex.length) + ghex
60
- sha256_hex(hashin).hex % n
63
+ sha256_int(BIG_PRIME_N, GENERATOR).hex
61
64
  end
62
65
 
63
- def calculate_m(aa, bb, s)
64
- hashin = '%x%x%x' % [aa, bb, s]
65
- sha256_str(hashin).hex
66
+ # turn two hex strings into byte arrays and xor them
67
+ #
68
+ # returns byte array
69
+ def byte_xor_hex(a, b)
70
+ a = [a].pack('H*')
71
+ b = [b].pack('H*')
72
+ a.bytes.each_with_index.map do |a_byte, i|
73
+ (a_byte ^ (b[i] || 0)).chr
74
+ end.join
66
75
  end
67
76
 
68
- def calculate_u(aa, bb, n)
69
- nlen = 2 * ((('%x' % [n]).length * 4 + 7) >> 3)
70
- aahex = '%x' % [aa]
71
- bbhex = '%x' % [bb]
72
- return sha256_str("%x%x" % [aa, bb]).hex
73
- end
74
77
  end
75
78
 
76
79
  end
data/ruby-srp.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ruby-srp"
3
- s.version = "0.0.2"
3
+ s.version = "0.1.0"
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.authors = ["Azul"]
6
6
  s.email = ["azul@leap.se"]
data/test/auth_test.rb CHANGED
@@ -1,23 +1,25 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
2
 
3
- class User
3
+ # single user test server.
4
+ # You obviously want sth. different for real life.
5
+ class Server
4
6
 
5
- include SRP::Authentication
7
+ attr_accessor :salt, :verifier, :username
6
8
 
7
- attr_accessor :salt, :verifier
8
-
9
- def initialize(salt, verifier)
9
+ def initialize(salt, verifier, username)
10
10
  @salt = salt
11
11
  @verifier = verifier
12
+ @username = username
12
13
  end
13
14
 
14
15
  def handshake(login, aa)
15
- @session = initialize_auth(aa)
16
+ # this can be serialized and needs to be persisted between requests
17
+ @session = SRP::Session.new(self, aa)
16
18
  return @session.bb
17
19
  end
18
20
 
19
21
  def validate(m)
20
- authenticate(m, @session)
22
+ @session.authenticate(m)
21
23
  end
22
24
 
23
25
  end
@@ -28,19 +30,21 @@ class AuthTest < Test::Unit::TestCase
28
30
  @username = 'user'
29
31
  @password = 'opensesami'
30
32
  @client = SRP::Client.new(@username, @password)
31
- @server = User.new(@client.salt, @client.verifier)
33
+ @server = Server.new(@client.salt, @client.verifier, @username)
32
34
  end
33
35
 
34
36
  def test_successful_auth
35
- assert @client.authenticate(@server, @username, @password)
37
+ assert @client.authenticate(@server)
36
38
  end
37
39
 
38
- def test_wrong_password
39
- assert !@client.authenticate(@server, @username, "wrong password")
40
+ def test_a_wrong_password
41
+ client = SRP::Client.new(@username, "wrong password", @client.salt)
42
+ assert !client.authenticate(@server)
40
43
  end
41
44
 
42
45
  def test_wrong_username
43
- assert !@client.authenticate(@server, "wrong username", @password)
46
+ client = SRP::Client.new("wrong username", @password, @client.salt)
47
+ assert !client.authenticate(@server)
44
48
  end
45
49
  end
46
50
 
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+
3
+ class ClientTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @login = "testuser"
7
+ @password = "password"
8
+ end
9
+
10
+ def test_calculation_of_private_key
11
+ @client = SRP::Client.new(@login, @password, "7686acb8".hex)
12
+ assert_equal "84d6bb567ddf584b1d8c8728289644d45dbfbb02deedd05c0f64db96740f0398",
13
+ "%x" % @client.send(:private_key)
14
+ end
15
+
16
+ # using python srp:
17
+ # s,V = pysrp.create_salted_verification_key("testuser", "password", pysrp.SHA256, pysrp.NG_1024)
18
+
19
+ def test_verifier
20
+ @client = SRP::Client.new(@login, @password, '4c78c3f8'.hex)
21
+ v = '474c26aa42d11f20544a00f7bf9711c4b5cf7aab95ed448df82b95521b96668e7480b16efce81c861870302560ddf6604c67df54f1d04b99d5bb9d0f02c6051ada5dc9d594f0d4314e12f876cfca3dcd99fc9c98c2e6a5e04298b11061fb8549a22cde0564e91514080df79bca1c38c682214d65d590f66b3719f954b078b83c'
22
+ assert_equal v, "%x" % @client.verifier
23
+ end
24
+ end
25
+
26
+
27
+
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+
3
+ class SessionTest < Test::Unit::TestCase
4
+
5
+ attr_accessor :salt, :verifier, :username
6
+
7
+ def setup
8
+ @username = "testuser"
9
+ @password = "password"
10
+ @salt = '4c78c3f8'.hex
11
+ @client = SRP::Client.new(@username, @password, @salt)
12
+ @verifier = @client.verifier
13
+ end
14
+
15
+ def test_equivalance_to_py_srp
16
+ aa = '9ff9d176b37d9100ad4d788b94ef887df6c88786f5fa2419c9a964001e1c1fa5cd22ea39dcf27682dac6cd8861d9de88184653451fd47f5654845ed24e828d531f95c44377c9bc3f5dd83a669716257c7b975a3a032d4d8adb605553cf4d45c483d7aceb7e6a23c5bd4b0aeeb2ef138b7fc75b27d9d706851c3ab9c721710272'.hex
17
+ b = 'ce414b3b52d13a1f67416b7e00cdefb07c874291aed395efeab9435ec1ad6ac3'.hex
18
+ bb = 'b2e852fe7af02d7931186f4958844b829d2976dd58c7bc7928ba3102ff269a9029c707112ab0b7cafdaf86a760f7b50ddd9c847e0c97f564d53cfd52daf61982f06582d49bbb3ea4ad6be55d513028eaf400a6d5a9d26b47689d3438a552716d65680d1b6ee77df3c9b3b6ba61023985562f2be4a6f1723282a2013160594565'.hex
19
+ m = 'a0c066844117ffe7a7999f84356f3a7c8dce38e4e936eca2b6979ab0fce6ff6d'.hex
20
+ m2 = '1f4a5ba9c5280b5b752465670f351bb1e61ff9ca06e02ad43c4418affeb3a1ef'.hex
21
+ session = SRP::Session.new(self, aa)
22
+ session.send(:initialize_server, aa, b) # seeding b to compare to py_srp
23
+ assert_equal bb.to_s(16), session.bb.to_s(16)
24
+ assert_equal m2, session.authenticate(m)
25
+ end
26
+
27
+
28
+ end
data/test/util_test.rb ADDED
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+
3
+ class UtilTest < Test::Unit::TestCase
4
+
5
+ include SRP::Util
6
+
7
+ # comparing to the hash created with python srp lib to make sure
8
+ # we use the same constants and hash the same way.
9
+ def test_sha256_of_prime
10
+ n = BIG_PRIME_N
11
+ nhex = '%x' % [n]
12
+ assert_equal "494b6a801b379f37c9ee25d5db7cd70ffcfe53d01b7c9e4470eaca46bda24b39",
13
+ sha256_hex(nhex)
14
+ end
15
+
16
+ def test_hashing
17
+ x = sha256_str("testuser:password")
18
+ assert_equal 'a5376a27a385bcd791d76cbd6484e1bde130129210e4647a4583e49f45de107f',
19
+ x
20
+ end
21
+
22
+ def test_packing_hex_to_byte_string
23
+ shex = "7686acb8"
24
+ assert_equal [118, 134, 172, 184].pack('C*'), [shex].pack('H*')
25
+ end
26
+
27
+ def test_multiplier
28
+ # >>> "%x" % pysrp.H(sha, N, g)
29
+ assert_equal 'bf66c44a428916cad64aa7c679f3fd897ad4c375e9bbb4cbf2f5de241d618ef0',
30
+ "%x" % multiplier
31
+ end
32
+
33
+ def test_hn_xor_hg
34
+ # >>> binascii.hexlify (pysrp.HNxorg(hashlib.sha256, N, g))
35
+ assert_equal '928ade491bc87bba9eb578701d44d30ed9080e60e542ba0d3b9c20ded9f592bf',
36
+ hn_xor_hg.bytes.map{|b| "%02x" % b.ord}.join
37
+ end
38
+
39
+ end
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 2
10
- version: 0.0.2
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Azul
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-08-06 00:00:00 Z
18
+ date: 2012-10-05 00:00:00 Z
19
19
  dependencies: []
20
20
 
21
21
  description: SRP client and server based on version 6 of the standard
@@ -29,6 +29,8 @@ extra_rdoc_files: []
29
29
 
30
30
  files:
31
31
  - .gitmodules
32
+ - .travis.yml
33
+ - Rakefile
32
34
  - Readme.md
33
35
  - example/config.ru
34
36
  - example/http-srp.rb
@@ -47,12 +49,15 @@ files:
47
49
  - example/views/signup.erb
48
50
  - example/views/verify.erb
49
51
  - lib/ruby-srp.rb
50
- - lib/srp/authentication.rb
51
52
  - lib/srp/client.rb
53
+ - lib/srp/session.rb
52
54
  - lib/srp/util.rb
53
55
  - ruby-srp.gemspec
54
56
  - test/auth_test.rb
57
+ - test/client_test.rb
58
+ - test/session_test.rb
55
59
  - test/test_helper.rb
60
+ - test/util_test.rb
56
61
  homepage: http://github.com/leapdev/ruby.srp
57
62
  licenses: []
58
63
 
@@ -1,59 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/util')
2
-
3
- module SRP
4
- module Authentication
5
-
6
- include Util
7
-
8
- class Session
9
- include Util
10
- attr_accessor :aa, :bb
11
-
12
- def initialize(aa, verifier)
13
- @aa = aa
14
- @b = bigrand(32).hex
15
- # B = g^b + k v (mod N)
16
- @bb = (modpow(GENERATOR, @b, PRIME_N) + multiplier * verifier) % PRIME_N
17
- end
18
-
19
- def u
20
- calculate_u(aa, bb, PRIME_N)
21
- end
22
-
23
- # do not cache this - it's secret and someone might store the
24
- # session in a CookieStore
25
- def secret(verifier)
26
- base = (modpow(verifier, u, PRIME_N) * aa) % PRIME_N
27
- modpow(base, @b, PRIME_N)
28
- end
29
-
30
- def m1(verifier)
31
- calculate_m(aa, bb, secret(verifier))
32
- end
33
-
34
- def m2(m1, verifier)
35
- calculate_m(aa, m1, secret(verifier))
36
- end
37
-
38
- end
39
-
40
- def initialize_auth(aa)
41
- return Session.new(aa, verifier)
42
- end
43
-
44
- def authenticate!(m, session)
45
- authenticate(m, session) || raise(SRP::WrongPassword)
46
- end
47
-
48
- def authenticate(m, session)
49
- if(m == session.m1(verifier))
50
- return session.m2(m, verifier)
51
- end
52
- end
53
-
54
-
55
- end
56
-
57
- end
58
-
59
-