Skip to content

Instantly share code, notes, and snippets.

@Zenithar
Last active September 19, 2023 13:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zenithar/028390b6ad799eb5939dc0f1d1bb0aff to your computer and use it in GitHub Desktop.
Save Zenithar/028390b6ad799eb5939dc0f1d1bb0aff to your computer and use it in GitHub Desktop.
SPAKE2+ based on Ed25519 in Golang. - https://www.ietf.org/id/draft-bar-cfrg-spake2plus-08.html
package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"math/big"
"filippo.io/edwards25519"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/hkdf"
)
// Based on https://www.ietf.org/id/draft-bar-cfrg-spake2plus-08.html
const (
// M represents edwards25519 SPAKE2+ constant
M = "d048032c6ea0b6d697ddc2e86bda85a33adac920f1bf18e1b0c6d166a5cecdaf"
// N represents edwards25519 SPAKE2+ constant
N = "d3bfb518f44f3430f29d0c92af503865a1ed3281dc69b35dd868ba85f886c4ab"
)
var cofactor = new(big.Int).SetInt64(8)
func concat(bytesArray ...[]byte) []byte {
result := []byte{}
for _, bytes := range bytesArray {
if len(bytes) > 0 {
bytesLen := make([]byte, 8)
binary.LittleEndian.PutUint64(bytesLen, uint64(len(bytes)))
result = append(result, bytesLen...)
result = append(result, bytes...)
}
}
return result
}
func scalarFromBytes(in []byte) (*edwards25519.Scalar, error) {
return (&edwards25519.Scalar{}).SetBytesWithClamping(in[:])
}
func randomScalar() (*edwards25519.Scalar, error) {
var x [32]byte
if _, err := io.ReadFull(rand.Reader, x[:]); err != nil {
return nil, err
}
return scalarFromBytes(x[:])
}
func cofactorScalar() (*edwards25519.Scalar, error) {
buf := make([]byte, 32)
copy(buf[:1], cofactor.Bytes())
return scalarFromBytes(buf)
}
func mac(key, data []byte) []byte {
vmac := hmac.New(sha256.New, key)
vmac.Write(data)
return vmac.Sum(nil)
}
func deriveW(password, idProver, idVerifier, salt []byte) (w0, w1 *edwards25519.Scalar) {
// Derive password using Argon2id
pw := argon2.IDKey(
concat(password, idProver, idVerifier), // len(pw) || pw || len(idProver) || idProver || len(idVerifier) || idVerifier
salt, // Salt
3, // Time
64*1024, // Memory
4, // Threads
64, // Output length
)
var err error
w0, err = scalarFromBytes(pw[:32])
if err != nil {
panic(err)
}
w1, err = scalarFromBytes(pw[32:])
if err != nil {
panic(err)
}
return
}
type Prover struct {
m *edwards25519.Point
n *edwards25519.Point
h *edwards25519.Scalar
}
func (p *Prover) Registration(w1 *edwards25519.Scalar) (L *edwards25519.Point) {
L = (&edwards25519.Point{}).ScalarBaseMult(w1)
return
}
func (p *Prover) Init(w0 *edwards25519.Scalar) (x *edwards25519.Scalar, X *edwards25519.Point) {
var err error
// x <- [0, p-1]
x, err = randomScalar()
if err != nil {
panic(err)
}
// X = x*P + w0*M
X = (&edwards25519.Point{}).VarTimeDoubleScalarBaseMult(w0, p.m, x)
return
}
func (p *Prover) Finish(w0, w1, x *edwards25519.Scalar, Y *edwards25519.Point) (Z, V *edwards25519.Point) {
// Check subgroup membership
if (&edwards25519.Point{}).MultByCofactor(Y).Equal(edwards25519.NewIdentityPoint()) != 0 {
panic("invalid point")
}
// Compute shared values
tmp := (&edwards25519.Point{}).ScalarMult(w0, p.n)
tmp = tmp.Subtract(Y, tmp)
// Z = h*x*(Y - w0*N)
Z = (&edwards25519.Point{}).ScalarMult(x, tmp)
Z = Z.ScalarMult(p.h, Z)
// V = h*w1*(Y - w0*N)
V = (&edwards25519.Point{}).ScalarMult(w1, tmp)
V = V.ScalarMult(p.h, V)
return Z, V
}
type Verifier struct {
m *edwards25519.Point
n *edwards25519.Point
h *edwards25519.Scalar
}
func (v *Verifier) Finish(w0 *edwards25519.Scalar, L, X *edwards25519.Point) (Y, Z, V *edwards25519.Point) {
// Check subgroup membership
if (&edwards25519.Point{}).MultByCofactor(X).Equal(edwards25519.NewIdentityPoint()) != 0 {
panic("invalid point")
}
// Compute verifier key share
// y <- [0, p-1]
y, err := randomScalar()
if err != nil {
panic(err)
}
// Y = y*P + w0*N
Y = (&edwards25519.Point{}).VarTimeDoubleScalarBaseMult(w0, v.n, y)
// Compute shared values
// Z = h*y*(X - w0*M)
tmp := (&edwards25519.Point{}).ScalarMult(w0, v.m)
tmp = tmp.Subtract(X, tmp)
Z = (&edwards25519.Point{}).ScalarMult(y, tmp)
Z = Z.ScalarMult(v.h, Z)
// V = h*y*L
V = (&edwards25519.Point{}).ScalarMult(y, L)
V = V.ScalarMult(v.h, V)
return Y, Z, V
}
func ComputeKeySchedule(TT []byte) (KconfirmP, KconfirmV, Kshared []byte) {
// K_main = Hash(TT)
// K_confirmP || K_confirmV = KDF(nil, K_main, "ConfirmationKeys")
ckdf := hkdf.New(sha256.New, TT, nil, []byte("ConfirmationKeys"))
confirmationKeys := make([]byte, 64)
if _, err := io.ReadFull(ckdf, confirmationKeys); err != nil {
panic(err)
}
// K_shared = KDF(nil, K_main, "SharedKey")
skdf := hkdf.New(sha256.New, TT, nil, []byte("SharedKey"))
sharedKey := make([]byte, 32)
if _, err := io.ReadFull(skdf, sharedKey); err != nil {
panic(err)
}
return confirmationKeys[:32], confirmationKeys[32:], sharedKey
}
func main() {
context := []byte("SPAKE2+-ED25519-SHA256-HKDF-SHA256-HMAC-SHA256")
idVerifier := []byte("server")
idProver := []byte("client")
password := []byte("very-secret-password")
salt := []byte("")
Mraw, _ := hex.DecodeString(M)
m, _ := (&edwards25519.Point{}).SetBytes(Mraw)
Nraw, _ := hex.DecodeString(N)
n, _ := (&edwards25519.Point{}).SetBytes(Nraw)
h, _ := cofactorScalar()
prover := &Prover{m, n, h}
verifier := &Verifier{m, n, h}
// Compute initial components
w0, w1 := deriveW(password, idProver, idVerifier, salt)
fmt.Println("* Registration")
fmt.Println("** Client")
fmt.Printf("w0: %x\n", w0.Bytes())
fmt.Printf("w1: %x\n", w1.Bytes())
L := prover.Registration(w1)
fmt.Printf("L: %x\n", L.Bytes())
fmt.Printf("Client -> Server - w0:%x / L:%x\n", w0.Bytes(), L.Bytes())
fmt.Println("\n* Authentication")
fmt.Println("** Client")
x, X := prover.Init(w0)
fmt.Printf("x: %x\n", x.Bytes())
fmt.Printf("X: %x\n", X.Bytes())
fmt.Printf("Client -> Server - X: %x\n", X.Bytes())
fmt.Println("** Server")
Y, Zv, Vv := verifier.Finish(w0, L, X)
fmt.Printf("Y: %x\n", Y.Bytes())
fmt.Printf("Z: %x\n", Zv.Bytes())
fmt.Printf("V: %x\n", Vv.Bytes())
TT := concat(context, idProver, idVerifier, Mraw, Nraw, X.Bytes(), Y.Bytes(), Zv.Bytes(), Vv.Bytes(), w0.Bytes())
fmt.Printf("TT: %x\n", TT)
KconfirmP, KconfirmV, sharedKey := ComputeKeySchedule(TT)
fmt.Printf("K_confirmV: %x\n", KconfirmV)
confirmV := mac(KconfirmV, Y.Bytes())
fmt.Printf("Server -> Client - Y: %x, confirmV: %x\n", Y.Bytes(), confirmV)
fmt.Println("** Client")
Zp, Vp := prover.Finish(w0, w1, x, Y)
fmt.Printf("Z: %x\n", Zp.Bytes())
fmt.Printf("V: %x\n", Vp.Bytes())
TT = concat(context, idProver, idVerifier, Mraw, Nraw, X.Bytes(), Y.Bytes(), Zp.Bytes(), Vp.Bytes(), w0.Bytes())
fmt.Printf("TT: %x\n", TT)
KconfirmP, KconfirmV, sharedKey = ComputeKeySchedule(TT)
fmt.Printf("K_confirmV: %x\n", KconfirmV)
if subtle.ConstantTimeCompare(confirmV, mac(KconfirmV, Y.Bytes())) != 1 {
panic("invalid verifier confirmation")
}
confirmP := mac(KconfirmP, X.Bytes())
fmt.Printf("Client -> Server - confirmP: %x\n", confirmP)
fmt.Println("** Server")
TT = concat(context, idProver, idVerifier, Mraw, Nraw, X.Bytes(), Y.Bytes(), Zv.Bytes(), Vv.Bytes(), w0.Bytes())
fmt.Printf("TT: %x\n", TT)
KconfirmP, KconfirmV, sharedKey = ComputeKeySchedule(TT)
fmt.Printf("K_confirmP: %x\n", KconfirmP)
if subtle.ConstantTimeCompare(confirmP, mac(KconfirmP, X.Bytes())) != 1 {
panic("invalid prover proof")
}
fmt.Println("Server -> Client - session established")
fmt.Println("\n** Both")
fmt.Printf("K_shared: %x\n", sharedKey)
}
* Registration
** Client
w0: 8fd2fb71907f9c7eeb82637e203dc4b1a2a2f7dfbf689a6ca9f45482e09d9f06
w1: 221850bda179f8b4aa5046261b9cd65ac63526eaf633304c7bbfcfb143e7d902
L: 930003f70927ce702ffbc893ed66a3458f0a1814810252f6a7576ac66e6222ba
Client -> Server - w0:8fd2fb71907f9c7eeb82637e203dc4b1a2a2f7dfbf689a6ca9f45482e09d9f06 / L:930003f70927ce702ffbc893ed66a3458f0a1814810252f6a7576ac66e6222ba
* Authentication
** Client
x: 7756cd5312db5c35a61a903fd195fa76796a2666f0df957f4ea437c840da410f
X: bac0d8b3fd25d7360c5fb41106574b4f23c7eecfb35fcde1aadb64a0e025d30d
Client -> Server - X: bac0d8b3fd25d7360c5fb41106574b4f23c7eecfb35fcde1aadb64a0e025d30d
** Server
Y: 7d80767084ea3edca7eee132461f869bb7f286d534ece4df5a0c9c1657938b34
Z: 89b9b33ff4f221e53745c12774bcbbcf143e10cd5811ae4912fed00cbdfa5da5
V: 724c81a42b75ee7242a19bfc909f59cb424dd1de22263f153fa76e5f246f4e7e
TT: 2e000000000000005350414b45322b2d454432353531392d5348413235362d484b44462d5348413235362d484d41432d5348413235360600000000000000636c69656e7406000000000000007365727665722000000000000000d048032c6ea0b6d697ddc2e86bda85a33adac920f1bf18e1b0c6d166a5cecdaf2000000000000000d3bfb518f44f3430f29d0c92af503865a1ed3281dc69b35dd868ba85f886c4ab2000000000000000bac0d8b3fd25d7360c5fb41106574b4f23c7eecfb35fcde1aadb64a0e025d30d20000000000000007d80767084ea3edca7eee132461f869bb7f286d534ece4df5a0c9c1657938b34200000000000000089b9b33ff4f221e53745c12774bcbbcf143e10cd5811ae4912fed00cbdfa5da52000000000000000724c81a42b75ee7242a19bfc909f59cb424dd1de22263f153fa76e5f246f4e7e20000000000000008fd2fb71907f9c7eeb82637e203dc4b1a2a2f7dfbf689a6ca9f45482e09d9f06
K_confirmV: 726ba7ad4415096a1dd79170917646a2b15776c7ee28b59bc86baf3cdbc30af0
Server -> Client - Y: 7d80767084ea3edca7eee132461f869bb7f286d534ece4df5a0c9c1657938b34, confirmV: 8ecbcb5a85ae8b0a9a3d2ca8b283b6ec2bc73d97bed134f6a4b6365e36cbf58e
** Client
Z: 89b9b33ff4f221e53745c12774bcbbcf143e10cd5811ae4912fed00cbdfa5da5
V: 724c81a42b75ee7242a19bfc909f59cb424dd1de22263f153fa76e5f246f4e7e
TT: 2e000000000000005350414b45322b2d454432353531392d5348413235362d484b44462d5348413235362d484d41432d5348413235360600000000000000636c69656e7406000000000000007365727665722000000000000000d048032c6ea0b6d697ddc2e86bda85a33adac920f1bf18e1b0c6d166a5cecdaf2000000000000000d3bfb518f44f3430f29d0c92af503865a1ed3281dc69b35dd868ba85f886c4ab2000000000000000bac0d8b3fd25d7360c5fb41106574b4f23c7eecfb35fcde1aadb64a0e025d30d20000000000000007d80767084ea3edca7eee132461f869bb7f286d534ece4df5a0c9c1657938b34200000000000000089b9b33ff4f221e53745c12774bcbbcf143e10cd5811ae4912fed00cbdfa5da52000000000000000724c81a42b75ee7242a19bfc909f59cb424dd1de22263f153fa76e5f246f4e7e20000000000000008fd2fb71907f9c7eeb82637e203dc4b1a2a2f7dfbf689a6ca9f45482e09d9f06
K_confirmV: 726ba7ad4415096a1dd79170917646a2b15776c7ee28b59bc86baf3cdbc30af0
Client -> Server - confirmP: 1344fbd496f5978427842321542b80a1ea6d40362da6a5aa27e9873892b3ad65
** Server
TT: 2e000000000000005350414b45322b2d454432353531392d5348413235362d484b44462d5348413235362d484d41432d5348413235360600000000000000636c69656e7406000000000000007365727665722000000000000000d048032c6ea0b6d697ddc2e86bda85a33adac920f1bf18e1b0c6d166a5cecdaf2000000000000000d3bfb518f44f3430f29d0c92af503865a1ed3281dc69b35dd868ba85f886c4ab2000000000000000bac0d8b3fd25d7360c5fb41106574b4f23c7eecfb35fcde1aadb64a0e025d30d20000000000000007d80767084ea3edca7eee132461f869bb7f286d534ece4df5a0c9c1657938b34200000000000000089b9b33ff4f221e53745c12774bcbbcf143e10cd5811ae4912fed00cbdfa5da52000000000000000724c81a42b75ee7242a19bfc909f59cb424dd1de22263f153fa76e5f246f4e7e20000000000000008fd2fb71907f9c7eeb82637e203dc4b1a2a2f7dfbf689a6ca9f45482e09d9f06
K_confirmP: 87010f3475802d0a9d28389477263b0cdbe1f8f84db5fa9e0ed86fb75fc4884d
Server -> Client - session established
** Both
K_shared: a5b86d0469711195958b0456993de35fcdcc62950e67f688e41609e2c1ba74ff
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment