srp

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2021 License: Apache-2.0 Imports: 10 Imported by: 0

README

srp – A Go package for Secure Remote Password

srp is a Go language package for Secure Remote Password (SRP). It is an implementation of:

However, the hashing and padding scheme in this package is not interoperable with those specs.

It was developed by AgileBits to support part of the 1Password authentication process. Although there are some hooks and interfaces designed specifically for those purposes, this golang package may be of general use to others.

Get started

To install srp, use go get:

go get github.com/1Password/srp

Although the focus of this implementation is safety and ease of use (as opposed to speed), like all cryptographic tools, some understanding of its operation is required to not shoot yourself in the foot.

Read the package documentation for a discussion of user security responsibilities.

Contribute

Issues are appreciated. Forks leading to pull requests are appreciated even more. 😎

Gosec scans

To run the securego/gosec tool you need to have that installed. Then simply

gosec -fmt=json -out=path/to/where/you/want/results.json ./...

Documentation

Overview

Package srp Secure Remote Password protocol

The principle interface provided by this package is the SRP type. The end aim of the caller is to to have an SRP server and SRP client arrive at the same Key. See the documentation for the SRP structure and its methods for the nitty gritty of use.

BUG(jpg): This does not use the same padding and hashing scheme as in RFC 5054, and therefore is not interoperable with those clients and servers. Perhaps someday we'll add an RFC 5054 mode that does that, but today is not that day.

The SRP protocol

It would be nice if this package could be used without having some understanding of the SRP protocol, but too much of the language and naming is depends on at least some familiarity. Here is a summary.

The Secure Remote Password protocol involves a server and a client proving to each other that they know (or can derive) their long term secrets. The client long term secret is known as "x" and the corresponding server secret, the verifier, is known as "v". The verifier is mathematically related to x and is computed by the client on first enrollment and transmistted to the server.

Typically the server will store the verifier and the client will derive x from a user secret such as a password. Because the verifier can used like a password hash with respect to cracking, the derivation of x should be designed to resist password cracking if the verifier compromised.

The client and the server must both use the same Diffie-Hellman group to perform their computations.

The server and the client each send an ephemeral public key to each other (The client sends A; the server sends B) With their private knowledge of their own ephemeral secrets (a or b) and their private knowledge of x (for the client) and v (for the server) along with public knowledge they are able to prove to each other that they know their respective secrets and can generate a session key, K, which may be used for further encryption during the session.

Quoting from http://srp.stanford.edu/design.html (with some modification for KDF)

    Names and notation
	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; k is a hash of the session ID within 1Password
  	H()  One-way hash function
  	^    (Modular) Exponentiation
  	u    Random scrambling parameter
  	a,b  Secret ephemeral values
  	A,B  Public ephemeral values
  	x    Long term client secret (derived via KDF)
	v    Long term server Verifier (derived from x)
	s    Salt for key derivation function
	I    User identifiers (username, account ID, etc)
	KDF()    Key Derivation Function

    The authentication protocol itself goes as follows

	User -> Host:  I, A = g^a                  (identifies self, a = random number)
	Host -> User:  s, B = kv + g^b             (sends salt, b = random number)

	Both:  u = H(A, B)

	User:  x = KDF(s, ...)             (user derives x)
	User:  S = (B - kg^x) ^ (a + ux)   (computes raw session key)
	User:  K = H(S)                    (computes session key)

	Host:  S = (Av^u) ^ b              (computes raw session key)
	Host:  K = H(S)                    (computes session key)

    Now the two parties have a shared, strong session key K.
    To complete authentication, they need to prove to each other that their keys match.

This package does not address the actual communication between client and server. But through the SRP type it not only performs the calculations needed, it also performs safety and sanity checks on its input, and it hides everything from the caller except what the caller absolutely needs to provide.

The key derivation function, KDF()

x is computed by client via KDF, user secrets, and random salt, s.

x = KDF(...)
v = g^x

v is sent to the server on first enrollment. v should be transmitted over a secure channel.
The server then stores {I, s, v} long term. v needs to be protected in the same way that
a password hash should be protected.

User's security responsibilities

The consumer is responsible for

1. Both client and server: Checking whether methods have returned without error. This is particularly true of SRP.Key() and SetOthersPublic()

2. Client: Using an appropriate key derivation function for deriving x from the user's password (and nudging user toward a good password)

3. Server: Storing the v (send by the client on first enrollment) securely. A captured v can be used to masquerade as the server and be used like a password hash in a password cracking attempt

4. Both: Proving to each other that both have the same key. The package includes methods that can assist with that.

Example

ExampleServerClientKey is an example

// This example has both a server and the corresponding client live
// in the same function. That is not something you would normally do.
// Normally, you would be running one side (client or server) only.
// If I understand channels better, I could probably set up a more
// realistic example.

var err error
var A, B *big.Int

/*** Part 1: First encounter. Enrollment ***/

// On first encounter between client and server, they will negotiate
// an SRP group to use. We will assume that they have settled on
// RFC5054Group3072

group := RFC5054Group3072

// The client will need a password from the user and will also need
// a salt.

pw := "Fido1961!" // It's the "!" that makes this password super secure

// Generate 8 bytes of random salt. Be sure to use crypto/rand for all
// of your random number needs
salt := make([]byte, 8)
rand.Read(salt)

username := "fred@fred.example"

// You would use a better Key Derivation Function than this one
x := KDFRFC5054(salt, username, pw) // Really. Don't use this KDF

// this is still our first use scenario, but the client needs to create
// an SRP client to generate the verifier.
firstClient := NewSRPClient(KnownGroups[group], x, nil)
if firstClient == nil {
	fmt.Println("couldn't setup client")
}
v, err := firstClient.Verifier()
if err != nil {
	fmt.Println(err)
}

// Now the client has all it needs to enroll with the server.
// Client sends salt, username, and v to the server

// Server will store long term the salt, username, an identifier for the SRP group
// used and v. It should store v securely.

/*** Part 2: An authentication session ***/

// Some time later, we actually want to authenticate with this stuff
// Client and server may talk. Depending on what the client has locally,
// The client may need to be told it's salt, and the SRP group to use
// But here we will assume that that the client knows this, and already has
// computed x.

client := NewSRPClient(KnownGroups[group], x, nil)

// The client will need to send its ephemeral public key to the server
// so we fetch that now.
A = client.EphemeralPublic()

// Now it is time for some stuff (though not much) on the server.
server := NewSRPServer(KnownGroups[group], v, nil)
if server == nil {
	fmt.Println("Couldn't set up server")
}

// The server will get A (clients ephemeral public key) from the client
// which the server will set using SetOthersPublic

// Server MUST check error status here as defense against
// a malicious A sent by client.
if err = server.SetOthersPublic(A); err != nil {
	fmt.Println(err)
	log.Fatal(err)
}

// server sends its ephemeral public key, B, to client
// client sets it as others public key.
if B = server.EphemeralPublic(); B == nil {
	fmt.Println("server couldn't make B")
}

// server can now make the key.
serverKey, err := server.Key()
if err != nil || serverKey == nil {
	fmt.Printf("something went wrong making server key: %s\n", err)
}

// Once the client receives B from the server it can set it.
// Client should check error status here as defense against
// a malicious B sent from server
if err = client.SetOthersPublic(B); err != nil {
	fmt.Println(err)
	log.Fatal(err)
}

// client can now make the session key
clientKey, err := client.Key()
if err != nil || clientKey == nil {
	fmt.Printf("something went wrong making server key: %s", err)
}

/*** Part 3: Server and client prove they have the same key ***/

// Server computes a proof, and sends it to the client

serverProof, err := server.M(salt, username)
if err != nil {
	fmt.Println(err)
}

// client tests tests that the server sent a good proof
if !client.GoodServerProof(salt, username, serverProof) {
	err = fmt.Errorf("bad proof from server")
	fmt.Println(err)
	// Client must bail and not send a its own proof back to the server
	log.Fatal(err)
}

// Only after having a valid server proof will the client construct its own
clientProof, err := client.ClientProof()
if err != nil {
	fmt.Println(err)
	log.Fatal(err)
}

// client sends its proof to the server. Server checks
if !server.GoodClientProof(clientProof) {
	err = fmt.Errorf("bad proof from client")
	fmt.Println(err)
	log.Fatal(err)
}

/*** Part 4: Server and Client exchange secret messages ***/

// Once you have confirmed that client and server are using the same key
// (thus proving that x and v have the right relation to each other)
// we can use that key to encrypt stuff.

// Let's have it be a missive from the server to the client

// server sets up a block cipher with the key
serverBlock, _ := aes.NewCipher(serverKey) // set with server's key
serverCryptor, _ := cipher.NewGCM(serverBlock)

// The client can set up its own cryptor. Note that it uses
// the key that it (the client) got from SRP
clientBlock, _ := aes.NewCipher(clientKey) // with the Client's key
clientCryptor, _ := cipher.NewGCM(clientBlock)

// We will use GCM with a 12 byte nonce for this example
// NEVER use the same nonce twice with the same key. Never.
nonce := make([]byte, 12)
rand.Read(nonce)

plaintext := []byte("Hi client! Will you be my Valentine?")
ciphertext := serverCryptor.Seal(nil, nonce, plaintext, nil)
// You can use the same serverCryptor many times within a session,
// (up to about 2^32) for different encryptions
// but you MUST use a new nonce for each encryption.

// Server sends the the ciphertext and the nonce to the client

message, err := clientCryptor.Open(nil, nonce, ciphertext, nil)
if err != nil {
	fmt.Printf("Decryption failed: %s", err)
	log.Fatal(err)
	// if decryption fails, do not trust anything about ciphertext
}

// If the message is successfully decrypted, then client and server
// can talk to each other using the key they derived
fmt.Printf("S -> C: %s\n", message)
// Output: Hi client! Will you be my Valentine?

// Client must generate a new nonce for all messages it sends.
// It MUST NOT reuse the nonce that was used in the message
// it received when encrypting a different message.

rand.Read(nonce) // get a fresh nonce
reply := []byte("Send me chocolate, not bits!")

replyCipherText := clientCryptor.Seal(nil, nonce, reply, nil)
// Note that this is a new nonce.

// client sends the new nonce and the reply to the server.

plainReply, err := serverCryptor.Open(nil, nonce, replyCipherText, nil)
if err != nil {
	fmt.Printf("Decryption failed: %s", err)
	log.Fatal(err)
}
fmt.Printf("C -> S: %s\n", plainReply)
Output:

S -> C: Hi client! Will you be my Valentine?
C -> S: Send me chocolate, not bits!

Index

Examples

Constants

View Source
const (
	// The values correspond to the numbering in Appendix A of RFC 5054
	// so not using iota mechanism for numbering here.
	RFC5054Group1024 = 1 // We won't allow this group
	RFC5054Group1536 = 2 // We aren't going to allow this one either
	RFC5054Group2048 = 3
	RFC5054Group3072 = 4
	RFC5054Group4096 = 5
	RFC5054Group6144 = 6
	RFC5054Group8192 = 7
)

RFC 5054 groups are listed by their numbers in Appendix A of the RFC

Variables

View Source
var KnownGroups = make(map[int]*Group)

KnownGroups is a map from strings to Diffie-Hellman group parameters.

View Source
var MinExponentSize = 32

MinExponentSize (in bytes) for generating ephemeral private keys.

View Source
var MinGroupSize = 2048

MinGroupSize (in bits) sets a lower bound on the size of DH groups that will pass certain internal checks. Defaults to 2048

Functions

func KDFRFC5054

func KDFRFC5054(salt []byte, username string, password string) (x *big.Int)

KDFRFC5054 is *not* recommended. Instead use a key derivation function (KDF) that involves a hashing scheme designed for password hashing. The SRP verifier that is stored by the server is like a password hash with respect to crackability. Choose a KDF that that makes the server stored verifiers hard to crack.

This computes the client's long term secret, x from a username, password, and salt as described in RFC 5054 §2.6, which says

x = SHA1(s | SHA1(I | ":" | P))

func NumberFromString

func NumberFromString(s string) *big.Int

NumberFromString converts a string (hex) to a number

func PreparePassword

func PreparePassword(s string) string

PreparePassword strips leading and trailing white space and normalizes to unicode NFKD

Types

type Group

type Group struct {
	Label        string
	ExponentSize int // RFC 3526 §8
	// contains filtered or unexported fields
}

Group is a Diffie-Hellman group and has an unexported generator and modulus. It has a Label or name that the group can call itself. Recommended ExponentSize (in bytes) is based on the lower estimates given in section 8 of RFC 3526 for the ephemeral random exponents.

func NewGroup

func NewGroup() *Group

NewGroup creates and initializes a an SRP group

func (*Group) Generator

func (g *Group) Generator() *big.Int

Generator returns little g, the generator for the group as a big int

func (*Group) N

func (g *Group) N() *big.Int

N returns the modulus of the the group

type SRP

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

SRP provides the primary interface to this package.

Your goal is for both your client and server to arrive at the same session key, SRP.Key(), while proving to each other that they each know their long term secrets (x is the client's secret and v is the server's secret). Although the key that you arrive at is 32 bytes, its strength is a function of the group size used.

