ruby-srp 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-