├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── auth.go ├── auth_test.go ├── cmd ├── client │ └── main.go └── server │ └── main.go ├── crypto.go ├── dhoprf.go ├── dhoprf_test.go ├── doc.go ├── envu.go ├── envu_test.go ├── go.mod ├── go.sum ├── internal └── pkg │ ├── authenc │ ├── authenc.go │ └── authenc_test.go │ ├── dh │ ├── dh.go │ └── dh_test.go │ └── util │ └── util.go ├── pwreg.go └── run-tests.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /client 2 | /server 3 | /client-not-ok.log 4 | /client-ok.log 5 | /client-reg.log 6 | /server.log 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.11.x 4 | 5 | env: 6 | global: 7 | - GO111MODULE=on 8 | 9 | script: 10 | - ./run-tests.sh 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OPAQUE in Go 2 | 3 | [![GoDoc](https://godoc.org/github.com/frekui/opaque?status.png)](https://godoc.org/github.com/frekui/opaque) 4 | [![Build Status](https://travis-ci.com/frekui/opaque.svg?branch=master)](https://travis-ci.com/frekui/opaque) 5 | 6 | This repo contains a Go implementation of OPAQUE, a password authenticated key 7 | exchange protocol described in [1] and [2]. 8 | 9 | **Important note**: This code has been written for educational purposes only. No 10 | experts in cryptography or IT security have reviewed it. Do not use it for 11 | anything important. 12 | 13 | ## Installation 14 | 15 | `go get -u github.com/frekui/opaque` 16 | 17 | ## Documentation 18 | 19 | https://godoc.org/github.com/frekui/opaque 20 | 21 | ## Testing 22 | 23 | `./run-tests.sh` 24 | 25 | ## Examples 26 | 27 | The repo contains a sample [client](cmd/client/main.go) and 28 | [server](cmd/server/main.go) that authenticates each other using package opaque. 29 | 30 | ## License 31 | 32 | Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com. Use of this source code 33 | is governed by the BSD-style license that can be found in the [LICENSE](LICENSE) 34 | file. 35 | 36 | ## References 37 | 38 | [1] https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-00 39 | 40 | [2] Jarecki, S., Krawczyk, H., and J. Xu, "OPAQUE: An Asymmetric PAKE Protocol 41 | Secure Against Pre-Computation Attacks", Eurocrypt , 2018. (Full version 42 | available at https://eprint.iacr.org/2018/163) 43 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package opaque 7 | 8 | import ( 9 | "crypto/hmac" 10 | "crypto/rsa" 11 | "crypto/x509" 12 | "encoding/pem" 13 | "errors" 14 | "io" 15 | "math/big" 16 | 17 | "github.com/frekui/opaque/internal/pkg/authenc" 18 | "golang.org/x/crypto/hkdf" 19 | ) 20 | 21 | // AuthClientSession keeps track of state needed on the client-side during a 22 | // run of the authentication protocol. 23 | type AuthClientSession struct { 24 | // Client ephemeral private D-H key for this session. 25 | x *big.Int 26 | dhPubClient *big.Int 27 | r *big.Int 28 | password string 29 | } 30 | 31 | // AuthServerSession keeps track of state needed on the server-side during a 32 | // run of the authentication protocol. 33 | type AuthServerSession struct { 34 | // Server ephemeral private D-H key for this session. 35 | y *big.Int 36 | dhPubClient *big.Int 37 | dhPubServer *big.Int 38 | dhMacKey []byte 39 | dhSharedSecret []byte 40 | pubS *rsa.PublicKey 41 | 42 | user *User 43 | } 44 | 45 | // AuthMsg1 is the first message in the authentication protocol. It is sent from 46 | // the client to the server. 47 | // 48 | // Users of package opaque does not need to read nor write to the A or DhPub 49 | // field fields in this struct except to serialize and deserialize the struct 50 | // when it's sent between the peers in the authentication protocol. 51 | type AuthMsg1 struct { 52 | // From the I-D: 53 | // Uid, a=H'(PwdU)*g^r, KE1 54 | 55 | Username string 56 | 57 | // a=H'(x)*g^r 58 | A *big.Int 59 | 60 | // First message of D-H key-exchange (KE1): g^x 61 | DhPubClient *big.Int 62 | } 63 | 64 | // AuthMsg2 is the second message in the authentication protocol. It is sent 65 | // from the server to the client. 66 | // 67 | // Users of package opaque does not need to read nor write to any fields in this 68 | // struct except to serialize and deserialize the struct when it's sent between 69 | // the peers in the authentication protocol. 70 | type AuthMsg2 struct { 71 | // From the I-D: 72 | // b=a^k, EnvU, KE2 73 | 74 | // v=g^k 75 | V *big.Int 76 | 77 | // k below is the salt. 78 | // b=a^k 79 | B *big.Int 80 | 81 | // EnvU contains data encrypted by the client which is stored 82 | // server-side. 83 | EnvU []byte 84 | 85 | // Second message of D-H key-exchange (KE2): g^y, Sig(PrivS; g^x, g^y), Mac(Km1; IdS) 86 | // g^y 87 | DhPubServer *big.Int 88 | 89 | // Sig(PrivS; g^x, g^y) 90 | // RSASSA-PSS is used to compute dhSig. 91 | DhSig []byte 92 | 93 | // Mac(Km1; IdS) 94 | DhMac []byte 95 | } 96 | 97 | // After receiving AuthMsg2 client can compute RwdU as H(x, v, b*v^{-r}). 98 | // 99 | // Client can now decrypt envU, which contains PrivU and PubS. Using PubS the 100 | // client can verify the signature AuthMsg2.DhSig. With PrivU the client can 101 | // compute AuthMsg3.DhSig. 102 | 103 | // AuthMsg3 is the third and final message in the authentication protocol. It is sent from 104 | // the client to the server. 105 | // 106 | // Users of package opaque does not need to read nor write to any fields in this 107 | // struct except to serialize and deserialize the struct when it's sent between 108 | // the peers in the authentication protocol. 109 | type AuthMsg3 struct { 110 | // From the I-D: 111 | // KE3 112 | 113 | // Third message of D-H key exchange (KE3): Sig(PrivU; g^y, g^x), Mac(Km2; IdU) 114 | // RSASSA-PSS is used to compute dhSig. 115 | DhSig []byte 116 | 117 | // Mac(Km2; IdU) 118 | DhMac []byte 119 | } 120 | 121 | // AuthInit initiates the authentication protocol. It's run on the client and, 122 | // on success, returns a nil error, a client auth session, and an AuthMsg1 123 | // struct. The AuthMsg1 struct should be sent to the server. 124 | // 125 | // A non-nil error is returned on failure. 126 | // 127 | // See also Auth1, Auth2, and Auth3. 128 | func AuthInit(username, password string) (*AuthClientSession, AuthMsg1, error) { 129 | var sess AuthClientSession 130 | sess.password = password 131 | var msg1 AuthMsg1 132 | var err error 133 | msg1.Username = username 134 | 135 | msg1.A, sess.r, err = dhOprf1(password) 136 | if err != nil { 137 | return nil, AuthMsg1{}, err 138 | } 139 | sess.x, err = dhGroup.GeneratePrivateKey() 140 | if err != nil { 141 | return nil, AuthMsg1{}, err 142 | } 143 | sess.dhPubClient = dhGroup.GeneratePublicKey(sess.x) 144 | msg1.DhPubClient = sess.dhPubClient 145 | 146 | return &sess, msg1, nil 147 | } 148 | 149 | // Auth1 is the processing done by the server when it receives an AuthMsg1 150 | // struct. On success a nil error is returned together with a AuthServerSession 151 | // and an AuthMsg2 struct. The AuthMsg2 struct should be sent to the client. 152 | // 153 | // privS is the server's private RSA key. It can be the same for all users. The 154 | // user argument needs to be created by the server (e.g., by looking it up based 155 | // on msg1.Username). 156 | // 157 | // A non-nil error is returned on failure. 158 | // 159 | // See also AuthInit, Auth2, and Auth3. 160 | func Auth1(privS *rsa.PrivateKey, user *User, msg1 AuthMsg1) (*AuthServerSession, AuthMsg2, error) { 161 | y, err := dhGroup.GeneratePrivateKey() 162 | if err != nil { 163 | return nil, AuthMsg2{}, err 164 | } 165 | var msg2 AuthMsg2 166 | 167 | msg2.V, msg2.B, err = dhOprf2(msg1.A, user.K) 168 | if err != nil { 169 | return nil, AuthMsg2{}, err 170 | } 171 | msg2.EnvU = user.EnvU 172 | msg2.DhPubServer = dhGroup.GeneratePublicKey(y) 173 | 174 | h := hasher() 175 | h.Write(dhGroup.Bytes(msg1.DhPubClient)) 176 | h.Write(dhGroup.Bytes(msg2.DhPubServer)) 177 | sig, err := rsa.SignPSS(randr, privS, hasherId, h.Sum(nil), nil) 178 | if err != nil { 179 | return nil, AuthMsg2{}, err 180 | } 181 | msg2.DhSig = sig 182 | dhSharedSecret, dhMacKey, err := dhSecrets(y, msg1.DhPubClient) 183 | if err != nil { 184 | return nil, AuthMsg2{}, err 185 | } 186 | msg2.DhMac = computeDhMac(dhMacKey, &privS.PublicKey) 187 | session := &AuthServerSession{ 188 | y: y, 189 | dhPubServer: msg2.DhPubServer, 190 | dhPubClient: msg1.DhPubClient, 191 | pubS: &privS.PublicKey, 192 | user: user, 193 | dhMacKey: dhMacKey, 194 | dhSharedSecret: dhSharedSecret, 195 | } 196 | return session, msg2, nil 197 | } 198 | 199 | // Auth2 is the processing done by the client when it receives an AuthMsg2 200 | // struct. On success a nil error is returned together with a secret byte slice 201 | // and an AuthMsg3 struct. The AuthMsg3 struct should be sent to the server. On 202 | // a successful completion of the protocol the secret will be shared between the 203 | // client and the server. Auth2 is the final round in the authentication 204 | // protocol for the client. 205 | // 206 | // If Auth2 returns a nil error the client has authenticated the server 207 | // (i.e., the server has proved to the client that it posses information 208 | // obtained from the password registration protocol for this user). 209 | // 210 | // A non-nil error is returned on failure. 211 | // 212 | // See also InitAuth, Auth1, and Auth3. 213 | func Auth2(sess *AuthClientSession, msg2 AuthMsg2) (secret []byte, msg3 AuthMsg3, err error) { 214 | rwdU, err := dhOprf3(sess.password, msg2.V, msg2.B, sess.r) 215 | if err != nil { 216 | return nil, AuthMsg3{}, err 217 | } 218 | encodedEnvU, err := authenc.AuthDec(rwdU[:16], msg2.EnvU) 219 | if err != nil { 220 | return nil, AuthMsg3{}, err 221 | } 222 | envU, err := decodeEnvU(encodedEnvU) 223 | if err != nil { 224 | return nil, AuthMsg3{}, err 225 | } 226 | h := hasher() 227 | h.Write(dhGroup.Bytes(sess.dhPubClient)) 228 | h.Write(dhGroup.Bytes(msg2.DhPubServer)) 229 | err = rsa.VerifyPSS(envU.pubS, hasherId, h.Sum(nil), msg2.DhSig, nil) 230 | if err != nil { 231 | return nil, AuthMsg3{}, err 232 | } 233 | dhSharedSecret, dhMacKey, err := dhSecrets(sess.x, msg2.DhPubServer) 234 | if err != nil { 235 | return nil, AuthMsg3{}, err 236 | } 237 | if !verifyDhMac(dhMacKey, envU.pubS, msg2.DhMac) { 238 | return nil, AuthMsg3{}, errors.New("MAC mismatch") 239 | } 240 | sig, err := rsa.SignPSS(randr, envU.privU, hasherId, h.Sum(nil), nil) 241 | if err != nil { 242 | return nil, AuthMsg3{}, err 243 | } 244 | mac := computeDhMac(dhMacKey, &envU.privU.PublicKey) 245 | return dhSharedSecret, AuthMsg3{DhSig: sig, DhMac: mac}, nil 246 | } 247 | 248 | // Auth3 is the processing done by the server when it receives an AuthMsg3 249 | // struct. On success a nil error is returned together with a secret. On 250 | // successful completion the secret returned by this function is equal to the 251 | // secret returned by Auth2 invoked on the client. Auth3 is the final round in 252 | // the authentication protocol. 253 | // 254 | // If Auth3 returns a nil error the server has authenticated the client (i.e., 255 | // the client has proved to the server that it posses information used when the 256 | // password registration protocol ran for this user). 257 | // 258 | // A non-nil error is returned on failure. 259 | // 260 | // See also AuthInit, Auth1, and Auth2. 261 | func Auth3(sess *AuthServerSession, msg3 AuthMsg3) (secret []byte, err error) { 262 | h := hasher() 263 | h.Write(dhGroup.Bytes(sess.dhPubClient)) 264 | h.Write(dhGroup.Bytes(sess.dhPubServer)) 265 | err = rsa.VerifyPSS(sess.user.PubU, hasherId, h.Sum(nil), msg3.DhSig, nil) 266 | if err != nil { 267 | return nil, err 268 | } 269 | if !verifyDhMac(sess.dhMacKey, sess.user.PubU, msg3.DhMac) { 270 | return nil, errors.New("MAC mismatch") 271 | } 272 | return sess.dhSharedSecret, nil 273 | } 274 | 275 | func computeDhMac(key []byte, pk *rsa.PublicKey) []byte { 276 | pemdata := pem.EncodeToMemory( 277 | &pem.Block{ 278 | Type: "RSA PUBLIC KEY", 279 | Bytes: x509.MarshalPKCS1PublicKey(pk), 280 | }, 281 | ) 282 | mac := hmac.New(hasher, key) 283 | mac.Write(pemdata) 284 | return mac.Sum(nil) 285 | } 286 | 287 | func verifyDhMac(key []byte, pk *rsa.PublicKey, origMac []byte) bool { 288 | mac := computeDhMac(key, pk) 289 | return hmac.Equal(mac, origMac) 290 | } 291 | 292 | func dhSecrets(dhPriv, dhPub *big.Int) (dhSharedSecret, dhMacKey []byte, err error) { 293 | kdf := hkdf.New(hasher, dhGroup.SharedSecret(dhPriv, dhPub), nil, nil) 294 | dhSharedSecret = make([]byte, 16) 295 | dhMacKey = make([]byte, 16) 296 | _, err = io.ReadFull(kdf, dhSharedSecret) 297 | if err != nil { 298 | return 299 | } 300 | _, err = io.ReadFull(kdf, dhMacKey) 301 | if err != nil { 302 | return 303 | } 304 | return 305 | } 306 | -------------------------------------------------------------------------------- /auth_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package opaque 7 | 8 | import ( 9 | "bytes" 10 | "crypto/rsa" 11 | "fmt" 12 | "math/big" 13 | "testing" 14 | ) 15 | 16 | func TestAuth(t *testing.T) { 17 | username := "user" 18 | password := "password" 19 | 20 | // First create the server's private RSA key. 21 | privS, err := rsa.GenerateKey(randr, 512) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | // Register the user. 27 | clientSession, msg1, err := PwRegInit(username, password, 512) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | serverSession, msg2, err := PwReg1(privS, msg1) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | msg3, err := PwReg2(clientSession, msg2) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | user := PwReg3(serverSession, msg3) 43 | 44 | // User has registered. Test authentication. 45 | for idx, tst := range []struct { 46 | password string 47 | skipMsg2Error bool 48 | err string 49 | }{ 50 | // Correct password, should succeed. 51 | {"password", false, ""}, 52 | // Check that client detects a wrong password. 53 | {"wrong password", false, "client: Authtag mismatch"}, 54 | // Check that server detects a wrong password. 55 | {"wrong password", true, "server: crypto/rsa: verification error"}, 56 | } { 57 | fmt.Printf("Test %d: %v\n", idx, tst) 58 | err = authenticate(privS, user, tst.password, nil, nil, nil, tst.skipMsg2Error) 59 | if err == nil { 60 | if tst.err != "" { 61 | t.Fatalf("Expected error '%s', got nil", tst.err) 62 | } 63 | } else { 64 | if err.Error() != tst.err { 65 | t.Fatalf("Expected error '%s', got '%s'", tst.err, err) 66 | } 67 | } 68 | } 69 | 70 | // Test that various corrupt messages are detected properly. The 71 | // corruption might be due to an adversary who has modified messages 72 | // in-flight. 73 | for idx, tst := range []struct { 74 | msg1Mod func(*AuthMsg1) 75 | msg2Mod func(*AuthMsg2) 76 | msg3Mod func(*AuthMsg3) 77 | err string 78 | }{ 79 | {func(msg1 *AuthMsg1) { msg1.A.SetInt64(0) }, nil, nil, "server: a is not in D-H group"}, 80 | {func(msg1 *AuthMsg1) { msg1.A.SetInt64(1) }, nil, nil, "server: a is in a small subgroup"}, 81 | {func(msg1 *AuthMsg1) { msg1.DhPubClient = big.NewInt(123) }, nil, nil, "client: crypto/rsa: verification error"}, 82 | 83 | {nil, func(msg2 *AuthMsg2) { msg2.V.SetInt64(0) }, nil, "client: v is not in D-H group"}, 84 | {nil, func(msg2 *AuthMsg2) { msg2.V.SetInt64(1) }, nil, "client: v is in a small subgroup"}, 85 | {nil, func(msg2 *AuthMsg2) { msg2.B.SetInt64(0) }, nil, "client: b is not in D-H group"}, 86 | {nil, func(msg2 *AuthMsg2) { msg2.B.SetInt64(1) }, nil, "client: b is in a small subgroup"}, 87 | {nil, func(msg2 *AuthMsg2) { msg2.EnvU = append([]byte(nil), msg2.EnvU...); msg2.EnvU[0] ^= 42 }, nil, "client: Authtag mismatch"}, 88 | {nil, func(msg2 *AuthMsg2) { msg2.DhSig[0] ^= 42 }, nil, "client: crypto/rsa: verification error"}, 89 | {nil, func(msg2 *AuthMsg2) { msg2.DhMac[0] ^= 42 }, nil, "client: MAC mismatch"}, 90 | {nil, func(msg2 *AuthMsg2) { msg2.DhPubServer = big.NewInt(-123) }, nil, "client: crypto/rsa: verification error"}, 91 | {nil, func(msg2 *AuthMsg2) { msg2.DhPubServer = big.NewInt(123) }, nil, "client: crypto/rsa: verification error"}, 92 | 93 | {nil, nil, func(msg3 *AuthMsg3) { msg3.DhSig[0] ^= 42 }, "server: crypto/rsa: verification error"}, 94 | {nil, nil, func(msg3 *AuthMsg3) { msg3.DhMac[0] ^= 42 }, "server: MAC mismatch"}, 95 | } { 96 | fmt.Printf("Test %d: %v\n", idx, tst) 97 | err = authenticate(privS, user, "password", tst.msg1Mod, tst.msg2Mod, tst.msg3Mod, false) 98 | if err == nil { 99 | if tst.err != "" { 100 | t.Fatalf("Expected error '%s', got nil", tst.err) 101 | } 102 | } else { 103 | if err.Error() != tst.err { 104 | t.Fatalf("Expected error '%s', got '%s'", tst.err, err) 105 | } 106 | } 107 | } 108 | } 109 | 110 | // authenticate attempts to authenticate with the server using the given 111 | // credentials. 112 | func authenticate(privS *rsa.PrivateKey, user *User, password string, msg1Mod func(*AuthMsg1), msg2Mod func(*AuthMsg2), msg3Mod func(*AuthMsg3), skipMsg2Error bool) error { 113 | cAuthSession, amsg1, err := AuthInit(user.Username, password) 114 | if err != nil { 115 | return err 116 | } 117 | if msg1Mod != nil { 118 | msg1Mod(&amsg1) 119 | } 120 | 121 | sAuthSession, amsg2, err := Auth1(privS, user, amsg1) 122 | if err != nil { 123 | return fmt.Errorf("server: %s", err) 124 | } 125 | if msg2Mod != nil { 126 | msg2Mod(&amsg2) 127 | } 128 | 129 | cSharedSecret, amsg3, err := Auth2(cAuthSession, amsg2) 130 | if !skipMsg2Error && err != nil { 131 | return fmt.Errorf("client: %s", err) 132 | } 133 | if msg3Mod != nil { 134 | msg3Mod(&amsg3) 135 | } 136 | 137 | sSharedSecret, err := Auth3(sAuthSession, amsg3) 138 | if err != nil { 139 | return fmt.Errorf("server: %s", err) 140 | } 141 | if !bytes.Equal(cSharedSecret, sSharedSecret) { 142 | return fmt.Errorf("Shared secrets differ") 143 | } 144 | return nil 145 | } 146 | 147 | func TestDhSecrets(t *testing.T) { 148 | priv, err := dhGroup.GeneratePrivateKey() 149 | if err != nil { 150 | t.Fatal(err) 151 | } 152 | pub := dhGroup.GeneratePublicKey(priv) 153 | shared, key, err := dhSecrets(priv, pub) 154 | if len(shared) < 16 { 155 | t.Fatalf("len(shared) = %d < 16", len(shared)) 156 | } 157 | if len(key) < 16 { 158 | t.Fatalf("len(key) = %d < 16", len(key)) 159 | } 160 | if bytes.Equal(shared, key) { 161 | t.Fatalf("shared = key = %v", shared) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /cmd/client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "bufio" 10 | "encoding/json" 11 | "flag" 12 | "fmt" 13 | "net" 14 | "os" 15 | 16 | "github.com/frekui/opaque" 17 | "github.com/frekui/opaque/internal/pkg/util" 18 | ) 19 | 20 | func main() { 21 | flag.Usage = func() { 22 | fmt.Fprintf(flag.CommandLine.Output(), "%s is a simple example client of the opaque package. It can be used together with cmd/server.\nUsage:\n", os.Args[0]) 23 | flag.PrintDefaults() 24 | } 25 | 26 | addr := flag.String("conn", "localhost:9999", "Host to connect to.") 27 | pwreg := flag.Bool("pwreg", false, "Register password.") 28 | auth := flag.Bool("auth", false, "Authenticate and send message to server") 29 | username := flag.String("username", "", "Username") 30 | password := flag.String("password", "", "Password") 31 | flag.Parse() 32 | if !*pwreg && !*auth { 33 | fmt.Fprintf(os.Stderr, "Exactly one of -pwreg and -auth must be given.\n") 34 | flag.Usage() 35 | os.Exit(1) 36 | } 37 | conn, err := net.Dial("tcp", *addr) 38 | if err != nil { 39 | panic(err) 40 | } 41 | defer conn.Close() 42 | 43 | r := bufio.NewReader(conn) 44 | w := bufio.NewWriter(conn) 45 | 46 | if *pwreg { 47 | err := util.Write(w, []byte("pwreg")) 48 | if err == nil { 49 | err = doPwreg(r, w, *username, *password) 50 | } 51 | if err != nil { 52 | fmt.Fprintf(os.Stderr, "pwreg: %s\n", err) 53 | os.Exit(1) 54 | } 55 | } else { 56 | err := util.Write(w, []byte("auth")) 57 | if err == nil { 58 | err = doAuth(r, w, *username, *password, "Hello from client") 59 | } 60 | if err != nil { 61 | fmt.Fprintf(os.Stderr, "auth: %s\n", err) 62 | os.Exit(1) 63 | } 64 | } 65 | } 66 | 67 | func doPwreg(r *bufio.Reader, w *bufio.Writer, username, password string) error { 68 | sess, msg1, err := opaque.PwRegInit(username, password, 512) 69 | if err != nil { 70 | return err 71 | } 72 | data1, err := json.Marshal(msg1) 73 | if err != nil { 74 | return err 75 | } 76 | if err := util.Write(w, data1); err != nil { 77 | return err 78 | } 79 | 80 | data2, err := util.Read(r) 81 | if err != nil { 82 | return err 83 | } 84 | var msg2 opaque.PwRegMsg2 85 | if err := json.Unmarshal(data2, &msg2); err != nil { 86 | return err 87 | } 88 | 89 | msg3, err := opaque.PwReg2(sess, msg2) 90 | if err != nil { 91 | return err 92 | } 93 | data3, err := json.Marshal(msg3) 94 | if err != nil { 95 | return err 96 | } 97 | if err := util.Write(w, data3); err != nil { 98 | return err 99 | } 100 | 101 | final, err := util.Read(r) 102 | if err != nil { 103 | return err 104 | } 105 | if string(final) != "ok" { 106 | return fmt.Errorf("expected final ok, got %s", string(final)) 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func doAuth(r *bufio.Reader, w *bufio.Writer, username, password, msg string) error { 113 | sess, msg1, err := opaque.AuthInit(username, password) 114 | if err != nil { 115 | return err 116 | } 117 | data1, err := json.Marshal(msg1) 118 | if err != nil { 119 | return err 120 | } 121 | if err := util.Write(w, data1); err != nil { 122 | return err 123 | } 124 | 125 | data2, err := util.Read(r) 126 | if err != nil { 127 | return err 128 | } 129 | var msg2 opaque.AuthMsg2 130 | if err := json.Unmarshal(data2, &msg2); err != nil { 131 | return err 132 | } 133 | 134 | sharedSecret, msg3, err := opaque.Auth2(sess, msg2) 135 | if err != nil { 136 | return err 137 | } 138 | data3, err := json.Marshal(msg3) 139 | if err != nil { 140 | return err 141 | } 142 | if err := util.Write(w, data3); err != nil { 143 | return err 144 | } 145 | 146 | ok, err := util.Read(r) 147 | if err != nil { 148 | return err 149 | } 150 | if string(ok) != "ok" { 151 | return fmt.Errorf("Expected ok, got '%s'", string(ok)) 152 | } 153 | 154 | // FIXME: Use a PRF to have separate keys for client->server and 155 | // server->client. 156 | key := sharedSecret[:16] 157 | plaintext, err := util.ReadAndDecrypt(r, key) 158 | if err != nil { 159 | return err 160 | } 161 | fmt.Printf("Received '%s'\n", plaintext) 162 | toServer := "Hi server!" 163 | fmt.Printf("Sending '%s'\n", toServer) 164 | if err := util.EncryptAndWrite(w, key, toServer); err != nil { 165 | return err 166 | } 167 | return nil 168 | } 169 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "bufio" 10 | "crypto/rand" 11 | "crypto/rsa" 12 | "encoding/json" 13 | "flag" 14 | "fmt" 15 | "net" 16 | "os" 17 | 18 | "github.com/frekui/opaque" 19 | "github.com/frekui/opaque/internal/pkg/util" 20 | ) 21 | 22 | // Server's private RSA key. 23 | var privS *rsa.PrivateKey 24 | 25 | // Map usernames to users. 26 | var users = map[string]*opaque.User{} 27 | 28 | func main() { 29 | flag.Usage = func() { 30 | fmt.Fprintf(flag.CommandLine.Output(), "%s is a simple example server of the opaque package. It can be used together with cmd/client.\nUsage:\n", os.Args[0]) 31 | flag.PrintDefaults() 32 | } 33 | 34 | addr := flag.String("l", ":9999", "Address to listen on.") 35 | flag.Parse() 36 | 37 | var err error 38 | privS, err = rsa.GenerateKey(rand.Reader, 512) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | ln, err := net.Listen("tcp", *addr) 44 | if err != nil { 45 | fmt.Fprintf(os.Stderr, "%v\n", err) 46 | os.Exit(1) 47 | } 48 | 49 | for { 50 | conn, err := ln.Accept() 51 | if err != nil { 52 | fmt.Fprintf(os.Stderr, "%v\n", err) 53 | continue 54 | } 55 | go handleConn(conn) 56 | } 57 | } 58 | 59 | func handleConn(conn net.Conn) { 60 | defer conn.Close() 61 | fmt.Printf("Got connection from %s\n", conn.RemoteAddr()) 62 | if err := doHandleConn(conn); err != nil { 63 | fmt.Printf("doHandleConn: %s\n", err) 64 | } 65 | } 66 | 67 | func doHandleConn(conn net.Conn) error { 68 | r := bufio.NewReader(conn) 69 | cmd, err := util.Read(r) 70 | if err != nil { 71 | return err 72 | } 73 | w := bufio.NewWriter(conn) 74 | switch string(cmd) { 75 | case "pwreg": 76 | if err := handlePwReg(r, w); err != nil { 77 | return fmt.Errorf("pwreg: %s", err) 78 | } 79 | case "auth": 80 | if err := handleAuth(r, w); err != nil { 81 | return fmt.Errorf("auth: %s", err) 82 | } 83 | default: 84 | return fmt.Errorf("Unknown command '%s'\n", string(cmd)) 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func handleAuth(r *bufio.Reader, w *bufio.Writer) error { 91 | data1, err := util.Read(r) 92 | if err != nil { 93 | return err 94 | } 95 | var msg1 opaque.AuthMsg1 96 | if err := json.Unmarshal(data1, &msg1); err != nil { 97 | return err 98 | } 99 | user, ok := users[msg1.Username] 100 | if !ok { 101 | return fmt.Errorf("No such user") 102 | } 103 | session, msg2, err := opaque.Auth1(privS, user, msg1) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | data2, err := json.Marshal(msg2) 109 | if err != nil { 110 | return err 111 | } 112 | if err := util.Write(w, data2); err != nil { 113 | return err 114 | } 115 | 116 | data3, err := util.Read(r) 117 | if err != nil { 118 | return err 119 | } 120 | var msg3 opaque.AuthMsg3 121 | if err := json.Unmarshal(data3, &msg3); err != nil { 122 | return err 123 | } 124 | sharedSecret, err := opaque.Auth3(session, msg3) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | if err := util.Write(w, []byte("ok")); err != nil { 130 | return err 131 | } 132 | 133 | key := sharedSecret[:16] 134 | toClient := "Hi client!" 135 | fmt.Printf("Sending '%s'\n", toClient) 136 | if err := util.EncryptAndWrite(w, key, toClient); err != nil { 137 | return err 138 | } 139 | plaintext, err := util.ReadAndDecrypt(r, key) 140 | if err != nil { 141 | return err 142 | } 143 | fmt.Printf("Received '%s'\n", plaintext) 144 | return nil 145 | } 146 | 147 | func handlePwReg(r *bufio.Reader, w *bufio.Writer) error { 148 | data1, err := util.Read(r) 149 | if err != nil { 150 | return err 151 | } 152 | var msg1 opaque.PwRegMsg1 153 | if err := json.Unmarshal(data1, &msg1); err != nil { 154 | return err 155 | } 156 | session, msg2, err := opaque.PwReg1(privS, msg1) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | data2, err := json.Marshal(msg2) 162 | if err != nil { 163 | return err 164 | } 165 | if err := util.Write(w, data2); err != nil { 166 | return err 167 | } 168 | 169 | data3, err := util.Read(r) 170 | if err != nil { 171 | return err 172 | } 173 | var msg3 opaque.PwRegMsg3 174 | if err := json.Unmarshal(data3, &msg3); err != nil { 175 | return err 176 | } 177 | user := opaque.PwReg3(session, msg3) 178 | if err := util.Write(w, []byte("ok")); err != nil { 179 | return err 180 | } 181 | fmt.Printf("Added user '%s'\n", user.Username) 182 | users[user.Username] = user 183 | return nil 184 | } 185 | -------------------------------------------------------------------------------- /crypto.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package opaque 7 | 8 | import ( 9 | "crypto" 10 | "crypto/rand" 11 | "crypto/sha256" 12 | "hash" 13 | 14 | "github.com/frekui/opaque/internal/pkg/dh" 15 | ) 16 | 17 | var randr = rand.Reader 18 | 19 | // This hash function is used as H from the I-D. 20 | func hasher() hash.Hash { 21 | return sha256.New() 22 | } 23 | 24 | var hasherId = crypto.SHA256 25 | 26 | var dhGroup = dh.Rfc3526_2048 27 | -------------------------------------------------------------------------------- /dhoprf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package opaque 7 | 8 | // This file contains functions to run the interactive protocol DH-OPRF 9 | // (Diffie-Hellman Oblivious Pseudorandom Function) from the I-D 10 | // https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-00. 11 | 12 | import ( 13 | "crypto/rand" 14 | "errors" 15 | "math/big" 16 | 17 | "golang.org/x/crypto/hkdf" 18 | ) 19 | 20 | // hashToGroup is an implementation of the H' hash function from the I-D. It 21 | // hashes byte slices to group elements (i.e., elements in Z^*_p). 22 | func hashToGroup(data []byte) *big.Int { 23 | kdf := hkdf.New(hasher, data, nil, nil) 24 | 25 | for { 26 | x, err := rand.Int(kdf, dhGroup.P) 27 | if err != nil { 28 | panic(err) 29 | } 30 | if x.Sign() != 0 { 31 | return x 32 | } 33 | } 34 | } 35 | 36 | // dhOprf1 is the first step in computing DF-OPRF. dhOprf1 is executed on the 37 | // client. 38 | // 39 | // From the I-D: 40 | // Protocol for computing DH-OPRF, U with input x and S with input k: 41 | // U: choose random r in [0..q-1], send a=H'(x)*g^r to S 42 | // 43 | // x is typically the password. 44 | func dhOprf1(x string) (a, r *big.Int, err error) { 45 | for { 46 | r, err = dhGroup.GeneratePrivateKey() 47 | if err != nil { 48 | return nil, nil, err 49 | } 50 | hPrime := hashToGroup([]byte(x)) 51 | a = new(big.Int) 52 | a.Exp(dhGroup.G, r, dhGroup.P) 53 | a.Mul(hPrime, a) 54 | a.Mod(a, dhGroup.P) 55 | 56 | // The probability that a is in a two element subgroup of 57 | // dhGroup is extremely small, but in case it is we try again 58 | // with a new r. 59 | if !dhGroup.IsInSmallSubgroup(a) { 60 | return 61 | } 62 | } 63 | } 64 | 65 | func generateSalt() (k *big.Int, err error) { 66 | k, err = dhGroup.GeneratePrivateKey() 67 | return 68 | } 69 | 70 | // dhOprf2 is the second step in computing DH-OPRF. dhOprf2 is executed on the 71 | // server. 72 | // 73 | // From the I-D: 74 | // S: upon receiving a value a, respond with v=g^k and b=a^k 75 | // 76 | // k is used a salt when the password is hashed. 77 | func dhOprf2(a, k *big.Int) (v *big.Int, b *big.Int, err error) { 78 | // From I-D: All received values (a, b, v) are checked to be non-unit 79 | // elements in G. 80 | // 81 | // First check that a is in Z^*_p. 82 | if !dhGroup.IsInGroup(a) { 83 | return nil, nil, errors.New("a is not in D-H group") 84 | } 85 | // Also check that a is not in a two element subgroup of dhGroup. 86 | if dhGroup.IsInSmallSubgroup(a) { 87 | return nil, nil, errors.New("a is in a small subgroup") 88 | } 89 | // v can be stored in User instead. 90 | v = new(big.Int) 91 | v.Exp(dhGroup.G, k, dhGroup.P) 92 | b = new(big.Int) 93 | b.Exp(a, k, dhGroup.P) 94 | return v, b, nil 95 | } 96 | 97 | // dhOprf3 is the third and final step in computing DH-OPRF. dhOprf3 is executed 98 | // on the client. 99 | // 100 | // From the I-D: 101 | // U: upon receiving values b and v, set the PRF output to H(x, v, b*v^{-r}) 102 | func dhOprf3(x string, v, b, r *big.Int) ([]byte, error) { 103 | // From I-D: All received values (a, b, v) are checked to be non-unit 104 | // elements in G. 105 | // 106 | // We check that v and b are in Z^*_p and they aren't in a two element 107 | // subgroup. 108 | if !dhGroup.IsInGroup(v) { 109 | return nil, errors.New("v is not in D-H group") 110 | } 111 | if dhGroup.IsInSmallSubgroup(v) { 112 | return nil, errors.New("v is in a small subgroup") 113 | } 114 | if !dhGroup.IsInGroup(b) { 115 | return nil, errors.New("b is not in D-H group") 116 | } 117 | if dhGroup.IsInSmallSubgroup(b) { 118 | return nil, errors.New("b is in a small subgroup") 119 | } 120 | z := new(big.Int) 121 | z.Exp(v, r, dhGroup.P) 122 | z.ModInverse(z, dhGroup.P) 123 | z.Mul(b, z) 124 | z.Mod(z, dhGroup.P) 125 | h := hasher() 126 | // FIXME: User iteration, see Section 3.4. 127 | h.Write([]byte(x)) 128 | h.Write(dhGroup.Bytes(v)) 129 | h.Write(dhGroup.Bytes(z)) 130 | return h.Sum(nil), nil 131 | } 132 | -------------------------------------------------------------------------------- /dhoprf_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package opaque 7 | 8 | import ( 9 | "bytes" 10 | "math/big" 11 | "testing" 12 | 13 | "github.com/go-test/deep" 14 | ) 15 | 16 | // dhoprf runs the DH-OPRF protocol on input x (the password) and k (the salt). 17 | func dhoprf(x string, k int64) (a, r *big.Int, h []byte) { 18 | // dhOprf1 is computed by the client. 19 | // func dhOprf1(x string) (a, r *big.Int, err error) 20 | var err error 21 | a, r, err = dhOprf1(x) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | // dhOprf2 is computed by the server. 27 | // func dhOprf2(a, k *big.Int) (v *big.Int, b *big.Int) 28 | v, b, err := dhOprf2(a, big.NewInt(k)) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | // dhOprf3 is computed by the client. 34 | // func dhOprf3(x string, v, b, r *big.Int) []byte 35 | h, err = dhOprf3(x, v, b, r) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return 40 | } 41 | 42 | func TestDhOprf(t *testing.T) { 43 | rs := map[string]bool{} 44 | as := map[string]bool{} 45 | var hPrev []byte 46 | iterations := 10 47 | for i := 0; i < iterations; i++ { 48 | a, r, h := dhoprf("password", 123) 49 | aStr := a.String() 50 | if as[aStr] { 51 | t.Fatalf("Already seen a %v", aStr) 52 | } 53 | as[aStr] = true 54 | 55 | rStr := r.String() 56 | if rs[rStr] { 57 | t.Fatalf("Already seen r %v", rStr) 58 | } 59 | rs[rStr] = true 60 | 61 | if hPrev == nil { 62 | hPrev = h 63 | } 64 | if diff := deep.Equal(h, hPrev); diff != nil { 65 | t.Fatalf("diff: %v", diff) 66 | } 67 | } 68 | if len(rs) < iterations { 69 | t.Fatalf("rs too small") 70 | } 71 | 72 | _, _, hNewSalt := dhoprf("password", 789) 73 | if bytes.Equal(hPrev, hNewSalt) { 74 | t.Fatalf("hash didn't change with new salt") 75 | } 76 | _, _, hNewPassword := dhoprf("new", 123) 77 | if bytes.Equal(hPrev, hNewPassword) { 78 | t.Fatalf("hash didn't change with new password") 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | /* 7 | Package opaque contains an implementation of OPAQUE, a password authenticated 8 | key exchange protocol described. OPAQUE is described in [1] and [2]. 9 | 10 | OPAQUE can be split into two parts, a password registration protocol and a 11 | protocol for authentication once the user's password has been registered. It's 12 | assumed that the password registration protocol runs over an authentication 13 | connection (the authentication protocol does, of course, not have such an 14 | assumption). The client initiates the password registration protocol by calling 15 | PwRegInit. Similarly, the authentication protocol is initiated by the client 16 | calling AuthInit. 17 | 18 | If the authentication protocol finishes successfully a newly generated random 19 | secret is shared between the client and server. The secret can be used to 20 | protect any future communication between the peers. 21 | 22 | A number of structs with messages for the two protocols (AuthMsg1, AuthMsg2, 23 | AuthMsg3, PwRegMsg1, PwRegMsg2, PwRegMsg3) are defined in this package. It's up 24 | to the user of the package to serialize and deserialize these structs and send 25 | them from the client/server to the peer on the other end. In the example server 26 | and client (cmd/server and cmd/client) the messages are serialized using JSON, 27 | which is simple and works but isn't the most efficient option. 28 | 29 | IMPORTANT NOTE: This code has been written for educational purposes only. No 30 | experts in cryptography or IT security have reviewed it. Do not use it for 31 | anything important. 32 | 33 | [1] https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-00 34 | 35 | [2] Jarecki, S., Krawczyk, H., and J. Xu, "OPAQUE: An Asymmetric PAKE Protocol 36 | Secure Against Pre-Computation Attacks", Eurocrypt , 2018. (Full version 37 | available at https://eprint.iacr.org/2018/163) 38 | */ 39 | package opaque 40 | -------------------------------------------------------------------------------- /envu.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package opaque 7 | 8 | import ( 9 | "crypto/rsa" 10 | "crypto/x509" 11 | "encoding/pem" 12 | "fmt" 13 | ) 14 | 15 | // envU is information stored encrypted on the server. The encryption key is 16 | // derived from the password together with the salt. 17 | type envU struct { 18 | // pubU is privU.Public() 19 | privU *rsa.PrivateKey 20 | pubS *rsa.PublicKey 21 | } 22 | 23 | // decodeEnvU decodes an envU from a slice of bytes. 24 | func decodeEnvU(pemdata []byte) (envU, error) { 25 | privblock, pemdata := pem.Decode(pemdata) 26 | if privblock == nil { 27 | return envU{}, fmt.Errorf("Failed to decode private key") 28 | } 29 | if privblock.Type != "RSA PRIVATE KEY" { 30 | return envU{}, fmt.Errorf("Unexpected type of block: %s", privblock.Type) 31 | } 32 | privkey, err := x509.ParsePKCS1PrivateKey(privblock.Bytes) 33 | if err != nil { 34 | return envU{}, err 35 | } 36 | pubblock, _ := pem.Decode(pemdata) 37 | if pubblock == nil { 38 | return envU{}, fmt.Errorf("Failed to decode public key") 39 | } 40 | if pubblock.Type != "RSA PUBLIC KEY" { 41 | return envU{}, fmt.Errorf("Unexpected type of block: %s", pubblock.Type) 42 | } 43 | pubkey, err := x509.ParsePKCS1PublicKey(pubblock.Bytes) 44 | if err != nil { 45 | return envU{}, err 46 | } 47 | return envU{privU: privkey, pubS: pubkey}, nil 48 | } 49 | 50 | // encodeEnvU encodes an envU as a slice of bytes. 51 | func encodeEnvU(env *envU) []byte { 52 | pemdata := pem.EncodeToMemory( 53 | &pem.Block{ 54 | Type: "RSA PRIVATE KEY", 55 | Bytes: x509.MarshalPKCS1PrivateKey(env.privU), 56 | }, 57 | ) 58 | pemdata = append(pemdata, pem.EncodeToMemory( 59 | &pem.Block{ 60 | Type: "RSA PUBLIC KEY", 61 | Bytes: x509.MarshalPKCS1PublicKey(env.pubS), 62 | }, 63 | )...) 64 | return pemdata 65 | } 66 | -------------------------------------------------------------------------------- /envu_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package opaque 7 | 8 | import ( 9 | "crypto/rand" 10 | "crypto/rsa" 11 | "testing" 12 | 13 | "github.com/go-test/deep" 14 | ) 15 | 16 | func TestEnvU(t *testing.T) { 17 | privU, err := rsa.GenerateKey(rand.Reader, 512) 18 | if err != nil { 19 | t.Fatalf("Failed to generate privU: %s", err) 20 | } 21 | privS, err := rsa.GenerateKey(rand.Reader, 512) 22 | if err != nil { 23 | t.Fatalf("Failed to generate privS: %s", err) 24 | } 25 | genEnvU := &envU{privU: privU, pubS: &privS.PublicKey} 26 | 27 | encodedEnvU := encodeEnvU(genEnvU) 28 | decodedEnvU, err := decodeEnvU(encodedEnvU) 29 | if err != nil { 30 | t.Fatalf("decoding failed: %s", err) 31 | } 32 | 33 | if diff := deep.Equal(*genEnvU, decodedEnvU); diff != nil { 34 | t.Fatalf("envU not equal! %v", diff) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/frekui/opaque 2 | 3 | require ( 4 | github.com/go-test/deep v1.0.1 5 | golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 6 | ) 7 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= 2 | github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 3 | golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU= 4 | golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 5 | -------------------------------------------------------------------------------- /internal/pkg/authenc/authenc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package authenc 7 | 8 | import ( 9 | "crypto/aes" 10 | "crypto/cipher" 11 | "crypto/hmac" 12 | "crypto/sha256" 13 | "fmt" 14 | "hash" 15 | "io" 16 | "io/ioutil" 17 | 18 | "golang.org/x/crypto/hkdf" 19 | ) 20 | 21 | // var debug = os.Stdout 22 | var debug = ioutil.Discard 23 | 24 | func hasher() hash.Hash { 25 | return sha256.New() 26 | } 27 | 28 | // AuthEnc performs authenticated encryption of the provided input using the 29 | // provided key. AES-128 is used in CBC mode with HMAC-SHA256 in 30 | // encrypt-then-authenticate mode. The output is IV || ciphertext || auth-tag, 31 | // where "||" is concatenation of byte slices. 32 | // 33 | // On success the ciphertext is returned together with a nil error. 34 | // 35 | // See also AuthDec. 36 | func AuthEnc(randr io.Reader, key []byte, plaintext []byte) ([]byte, error) { 37 | if len(key) != 16 { 38 | return nil, fmt.Errorf("Got key length %d, expected 16", len(key)) 39 | } 40 | kdfr := hkdf.New(hasher, key, nil, nil) 41 | cbcKey := make([]byte, 16) 42 | hmacKey := make([]byte, 16) 43 | fmt.Fprintf(debug, "AuthEnc: hmacKey %v\n", hmacKey) 44 | _, err := io.ReadFull(kdfr, cbcKey) 45 | if err != nil { 46 | return nil, err 47 | } 48 | _, err = io.ReadFull(kdfr, hmacKey) 49 | if err != nil { 50 | return nil, err 51 | } 52 | ciph, err := aes.NewCipher(cbcKey) 53 | if err != nil { 54 | panic("aes.NewCipher failed") 55 | } 56 | iv := make([]byte, ciph.BlockSize()) 57 | _, err = io.ReadFull(randr, iv) 58 | if err != nil { 59 | return nil, err 60 | } 61 | enc := cipher.NewCBCEncrypter(ciph, iv) 62 | numBlocks := len(plaintext)/ciph.BlockSize() + 1 63 | res := make([]byte, 64 | ciph.BlockSize()+ // IV 65 | numBlocks*ciph.BlockSize()+ // cipher text, including padding 66 | hasher().Size()+ // authtag 67 | 0) 68 | // Copy IV to res. 69 | copy(res, iv) 70 | // Encrypt all blocks except for the last one and store the result in res. 71 | enc.CryptBlocks(res[ciph.BlockSize():], plaintext[0:(numBlocks-1)*ciph.BlockSize()]) 72 | // Pad and encrypt the last block. Store the result in res. 73 | lastBlock := addPadding(ciph.BlockSize(), plaintext[(numBlocks-1)*ciph.BlockSize():]) 74 | fmt.Fprintf(debug, "AuthEnc last block: %v\n", lastBlock) 75 | enc.CryptBlocks(res[ciph.BlockSize()*numBlocks:], lastBlock) 76 | fmt.Fprintf(debug, "AuthEnc: res (before HMAC): %v\n", res) 77 | 78 | mac := hmac.New(hasher, hmacKey) 79 | fmt.Fprintf(debug, "AuthEnc mac.Write %v\n", res) 80 | if _, err = mac.Write(res[0 : ciph.BlockSize()*(numBlocks+1)]); err != nil { 81 | return nil, err 82 | } 83 | authtag := mac.Sum(nil) 84 | fmt.Fprintf(debug, "AuthEnc: authtag: %v\n", authtag) 85 | copy(res[ciph.BlockSize()*(numBlocks+1):], authtag) 86 | fmt.Fprintf(debug, "AuthEnc: res: %v\n", res) 87 | return res, nil 88 | } 89 | 90 | // AuthtagMismatch is returned by AuthDec if authentication of the ciphertext 91 | // failed. 92 | var AuthtagMismatch = fmt.Errorf("Authtag mismatch") 93 | 94 | // AuthDec performs authenticated decryption of the provided input using the 95 | // provided key. See AuthEnc for more details. 96 | // 97 | // On success the plaintext is returned together with a nil error. 98 | func AuthDec(key []byte, input []byte) ([]byte, error) { 99 | if len(key) != 16 { 100 | return nil, fmt.Errorf("Got key length %d, expected 16", len(key)) 101 | } 102 | if len(input) < 3*16 { 103 | return nil, fmt.Errorf("AuthDec: Input too short") 104 | } 105 | if len(input)%16 != 0 { 106 | return nil, fmt.Errorf("AuthDec: Invalid input length") 107 | } 108 | iv := input[:16] 109 | ciphertext := input[16 : len(input)-hasher().Size()] 110 | authtag := input[len(input)-hasher().Size():] 111 | fmt.Fprintf(debug, "AuthDec: iv: %v\n", iv) 112 | fmt.Fprintf(debug, "AuthDec: ciphertext: %v\n", ciphertext) 113 | fmt.Fprintf(debug, "AuthDec: authtag: %v\n", authtag) 114 | 115 | kdfr := hkdf.New(hasher, key, nil, nil) 116 | cbcKey := make([]byte, 16) 117 | hmacKey := make([]byte, 16) 118 | fmt.Fprintf(debug, "AuthDec: hmacKey %v\n", hmacKey) 119 | _, err := io.ReadFull(kdfr, cbcKey) 120 | if err != nil { 121 | return nil, err 122 | } 123 | _, err = io.ReadFull(kdfr, hmacKey) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | mac := hmac.New(hasher, hmacKey) 129 | fmt.Fprintf(debug, "AuthDec mac.Write %v\n", iv) 130 | if _, err = mac.Write(iv); err != nil { 131 | return nil, err 132 | } 133 | fmt.Fprintf(debug, "AuthDec mac.Write %v\n", ciphertext) 134 | if _, err = mac.Write(ciphertext); err != nil { 135 | return nil, err 136 | } 137 | fmt.Fprintf(debug, "AuthDec hmac.Sum: %v\n", mac.Sum(nil)) 138 | if !hmac.Equal(mac.Sum(nil), authtag) { 139 | return nil, AuthtagMismatch 140 | } 141 | 142 | ciph, err := aes.NewCipher(cbcKey) 143 | if err != nil { 144 | panic("aes.NewCipher failed") 145 | } 146 | enc := cipher.NewCBCDecrypter(ciph, iv) 147 | plaintext := make([]byte, len(ciphertext)) 148 | enc.CryptBlocks(plaintext, ciphertext) 149 | fmt.Fprintf(debug, "AuthDec plaintext: %v\n", plaintext) 150 | plaintext = removePadding(ciph.BlockSize(), plaintext) 151 | return plaintext, nil 152 | } 153 | 154 | // addPadding pads "input" using the padding algorithm from 155 | // https://tools.ietf.org/html/rfc5652#section-6.3 156 | func addPadding(blockSize int, input []byte) []byte { 157 | out := make([]byte, blockSize*(len(input)/blockSize+1)) 158 | copy(out, input) 159 | var b byte = byte(blockSize - len(input)%blockSize) 160 | for i := len(input); i < len(out); i++ { 161 | out[i] = b 162 | } 163 | return out 164 | } 165 | 166 | // removePadding removes the padding from "input". See also addPadding. 167 | func removePadding(blockSize int, input []byte) []byte { 168 | if len(input)%blockSize != 0 { 169 | panic("removePadding: Input length is not a multiple of block size") 170 | } 171 | if len(input) == 0 { 172 | panic("removePadding: Empty input") 173 | } 174 | b := input[len(input)-1] 175 | if int(b) > blockSize { 176 | panic("removePadding: Invalid padding") 177 | } 178 | input = input[:len(input)-int(b)] 179 | return input 180 | } 181 | -------------------------------------------------------------------------------- /internal/pkg/authenc/authenc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package authenc 7 | 8 | import ( 9 | "bytes" 10 | "crypto/rand" 11 | "testing" 12 | ) 13 | 14 | func TestPadding(t *testing.T) { 15 | bs := 16 16 | for _, tst := range []struct { 17 | in, expected []byte 18 | }{ 19 | {[]byte{}, []byte{16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}}, 20 | {[]byte{7}, []byte{7, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15}}, 21 | {[]byte{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}, 22 | []byte{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 23 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}}, 24 | {[]byte{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}, 25 | []byte{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 26 | 7, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14}}, 27 | } { 28 | // fmt.Printf("Testing padding %v\n", tst) 29 | padded := addPadding(bs, tst.in) 30 | if !bytes.Equal(padded, tst.expected) { 31 | t.Errorf("Got %v", padded) 32 | } 33 | 34 | orig := removePadding(bs, padded) 35 | if !bytes.Equal(orig, tst.in) { 36 | t.Errorf("Failed to remove padding, got %v", orig) 37 | } 38 | } 39 | } 40 | 41 | type DevZero int 42 | 43 | func (z DevZero) Read(b []byte) (n int, err error) { 44 | for i := range b { 45 | b[i] = 0 46 | } 47 | 48 | return len(b), nil 49 | } 50 | 51 | var authEncDecTests = []struct { 52 | key, plaintext, expected []byte 53 | }{ 54 | {[]byte{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}, 55 | []byte{}, 56 | []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xd6, 0x3e, 0x8c, 0x60, 0xb3, 0xa8, 0xbb, 0x77, 0x33, 0x83, 0x96, 0x67, 0x45, 0x2f, 0x42, 0xe9, 0x85, 0xf2, 0x9b, 0xa8, 0x78, 0xb1, 0x32, 0x74, 0x5, 0xb2, 0xcd, 0x9d, 0xfe, 0xfa, 0x2f, 0xfa, 0xe5, 0xc9, 0x2f, 0xbc, 0x65, 0x7b, 0xd9, 0x40, 0x94, 0xf1, 0xa5, 0xbe, 0x29, 0xe, 0xf}}, 57 | {[]byte{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}, 58 | []byte{1, 2, 3}, 59 | []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xde, 0x41, 0xb, 0x46, 0xeb, 0xb0, 0x14, 0xd, 0xd7, 0x6b, 0xeb, 0x4e, 0xc0, 0xcb, 0x65, 0xc8, 0x1b, 0xd9, 0x96, 0x1, 0x9, 0xcb, 0x36, 0xb5, 0x23, 0x24, 0x46, 0x56, 0xfd, 0x30, 0x68, 0x44, 0xec, 0xfd, 0x57, 0x44, 0xa4, 0x81, 0xb6, 0x17, 0x80, 0x0, 0x28, 0xc8, 0xb3, 0x73, 0x62}, 60 | }, 61 | } 62 | 63 | func TestAuthEncDecDeterministic(t *testing.T) { 64 | var zeroReader DevZero 65 | for _, tst := range authEncDecTests { 66 | // fmt.Printf("Testing deterministic enc/dec %v\n", tst) 67 | dst, err := AuthEnc(zeroReader, tst.key, tst.plaintext) 68 | if err != nil { 69 | t.Errorf("AuthEnc failed: %v", err) 70 | } 71 | if !bytes.Equal(dst, tst.expected) { 72 | t.Errorf("AuthEnc result doesn't match expected value, got\n%#v expected\n%#v", dst, tst.expected) 73 | } 74 | actualPlaintext, err := AuthDec(tst.key, dst) 75 | if err != nil { 76 | t.Errorf("AuthDec failed: %v", err) 77 | } 78 | if !bytes.Equal(tst.plaintext, actualPlaintext) { 79 | t.Errorf("Failed to decrypt, got %v", actualPlaintext) 80 | } 81 | 82 | wrongKey := append([]byte{}, tst.key...) 83 | wrongKey[0] = wrongKey[0] ^ 1 84 | _, err = AuthDec(wrongKey, dst) 85 | if err != AuthtagMismatch { 86 | t.Errorf("AuthDec didn't fail when wrong key was used") 87 | } 88 | 89 | wrongAuthTag := append([]byte{}, dst...) 90 | wrongAuthTag[len(wrongAuthTag)-1] ^= 1 91 | _, err = AuthDec(tst.key, wrongAuthTag) 92 | if err != AuthtagMismatch { 93 | t.Errorf("AuthDec didn't fail when wrong auth tag was used") 94 | } 95 | } 96 | } 97 | 98 | // Test AuthEnc and AuthDec with randomized IV. We run each test a number of 99 | // times and make sure that we never see the same ciphtertext. 100 | func TestAuthEncDec(t *testing.T) { 101 | for _, tst := range authEncDecTests { 102 | // fmt.Printf("Testing enc/dec %v\n", tst) 103 | dsts := map[string]bool{} 104 | for i := 0; i < 10; i++ { 105 | dst, err := AuthEnc(rand.Reader, tst.key, tst.plaintext) 106 | if err != nil { 107 | t.Errorf("AuthEnc failed: %v", err) 108 | } 109 | // fmt.Printf("Got dst %v\n", dst) 110 | if dsts[string(dst)] { 111 | t.Errorf("Got same dst twice, %v", dst) 112 | } 113 | dsts[string(dst)] = true 114 | actualPlaintext, err := AuthDec(tst.key, dst) 115 | if err != nil { 116 | t.Errorf("AuthDec failed: %v", err) 117 | } 118 | if !bytes.Equal(tst.plaintext, actualPlaintext) { 119 | t.Errorf("Failed to decrypt, got %v", actualPlaintext) 120 | } 121 | 122 | wrongKey := append([]byte{}, tst.key...) 123 | wrongKey[0] = wrongKey[0] ^ 1 124 | _, err = AuthDec(wrongKey, dst) 125 | if err != AuthtagMismatch { 126 | t.Errorf("AuthDec didn't fail when wrong key was used") 127 | } 128 | 129 | wrongAuthTag := append([]byte{}, dst...) 130 | wrongAuthTag[len(wrongAuthTag)-1] ^= 1 131 | _, err = AuthDec(tst.key, wrongAuthTag) 132 | if err != AuthtagMismatch { 133 | t.Errorf("AuthDec didn't fail when wrong auth tag was used") 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /internal/pkg/dh/dh.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | // 6 | 7 | // Package dh contains functions to perform a Diffie-Hellman key exchange over 8 | // the group Z^*_p for a prime p. 9 | package dh 10 | 11 | import ( 12 | "crypto/rand" 13 | "crypto/sha256" 14 | "math" 15 | "math/big" 16 | ) 17 | 18 | // Group represents the group Z^*_p. 19 | type Group struct { 20 | // Group generator. 21 | G *big.Int 22 | 23 | // Group order. 24 | P *big.Int 25 | } 26 | 27 | // Rfc3526_2048 is the 2048-bit MODP Group from RFC 3526. 28 | var Rfc3526_2048 Group 29 | 30 | func init() { 31 | p, ok := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16) 32 | if !ok { 33 | panic("big.Int SetString failed") 34 | } 35 | g := new(big.Int).SetInt64(2) 36 | Rfc3526_2048 = Group{G: g, P: p} 37 | } 38 | 39 | // Bytes returns the absolute value of x as a big-endian byte slice. The length 40 | // of the slice is padded with zeros so that the length of the returned slice is 41 | // always the same for a given group. 42 | func (g Group) Bytes(x *big.Int) []byte { 43 | z := new(big.Int) 44 | z.Mod(x, g.P) 45 | b := z.Bytes() 46 | bytelen := int(math.Ceil(float64(g.P.BitLen()) / 8)) 47 | padLen := bytelen - len(b) 48 | res := make([]byte, bytelen) 49 | copy(res[padLen:], b) 50 | return res 51 | } 52 | 53 | // IsInSmallSubgroup returns true if x belongs to a small subgroup of Z^*_p. 54 | // 55 | // Precondition: p is a safe prime (i.e., p is prime and (p-1)/2 is prime.). 56 | // 57 | // As p is a safe prime there are only three sizes of subgroups: one, two, and, 58 | // (p-1)/2 elements. The subgroups containing one and two elements are 59 | // considered to be small. 60 | func (g Group) IsInSmallSubgroup(x *big.Int) bool { 61 | if x.Cmp(big.NewInt(1)) == 0 { 62 | return true 63 | } 64 | pm1 := new(big.Int) 65 | pm1.Sub(g.P, big.NewInt(1)) 66 | if x.Cmp(pm1) == 0 { 67 | return true 68 | } 69 | return false 70 | } 71 | 72 | // IsInGroup returns true if x is in the group Z^*_p and false otherwise. 73 | func (g Group) IsInGroup(x *big.Int) bool { 74 | if big.NewInt(0).Cmp(x) != -1 || x.Cmp(g.P) != -1 { 75 | return false 76 | } 77 | return true 78 | } 79 | 80 | // GeneratePrivateKey generates a private key to be used in a Diffie-Hellman key 81 | // exchange in the group g. 82 | func (g Group) GeneratePrivateKey() (*big.Int, error) { 83 | for { 84 | key, err := rand.Int(rand.Reader, g.P) 85 | if err != nil { 86 | return nil, err 87 | } 88 | if key.Sign() != 0 { 89 | return key, nil 90 | } 91 | } 92 | } 93 | 94 | // GeneratePublicKey creates a public key which corresponds to the private key 95 | // privKey. 96 | func (g Group) GeneratePublicKey(privKey *big.Int) *big.Int { 97 | ret := new(big.Int) 98 | return ret.Exp(g.G, privKey, g.P) 99 | } 100 | 101 | // SharedSecret returns a byte slice which is the secret shared between two 102 | // peers who have performed a Diffie-Hellman key exchange. 103 | // 104 | // If priv1 and priv2 are two private keys generated by GeneratePrivateKey and 105 | // pub1 and pub2 are public keys created using GeneratePublicKey from priv1 and 106 | // priv2, respectively, then the byte slices returned by SharedSecret(priv1, 107 | // pub2) and SharedSecret(priv2, pub1) are identical. 108 | func (g Group) SharedSecret(privKey *big.Int, otherPubKey *big.Int) []byte { 109 | s := new(big.Int) 110 | s.Exp(otherPubKey, privKey, g.P) 111 | h := sha256.New() 112 | h.Write(s.Bytes()) 113 | return h.Sum(nil) 114 | } 115 | -------------------------------------------------------------------------------- /internal/pkg/dh/dh_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package dh 7 | 8 | import ( 9 | "bytes" 10 | "math/big" 11 | "testing" 12 | 13 | "github.com/go-test/deep" 14 | ) 15 | 16 | func TestDh(t *testing.T) { 17 | g := Rfc3526_2048 18 | privA, err := g.GeneratePrivateKey() 19 | if err != nil { 20 | panic(err) 21 | } 22 | pubA := g.GeneratePublicKey(privA) 23 | 24 | privB, err := g.GeneratePrivateKey() 25 | if err != nil { 26 | panic(err) 27 | } 28 | pubB := g.GeneratePublicKey(privB) 29 | 30 | sharedA := g.SharedSecret(privA, pubB) 31 | sharedB := g.SharedSecret(privB, pubA) 32 | if !bytes.Equal(sharedA, sharedB) { 33 | t.Fatalf("sharedA != sharedB") 34 | } 35 | } 36 | 37 | // isSafePrime returns true if x is probably a safe prime (i.e., p is prime and 38 | // (p-1)/2 is prime.). 39 | func isSafePrime(x *big.Int) bool { 40 | if !x.ProbablyPrime(100) { 41 | return false 42 | } 43 | q := new(big.Int) 44 | q.Sub(x, big.NewInt(1)) 45 | q.Div(q, big.NewInt(2)) 46 | return q.ProbablyPrime(100) 47 | } 48 | 49 | func TestIsSafePrime(t *testing.T) { 50 | // List from https://oeis.org/A005385 51 | for _, x := range []int64{5, 7, 11, 23, 47, 59, 83, 107, 167, 179, 227, 263, 347, 359, 383, 467, 479, 503, 563, 587, 719, 839, 863, 887, 983, 1019, 1187, 1283, 1307, 1319, 1367, 1439, 1487, 1523, 1619, 1823, 1907} { 52 | if !isSafePrime(big.NewInt(x)) { 53 | t.Fatalf("%v is safe but IsSafePrime returned false", x) 54 | } 55 | } 56 | for _, x := range []int64{17} { 57 | if isSafePrime(big.NewInt(x)) { 58 | t.Fatalf("%v is not safe but IsSafePrime returned false", x) 59 | } 60 | } 61 | 62 | if !isSafePrime(Rfc3526_2048.P) { 63 | t.Fatalf("Rfc3526_2048.P is not safe") 64 | } 65 | } 66 | 67 | func TestIsInSmallSubgroup(t *testing.T) { 68 | for _, x := range []int64{2, 3, 4, 5, 6, 7, 8, 9} { 69 | g := Group{G: big.NewInt(2), P: big.NewInt(11)} 70 | if g.IsInSmallSubgroup(big.NewInt(x)) { 71 | t.Fatalf("%v unexpectedly in small subgroup", x) 72 | } 73 | } 74 | for _, x := range []int64{1, 10} { 75 | g := Group{G: big.NewInt(2), P: big.NewInt(11)} 76 | if !g.IsInSmallSubgroup(big.NewInt(x)) { 77 | t.Fatalf("%v unexpectedly not in small subgroup", x) 78 | } 79 | } 80 | } 81 | 82 | func TestBytes(t *testing.T) { 83 | for _, tst := range []struct { 84 | x int64 85 | p int64 86 | b []byte 87 | }{ 88 | {0, 11, []byte{0}}, 89 | {5, 11, []byte{5}}, 90 | {300, 373, []byte{1, 44}}, 91 | {1, 373, []byte{0, 1}}, 92 | } { 93 | g := Group{G: big.NewInt(2), P: big.NewInt(tst.p)} 94 | actual := g.Bytes(big.NewInt(tst.x)) 95 | if diff := deep.Equal(actual, tst.b); diff != nil { 96 | t.Fatalf("diff: %v\n", diff) 97 | } 98 | } 99 | } 100 | 101 | func TestIsInGroup(t *testing.T) { 102 | for _, tst := range []struct { 103 | x int64 104 | p int64 105 | expected bool 106 | }{ 107 | {-1, 11, false}, 108 | {0, 11, false}, 109 | {1, 11, true}, 110 | {10, 11, true}, 111 | {11, 11, false}, 112 | {12, 11, false}, 113 | } { 114 | g := Group{G: big.NewInt(2), P: big.NewInt(tst.p)} 115 | actual := g.IsInGroup(big.NewInt(tst.x)) 116 | if actual != tst.expected { 117 | t.Fatalf("x=%v got %v", tst.x, actual) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /internal/pkg/util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | // Package util contains functions to simplify the example server and client in 7 | // cmd/. 8 | package util 9 | 10 | import ( 11 | "bufio" 12 | "crypto/rand" 13 | "encoding/base64" 14 | "fmt" 15 | 16 | "github.com/frekui/opaque/internal/pkg/authenc" 17 | ) 18 | 19 | func Write(w *bufio.Writer, data []byte) error { 20 | fmt.Printf("> %s\n", string(data)) 21 | w.Write(data) 22 | w.Write([]byte("\n")) 23 | if err := w.Flush(); err != nil { 24 | return err 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func Read(r *bufio.Reader) ([]byte, error) { 31 | fmt.Print("< ") 32 | data, err := r.ReadBytes('\n') 33 | if err != nil { 34 | return nil, err 35 | } 36 | fmt.Print(string(data)) 37 | return data[:len(data)-1], nil 38 | } 39 | 40 | func EncryptAndWrite(w *bufio.Writer, key []byte, plaintext string) error { 41 | ciphertext, err := authenc.AuthEnc(rand.Reader, key, []byte(plaintext)) 42 | if err != nil { 43 | return err 44 | } 45 | encoded := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext))) 46 | base64.StdEncoding.Encode(encoded, ciphertext) 47 | if err := Write(w, encoded); err != nil { 48 | return err 49 | } 50 | return nil 51 | } 52 | 53 | func ReadAndDecrypt(r *bufio.Reader, key []byte) (string, error) { 54 | encodedCiphertext, err := Read(r) 55 | if err != nil { 56 | return "", err 57 | } 58 | ciphertext := make([]byte, base64.StdEncoding.DecodedLen(len(encodedCiphertext))) 59 | n, err := base64.StdEncoding.Decode(ciphertext, encodedCiphertext) 60 | if err != nil { 61 | return "", err 62 | } 63 | ciphertext = ciphertext[:n] 64 | plaintext, err := authenc.AuthDec(key, ciphertext) 65 | if err != nil { 66 | return "", err 67 | } 68 | return string(plaintext), nil 69 | } 70 | -------------------------------------------------------------------------------- /pwreg.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Fredrik Kuivinen, frekui@gmail.com 2 | // 3 | // Use of this source code is governed by the BSD-style license that can be 4 | // found in the LICENSE file. 5 | 6 | package opaque 7 | 8 | // References: 9 | // OPAQUE: An Asymmetric PAKE Protocol Secure Against Pre-Computation Attacks, https://eprint.iacr.org/2018/163.pdf 10 | // https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-00 11 | // http://webee.technion.ac.il/~hugo/sigma-pdf.pdf 12 | 13 | import ( 14 | "crypto/rsa" 15 | "math/big" 16 | 17 | "github.com/frekui/opaque/internal/pkg/authenc" 18 | ) 19 | 20 | // The User struct is the state that the server needs to store for each 21 | // registered used. Values of this struct are created by PwReg3. 22 | type User struct { 23 | // Name of this user. 24 | Username string 25 | 26 | // OPRF key for this user. This is the salt. 27 | K *big.Int 28 | 29 | V *big.Int 30 | 31 | // EnvU and PubU are generated by the client during password 32 | // registration and stored at the server. 33 | EnvU []byte 34 | PubU *rsa.PublicKey 35 | } 36 | 37 | // PwRegServerSession keeps track of state needed on the server-side during a 38 | // run of the password registration protocol. 39 | type PwRegServerSession struct { 40 | username string 41 | k *big.Int 42 | v *big.Int 43 | } 44 | 45 | // PwRegClientSession keeps track of state needed on the client-side during a 46 | // run of the password registration protocol. 47 | type PwRegClientSession struct { 48 | a *big.Int 49 | 50 | // Random integer in [0..q-1]. Used when computing DF-OPRF. 51 | r *big.Int 52 | 53 | password string 54 | 55 | // Number of bits in RSA private key. 56 | bits int 57 | } 58 | 59 | // PwRegMsg1 is the first message during password registration. It is sent from 60 | // the client to the server. 61 | // 62 | // Users of package opaque does not need to read nor write to any fields in this 63 | // struct except to serialize and deserialize the struct when it's sent between 64 | // the peers in the authentication protocol. 65 | type PwRegMsg1 struct { 66 | Username string 67 | R *big.Int 68 | A *big.Int 69 | } 70 | 71 | // PwRegMsg2 is the second message in password registration. Sent from server to 72 | // client. 73 | // 74 | // Users of package opaque does not need to read nor write to any fields in this 75 | // struct except to serialize and deserialize the struct when it's sent between 76 | // the peers in the authentication protocol. 77 | type PwRegMsg2 struct { 78 | V *big.Int 79 | B *big.Int 80 | PubS *rsa.PublicKey 81 | } 82 | 83 | // PwRegMsg3 is the third and final message in password registration. Sent from 84 | // client to server. 85 | // 86 | // Users of package opaque does not need to read nor write to any fields in this 87 | // struct except to serialize and deserialize the struct when it's sent between 88 | // the peers in the authentication protocol. 89 | type PwRegMsg3 struct { 90 | EnvU []byte 91 | PubU *rsa.PublicKey 92 | } 93 | 94 | // PwRegInit initiates the password registration protocol. It's invoked by the 95 | // client. The bits argument specifies the number of bits that should be used in 96 | // the client-specific RSA key. 97 | // 98 | // On success a nil error is returned together with a client session and a 99 | // PwRegMsg1 struct. The PwRegMsg1 struct should be sent to the server. A 100 | // precondition of the password registration protocol is that it's running over 101 | // an authenticated connection. 102 | // 103 | // A non-nil error is returned on failure. 104 | // 105 | // See also PwReg1, PwReg2, and PwReg3. 106 | func PwRegInit(username, password string, bits int) (*PwRegClientSession, PwRegMsg1, error) { 107 | // From the I-D: 108 | // 109 | // U and S run OPRF(kU;PwdU) with only U learning the result, 110 | // denoted RwdU (mnemonics for "randomized password"). 111 | // 112 | // Protocol for computing DH-OPRF, U with input x and S with input k: 113 | // U: choose random r in [0..q-1], send a=H'(x)*g^r to S 114 | 115 | a, r, err := dhOprf1(password) 116 | if err != nil { 117 | return nil, PwRegMsg1{}, err 118 | } 119 | session := &PwRegClientSession{ 120 | a: a, 121 | r: r, 122 | password: password, 123 | bits: bits, 124 | } 125 | msg1 := PwRegMsg1{ 126 | Username: username, 127 | R: r, 128 | A: a, 129 | } 130 | 131 | return session, msg1, nil 132 | } 133 | 134 | // PwReg1 is the processing done by the server when it has received a PwRegMsg1 135 | // struct from a client. 136 | // 137 | // privS is the server's private RSA key. It can be the same for all users. 138 | // 139 | // A non-nil error is returned on failure. 140 | // 141 | // See also PwRegInit, PwReg2, and PwReg3. 142 | func PwReg1(privS *rsa.PrivateKey, msg1 PwRegMsg1) (*PwRegServerSession, PwRegMsg2, error) { 143 | // From the I-D: 144 | // 145 | // S chooses OPRF key kU (random and independent for each user U) and sets vU 146 | // = g^kU; it also chooses its own pair of private-public keys PrivS and PubS 147 | // for use with protocol KE (the server can use the same pair of keys with 148 | // multiple users), and sends PubS to U. 149 | // 150 | // S: upon receiving a value a, respond with v=g^k and b=a^k 151 | k, err := generateSalt() 152 | if err != nil { 153 | return nil, PwRegMsg2{}, err 154 | } 155 | // func dhOprf2(a, k *big.Int) (v *big.Int, b *big.Int) 156 | v, b, err := dhOprf2(msg1.A, k) 157 | if err != nil { 158 | return nil, PwRegMsg2{}, err 159 | } 160 | session := &PwRegServerSession{ 161 | username: msg1.Username, 162 | k: k, 163 | v: v, 164 | } 165 | msg2 := PwRegMsg2{V: v, B: b, PubS: &privS.PublicKey} 166 | return session, msg2, nil 167 | } 168 | 169 | // PwReg2 is invoked on the client when it has received a PwRegMsg2 struct from 170 | // the server. 171 | // 172 | // A non-nil error is returned on failure. 173 | // 174 | // See also PwRegInit, PwReg1, and PwReg3. 175 | func PwReg2(sess *PwRegClientSession, msg2 PwRegMsg2) (PwRegMsg3, error) { 176 | // From the I-D: 177 | // U: upon receiving values b and v, set the PRF output to H(x, v, b*v^{-r}) 178 | // 179 | // U generates an "envelope" EnvU defined as EnvU = AuthEnc(RwdU; PrivU, PubU, 180 | // PubS, vU) 181 | 182 | rwdU, err := dhOprf3(sess.password, msg2.V, msg2.B, sess.r) 183 | if err != nil { 184 | return PwRegMsg3{}, err 185 | } 186 | privU, err := rsa.GenerateKey(randr, sess.bits) 187 | if err != nil { 188 | return PwRegMsg3{}, err 189 | } 190 | env := envU{ 191 | privU: privU, 192 | pubS: msg2.PubS, 193 | } 194 | 195 | encodedEnvU := encodeEnvU(&env) 196 | encryptedEnvU, err := authenc.AuthEnc(randr, rwdU[:16], encodedEnvU) 197 | if err != nil { 198 | return PwRegMsg3{}, err 199 | } 200 | return PwRegMsg3{EnvU: encryptedEnvU, PubU: &privU.PublicKey}, nil 201 | } 202 | 203 | // PwReg3 is invoked on the server after it has received a PwRegMsg3 struct from 204 | // the client. 205 | // 206 | // The returned User struct should be stored by the server and associated with 207 | // the username. 208 | // 209 | // See also PwRegInit, PwReg1, and PwReg2. 210 | func PwReg3(sess *PwRegServerSession, msg3 PwRegMsg3) *User { 211 | // From the I-D: 212 | // 213 | // o U sends EnvU and PubU to S and erases PwdU, RwdU and all keys. 214 | // S stores (EnvU, PubS, PrivS, PubU, kU, vU) in a user-specific 215 | // record. If PrivS and PubS are used for different users, they can 216 | // be stored separately and omitted from the record. 217 | return &User{ 218 | Username: sess.username, 219 | K: sess.k, 220 | V: sess.v, 221 | EnvU: msg3.EnvU, 222 | PubU: msg3.PubU, 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | go vet 6 | go test ./... 7 | go build ./cmd/server 8 | go build ./cmd/client 9 | 10 | ./server > server.log & 11 | serverpid=$! 12 | cleanup() { 13 | if [ "$test_ok" != 1 ]; 14 | then 15 | echo Test failed. 16 | fi 17 | kill $serverpid 18 | } 19 | trap cleanup EXIT 20 | sleep 1 21 | 22 | ./client -pwreg -username foo -password bar > client-reg.log 23 | grep "Added user 'foo'" server.log || exit 1 24 | 25 | ./client -auth -username foo -password bar > client-ok.log 26 | sleep 1 27 | fgrep "Received 'Hi client!'" client-ok.log > /dev/null 28 | fgrep "Sending 'Hi server!'" client-ok.log > /dev/null 29 | fgrep "Received 'Hi server!'" server.log > /dev/null 30 | fgrep "Sending 'Hi client!'" server.log > /dev/null 31 | 32 | ./client -auth -username foo -password wrong >& client-not-ok.log && exit 1 33 | fgrep "auth: Authtag mismatch" client-not-ok.log > /dev/null 34 | 35 | set +x 36 | test_ok=1 37 | echo Test successful. 38 | --------------------------------------------------------------------------------