Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Commit

Permalink
SIRP module no longer exports public module methods. Just include SIR…
Browse files Browse the repository at this point in the history
…P where needed. Client and Verifier provide all the API.
  • Loading branch information
grempe committed May 14, 2016
1 parent 83c04b9 commit 87212b4
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 152 deletions.
19 changes: 10 additions & 9 deletions lib/sirp/client.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
module SIRP
class Client
include SIRP
attr_reader :N, :g, :k, :a, :A, :S, :K, :M, :H_AMK, :hash

def initialize(group = 2048)
# select modulus (N) and generator (g)
@N, @g, @hash = SIRP.Ng(group)
@k = SIRP.calc_k(@N, @g, hash)
@N, @g, @hash = Ng(group)
@k = calc_k(@N, @g, hash)
end

def start_authentication
# Generate a/A private and public components
@a ||= SecureRandom.hex(32).hex
@A = SIRP.num_to_hex(SIRP.calc_A(@a, @N, @g))
@A = num_to_hex(calc_A(@a, @N, @g))
end

# Process initiated authentication challenge.
Expand All @@ -23,21 +24,21 @@ def process_challenge(username, password, xsalt, xbb)
# SRP-6a safety check
return false if (bb % @N) == 0

x = SIRP.calc_x(username, password, xsalt, hash)
u = SIRP.calc_u(@A, xbb, @N, hash)
x = calc_x(username, password, xsalt, hash)
u = calc_u(@A, xbb, @N, hash)

# SRP-6a safety check
return false if u == 0

# calculate session key
@S = SIRP.num_to_hex(SIRP.calc_client_S(bb, @a, @k, x, u, @N, @g))
@K = SIRP.sha_hex(@S, hash)
@S = num_to_hex(calc_client_S(bb, @a, @k, x, u, @N, @g))
@K = sha_hex(@S, hash)

# calculate match
@M = SIRP.calc_M(@A, xbb, @K, hash)
@M = calc_M(@A, xbb, @K, hash)

# calculate verifier
@H_AMK = SIRP.num_to_hex(SIRP.calc_H_AMK(@A, @M, @K, hash))
@H_AMK = num_to_hex(calc_H_AMK(@A, @M, @K, hash))

@M
end
Expand Down
2 changes: 1 addition & 1 deletion lib/sirp/parameters.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module SIRP
def self.Ng(group)
def Ng(group)
case group
when 1024
@N = %w(
Expand Down
214 changes: 106 additions & 108 deletions lib/sirp/sirp.rb
Original file line number Diff line number Diff line change
@@ -1,111 +1,109 @@
module SIRP
class << self
# http://stackoverflow.com/questions/3772410/convert-a-string-of-0-f-into-a-byte-array-in-ruby
def hex_to_bytes(str)
[str].pack('H*').unpack('C*')
end

def num_to_hex(num)
hex_str = num.to_s(16)
even_hex_str = hex_str.length.odd? ? '0' + hex_str : hex_str
even_hex_str.downcase
end

def sha_hex(h, hash_klass)
hash_klass.hexdigest([h].pack('H*'))
end

def sha_str(s, hash_klass)
hash_klass.hexdigest(s)
end

# Modular Exponentiation
# https://en.m.wikipedia.org/wiki/Modular_exponentiation
# http://rosettacode.org/wiki/Modular_exponentiation#Ruby
#
# a^b (mod m)
def mod_exp(a, b, m)
# Use OpenSSL::BN#mod_exp
a.to_bn.mod_exp(b, m)
end

# Hashing function with padding.
# Input is prefixed with 0 to meet N hex width.
def H(hash_klass, n, *a)
nlen = 2 * ((('%x' % [n]).length * 4 + 7) >> 3)

hashin = a.map do |s|
next unless s
shex = (s.class == String) ? s : num_to_hex(s)
if shex.length > nlen
raise 'Bit width does not match - client uses different prime'
end
'0' * (nlen - shex.length) + shex
end.join('')

sha_hex(hashin, hash_klass).hex % n
end

# Multiplier parameter
# k = H(N, g) (in SIRP-6a)
def calc_k(n, g, hash_klass)
H(hash_klass, n, n, g)
end

# Private key (derived from username, raw password and salt)
# x = H(salt || H(username || ':' || password))
def calc_x(username, password, salt, hash_klass)
spad = salt.length.odd? ? '0' : ''
sha_hex(spad + salt + sha_str([username, password].join(':'), hash_klass), hash_klass).hex
end

# Random scrambling parameter
# u = H(A, B)
def calc_u(xaa, xbb, n, hash_klass)
H(hash_klass, n, xaa, xbb)
end

# Password verifier
# v = g^x (mod N)
def calc_v(x, n, g)
mod_exp(g, x, n)
end

# A = g^a (mod N)
def calc_A(a, n, g)
mod_exp(g, a, n)
end

# B = g^b + k v (mod N)
def calc_B(b, k, v, n, g)
(mod_exp(g, b, n) + k * v) % n
end

# Client secret
# S = (B - (k * g^x)) ^ (a + (u * x)) % N
def calc_client_S(bb, a, k, x, u, n, g)
mod_exp((bb - k * mod_exp(g, x, n)) % n, (a + x * u), n)
end

# Server secret
# S = (A * v^u) ^ b % N
def calc_server_S(aa, b, v, u, n)
mod_exp((mod_exp(v, u, n) * aa), b, n)
end

# M = H(A, B, K)
def calc_M(xaa, xbb, xkk, hash_klass)
digester = hash_klass.new
digester << hex_to_bytes(xaa).pack('C*')
digester << hex_to_bytes(xbb).pack('C*')
digester << hex_to_bytes(xkk).pack('C*')
digester.hexdigest
end

# H(A, M, K)
def calc_H_AMK(xaa, xmm, xkk, hash_klass)
byte_string = hex_to_bytes([xaa, xmm, xkk].join('')).pack('C*')
sha_str(byte_string, hash_klass).hex
end
# http://stackoverflow.com/questions/3772410/convert-a-string-of-0-f-into-a-byte-array-in-ruby
def hex_to_bytes(str)
[str].pack('H*').unpack('C*')
end

def num_to_hex(num)
hex_str = num.to_s(16)
even_hex_str = hex_str.length.odd? ? '0' + hex_str : hex_str
even_hex_str.downcase
end

def sha_hex(h, hash_klass)
hash_klass.hexdigest([h].pack('H*'))
end

def sha_str(s, hash_klass)
hash_klass.hexdigest(s)
end

# Modular Exponentiation
# https://en.m.wikipedia.org/wiki/Modular_exponentiation
# http://rosettacode.org/wiki/Modular_exponentiation#Ruby
#
# a^b (mod m)
def mod_exp(a, b, m)
# Use OpenSSL::BN#mod_exp
a.to_bn.mod_exp(b, m)
end

# Hashing function with padding.
# Input is prefixed with 0 to meet N hex width.
def H(hash_klass, n, *a)
nlen = 2 * ((('%x' % [n]).length * 4 + 7) >> 3)

hashin = a.map do |s|
next unless s
shex = (s.class == String) ? s : num_to_hex(s)
if shex.length > nlen
raise 'Bit width does not match - client uses different prime'
end
'0' * (nlen - shex.length) + shex
end.join('')

sha_hex(hashin, hash_klass).hex % n
end

# Multiplier parameter
# k = H(N, g) (in SIRP-6a)
def calc_k(n, g, hash_klass)
H(hash_klass, n, n, g)
end

# Private key (derived from username, raw password and salt)
# x = H(salt || H(username || ':' || password))
def calc_x(username, password, salt, hash_klass)
spad = salt.length.odd? ? '0' : ''
sha_hex(spad + salt + sha_str([username, password].join(':'), hash_klass), hash_klass).hex
end

# Random scrambling parameter
# u = H(A, B)
def calc_u(xaa, xbb, n, hash_klass)
H(hash_klass, n, xaa, xbb)
end

# Password verifier
# v = g^x (mod N)
def calc_v(x, n, g)
mod_exp(g, x, n)
end

# A = g^a (mod N)
def calc_A(a, n, g)
mod_exp(g, a, n)
end

# B = g^b + k v (mod N)
def calc_B(b, k, v, n, g)
(mod_exp(g, b, n) + k * v) % n
end

# Client secret
# S = (B - (k * g^x)) ^ (a + (u * x)) % N
def calc_client_S(bb, a, k, x, u, n, g)
mod_exp((bb - k * mod_exp(g, x, n)) % n, (a + x * u), n)
end

# Server secret
# S = (A * v^u) ^ b % N
def calc_server_S(aa, b, v, u, n)
mod_exp((mod_exp(v, u, n) * aa), b, n)
end

# M = H(A, B, K)
def calc_M(xaa, xbb, xkk, hash_klass)
digester = hash_klass.new
digester << hex_to_bytes(xaa).pack('C*')
digester << hex_to_bytes(xbb).pack('C*')
digester << hex_to_bytes(xkk).pack('C*')
digester.hexdigest
end

# H(A, M, K)
def calc_H_AMK(xaa, xmm, xkk, hash_klass)
byte_string = hex_to_bytes([xaa, xmm, xkk].join('')).pack('C*')
sha_str(byte_string, hash_klass).hex
end
end
25 changes: 13 additions & 12 deletions lib/sirp/verifier.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
module SIRP
class Verifier
include SIRP
attr_reader :N, :g, :k, :A, :B, :b, :S, :K, :M, :H_AMK, :hash

def initialize(group = 2048)
# select modulus (N) and generator (g)
@N, @g, @hash = SIRP.Ng(group)
@k = SIRP.calc_k(@N, @g, hash)
@N, @g, @hash = Ng(group)
@k = calc_k(@N, @g, hash)
end

# Initial user creation for the persistance layer.
# Not part of the authentication process.
# Returns { <username>, <password verifier>, <salt> }
def generate_userauth(username, password)
@salt ||= SecureRandom.hex(10)
x = SIRP.calc_x(username, password, @salt, hash)
v = SIRP.calc_v(x, @N, @g)
{ username: username, verifier: SIRP.num_to_hex(v), salt: @salt }
x = calc_x(username, password, @salt, hash)
v = calc_v(x, @N, @g)
{ username: username, verifier: num_to_hex(v), salt: @salt }
end

# Authentication phase 1 - create challenge.
Expand All @@ -28,7 +29,7 @@ def get_challenge_and_proof(username, xverifier, xsalt, xaa)

{
challenge: { B: @B, salt: xsalt },
proof: { A: xaa, B: @B, b: SIRP.num_to_hex(@b), I: username, s: xsalt, v: xverifier }
proof: { A: xaa, B: @B, b: num_to_hex(@b), I: username, s: xsalt, v: xverifier }
}
end

Expand All @@ -41,21 +42,21 @@ def verify_session(proof, client_M)
@b = proof[:b].to_i(16)
v = proof[:v].to_i(16)

u = SIRP.calc_u(@A, @B, @N, hash)
u = calc_u(@A, @B, @N, hash)

# SRP-6a safety check
return false if u == 0

# calculate session key
@S = SIRP.num_to_hex(SIRP.calc_server_S(@A.to_i(16), @b, v, u, @N))
@K = SIRP.sha_hex(@S, hash)
@S = num_to_hex(calc_server_S(@A.to_i(16), @b, v, u, @N))
@K = sha_hex(@S, hash)

# calculate match
@M = SIRP.calc_M(@A, @B, @K, hash)
@M = calc_M(@A, @B, @K, hash)

if @M == client_M
# authentication succeeded
@H_AMK = SIRP.num_to_hex(SIRP.calc_H_AMK(@A, @M, @K, hash))
@H_AMK = num_to_hex(calc_H_AMK(@A, @M, @K, hash))
else
false
end
Expand All @@ -66,7 +67,7 @@ def verify_session(proof, client_M)
def generate_B(xverifier)
v = xverifier.to_i(16)
@b ||= SecureRandom.hex(32).hex
@B = SIRP.num_to_hex(SIRP.calc_B(@b, k, v, @N, @g))
@B = num_to_hex(calc_B(@b, k, v, @N, @g))
end
end
end
5 changes: 3 additions & 2 deletions spec/parameters_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'spec_helper'

describe SIRP do
include SIRP
# Test predefined values for N and g.
# Values are from vectors listed in RFC 5054 Appendix B.
#
Expand Down Expand Up @@ -32,9 +33,9 @@
end
end

it 'should be correct when accessed through a SIRP.Ng' do
it 'should be correct when accessed through a Ng' do
@params.each do |p|
nn, g, h = SIRP.Ng(p[:group])
nn, g, h = Ng(p[:group])
expect(('%b' % nn).length).to eq(p[:group])
expect(Digest::SHA256.hexdigest(('%x' % nn))).to eq(p[:hash_nn])
expect(g).to eq(p[:generator])
Expand Down
Loading

0 comments on commit 87212b4

Please sign in to comment.