all repos — lindell @ 9c0fef24a858273ba20a052eff08d6abdfdb5a60

Anonymous Authentication after Lindell

init commit
Raphael
Fri, 03 Jul 2020 00:28:36 +0200
commit

9c0fef24a858273ba20a052eff08d6abdfdb5a60

3 files changed, 205 insertions(+), 0 deletions(-)

jump to
A Readme.md

@@ -0,0 +1,12 @@

+Anonymous Authentication +======================== + +This software implements the anonymous authentication as described by Lindell, page 12 +(https://www.blackhat.com/presentations/bh-usa-07/Lindell/Whitepaper/bh-usa-07-lindell-WP.pdf). + +It uses the well known NaCl library for public key cryptography. To verify the client's anonymity in step 4, +the cypher text must be deterministic. For this reason, a deterministic random number generator is used. + +## What's the use? + +Please refer to above paper to get an overview of what anonymous authentication is.
A go.mod

@@ -0,0 +1,5 @@

+module lindell + +go 1.14 + +require golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
A main.go

@@ -0,0 +1,188 @@

+package main + +import ( + "bytes" + "crypto/rand" + "crypto/sha512" + "errors" + "fmt" + "golang.org/x/crypto/nacl/box" + "io" + mathRand "math/rand" +) + +func main() { + numberParticipants := 4 + + // Generate n public/private key pairs + pubs, privs, err := genKeyPairs(numberParticipants) + if err != nil { + fmt.Printf("generating key pairs: %s", err.Error()) + return + } + + // Protocol step 1 + // Generate a random string w + w := make([]byte, 8) + _, err = rand.Read(w) + if err != nil { + fmt.Printf("generating random w: %s", err.Error()) + return + } + // add magic bytes to identify correct w on client side + w = append([]byte("li"), w...) + fmt.Printf("Server random string w: %x\n", w) + + // For every participant, generate a random coin + coins := make([][]byte, numberParticipants) + for i := 0; i < numberParticipants; i++ { + c := make([]byte, 8) + _, err = rand.Read(c) + if err != nil { + fmt.Printf("generating random coin: %s", err.Error()) + return + } + coins[i] = c + } + + // Encrypt w with coin for every public key + encMessages, err := encrypt(pubs, w, coins) + if err != nil { + fmt.Printf("generating random w: %s\n", err.Error()) + return + } + + // Protocol step 2 + // prepare client + c := client{ + allPublicKeys: pubs, + privateKey: privs[mathRand.Intn(numberParticipants)], // Choose random participant to proof anonymity + encMessages: encMessages, + } + + wDash, err := c.decrypt() + if err != nil { + fmt.Printf("client-side decrypt: %s\n", err.Error()) + return + } + fmt.Printf("Client w': %x\n", wDash) + + // Protocol step 3 + // compare w and wDash + if !bytes.Equal(w, wDash) { + fmt.Println("Access denied") + return + } + fmt.Println("Access granted") + + // Protocol step 4 + // Verify server did not cheat on client + // insert the following code snippet to pretend a cheat + // coins[0] = []byte("cheat") + err = c.verify(coins) + if err != nil { + fmt.Printf("client verification: %s\n", err.Error()) + return + } + fmt.Println("Verification successful") +} + +type client struct { + allPublicKeys []*[32]byte + privateKey *[32]byte + encMessages [][]byte + wDash []byte +} + +func genKeyPairs(n int) ([]*[32]byte, []*[32]byte, error) { + pubOut := make([]*[32]byte, n) + privOut := make([]*[32]byte, n) + for i := 0; i < n; i++ { + pub, priv, err := box.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, err + } + pubOut[i] = pub + privOut[i] = priv + } + return pubOut, privOut, nil +} + +func encrypt(pubs []*[32]byte, w []byte, coins [][]byte) ([][]byte, error) { + if len(pubs) != len(coins) { + return nil, errors.New("length public keys and coins do not match") + } + + out := make([][]byte, len(pubs)) + for cnt, p := range pubs { + enc, err := box.SealAnonymous(nil, append(w, coins[cnt]...), p, newDetermRand([]byte("deterministic"))) + if err != nil { + return nil, err + } + out[cnt] = enc + } + + return out, nil +} + +func (c *client) decrypt() ([]byte, error) { + if len(c.allPublicKeys) != len(c.encMessages) { + return nil, errors.New("length of public keys and encrypted messages do not match") + } + + for cnt, p := range c.allPublicKeys { + decrypted, ok := box.OpenAnonymous(nil, c.encMessages[cnt], p, c.privateKey) + if !ok { + continue + } + // Verify magic byte + if bytes.HasPrefix(decrypted, []byte("li")) { + c.wDash = decrypted[:10] + return decrypted[:10], nil + } + } + + return nil, errors.New("decryption not possible") +} + +func (c client) verify(coins [][]byte) error { + if len(c.allPublicKeys) != len(coins) || len(coins) != len(c.encMessages) { + return errors.New("length of public keys and encrypted messages do not match") + } + + for cnt, p := range c.allPublicKeys { + enc, err := box.SealAnonymous(nil, append(c.wDash, coins[cnt]...), p, newDetermRand([]byte("deterministic"))) + if err != nil { + return err + } + if !bytes.Equal(enc, c.encMessages[cnt]) { + fmt.Println("CHEAT") + return errors.New("it appears the server cheated on us") + } + } + return nil +} + +// As suggested by jpillora https://gist.github.com/jpillora/5a0471b246d541b984ab +func newDetermRand(seed []byte) io.Reader { + return &determRand{next: seed} +} + +type determRand struct { + next []byte +} + +func (d *determRand) cycle() []byte { + result := sha512.Sum512(d.next) + d.next = result[:sha512.Size/2] + return result[sha512.Size/2:] +} + +func (d *determRand) Read(b []byte) (int, error) { + n := 0 + for n < len(b) { + out := d.cycle() + n += copy(b[n:], out) + } + return n, nil +}