This should prevent detection of successful password guess try.
Thanks to Watson Ladd for the suggestion!
Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
@item Password authenticated key agreement
DH-A-EKE powered by @url{http://cr.yp.to/ecdh.html, Curve25519}
and @url{http://ed25519.cr.yp.to/, Ed25519}
+@item DH elliptic-curve point encoding for public keys
+@url{http://elligator.cr.yp.to/, Elligator}
@item Key derivation function for verifier generation
@url{https://en.wikipedia.org/wiki/PBKDF2, PBKDF2} based on
@url{https://en.wikipedia.org/wiki/SHA-2, SHA-512}
This manual is for GoVPN -- simple secure free software virtual private
network (VPN) daemon, written entirely on Go programming language.
-Copyright @copyright{} 2014-2015 Sergey Matveev @email{stargrave@@stargrave.org}
+Copyright @copyright{} 2014-2015 @email{stargrave@@stargrave.org, Sergey Matveev}
@quotation
Permission is granted to copy, distribute and/or modify this document
@item
Client generates DH keypair: @code{CDHPub} and @code{CDHPriv}.
Also it generates random 64-bit @code{R} that is used as a nonce for
-symmetric encryption.
+symmetric encryption. @code{El()} is Elligator point encoding algorithm.
@end enumerate
@strong{Interaction stage}:
@enumerate
@item
-@verb{|R + enc(H(DSAPub), R, CDHPub) + IDtag -> Server|} [48 bytes]
+@verb{|R + enc(H(DSAPub), R, El(CDHPub)) + IDtag -> Server|} [48 bytes]
@item
@itemize @bullet
@item Server remembers client address.
-@item Decrypts @code{CDHPub}.
+@item Decrypts @code{El(CDHPub)}.
+@item Inverts @code{El()} encoding and gets @code{CDHPub}.
@item Generates DH keypair: @code{SDHPriv}/@code{SDHPub}.
@item Computes common shared key @code{K = H(DH(SDHPriv, CDHPub))}.
@item Generates 64-bit random number @code{RS}.
@end itemize
@item
-@verb{|enc(H(DSAPub), R+1, SDHPub) + enc(K, R, RS + SS) + IDtag -> Client|} [80 bytes]
+@verb{|enc(H(DSAPub), R+1, El(SDHPub)) + enc(K, R, RS + SS) + IDtag -> Client|} [80 bytes]
@item
@itemize @bullet
-@item Client decrypts @code{SDHPub}.
+@item Client decrypts @code{El(SDHPub)}.
+@item Inverts @code{El()} encoding and gets @code{SDHPub}.
@item Computes @code{K}.
@item Decrypts @code{RS} and @code{SS}.
@item Remembers @code{SS}.
Client -> Client : R=rand(64bit)
Client -> Client : CDHPriv=rand(256bit)
-Client -> Server : R, enc(H(DSAPub), R, CDHPub)
+Client -> Server : R, enc(H(DSAPub), R, El(CDHPub))
Server -> Server : SDHPriv=rand(256bit)
Server -> Server : K=H(DH(SDHPriv, CDHPub))
Server -> Server : RS=rand(64bit)
Server -> Server : SS=rand(256bit)
-Server -> Client : enc(H(DSAPub), R+1, SDHPub); enc(K, R, RS+SS)
+Server -> Client : enc(H(DSAPub), R+1, El(SDHPub)); enc(K, R, RS+SS)
Client -> Client : K=H(DH(CDHPriv, SDHPub))
Client -> Client : RC=rand(64bit); SC=rand(256bit)
Client -> Server : enc(K, R+1, RS+RC+SC+Sign(DSAPriv, K))
exchange, EKE, Diffie-Hellman, DH, DH-EKE, Augmented EKE, A-EKE,
security, encrypted key exchange, 128-bit margin, DPI, censorship,
resistance, free software, GPLv3+, reviewability, easy, simple,
-Curve25519, Ed25519, SHA-512, Salsa20, Poly1305, AEAD, XTEA, PBKDF2,
+Curve25519, Ed25519, Elligator, SHA-512, Salsa20, Poly1305, AEAD, XTEA, PBKDF2,
PRP, signatures, asymmetric cryptography, zero-knowledge password proof,
PAKE, password, passphrase, password authenticated key exchange, perfect
forward secrecy, PFS, MAC, nonce, verifier, rehandshake, heartbeat,
hash function,
@url{https://en.wikipedia.org/wiki/Encrypted_key_exchange,
Diffie-Hellman Augmented Encrypted Key Exchange}
-(DH-A-EKE) powered by @url{http://cr.yp.to/ecdh.html, Curve25519} and
-@url{http://ed25519.cr.yp.to/, Ed25519} signatures.
+(DH-A-EKE) powered by @url{http://cr.yp.to/ecdh.html, Curve25519},
+@url{http://ed25519.cr.yp.to/, Ed25519} signatures and
+@url{http://elligator.cr.yp.to/, Elligator} curve-point encoding.
Strong
@url{https://en.wikipedia.org/wiki/Zero-knowledge_password_proof, zero-knowledge}
mutual authentication with key exchange stage is invulnerable
@itemize @bullet
@item
@url{https://www.schneier.com/books/applied_cryptography/, Applied Cryptography}
-@copyright{} 1996 Bruce Schneier
+@copyright{} 1996 Bruce Schneier.
@item
@url{http://tnlandforms.us/cns05/speke.pdf, Strong Password-Only Authenticated Key Exchange}
-@copyright{} 1996 David P. Jablon
+@copyright{} 1996 David P. Jablon.
@item
@url{https://www.cs.columbia.edu/~smb/papers/aeke.pdf, Augmented Encrypted Key Exchange}:
a Password-Based Protocol Secure Against Dictionary Attacks and Password
-File Compromise @copyright{} Steven M. Belloving, Michael Merrit
-@item @url{http://cr.yp.to/ecdh.html, A state-of-the-art Diffie-Hellman function}
-@item @url{http://cr.yp.to/snuffle.html, Snuffle 2005: the Salsa20 encryption function}
-@item @url{http://cr.yp.to/mac.html, A state-of-the-art message-authentication code}
-@item @url{http://ed25519.cr.yp.to/, Ed25519: high-speed high-security signatures}
+File Compromise @copyright{} Steven M. Belloving, Michael Merrit.
+@item @url{http://cr.yp.to/ecdh.html, A state-of-the-art Diffie-Hellman function}.
+@item @url{http://cr.yp.to/snuffle.html, Snuffle 2005: the Salsa20 encryption function}.
+@item @url{http://cr.yp.to/mac.html, A state-of-the-art message-authentication code}.
+@item @url{http://ed25519.cr.yp.to/, Ed25519: high-speed high-security signatures}.
+@item @email{watsonbladd@@gmail.com, Watson Ladd} for suggestion of
+ @url{http://elligator.cr.yp.to/, Elligator} encoding.
@end itemize
"time"
"github.com/agl/ed25519"
+ "github.com/agl/ed25519/extra25519"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/salsa20"
"golang.org/x/crypto/salsa20/salsa"
return nonce
}
-func dhPrivGen() *[32]byte {
- dh := new([32]byte)
- if _, err := rand.Read(dh[:]); err != nil {
- panic("Can not read random for DH private key")
+func dhKeypairGen() (*[32]byte, *[32]byte) {
+ priv := new([32]byte)
+ pub := new([32]byte)
+ repr := new([32]byte)
+ reprFound := false
+ for !reprFound {
+ if _, err := rand.Read(priv[:]); err != nil {
+ panic("Can not read random for DH private key")
+ }
+ reprFound = extra25519.ScalarBaseMult(pub, repr, priv)
}
- return dh
+ return priv, repr
}
func dhKeyGen(priv, pub *[32]byte) *[32]byte {
func HandshakeStart(conf *PeerConf, conn *net.UDPConn, addr *net.UDPAddr) *Handshake {
state := HandshakeNew(addr, conf)
- state.dhPriv = dhPrivGen()
- dhPub := new([32]byte)
- curve25519.ScalarBaseMult(dhPub, state.dhPriv)
+ var dhPubRepr *[32]byte
+ state.dhPriv, dhPubRepr = dhKeypairGen()
state.rNonce = new([RSize]byte)
if _, err := rand.Read(state.rNonce[:]); err != nil {
panic("Can not read random for handshake nonce")
}
enc := make([]byte, 32)
- salsa20.XORKeyStream(enc, dhPub[:], state.rNonce[:], state.dsaPubH)
+ salsa20.XORKeyStream(enc, dhPubRepr[:], state.rNonce[:], state.dsaPubH)
data := append(state.rNonce[:], enc...)
data = append(data, idTag(state.Conf.Id, state.rNonce[:])...)
if _, err := conn.WriteTo(data, addr); err != nil {
// will be created and used as a transport. If no mutually
// authenticated Peer is ready, then return nil.
func (h *Handshake) Server(conn *net.UDPConn, data []byte) *Peer {
- // R + ENC(H(DSAPub), R, CDHPub) + IDtag
+ // R + ENC(H(DSAPub), R, El(CDHPub)) + IDtag
if len(data) == 48 && h.rNonce == nil {
- // Generate private DH key
- h.dhPriv = dhPrivGen()
- dhPub := new([32]byte)
- curve25519.ScalarBaseMult(dhPub, h.dhPriv)
+ // Generate DH keypair
+ var dhPubRepr *[32]byte
+ h.dhPriv, dhPubRepr = dhKeypairGen()
h.rNonce = new([RSize]byte)
copy(h.rNonce[:], data[:RSize])
// Decrypt remote public key and compute shared key
- dec := new([32]byte)
- salsa20.XORKeyStream(dec[:], data[RSize:RSize+32], h.rNonce[:], h.dsaPubH)
- h.key = dhKeyGen(h.dhPriv, dec)
+ cDHRepr := new([32]byte)
+ salsa20.XORKeyStream(
+ cDHRepr[:],
+ data[RSize:RSize+32],
+ h.rNonce[:],
+ h.dsaPubH,
+ )
+ cDH := new([32]byte)
+ extra25519.RepresentativeToPublicKey(cDH, cDHRepr)
+ h.key = dhKeyGen(h.dhPriv, cDH)
encPub := make([]byte, 32)
- salsa20.XORKeyStream(encPub, dhPub[:], h.rNonceNext(1), h.dsaPubH)
+ salsa20.XORKeyStream(encPub, dhPubRepr[:], h.rNonceNext(1), h.dsaPubH)
// Generate R* and encrypt them
h.rServer = new([RSize]byte)
// authenticated Peer is ready, then return nil.
func (h *Handshake) Client(conn *net.UDPConn, data []byte) *Peer {
switch len(data) {
- case 80: // ENC(H(DSAPub), R+1, SDHPub) + ENC(K, R, RS + SS) + IDtag
+ case 80: // ENC(H(DSAPub), R+1, El(SDHPub)) + ENC(K, R, RS + SS) + IDtag
if h.key != nil {
log.Println("Invalid handshake stage from", h.addr)
return nil
}
// Decrypt remote public key and compute shared key
- dec := new([32]byte)
- salsa20.XORKeyStream(dec[:], data[:32], h.rNonceNext(1), h.dsaPubH)
- h.key = dhKeyGen(h.dhPriv, dec)
+ sDHRepr := new([32]byte)
+ salsa20.XORKeyStream(sDHRepr[:], data[:32], h.rNonceNext(1), h.dsaPubH)
+ sDH := new([32]byte)
+ extra25519.RepresentativeToPublicKey(sDH, sDHRepr)
+ h.key = dhKeyGen(h.dhPriv, sDH)
// Decrypt Rs
decRs := make([]byte, RSize+SSize)