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 ¶
- Constants
- Variables
- func KDFRFC5054(salt []byte, username string, password string) (x *big.Int)
- func NumberFromString(s string) *big.Int
- func PreparePassword(s string) string
- type Group
- type SRP
- func (s *SRP) ClientProof() ([]byte, error)
- func (s *SRP) EphemeralPublic() *big.Int
- func (s *SRP) GoodClientProof(proof []byte) bool
- func (s *SRP) GoodServerProof(salt []byte, uname string, proof []byte) bool
- func (s *SRP) IsPublicValid(AorB *big.Int) bool
- func (s *SRP) Key() ([]byte, error)
- func (s *SRP) M(salt []byte, uname string) ([]byte, error)
- func (s *SRP) SetOthersPublic(AorB *big.Int) error
- func (s *SRP) TestOnlyResetEphemeralPublic()deprecated
- func (s *SRP) TestOnlyResetKey()deprecated
- func (s *SRP) TestOnlySetEphemeralSecret(secret *big.Int)deprecated
- func (s *SRP) Verifier() (*big.Int, error)
- Bugs
Examples ¶
Constants ¶
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 ¶
var KnownGroups = make(map[int]*Group)
KnownGroups is a map from strings to Diffie-Hellman group parameters.
var MinExponentSize = 32
MinExponentSize (in bytes) for generating ephemeral private keys.
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 ¶
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 ¶
NumberFromString converts a string (hex) to a number
func PreparePassword ¶
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.
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 ¶
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 ¶
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 ¶
ClientProof constructs the clients proof that it knows the key
func (*SRP) EphemeralPublic ¶
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 ¶
GoodClientProof returns true if the given proof is the same as what we calculate
func (*SRP) GoodServerProof ¶
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 ¶
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 ¶
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) SetOthersPublic ¶
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
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