Creating the SRP object with with NewSRPServer()/NewSRPClient() takes care of generating your ephemeral secret (a or b depending on whether you are a client or server), your public ephemeral key (A or B depending on whether you are a client or server), the multiplier k. (There is a setter for k if you wish to use a different scheme to set those.

A typical use by a server might be something like

server := NewSRPServer(KnownGroups[RFC5054Group4096], v, nil)

A := getAfromYourClientConnection(...) // your code
if result, err := server.SetOthersPublic(A); result == nil || err != nil {
	// client sent a malicious A. Kill this session now
}

sendBtoClientSomehow(server.EphemeralPublic())

if sessionKey, err := server.Key(); sessionKey == nil || err != nil {
	// something went wrong
}

// You must still prove that both server and client created the same Key.

This still leaves some work outside of what the SRP object provides:

1. The key derivation of x is not handled by this object.

2. The communication between client and server is not handled by this object.

func NewSRPClient

func NewSRPClient(group *Group, x *big.Int, k *big.Int) *SRP

NewSRPClient sets up an SRP object for a client.

group *Group: Pointer to the Diffie-Hellman group to be used.

x *big.Int: Your long term secret, x.

k *big.Int: If you wish to manually set the multiplier, little k, pass in a non-nil bigInt. If you set this to nil, then we will generate one for you. You need the same k on both server and client.

func NewSRPServer

func NewSRPServer(group *Group, v *big.Int, k *big.Int) *SRP

NewSRPServer sets up an SRP object for a server.

group *Group: Pointer to the Diffie-Hellman group to be used.

v *big.Int: Your long term secret, v.

k *big.Int: If you wish to manually set the multiplier, little k, pass in a non-nil bigInt. If you set this to nil, then we will generate one for you. You need the same k on both server and client.

func (*SRP) ClientProof

func (s *SRP) ClientProof() ([]byte, error)

ClientProof constructs the clients proof that it knows the key

func (*SRP) EphemeralPublic

func (s *SRP) EphemeralPublic() *big.Int

EphemeralPublic returns A on client or B on server If you are a client, you will need to send A to the server. If you are a server, you will need to send B to the client. But this abstracts away from user needing to keep A and B straight. Caller just needs to send EphemeralPublic() to the other party.

func (*SRP) GoodClientProof

func (s *SRP) GoodClientProof(proof []byte) bool

GoodClientProof returns true if the given proof is the same as what we calculate

func (*SRP) GoodServerProof

func (s *SRP) GoodServerProof(salt []byte, uname string, proof []byte) bool

GoodServerProof takes the post-key negotiation proof from the server and compares it with what we (the client think it should be)

func (*SRP) IsPublicValid

func (s *SRP) IsPublicValid(AorB *big.Int) bool

IsPublicValid checks to see whether public A or B is valid within the group A client can do very bad things by sending a malicious A to the server. The server can do mildly bad things by sending a malicious B to the client. This method is public in case the user wishes to check those values earlier than than using SetOthersPublic(), which also performs this check.

func (*SRP) Key

func (s *SRP) Key() ([]byte, error)

Key creates and returns the session Key

Caller MUST check error status.

Once the ephemeral public key is received from the other party and properly set, SRP should have enough information to compute the session key.

If and only if, each party knowns their respective long term secret (x for client, v for server) will both parties compute the same Key. Be sure to confirm that client and server have the same key before using it.

Note that although the resulting key is 256 bits, its effective strength is (typically) far less and depends on the group used. 8 * (SRP.Group.ExponentSize / 2) should provide a reasonable estimate if you need that.

func (*SRP) M

func (s *SRP) M(salt []byte, uname string) ([]byte, error)

M returns the server's proof of knowledge of key

func (*SRP) SetOthersPublic

func (s *SRP) SetOthersPublic(AorB *big.Int) error

SetOthersPublic sets A if server and B if client Caller *MUST* check for error status and abort the session on error. This setter will invoke IsPublicValid() and error status must be heeded, as other party may attempt to send a malicious ephemeral public key (A or B).

When used by the server, this sets A, when used by the client it sets B. But caller doesn't need to worry about whether this is A or B. Instead the caller just needs to know that they are setting the public ephemeral key received from the other party.

func (*SRP) TestOnlyResetEphemeralPublic deprecated

func (s *SRP) TestOnlyResetEphemeralPublic()

TestOnlyResetEphemeralPublic should only be used when constructing tests of SRP integration with the consumer.

Deprecated: This is for testing only. It is not meant to be used in real code, and may disappear at any moment.

func (*SRP) TestOnlyResetKey deprecated

func (s *SRP) TestOnlyResetKey()

TestOnlyResetKey sets to final key back to nil. This is used only for testing integration with caller

Deprecated: This is only used for testing integration with caller. Never if real life

func (*SRP) TestOnlySetEphemeralSecret deprecated

func (s *SRP) TestOnlySetEphemeralSecret(secret *big.Int)

TestOnlySetEphemeralSecret should only be used when constructing tests of SRP integration with the consumer.

Deprecated: This is for testing only. It is not meant to be used in real code, and may disappear at any moment.

func (*SRP) Verifier

func (s *SRP) Verifier() (*big.Int, error)

Verifier retruns the verifier as calculated by the client. On first enrollment, the client will need to send the verifier to the server, which the server will store as its long term secret. Only a client can compute the verifier as it requires knowledge of x.

Notes

Bugs

  • Creation of multiplier, little k, does _not_ conform to RFC 5054 padding

  • Calculation of u does not use RFC 5054 compatable padding/hashing The scheme we use (see source) is to use sha512 of the concatenation of A and B each represented as a lowercase hexadecimal string. Additionally those hex strings have leading "0" removed even if that makes them of odd length

Jump to

Keyboard shortcuts

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