├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── client.go ├── params.go ├── server.go ├── srp.go ├── srp_test.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Vendor 11 | vendor 12 | 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | 23 | _testmain.go 24 | 25 | *.exe 26 | *.test 27 | *.prof 28 | 29 | # IDEs and environments 30 | .idea 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5 5 | - master -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Insomnia REST Client 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-srp 2 | [![](https://api.travis-ci.org/kong/go-srp.svg)](https://travis-ci.org/kong/go-srp) 3 | [![](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/kong/go-srp) 4 | 5 | _NOTE: This is a port of [node-srp](https://github.com/mozilla/node-srp) to Go. I recommend 6 | reading their README for general information about the use of SRP._ 7 | 8 | ## Installation 9 | 10 | ``` 11 | go get github.com/kong/go-srp 12 | ``` 13 | 14 | ## Usage 15 | 16 | View [GoDoc](https://godoc.org/github.com/kong/go-srp) for full details 17 | 18 | To use SRP, first decide on they parameters you will use. Both client and server must 19 | use the same set. 20 | 21 | ```go 22 | params := srp.GetParams(2048) 23 | ``` 24 | 25 | ### Account Creation 26 | 27 | To create a new account, generate a verifier from the client, and store it 28 | on the server. 29 | 30 | ```go 31 | verifier := srp.ComputeVerifier(params, salt, identity, password) 32 | ``` 33 | 34 | ### Login 35 | 36 | From the client... generate a new secret key, initialize the client, and compute A. 37 | Once you have A, you can send A to the server. 38 | 39 | ```go 40 | secret1 := srp.GenKey() 41 | client := NewClient(params, salt, identity, secret, a) 42 | srpA := client.computeA() 43 | 44 | sendToServer(srpA) 45 | ``` 46 | 47 | From the server... generate another secret key, initialize the server, and compute B. 48 | Once you have B, you can send B to the client. 49 | 50 | ```go 51 | secret2 := srp.GenKey() 52 | server := NewServer(params, verifier, secret2) 53 | srpB := client.computeB() 54 | 55 | sendToClient(srpB) 56 | ``` 57 | 58 | Once the client received B from the server, it can compute M1 based on A and B. 59 | Once you have M1, send M1 to the server. 60 | 61 | ```go 62 | client.setB(srpB) 63 | srpM1 := client.ComputeM1() 64 | sendM1ToServer(srpM1) 65 | ``` 66 | 67 | Once the server receives M1, it can verify that it is correct. If checkM1() returns 68 | an error, authentication failed. If it succeeds it should be sent to the client. 69 | 70 | ```go 71 | srpM2, err := server.checkM1(srpM1) 72 | ``` 73 | 74 | Once the client receives M2, it can verify that it is correct, and know that authentication 75 | was successful. 76 | 77 | ```go 78 | err = client.CheckM2(serverM2) 79 | ```` 80 | 81 | Now that both client and server have completed a successful authentication, they can 82 | both compute K independently. K can now be used as either a key to encrypt communication 83 | or as a session ID. 84 | 85 | ```go 86 | clientK := client.ComputeK() 87 | serverK := server.ComputeK() 88 | ``` 89 | 90 | ## Running Tests 91 | 92 | ``` 93 | go test 94 | ``` 95 | 96 | _Tests include vectors from 97 | [RFC 5054, Appendix B.](https://tools.ietf.org/html/rfc5054#appendix-B)_ 98 | 99 | 100 | ## Licence 101 | 102 | MIT 103 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package srp 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "math/big" 7 | ) 8 | 9 | type SRPClient struct { 10 | Params *SRPParams 11 | Secret1 *big.Int 12 | Multiplier *big.Int 13 | A *big.Int 14 | X *big.Int 15 | M1 []byte 16 | M2 []byte 17 | K []byte 18 | u *big.Int 19 | s *big.Int 20 | } 21 | 22 | func NewClient(params *SRPParams, salt, identity, password, secret1 []byte) *SRPClient { 23 | multiplier := getMultiplier(params) 24 | secret1Int := intFromBytes(secret1) 25 | Ab := getA(params, secret1Int) 26 | A := intFromBytes(Ab) 27 | x := getx(params, salt, identity, password) 28 | 29 | return &SRPClient{ 30 | Params: params, 31 | Multiplier: multiplier, 32 | Secret1: secret1Int, 33 | A: A, 34 | X: x, 35 | } 36 | } 37 | 38 | func (c *SRPClient) ComputeA() []byte { 39 | return intToBytes(c.A) 40 | } 41 | 42 | // ComputeVerifier returns a verifier that is calculated as described in 43 | // Section 3 of [SRP-RFC] 44 | func ComputeVerifier(params *SRPParams, salt, identity, password []byte) []byte { 45 | x := getx(params, salt, identity, password) 46 | vNum := new(big.Int) 47 | vNum.Exp(params.G, x, params.N) 48 | 49 | return padToN(vNum, params) 50 | } 51 | 52 | func (c *SRPClient) SetB(Bb []byte) { 53 | B := intFromBytes(Bb) 54 | u := getu(c.Params, c.A, B) 55 | S := clientGetS(c.Params, c.Multiplier, c.X, c.Secret1, B, u) 56 | 57 | c.K = getK(c.Params, S) 58 | c.M1 = getM1(c.Params, intToBytes(c.A), Bb, S) 59 | c.M2 = getM2(c.Params, intToBytes(c.A), c.M1, c.K) 60 | 61 | c.u = u // Only for tests 62 | c.s = intFromBytes(S) // Only for tests 63 | } 64 | 65 | func (c *SRPClient) ComputeM1() []byte { 66 | if c.M1 == nil { 67 | panic("Incomplete protocol") 68 | } 69 | 70 | return c.M1 71 | } 72 | 73 | func (c *SRPClient) ComputeK() []byte { 74 | return c.K 75 | } 76 | 77 | func (c *SRPClient) CheckM2(M2 []byte) error { 78 | if !bytes.Equal(c.M2, M2) { 79 | return errors.New("M2 didn't check") 80 | } else { 81 | return nil 82 | } 83 | } 84 | 85 | func getA(params *SRPParams, a *big.Int) []byte { 86 | ANum := new(big.Int) 87 | ANum.Exp(params.G, a, params.N) 88 | return padToN(ANum, params) 89 | } 90 | 91 | func clientGetS(params *SRPParams, k, x, a, B, u *big.Int) []byte { 92 | BLessThan0 := B.Cmp(big.NewInt(0)) <= 0 93 | NLessThanB := params.N.Cmp(B) <= 0 94 | if BLessThan0 || NLessThanB { 95 | panic("invalid server-supplied 'B', must be 1..N-1") 96 | } 97 | 98 | result1 := new(big.Int) 99 | result1.Exp(params.G, x, params.N) 100 | 101 | result2 := new(big.Int) 102 | result2.Mul(k, result1) 103 | 104 | result3 := new(big.Int) 105 | result3.Sub(B, result2) 106 | 107 | result4 := new(big.Int) 108 | result4.Mul(u, x) 109 | 110 | result5 := new(big.Int) 111 | result5.Add(a, result4) 112 | 113 | result6 := new(big.Int) 114 | result6.Exp(result3, result5, params.N) 115 | 116 | result7 := new(big.Int) 117 | result7.Mod(result6, params.N) 118 | 119 | return padToN(result7, params) 120 | } 121 | 122 | func getx(params *SRPParams, salt, I, P []byte) *big.Int { 123 | var ipBytes []byte 124 | ipBytes = append(ipBytes, I...) 125 | ipBytes = append(ipBytes, []byte(":")...) 126 | ipBytes = append(ipBytes, P...) 127 | 128 | hashIP := params.Hash.New() 129 | hashIP.Write(ipBytes) 130 | 131 | hashX := params.Hash.New() 132 | hashX.Write(salt) 133 | hashX.Write(hashToBytes(hashIP)) 134 | 135 | return hashToInt(hashX) 136 | } 137 | -------------------------------------------------------------------------------- /params.go: -------------------------------------------------------------------------------- 1 | package srp 2 | 3 | import ( 4 | "crypto" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | // Map of bits to tuple 10 | type SRPParams struct { 11 | G *big.Int 12 | N *big.Int 13 | Hash crypto.Hash 14 | NLengthBits int 15 | } 16 | 17 | var knownGroups map[int]*SRPParams 18 | 19 | func createParams(G int64, nBitLength int, hash crypto.Hash, NHex string) *SRPParams { 20 | p := SRPParams{ 21 | G: big.NewInt(G), 22 | N: new(big.Int), 23 | NLengthBits: nBitLength, 24 | Hash: hash, 25 | } 26 | 27 | b := bytesFromHexString(NHex) 28 | p.N.SetBytes(b) 29 | return &p 30 | } 31 | 32 | func GetParams(G int) *SRPParams { 33 | params := knownGroups[G] 34 | if params == nil { 35 | panic(fmt.Sprintf("Params don't exist for %v", G)) 36 | } else { 37 | return params 38 | } 39 | } 40 | 41 | func init() { 42 | knownGroups = make(map[int]*SRPParams) 43 | 44 | knownGroups[1024] = createParams(2, 1024, crypto.SHA1, ` 45 | EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B 9EA2314C 46 | 9C256576 D674DF74 96EA81D3 383B4813 D692C6E0 E0D5D8E2 50B98BE4 47 | 8E495C1D 6089DAD1 5DC7D7B4 6154D6B6 CE8EF4AD 69B15D49 82559B29 48 | 7BCF1885 C529F566 660E57EC 68EDBC3C 05726CC0 2FD4CBF4 976EAA9A 49 | FD5138FE 8376435B 9FC61D2F C0EB06E3`) 50 | 51 | knownGroups[1536] = createParams(2, 1536, crypto.SHA1, ` 52 | 9DEF3CAF B939277A B1F12A86 17A47BBB DBA51DF4 99AC4C80 BEEEA961 53 | 4B19CC4D 5F4F5F55 6E27CBDE 51C6A94B E4607A29 1558903B A0D0F843 54 | 80B655BB 9A22E8DC DF028A7C EC67F0D0 8134B1C8 B9798914 9B609E0B 55 | E3BAB63D 47548381 DBC5B1FC 764E3F4B 53DD9DA1 158BFD3E 2B9C8CF5 56 | 6EDF0195 39349627 DB2FD53D 24B7C486 65772E43 7D6C7F8C E442734A 57 | F7CCB7AE 837C264A E3A9BEB8 7F8A2FE9 B8B5292E 5A021FFF 5E91479E 58 | 8CE7A28C 2442C6F3 15180F93 499A234D CF76E3FE D135F9BB 59 | `) 60 | 61 | knownGroups[2048] = createParams(2, 2048, crypto.SHA256, ` 62 | AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294 63 | 3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D 64 | CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB 65 | D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74 66 | 7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A 67 | 436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D 68 | 5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73 69 | 03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6 70 | 94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F 71 | 9E4AFF73 72 | `) 73 | 74 | knownGroups[4096] = createParams(5, 4096, crypto.SHA256, ` 75 | FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 76 | 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 77 | 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 78 | A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 79 | 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 80 | FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D 81 | 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C 82 | 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718 83 | 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D 84 | 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D 85 | B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 86 | 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C 87 | BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC 88 | E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26 89 | 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB 90 | 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2 91 | 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 92 | D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199 93 | FFFFFFFF FFFFFFFF 94 | `) 95 | } 96 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package srp 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "math/big" 7 | ) 8 | 9 | type SRPServer struct { 10 | Params *SRPParams 11 | Verifier *big.Int 12 | Secret2 *big.Int 13 | B *big.Int 14 | M1 []byte 15 | M2 []byte 16 | K []byte 17 | u *big.Int 18 | s *big.Int 19 | } 20 | 21 | func NewServer(params *SRPParams, Vb []byte, S2b []byte) *SRPServer { 22 | multiplier := getMultiplier(params) 23 | V := intFromBytes(Vb) 24 | secret2 := intFromBytes(S2b) 25 | 26 | Bb := getB(params, multiplier, V, secret2) 27 | B := intFromBytes(Bb) 28 | 29 | return &SRPServer{ 30 | Params: params, 31 | Secret2: secret2, 32 | Verifier: V, 33 | B: B, 34 | } 35 | } 36 | 37 | func (s *SRPServer) ComputeB() []byte { 38 | return intToBytes(s.B) 39 | } 40 | 41 | func (s *SRPServer) SetA(A []byte) { 42 | AInt := intFromBytes(A) 43 | U := getu(s.Params, AInt, s.B) 44 | S := serverGetS(s.Params, s.Verifier, AInt, s.Secret2, U) 45 | 46 | s.K = getK(s.Params, S) 47 | s.M1 = getM1(s.Params, A, intToBytes(s.B), S) 48 | s.M2 = getM2(s.Params, A, s.M1, s.K) 49 | 50 | s.u = U // only for tests 51 | s.s = intFromBytes(S) // only for tests 52 | } 53 | 54 | func (s *SRPServer) CheckM1(M1 []byte) ([]byte, error) { 55 | if !bytes.Equal(s.M1, M1) { 56 | return nil, errors.New("Client did not use the same password") 57 | } else { 58 | return s.M2, nil 59 | } 60 | } 61 | 62 | func (s *SRPServer) ComputeK() []byte { 63 | return s.K 64 | } 65 | 66 | // Helpers 67 | 68 | func serverGetS(params *SRPParams, V, A, S2, U *big.Int) []byte { 69 | ALessThan0 := A.Cmp(big.NewInt(0)) <= 0 70 | NLessThanA := params.N.Cmp(A) <= 0 71 | if ALessThan0 || NLessThanA { 72 | panic("invalid client-supplied 'A', must be 1..N-1") 73 | } 74 | 75 | result1 := new(big.Int) 76 | result1.Exp(V, U, params.N) 77 | 78 | result2 := new(big.Int) 79 | result2.Mul(A, result1) 80 | 81 | result3 := new(big.Int) 82 | result3.Exp(result2, S2, params.N) 83 | 84 | result4 := new(big.Int) 85 | result4.Mod(result3, params.N) 86 | 87 | return padToN(result4, params) 88 | } 89 | 90 | func getB(params *SRPParams, multiplier, V, b *big.Int) []byte { 91 | gModPowB := new(big.Int) 92 | gModPowB.Exp(params.G, b, params.N) 93 | 94 | kMulV := new(big.Int) 95 | kMulV.Mul(multiplier, V) 96 | 97 | leftSide := new(big.Int) 98 | leftSide.Add(kMulV, gModPowB) 99 | 100 | final := new(big.Int) 101 | final.Mod(leftSide, params.N) 102 | 103 | return padToN(final, params) 104 | } 105 | -------------------------------------------------------------------------------- /srp.go: -------------------------------------------------------------------------------- 1 | // Package srp is port of node-srp to Go. 2 | // 3 | // 4 | // To use SRP, first decide on they parameters you will use. Both client and server must 5 | // use the same set. 6 | // params := srp.GetParams(4096) 7 | // 8 | // From the client... generate a new secret key, initialize the client, and compute A. 9 | // Once you have A, you can send A to the server. 10 | // secret1 := srp.GenKey() 11 | // client := NewClient(params, salt, identity, secret, a) 12 | // srpA := client.computeA() 13 | // 14 | // sendToServer(srpA) 15 | // 16 | // From the server... generate another secret key, initialize the server, and compute B. 17 | // Once you have B, you can send B to the client. 18 | // secret2 := srp.GenKey() 19 | // server := NewServer(params, verifier, secret2) 20 | // srpB := client.computeB() 21 | // 22 | // sendToClient(srpB) 23 | // 24 | // Once the client received B from the server, it can compute M1 based on A and B. 25 | // Once you have M1, send M1 to the server. 26 | // client.setB(srpB) 27 | // srpM1 := client.ComputeM1() 28 | // sendM1ToServer(srpM1) 29 | // 30 | // Once the server receives M1, it can verify that it is correct. If checkM1() returns 31 | // an error, authentication failed. If it succeeds it should be sent to the client. 32 | // srpM2, err := server.checkM1(srpM1) 33 | // 34 | // Once the client receives M2, it can verify that it is correct, and know that authentication 35 | // was successful. 36 | // err = client.CheckM2(serverM2) 37 | // 38 | // Now that both client and server have completed a successful authentication, they can 39 | // both compute K independently. K can now be used as either a key to encrypt communication 40 | // or as a session ID. 41 | // clientK := client.ComputeK() 42 | // serverK := server.ComputeK() 43 | // 44 | package srp 45 | 46 | import ( 47 | "crypto/rand" 48 | "io" 49 | "math/big" 50 | ) 51 | 52 | func GenKey() []byte { 53 | bytes := make([]byte, 32) 54 | _, err := io.ReadFull(rand.Reader, bytes) 55 | if err != nil { 56 | panic("Random source is broken!") 57 | } 58 | 59 | return bytes 60 | } 61 | 62 | func getK(params *SRPParams, S []byte) []byte { 63 | hashK := params.Hash.New() 64 | hashK.Write(S) 65 | return hashToBytes(hashK) 66 | } 67 | 68 | func getu(params *SRPParams, A, B *big.Int) *big.Int { 69 | hashU := params.Hash.New() 70 | hashU.Write(A.Bytes()) 71 | hashU.Write(B.Bytes()) 72 | 73 | return hashToInt(hashU) 74 | } 75 | 76 | func getM1(params *SRPParams, A, B, S []byte) []byte { 77 | hashM1 := params.Hash.New() 78 | hashM1.Write(A) 79 | hashM1.Write(B) 80 | hashM1.Write(S) 81 | return hashToBytes(hashM1) 82 | } 83 | 84 | func getM2(params *SRPParams, A, M, K []byte) []byte { 85 | hashM1 := params.Hash.New() 86 | hashM1.Write(A) 87 | hashM1.Write(M) 88 | hashM1.Write(K) 89 | return hashToBytes(hashM1) 90 | } 91 | 92 | func getMultiplier(params *SRPParams) *big.Int { 93 | hashK := params.Hash.New() 94 | hashK.Write(padToN(params.N, params)) 95 | hashK.Write(padToN(params.G, params)) 96 | 97 | return hashToInt(hashK) 98 | } 99 | -------------------------------------------------------------------------------- /srp_test.go: -------------------------------------------------------------------------------- 1 | package srp 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "math/big" 6 | "testing" 7 | ) 8 | 9 | var salt = []byte("salty") 10 | var identity = []byte("alice") 11 | var password = []byte("password123") 12 | 13 | func getAAndB() ([]byte, []byte) { 14 | a := GenKey() 15 | b := GenKey() 16 | return a, b 17 | } 18 | 19 | func getVerifier() []byte { 20 | return bytesFromHexString(` 21 | F0E47F50 F5DEAD8D B8D93A27 9E3B62D6 FF50854B 31FBD347 4A886BEF 22 | 91626171 7E84DD4F B8B4D27F EAA5146D B7B1CBBC 274FDF96 A132B502 23 | 9C2CD725 27427A9B 9809D5A4 D0182529 28B4FC34 3BC17CE6 3C1859D5 24 | 806F5466 014FC361 002D8890 AEB4D631 6FF37331 FC2761BE 0144C91C 25 | DD8E00ED 0138C0CE 51534D1B 9A9BA629 D7BE34D2 742DD409 7DAABC9E 26 | CB7AAAD8 9E53C342 B038F1D2 ADAE1F24 10B7884A 3E9A124C 357E421B 27 | CCD45244 67E19226 60E0A446 0C5F7C38 C0877B65 F6E32F28 296282A9 28 | 3FC11BBA BB7BB69B F1B3F939 1991D8A8 6DD05E15 000B7E38 BA38A536 29 | BB0BF59C 808EC25E 791B8944 719488B8 087DF8BF D7FF2082 2997A53F 30 | 6C86F3D4 5D004476 D6303301 376BB25A 9F94B552 CCE5ED40 DE5DD7DA 31 | 8027D754 FA5F6673 8C7E3FC4 EF3E20D6 25DF62CB E6E7ADFC 21E47880 32 | D8A6ADA3 7E60370F D4D8FC82 672A90C2 9F2E72F3 5652649D 68348DE6 33 | F36D0E43 5C8BD42D D00155D3 5D501BEC C0661B43 E04CDB2D A84CE92B 34 | 8BF49935 D73D75EF CBD1176D 7BBCCC3C C4D4B5FE FCC02D47 8614EE16 35 | 81D2FF3C 711A61A7 686EB852 AE06FB82 27BE21FB 8802719B 1271BA1C 36 | 02B13BBF 0A2C2E45 9D9BEDCC 8D1269F6 A785CB45 63AA791B 38FB0382 37 | 69F63F58 F47E9051 49954978 9269CC7B 8EC7026F C34BA732 89C4AF82 38 | 9D5A532E 723967CE 9B6C023E F0FD0CFE 37F51F10 F19463B6 534159A0 39 | 9DDD2F51 F3B30033 40 | `) 41 | } 42 | 43 | func TestCreateVerifier(t *testing.T) { 44 | verifier := ComputeVerifier(GetParams(4096), salt, identity, password) 45 | expected := getVerifier() 46 | assert.Equal(t, expected, verifier, "Verifier did not match") 47 | } 48 | 49 | func TestUseAAndB(t *testing.T) { 50 | params := GetParams(4096) 51 | a, b := getAAndB() 52 | 53 | // Create client 54 | client := NewClient(params, salt, identity, password, a) 55 | 56 | // Client produces A 57 | A := client.ComputeA() 58 | 59 | // Create server 60 | server := NewServer(params, getVerifier(), b) 61 | 62 | // Server produces B 63 | B := server.ComputeB() 64 | 65 | // Server accepts A 66 | server.SetA(A) 67 | 68 | // Client doesn't produce M1 too early 69 | assert.Panics(t, func() { 70 | client.ComputeM1() 71 | }, "Client should have paniced") 72 | 73 | // Client accepts B 74 | client.SetB(B) 75 | 76 | // Client produces M1 now 77 | M1 := client.ComputeM1() 78 | 79 | // Server likes client's M1 80 | serverM2, err := server.CheckM1(M1) 81 | assert.NoError(t, err, "Server should have liked M1") 82 | 83 | // Client and server agree on K 84 | clientK := client.ComputeK() 85 | serverK := server.ComputeK() 86 | assert.Equal(t, clientK, serverK, "K's should match") 87 | 88 | err = client.CheckM2(serverM2) 89 | assert.NoError(t, err, "M2 should have been valid") 90 | } 91 | 92 | func TestServerRejectsWrongM1(t *testing.T) { 93 | a, b := getAAndB() 94 | params := GetParams(4096) 95 | badClient := NewClient(params, salt, identity, []byte("Bad"), a) 96 | server := NewServer(params, getVerifier(), b) 97 | badClient.SetB(server.ComputeB()) 98 | _, err := server.CheckM1(badClient.ComputeM1()) 99 | assert.EqualError(t, err, "Client did not use the same password", "M1 check should have failed") 100 | } 101 | 102 | func TestServerRejectsBadA(t *testing.T) { 103 | // client's "A" must be 1..N-1 . Reject 0 and N and N+1. We should 104 | // reject 2*N too, but our Buffer-length checks reject it before the 105 | // number itself is examined. 106 | 107 | _, b := getAAndB() 108 | params := GetParams(4096) 109 | server := NewServer(params, getVerifier(), b) 110 | 111 | assert.Panics(t, func() { 112 | server.SetA(intToBytes(big.NewInt(0))) 113 | }, "Server should have paniced") 114 | 115 | assert.Panics(t, func() { 116 | server.SetA(intToBytes(params.N)) 117 | }, "Server should have paniced") 118 | 119 | assert.Panics(t, func() { 120 | NPlus1 := new(big.Int) 121 | NPlus1.Add(params.N, big.NewInt(1)) 122 | server.SetA(intToBytes(NPlus1)) 123 | }, "Server should have paniced") 124 | } 125 | 126 | func TestClientRejectsBadB(t *testing.T) { 127 | // server's "B" must be 1..N-1 . Reject 0 and N and N+1 128 | a, _ := getAAndB() 129 | params := GetParams(4096) 130 | client := NewClient(params, salt, identity, password, a) 131 | 132 | assert.Panics(t, func() { 133 | client.SetB(intToBytes(big.NewInt(0))) 134 | }, "Client should have paniced") 135 | 136 | assert.Panics(t, func() { 137 | client.SetB(intToBytes(params.N)) 138 | }, "Client should have paniced") 139 | 140 | assert.Panics(t, func() { 141 | NPlus1 := new(big.Int) 142 | NPlus1.Add(params.N, big.NewInt(1)) 143 | client.SetB(intToBytes(NPlus1)) 144 | }, "Client should have paniced") 145 | } 146 | 147 | func TestClientRejectsBadM2(t *testing.T) { 148 | a, b := getAAndB() 149 | params := GetParams(4096) 150 | client := NewClient(params, salt, identity, password, a) 151 | 152 | // Client produces A 153 | A := client.ComputeA() 154 | 155 | // Create server 156 | server := NewServer(params, getVerifier(), b) 157 | 158 | // Server produced B 159 | B := server.ComputeB() 160 | 161 | // Server accepts A 162 | server.SetA(A) 163 | 164 | // Client accepts B 165 | client.SetB(B) 166 | 167 | // Client produces M1 now 168 | M1 := client.ComputeM1() 169 | 170 | // Server likes client's M1 171 | server.CheckM1(M1) 172 | 173 | // We tamper with server's M2 174 | tamperedM2 := append(server.M2, 'a') 175 | 176 | // Client and server agree on K 177 | clientK := client.ComputeK() 178 | serverK := server.ComputeK() 179 | assert.Equal(t, clientK, serverK, "Ks should match") 180 | 181 | err := client.CheckM2(tamperedM2) 182 | assert.EqualError(t, err, "M2 didn't check", "Client should reject M2") 183 | } 184 | 185 | func TestRFC5054(t *testing.T) { 186 | params := GetParams(1024) 187 | I := []byte("alice") 188 | P := []byte("password123") 189 | s := bytesFromHexString("beb25379d1a8581eb5a727673a2441ee") 190 | kExpected := bytesFromHexString("7556aa045aef2cdd07abaf0f665c3e818913186f") 191 | xExpected := bytesFromHexString("94b7555aabe9127cc58ccf4993db6cf84d16c124") 192 | vExpected := bytesFromHexString(` 193 | 7e273de8 696ffc4f 4e337d05 b4b375be b0dde156 9e8fa00a 9886d812 194 | 9bada1f1 822223ca 1a605b53 0e379ba4 729fdc59 f105b478 7e5186f5 195 | c671085a 1447b52a 48cf1970 b4fb6f84 00bbf4ce bfbb1681 52e08ab5 196 | ea53d15c 1aff87b2 b9da6e04 e058ad51 cc72bfc9 033b564e 26480d78 197 | e955a5e2 9e7ab245 db2be315 e2099afb`) 198 | a := bytesFromHexString("60975527035cf2ad1989806f0407210bc81edc04e2762a56afd529ddda2d4393") 199 | b := bytesFromHexString("e487cb59d31ac550471e81f00f6928e01dda08e974a004f49e61f5d105284d20") 200 | AExpected := bytesFromHexString(` 201 | 61d5e490 f6f1b795 47b0704c 436f523d d0e560f0 c64115bb 72557ec4 202 | 4352e890 3211c046 92272d8b 2d1a5358 a2cf1b6e 0bfcf99f 921530ec 203 | 8e393561 79eae45e 42ba92ae aced8251 71e1e8b9 af6d9c03 e1327f44 204 | be087ef0 6530e69f 66615261 eef54073 ca11cf58 58f0edfd fe15efea 205 | b349ef5d 76988a36 72fac47b 0769447b`) 206 | BExpected := bytesFromHexString(` 207 | bd0c6151 2c692c0c b6d041fa 01bb152d 4916a1e7 7af46ae1 05393011 208 | baf38964 dc46a067 0dd125b9 5a981652 236f99d9 b681cbf8 7837ec99 209 | 6c6da044 53728610 d0c6ddb5 8b318885 d7d82c7f 8deb75ce 7bd4fbaa 210 | 37089e6f 9c6059f3 88838e7a 00030b33 1eb76840 910440b1 b27aaeae 211 | eb4012b7 d7665238 a8e3fb00 4b117b58`) 212 | uExpected := bytesFromHexString("ce38b9593487da98554ed47d70a7ae5f462ef019") 213 | SExpected := bytesFromHexString(` 214 | b0dc82ba bcf30674 ae450c02 87745e79 90a3381f 63b387aa f271a10d 215 | 233861e3 59b48220 f7c4693c 9ae12b0a 6f67809f 0876e2d0 13800d6c 216 | 41bb59b6 d5979b5c 00a172b4 a2a5903a 0bdcaf8a 709585eb 2afafa8f 217 | 3499b200 210dcc1f 10eb3394 3cd67fc8 8a2f39a4 be5bec4e c0a3212d 218 | c346d7e4 74b29ede 8a469ffe ca686e5a`) 219 | 220 | verifier := ComputeVerifier(params, s, I, P) 221 | client := NewClient(params, s, I, P, a) 222 | 223 | // X 224 | assert.Equal(t, xExpected, intToBytes(client.X), "x should match") 225 | 226 | // V 227 | assert.Equal(t, vExpected, verifier, "Verifier should match") 228 | 229 | // k 230 | assert.Equal(t, kExpected, intToBytes(client.Multiplier), "k should match") 231 | 232 | // A 233 | assert.Equal(t, AExpected, client.ComputeA(), "A should match") 234 | 235 | // B 236 | server := NewServer(params, verifier, b) 237 | assert.Equal(t, BExpected, server.ComputeB(), "B should match") 238 | 239 | // u and S client 240 | client.SetB(BExpected) 241 | assert.Equal(t, uExpected, intToBytes(client.u), "u should match") 242 | assert.Equal(t, SExpected, intToBytes(client.s), "S should match") 243 | 244 | // S server 245 | server.SetA(AExpected) 246 | assert.Equal(t, SExpected, intToBytes(server.s), "S should match") 247 | } 248 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package srp 2 | 3 | import ( 4 | "encoding/hex" 5 | "hash" 6 | "math/big" 7 | "regexp" 8 | ) 9 | 10 | // Helpers 11 | 12 | func padTo(bytes []byte, length int) []byte { 13 | paddingLength := length - len(bytes) 14 | padding := make([]byte, paddingLength, paddingLength) 15 | 16 | return append(padding, bytes...) 17 | } 18 | 19 | func padToN(number *big.Int, params *SRPParams) []byte { 20 | return padTo(number.Bytes(), params.NLengthBits/8) 21 | } 22 | 23 | func hashToBytes(h hash.Hash) []byte { 24 | return h.Sum(nil) 25 | } 26 | 27 | func hashToInt(h hash.Hash) *big.Int { 28 | U := new(big.Int) 29 | U.SetBytes(hashToBytes(h)) 30 | return U 31 | } 32 | 33 | func intFromBytes(bytes []byte) *big.Int { 34 | i := new(big.Int) 35 | i.SetBytes(bytes) 36 | return i 37 | } 38 | 39 | func intToBytes(i *big.Int) []byte { 40 | return i.Bytes() 41 | } 42 | 43 | func bytesFromHexString(s string) []byte { 44 | re, _ := regexp.Compile("[^0-9a-fA-F]") 45 | h := re.ReplaceAll([]byte(s), []byte("")) 46 | b, _ := hex.DecodeString(string(h)) 47 | return b 48 | } 49 | --------------------------------------------------------------------------------