From 09a7a8c0fb28ff49fac64f282aa136f8a2c20dfe Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 18 Jun 2012 12:34:11 +0200 Subject: initial commit - testing srp auth * This is lacking a few steps. We confirm the secret is the same but no key is generated from it and it is transfered over the wire in clear. * this was inspired by https://gist.github.com/790048 * seperated util, client, server and test code --- lib/srp.rb | 13 +++++++++++++ lib/srp/client.rb | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/srp/server.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ lib/srp/util.rb | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test/auth_test.rb | 25 +++++++++++++++++++++++++ test/test_helper.rb | 3 +++ 6 files changed, 184 insertions(+) create mode 100644 lib/srp.rb create mode 100644 lib/srp/client.rb create mode 100644 lib/srp/server.rb create mode 100644 lib/srp/util.rb create mode 100644 test/auth_test.rb create mode 100644 test/test_helper.rb diff --git a/lib/srp.rb b/lib/srp.rb new file mode 100644 index 0000000..999f9b6 --- /dev/null +++ b/lib/srp.rb @@ -0,0 +1,13 @@ +# Ruby library for the server side of the Secure Remote Password protocol + +# References +# `The Stanford SRP Homepage', +# http://srp.stanford.edu/ +# `SRP JavaScript Demo', +# http://srp.stanford.edu/demo/demo.html + +$:.unshift File.dirname(__FILE__) +module SRP + autoload :Client, 'srp/client' + autoload :Server, 'srp/server' +end diff --git a/lib/srp/client.rb b/lib/srp/client.rb new file mode 100644 index 0000000..f4662e6 --- /dev/null +++ b/lib/srp/client.rb @@ -0,0 +1,48 @@ +require File.expand_path(File.dirname(__FILE__) + '/util') + +module SRP + class Client + + include Util + + attr_reader :salt, :verifier + + def initialize(username, password) + @username = username + @password = password + @salt = bigrand(10).hex + @multiplier = multiplier # let's cache it + calculate_verifier + end + + def authenticate(server, username, password) + x = calculate_x(username, password, salt) + a = bigrand(32).hex + aa = modpow(GENERATOR, a, PRIME_N) # A = g^a (mod N) + bb, u = server.initialize_auth(aa) + client_s = calculate_client_s(x, a, bb, u) + server.authenticate(aa, client_s) + end + + protected + def calculate_verifier + x = calculate_x(@username, @password, @salt) + @verifier = modpow(GENERATOR, x, PRIME_N) + end + + def calculate_x(username, password, salt) + shex = '%x' % [salt] + spad = if shex.length.odd? then '0' else '' end + sha1_hex(spad + shex + sha1_str([username, password].join(':'))).hex + end + + def calculate_client_s(x, a, bb, u) + base = bb + base += PRIME_N * @multiplier + base -= modpow(GENERATOR, x, PRIME_N) * @multiplier + base = base % PRIME_N + modpow(base, x * u + a, PRIME_N) + end + end +end + diff --git a/lib/srp/server.rb b/lib/srp/server.rb new file mode 100644 index 0000000..a1189a1 --- /dev/null +++ b/lib/srp/server.rb @@ -0,0 +1,42 @@ +require File.expand_path(File.dirname(__FILE__) + '/util') + +module SRP + class Server + + include Util + + def initialize(salt, verifier) + @salt = salt + @verifier = verifier + end + + def initialize_auth(aa) + @b = bigrand(32).hex + # B = g^b + k v (mod N) + @bb = (modpow(GENERATOR, @b, PRIME_N) + multiplier * @verifier) % PRIME_N + u = calculate_u(aa, @bb, PRIME_N) + return @bb, u + end + + def authenticate(aa, client_s) + u = calculate_u(aa, @bb, PRIME_N) + base = (modpow(@verifier, u, PRIME_N) * aa) % PRIME_N + server_s = modpow(base, @b, PRIME_N) + return client_s == server_s + end + + + protected + + def calculate_u(aa, bb, n) + nlen = 2 * ((('%x' % [n]).length * 4 + 7) >> 3) + aahex = '%x' % [aa] + bbhex = '%x' % [bb] + hashin = '0' * (nlen - aahex.length) + aahex \ + + '0' * (nlen - bbhex.length) + bbhex + sha1_hex(hashin).hex + end + end +end + + diff --git a/lib/srp/util.rb b/lib/srp/util.rb new file mode 100644 index 0000000..6792105 --- /dev/null +++ b/lib/srp/util.rb @@ -0,0 +1,53 @@ +require 'digest' +require 'openssl' + +module SRP + module Util + + # constants both sides know + PRIME_N = <<-EOS.split.join.hex # 1024 bits modulus (N) +eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c25657 +6d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089da +d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5 +7ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb +06e3 + EOS + GENERATOR = 2 # g + + # a^n (mod m) + def modpow(a, n, m) + r = 1 + while true + r = r * a % m if n[0] == 1 + n >>= 1 + return r if n == 0 + a = a * a % m + end + end + + def sha1_hex(h) + Digest::SHA1.hexdigest([h].pack('H*')) + end + + def sha1_str(s) + Digest::SHA1.hexdigest(s) + end + + def bigrand(bytes) + OpenSSL::Random.random_bytes(bytes).unpack("H*")[0] + end + + def multiplier + n = PRIME_N + g = GENERATOR + nhex = '%x' % [n] + nlen = nhex.length + (nhex.length.odd? ? 1 : 0 ) + ghex = '%x' % [g] + hashin = '0' * (nlen - nhex.length) + nhex \ + + '0' * (nlen - ghex.length) + ghex + sha1_hex(hashin).hex % n + end + end + +end + diff --git a/test/auth_test.rb b/test/auth_test.rb new file mode 100644 index 0000000..e6c4017 --- /dev/null +++ b/test/auth_test.rb @@ -0,0 +1,25 @@ +require File.expand_path(File.dirname(__FILE__) + '/test_helper') + +class AuthTest < Test::Unit::TestCase + + def setup + @username = 'user' + @password = 'opensasemi' + @client = SRP::Client.new(@username, @password) + @server = SRP::Server.new(@client.salt, @client.verifier) + end + + def test_successful_auth + assert @client.authenticate(@server, @username, @password) + end + + def test_wrong_password + assert !@client.authenticate(@server, @username, "password") + end + + def test_wrong_username + assert !@client.authenticate(@server, "username", @password) + end +end + + diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..68d3acf --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,3 @@ +require "rubygems" +require 'test/unit' +require File.expand_path(File.dirname(__FILE__) + '/../lib/srp.rb') -- cgit v1.2.3