TECHNICAL INTERNALS
+Nonce encryption: XTEA
Encryption: Salsa20
Message authentication: Poly1305
Password authenticated key agreement: Curve25519 based DH-EKE
Transport protocol
- SERIAL + ENC(KEY, SERIAL, DATA) + AUTH(SERIAL + ENC_DATA)
+ ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA) + AUTH(ENCn(SERIAL) + ENC_DATA)
-where SERIAL is message serial number. Odds are reserved for
-client->server, evens are for server->client. SERIAL is used as a nonce
-for DATA encryption: encryption key is different during each handshake,
-so (key, nonce) pair is always used once.
+Each transport message is indistinguishable from pseudo random noise.
+
+SERIAL is an encrypted message serial number. Odds are reserved for
+client(→server) messages, evens for server(→client) messages.
+
+ENCn is XTEA block cipher algorithm used here as PRP (pseudo random
+permutation) to randomize, obfuscate SERIAL. Plaintext SERIAL state is
+kept in peers internal state, but encrypted before transmission. XTEA is
+compact and fast enough. Salsa20 is PRF function and requires much more
+code to create PRP from it. XTEA's encryption key is the first 128-bit
+of Salsa20's output with established common key and zero nonce (message
+nonces start from 1).
+
+Encrypted SERIAL is used as a nonce for DATA encryption: encryption key
+is different during each handshake, so (key, nonce) pair is always used
+only once.
We generate Salsa20's output using this key and nonce for each message:
* first 256 bits are used as a one-time key for Poly1305 authentication
"golang.org/x/crypto/poly1305"
"golang.org/x/crypto/salsa20"
+ "golang.org/x/crypto/xtea"
)
var (
}
type Peer struct {
- addr *net.UDPAddr
- key *[KeySize]byte // encryption key
- nonceOur uint64 // nonce for our messages
- nonceRecv uint64 // latest received nonce from remote peer
+ addr *net.UDPAddr
+ key *[KeySize]byte // encryption key
+ nonceOur uint64 // nonce for our messages
+ nonceRecv uint64 // latest received nonce from remote peer
+ nonceCipher *xtea.Cipher // nonce cipher
}
type UDPPkt struct {
udpSinkReady <- true
continue
}
- nonceRecv, _ = binary.Uvarint(udpPktData[:8])
- if nonceRecv < peer.nonceRecv-noncediff {
- fmt.Print("R")
- udpSinkReady <- true
- continue
- }
copy(buf[:KeySize], emptyKey)
copy(tag[:], udpPktData[udpPkt.size-poly1305.TagSize:])
copy(buf[S20BS:], udpPktData[NonceSize:udpPkt.size-poly1305.TagSize])
fmt.Print("T")
continue
}
+ peer.nonceCipher.Decrypt(buf, udpPktData[:NonceSize])
+ nonceRecv, _ = binary.Uvarint(buf[:NonceSize])
+ if nonceRecv < peer.nonceRecv-noncediff {
+ fmt.Print("R")
+ udpSinkReady <- true
+ continue
+ }
udpSinkReady <- true
peer.nonceRecv = nonceRecv
timeouts = 0
ethSinkReady <- true
continue
}
+
peer.nonceOur = peer.nonceOur + 2
+ for i := 0; i < NonceSize; i++ {
+ nonce[i] = '\x00'
+ }
binary.PutUvarint(nonce, peer.nonceOur)
+ peer.nonceCipher.Encrypt(nonce, nonce)
+
copy(buf[:KeySize], emptyKey)
if ethPktSize > -1 {
copy(buf[S20BS:], ethBuf[:ethPktSize])
"golang.org/x/crypto/poly1305"
"golang.org/x/crypto/salsa20"
"golang.org/x/crypto/salsa20/salsa"
+ "golang.org/x/crypto/xtea"
)
type Handshake struct {
return k
}
+func NewNonceCipher(key *[32]byte) *xtea.Cipher {
+ nonceKey := make([]byte, 16)
+ salsa20.XORKeyStream(nonceKey, make([]byte, 32), make([]byte, 8), key)
+ ciph, err := xtea.NewCipher(nonceKey)
+ if err != nil {
+ panic(err)
+ }
+ return ciph
+}
+
// Check if it is valid handshake-related message
// Minimal size and last 16 zero bytes
func isValidHandshakePkt(pkt []byte) bool {
// Switch peer
peer := Peer{
- addr: h.addr,
- nonceOur: noncediff + 0,
+ addr: h.addr,
+ nonceOur: noncediff + 0,
nonceRecv: noncediff + 0,
+ key: KeyFromSecrets(h.sServer[:], decRs[8+8:]),
}
- peer.key = KeyFromSecrets(h.sServer[:], decRs[8+8:])
+ peer.nonceCipher = NewNonceCipher(peer.key)
fmt.Print("[OK]")
return &peer
default:
// Switch peer
peer := Peer{
- addr: h.addr,
- nonceOur: noncediff + 1,
+ addr: h.addr,
+ nonceOur: noncediff + 1,
nonceRecv: noncediff + 0,
+ key: KeyFromSecrets(h.sServer[:], h.sClient[:]),
}
- peer.key = KeyFromSecrets(h.sServer[:], h.sClient[:])
+ peer.nonceCipher = NewNonceCipher(peer.key)
fmt.Print("[OK]")
return &peer
default: