srp

package module
v0.6.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 18, 2021 License: MIT Imports: 11 Imported by: 0

README

Standalone SRP-6a implementation in go-lang

GoDoc Go Report Card

This is a standalone implementation of SRP in golang. It uses the go standard libraries and has no other external dependencies. This library can be used by SRP clients or servers.

SRP is a protocol to authenticate a user and derive safe session keys. It is the latest in the category of "strong authentication protocols".

SRP is documented here: http://srp.stanford.edu/doc.html. Briefly,

Design Overview

Conventions
N    A large safe prime (N = 2q+1, where q is prime)
   All arithmetic is done modulo N.
g    A generator modulo N
k    Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
s    User's salt
I    Username
p    Cleartext Password
H()  One-way hash function
^    (Modular) Exponentiation
u    Random scrambling parameter
a,b  Secret ephemeral values
A,B  Public ephemeral values
x    Private key (derived from p and s)
v    Password verifier
Differences from SRP-6a and RFC 5054

We differ from the SRP-6a spec and RFC 5054 in a couple of key ways:

  • We hash the identity I; this provides some (minimal) protection against dictionary attacks on the username.
  • We hash the user passphrase p; this expands shorter passphrase into longer ones and extends the alphabet used in the passphrase.
  • We differ from RFC 5054 in our choice of hash function; we use Blake-2b. SHA-1 is getting long in the tooth, Blake2b is the current state-of-the art. Equivalently, one may use SHA3 (see below for using a user supplied hash function).
Generating and Storing the Password Verifier

The host calculates the password verifier using the following formula:

s = randomsalt()          (same length as N)
I = H(I)
p = H(p)                  (hash/expand I & p)
t = H(I, ":", p)
x = H(s, t)
v = g^x                   (computes password verifier)

The host then stores {I, s, v} in its password database - such that the triple can be retrieved by using I as the index/key.

Authentication Protocol

The authentication protocol itself goes as follows:

Client                       Server
--------------               ----------------
un, pw = < user input >
I = H(un)
p = H(pw)
a = random()
A = g^a % N
            I, A -->
                          s, v = lookup(I)
                          b = random()
                          B = (kv + g^b) % N
                          u = H(A, B)
                          S = ((A * v^u) ^ b) % N
                          K = H(S)
                          M' = H(K, A, B, I, s, N, g)
             <-- s, B
u = H(A, B)
x = H(s, p)
S = ((B - k (g^x)) ^ (a + ux)) % N
K = H(S)
M = H(K, A, B, I, s, N, g)

            M -->
                          M must be equal to M'
                          Z = H(M, K)
            <-- Z

Z' = H(M, K)
Z' must equal Z

When the server receives <I, A>, it can compute everything: shared key and proof-of-generation M'. The shared key is K.

To verify that the client has generated the same key K, the client sends M -- a hash of all the data it has and it received from the server. To validate that the server also has the same value, it requires the server to send its own proof. In the SRP paper, the authors use:

M = H(H(N) xor H(g), H(I), s, A, B, K)
M' = H(A, M, K)

We use a simpler construction:

M = H(K, A, B, I, s, N, g)
M' = H(M, K)

The two parties also employ the following safeguards:

  1. The user will abort if he receives B == 0 (mod N) or u == 0.
  2. The host will abort if it detects that A == 0 (mod N).
  3. The user must show his proof of K first. If the server detects that the user's proof is incorrect, it must abort without showing its own proof of K.

Implementation Notes

In our implementation:

  • The standard hash function is Blake2b-256; this can be changed by choosing an appropriate hash from crypto:
    
         s, err := srp.NewWithHash(crypto.SHA256, 4096)
    
Setting up the Verifiers on the Server

In order to authenticate and derive session keys, verifiers must be stored in a non-volatile medium on the server. The client provides the prime-field size, username and password when creating the verifier. The server stores the triple in a non-volatile medium. The verifiers are generated once when a user is created on the server.

The Client is the entity where the user enters their password and wishes to be authenticated with a SRP server. The communication between client and server can happen in clear text - SRP is immune to man in the middle attacks.

Depending on the resources available on a given client, it can choose a small or large prime-field; but once chosen it is recorded on the server until a new verifier is generated.

For example, a client will do:


    s, err := srp.New(n_bits)

    v, err := s.Verifier(username, password)
    id, verif := v.Encode()

    // Now, store 'id', 'verif' in non-volatile storage such that 'verif' can be
    // retrieved by providing 'id'.

Note that id is the hashed identity string for username. The server should store the encoded verifier string verif in a DB such that it can be looked up using id as the key.

Changing the default hash function

A client may wish to change the default hash function to something else. e.g.,::


    s, err := srp.NewWithHash(crypto.SHA256, n_bits)

    v, err := s.Verifier(username, password)
    id, verif := v.Encode()
Authentication attempt from the Client

The client performs the following sequence of steps to authenticate and derive session keys:


    s, err := srp.New(n_bits)

    c, err := s.NewClient(user, pass)
    creds := c.Credentials()

    // 1. send the credentials to the server. It is already in ASCII string form; this
    //    is essentially the encoded form of identity and a random public key.

    // 2. Receive the server credentials into 'server_creds'; this is the server
    //    public key and random salt generated when the verifier was created.

    // It is assumed that there is some network communication that happens
    // to get this string from the server.

    // Now, generate a mutual authenticator to be sent to the server
    auth, err := c.Generate(server_creds)

    // 3. Send the mutual authenticator to the server
    // 4. receive "proof" that the server too computed the same result.

    // Verify that the server actually did what it claims
    if !c.ServerOk(proof) {
        panic("authentication failed")
    }

    // Generate session key
    rawkey := c.RawKey()
Authenticating a Client on the Server

On the server, the authentication attempt begins after receiving the initial user credentials. This is used to lookup the stored verifier and other bits.:


    // Assume that we received the user credentials via the network into 'creds'


    // Parse the user info and authenticator from the 'creds' string
    id, A, err := srp.ServerBegin(creds)

    // Use 'id' to lookup the user in some non-volatile DB and obtain
    // previously stored *encoded* verifier 'v'.
    verifier := db.Lookup(id)


    // Create an SRP instance and Verifier instance from the stored data.
    s, v, err := srp.MakeSRPVerifier(verifier)

    // Begin a new client-server SRP session using the verifier and received
    // public key.
    srv, err := s.NewServer(v, A)

    // Generate server credentials to send to the user
    s_creds := srv.Credentials()

    // 1. send 's_creds' to the client
    // 2. receive 'm_auth' from the client

    // Authenticate user and generate mutual proof of authentication
    proof, ok := srv.ClientOk(m_auth)
    if ok != nil {
         panic("Authentication failed")
    }

    // 3. Send proof to client

    // Auth succeeded, derive session key
    rawkey := s.RawKey()
Generating new Safe Primes & Prime Field Generators

The SRP library uses a pre-calculated list of large safe prime for common widths along wit their field generators. But, this is not advisable for large scale production use. It is best that a separate background process be used to generate safe primes & the corresponding field generators - and store them in some cache. The function findPrimeField() can be modified to fetch from this cache. Depending on the security stance, the cache can decide on a "use once" policy or "use N times" policy.

The function srp.NewPrimeField() generates and returns a new large safe prime and its field generator.

Building SRP

There is an example program that shows you the API usage (documented above).:

    $ git clone https://github.com/ooozws/go-srp
    $ cd go-srp
    $ go test -v

Finally, build the example program:

    $ go build -o ex example/example.go
    $ ./ex

The example program outputs the raw-key from the client & server's perspective (they should be identical).

There is also a companion program in the example directory that generates prime fields of a given size:

    $ go build -o pf example/primefield.go
    $ ./pf 1024

The above program can be run to generate multiple fields on the command line:

    $ ./pf 1024 2048 4096 8192

The library uses go modules; so, it should be straight forward to import and use.

Using the SRP Raw key to derive session keys

The client and server both derive the same value for RawKey(). This is the crux of the SRP protocol. Treat this as a "master key". It is not advisable to use the RawKey() for encryption purposes. It is better to derive a separate key for each direction (client->server and server->client). e.g.,:

    c2s_k = KDF(rawkey, counter, "C2S")
    s2s_k = KDF(rawkey, counter, "S2C")

KDF above can be a reputable key derivation function such as PBKDF2 or Scrypt. The "counter" is incremented every time you derive a new key.

I am not a cryptographer. Please consult your favorite crypto book for deriving encryption keys from a master key.

Using scrypt as the KDF

Here is a example KDF using scrypt:

    import "golang.org/x/crypto/scrypt"

    // Safe values for Scrypt() parameters
    const _N int = 65536
    const _r int = 1024
    const _p int = 64

    // Kdf derives a 'sz' byte key for use 'usage'
    func Kdf(key []byte, salt []byte, usage string, sz int) []byte {

        u0 := []byte(usage)
        pw := append(key, u0...)
        k, _ := scrypt.Key(pw, salt, _N, _r, _p, sz)
        return k
    }
Using argon2 as the KDF

Argon is the new state of the art (2018) key derivation algorithm. The Argon2id variant is resistant to timing, side-channel and Time-memory tradeoff attacks. Here is an example using the Argon2id variant:

    import (
        "runtime"
        "golang.org/x/crypto/argon2"
    )

    // safe values for IDKey() borrowed from libsodium
    const _Time uint32 = 3
    const _Mem  uint32 = 256 * 1048576  // 256 MB

    // Kdf derives a 'sz' byte key for use 'usage'
    func Kdf(key, salt []byte, usage string, sz int) []byte {
        u0 := []byte(usage)
        pw := append(key, u0...)

        return argon2.IDKey(pw, salt, _Time, _Mem, runtime.NumCPU(), uint32(sz))
    }

Documentation

Overview

prime.go - Generate safe primes

Copyright 2013-2017 Sudhi Herle <sudhi.herle-at-gmail-dot-com> License: MIT

Package srp implements SRP-6a per [1]. It uses the standard library and the golang extended library and nothing else.

This implementation is accurate as of Aug 2012 edition of the SRP specification [1].

To verify that the client has generated the same key "K", the client sends "M" -- a hash of all the data it has and it received from the server. To validate that the server also has the same value, it requires the server to send its own proof. In the SRP paper [1], the authors use:

M = H(H(N) xor H(g), H(I), s, A, B, K)
M' = H(A, M, K)

We use a simpler construction:

M = H(K, A, B, I, s, N, g)
M' = H(M, K)

In this implementation:

H  = BLAKE2()
k  = H(N, g)
x  = H(s, I, P)
I  = anonymized form of user identity (BLAKE2 of value sent by client)
P  = hashed password (expands short passwords)

Per RFC-5054, we adopt the following padding convention:

k = H(N, pad(g))
u = H(pad(A), pad(B))

References:

[1] http://srp.stanford.edu/design.html
[2] http://srp.stanford.edu/

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MakeSRPVerifier

func MakeSRPVerifier(b string) (*SRP, *Verifier, error)

MakeSRPVerifier decodes the encoded verifier into an SRP environment and Verifier instance. 'b' is an encoded verifier string previously returned by Verifier.Encode(). A caller of this function uses the identity provided by the SRP Client to lookup some DB to find the corresponding encoded verifier string; this encoded data contains enough information to create a valid SRP instance and Verifier instance.

func NewPrimeField

func NewPrimeField(nbits int) (p, g *big.Int, err error)

Make a new prime field (safe prime & generator) that is 'nbits' long Return prime p, generator g

func ServerBegin

func ServerBegin(creds string) (string, *big.Int, error)

ServerBegin processes the first message from an SRP client and returns a decoded identity string and client public key. The caller is expected to use the identity to lookup durable storage and find the corresponding encoded Verifier. This verifier is given to MakeSRPVerifier() to create an instance of SRP and Verifier.

Types

type Client

type Client struct {
	I []byte

	P []byte
	// contains filtered or unexported fields
}

Client represents an SRP client instance

func (*Client) Credentials

func (c *Client) Credentials() string

Credentials returns client public credentials to send to server Send <I, A> to server

func (*Client) Generate

func (c *Client) Generate(srv string) (string, error)

Generate validates the server public credentials and generate session key Return the mutual authenticator. NB: We don't send leak any information in error messages.

func (*Client) RawKey

func (c *Client) RawKey() []byte

RawKey returns the raw key computed as part of the protocol

func (*Client) ServerOk

func (c *Client) ServerOk(proof string) bool

ServerOk takes a 'proof' offered by the server and verifies that it is valid. i.e., we should compute the same hash() on M that the server did.

func (*Client) String

func (c *Client) String() string

String represents the client parameters as a string value

type SRP

type SRP struct {
	// contains filtered or unexported fields
}

SRP represents an environment for the client and server to share certain properties; notably the hash function and prime-field size. The default hash function is Blake2b-256. Any valid hash function as documented in "crypto" can be used. There are two ways for creating an SRP environment:

New()
NewWithHash()

func New

func New(bits int) (*SRP, error)

New creates a new SRP environment using a 'bits' sized prime-field for use by SRP clients and Servers.The default hash function is Blake-2b-256.

func NewWithHash

func NewWithHash(h crypto.Hash, bits int) (*SRP, error)

NewWithHash creates a new SRP environment using the hash function 'h' and 'bits' sized prime-field size.

func (*SRP) FieldSize

func (s *SRP) FieldSize() int

FieldSize returns this instance's prime-field size in bits

func (*SRP) NewClient

func (s *SRP) NewClient(I, p []byte) (*Client, error)

NewClient constructs an SRP client instance.

func (*SRP) NewServer

func (s *SRP) NewServer(v *Verifier, A *big.Int) (*Server, error)

NewServer constructs a Server instance for computing a shared secret.

func (*SRP) Verifier

func (s *SRP) Verifier(I, p []byte) (*Verifier, error)

Verifier generates a password verifier for user I and passphrase p in the environment 's'. It returns an instance of Verifier that holds the parameters needed for a future authentication.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server represents an SRP server instance

func UnmarshalServer

func UnmarshalServer(s string) (*Server, error)

UnmarshalServer parses the encoded string generated by Marshal and returns a populated Server struct with the data if possible, otherwise it returns an error.

func (*Server) ClientOk

func (s *Server) ClientOk(m string) (proof string, ok bool)

ClientOk verifies that the client has generated the same password as the server and return proof that the server too has done the same.

func (*Server) Credentials

func (s *Server) Credentials() string

Credentials returns the server credentials (s,B) in a network portable format.

func (*Server) Marshal

func (s *Server) Marshal() string

Marshal returns a string encoding of the Server. This encoded string can be stored by the server for use later in the SRP process in the case that the client and server can not maintain a session and thus a live copy of the Server struct.

func (*Server) RawKey

func (s *Server) RawKey() []byte

RawKey returns the raw key negotiated as part of the SRP

func (*Server) String

func (s *Server) String() string

String represents the Server parameters as a string value

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier represents password verifier that resides on an SRP server.

func (*Verifier) Encode

func (v *Verifier) Encode() (string, string)

Encode the verifier into a portable format - returns a tuple <Identity, Verifier> as portable strings. The caller can store the Verifier against the Identity in non-volatile storage. An SRP client will supply Identity and its public key - whereupon, an SRP server will use the Identity as a key to lookup the rest of the encoded verifier data.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL