]> Sergey Matveev's repositories - vors.git/commitdiff
SipHash24 for short messages is much faster and secure enough v2.0.0
authorSergey Matveev <stargrave@stargrave.org>
Mon, 15 Apr 2024 21:50:34 +0000 (00:50 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Tue, 16 Apr 2024 05:25:14 +0000 (08:25 +0300)
12 files changed:
cmd/client/main.go
cmd/server/main.go
cmd/server/peer.go
doc/features.texi
doc/index.texi
doc/integrity.texi
doc/proto.texi
go.mod
go.sum
internal/noise.go
internal/var.go
internal/version.go

index fadaf51fd28367cfbed9a8468f666009e8433415a1dfffb436a083f2b60b2243..6ac75099d2099c8beaf373edb4e32d3bc1f9277a15914b5b59d35eac64929709 100644 (file)
@@ -32,13 +32,13 @@ import (
        "strings"
        "time"
 
        "strings"
        "time"
 
+       "github.com/dchest/siphash"
        "github.com/flynn/noise"
        "github.com/jroimartin/gocui"
        "go.stargrave.org/opus/v2"
        vors "go.stargrave.org/vors/internal"
        "golang.org/x/crypto/blake2s"
        "golang.org/x/crypto/chacha20"
        "github.com/flynn/noise"
        "github.com/jroimartin/gocui"
        "go.stargrave.org/opus/v2"
        vors "go.stargrave.org/vors/internal"
        "golang.org/x/crypto/blake2s"
        "golang.org/x/crypto/chacha20"
-       "golang.org/x/crypto/poly1305"
 )
 
 type Stream struct {
 )
 
 type Stream struct {
@@ -268,14 +268,20 @@ func main() {
                }
        }
 
                }
        }
 
-       var keyOur []byte
+       var keyCiphOur []byte
+       var keyMACOur []byte
        {
        {
-               h, err := blake2s.New256(hs.ChannelBinding())
+               xof, err := blake2s.NewXOF(32+16, nil)
                if err != nil {
                        log.Fatalln(err)
                }
                if err != nil {
                        log.Fatalln(err)
                }
-               h.Write([]byte(vors.NoisePrologue))
-               keyOur = h.Sum(nil)
+               xof.Write([]byte(vors.NoisePrologue))
+               xof.Write(hs.ChannelBinding())
+               buf := make([]byte, 32+16)
+               if _, err = io.ReadFull(xof, buf); err != nil {
+                       log.Fatalln(err)
+               }
+               keyCiphOur, keyMACOur = buf[:32], buf[32:]
        }
 
        seen := time.Now()
        }
 
        seen := time.Now()
@@ -352,6 +358,7 @@ func main() {
                                if err != nil {
                                        log.Fatal(err)
                                }
                                if err != nil {
                                        log.Fatal(err)
                                }
+                               keyCiph, keyMAC := key[:32], key[32:]
                                stream := &Stream{
                                        name:  name,
                                        in:    make(chan []byte, 1<<10),
                                stream := &Stream{
                                        name:  name,
                                        in:    make(chan []byte, 1<<10),
@@ -402,9 +409,8 @@ func main() {
                                        }
 
                                        var ciph *chacha20.Cipher
                                        }
 
                                        var ciph *chacha20.Cipher
-                                       var macKey [32]byte
-                                       var mac *poly1305.MAC
-                                       tag := make([]byte, poly1305.TagSize)
+                                       mac := siphash.New(keyMAC)
+                                       tag := make([]byte, siphash.Size)
                                        var ctr uint32
                                        pcm := make([]int16, vors.FrameLen)
                                        nonce := make([]byte, 12)
                                        var ctr uint32
                                        pcm := make([]int16, vors.FrameLen)
                                        nonce := make([]byte, 12)
@@ -413,26 +419,23 @@ func main() {
                                        var lastDur int
                                        for buf := range stream.in {
                                                copy(nonce[len(nonce)-4:], buf)
                                        var lastDur int
                                        for buf := range stream.in {
                                                copy(nonce[len(nonce)-4:], buf)
-                                               ciph, err = chacha20.NewUnauthenticatedCipher(key, nonce)
-                                               if err != nil {
-                                                       log.Fatal(err)
-                                               }
-                                               clear(macKey[:])
-                                               ciph.XORKeyStream(macKey[:], macKey[:])
-                                               ciph.SetCounter(1)
-                                               mac = poly1305.New(&macKey)
-                                               if _, err = mac.Write(buf[4 : len(buf)-vors.TagLen]); err != nil {
+                                               mac.Reset()
+                                               if _, err = mac.Write(buf[: len(buf)-siphash.Size]); err != nil {
                                                        log.Fatal(err)
                                                }
                                                mac.Sum(tag[:0])
                                                if subtle.ConstantTimeCompare(
                                                        log.Fatal(err)
                                                }
                                                mac.Sum(tag[:0])
                                                if subtle.ConstantTimeCompare(
-                                                       tag[:vors.TagLen],
-                                                       buf[len(buf)-vors.TagLen:],
+                                                       tag[:siphash.Size],
+                                                       buf[len(buf)-siphash.Size:],
                                                ) != 1 {
                                                        stream.stats.bads++
                                                        continue
                                                }
                                                ) != 1 {
                                                        stream.stats.bads++
                                                        continue
                                                }
-                                               pkt = buf[4 : len(buf)-vors.TagLen]
+                                               ciph, err = chacha20.NewUnauthenticatedCipher(keyCiph, nonce)
+                                               if err != nil {
+                                                       log.Fatal(err)
+                                               }
+                                               pkt = buf[4 : len(buf)-siphash.Size]
                                                ciph.XORKeyStream(pkt, pkt)
 
                                                ctr = binary.BigEndian.Uint32(nonce[len(nonce)-4:])
                                                ciph.XORKeyStream(pkt, pkt)
 
                                                ctr = binary.BigEndian.Uint32(nonce[len(nonce)-4:])
@@ -532,7 +535,7 @@ func main() {
                                log.Println("wrong addr:", from)
                                continue
                        }
                                log.Println("wrong addr:", from)
                                continue
                        }
-                       if n <= 4+vors.TagLen {
+                       if n <= 4+siphash.Size {
                                log.Println("too small:", n)
                                continue
                        }
                                log.Println("too small:", n)
                                continue
                        }
@@ -573,9 +576,8 @@ func main() {
                }
                <-LoggerReady
                var ciph *chacha20.Cipher
                }
                <-LoggerReady
                var ciph *chacha20.Cipher
-               var macKey [32]byte
-               var mac *poly1305.MAC
-               tag := make([]byte, poly1305.TagSize)
+               mac := siphash.New(keyMACOur)
+               tag := make([]byte, siphash.Size)
                buf := make([]byte, 2*vors.FrameLen)
                pcm := make([]int16, vors.FrameLen)
                nonce := make([]byte, 12)
                buf := make([]byte, 2*vors.FrameLen)
                pcm := make([]int16, vors.FrameLen)
                nonce := make([]byte, 12)
@@ -608,21 +610,18 @@ func main() {
 
                        incr(nonce[len(nonce)-3:])
                        copy(buf, nonce[len(nonce)-4:])
 
                        incr(nonce[len(nonce)-3:])
                        copy(buf, nonce[len(nonce)-4:])
-                       ciph, err = chacha20.NewUnauthenticatedCipher(keyOur, nonce)
+                       ciph, err = chacha20.NewUnauthenticatedCipher(keyCiphOur, nonce)
                        if err != nil {
                                log.Fatal(err)
                        }
                        if err != nil {
                                log.Fatal(err)
                        }
-                       clear(macKey[:])
-                       ciph.XORKeyStream(macKey[:], macKey[:])
-                       ciph.SetCounter(1)
                        ciph.XORKeyStream(buf[4:4+n], buf[4:4+n])
                        ciph.XORKeyStream(buf[4:4+n], buf[4:4+n])
-                       mac = poly1305.New(&macKey)
-                       if _, err = mac.Write(buf[: 4+n]); err != nil {
+                       mac.Reset()
+                       if _, err = mac.Write(buf[: 4+n]); err != nil {
                                log.Fatal(err)
                        }
                        mac.Sum(tag[:0])
                                log.Fatal(err)
                        }
                        mac.Sum(tag[:0])
-                       copy(buf[4+n:], tag[:vors.TagLen])
-                       pkt = buf[:4+n+vors.TagLen]
+                       copy(buf[4+n:], tag)
+                       pkt = buf[:4+n+siphash.Size]
 
                        OurStats.pkts++
                        OurStats.bytes += vors.IPHdrLen(srvAddrUDP.IP) + 8 + uint64(len(pkt))
 
                        OurStats.pkts++
                        OurStats.bytes += vors.IPHdrLen(srvAddrUDP.IP) + 8 + uint64(len(pkt))
index 514e8feb04915b12bccb0d1e893ab4783a30c0c0821cf4c1f97f1dd93880f31f..b6b72553beee9b38afae7afcc0c6df8df93ffd846bb98f9fbae3c280158658f0 100644 (file)
@@ -33,12 +33,11 @@ import (
        "strings"
        "time"
 
        "strings"
        "time"
 
+       "github.com/dchest/siphash"
        "github.com/flynn/noise"
        "github.com/jroimartin/gocui"
        vors "go.stargrave.org/vors/internal"
        "golang.org/x/crypto/blake2s"
        "github.com/flynn/noise"
        "github.com/jroimartin/gocui"
        vors "go.stargrave.org/vors/internal"
        "golang.org/x/crypto/blake2s"
-       "golang.org/x/crypto/chacha20"
-       "golang.org/x/crypto/poly1305"
 )
 
 var (
 )
 
 var (
@@ -257,12 +256,17 @@ func newPeer(conn *net.TCPConn) {
        }
 
        {
        }
 
        {
-               h, err := blake2s.New256(hs.ChannelBinding())
+               xof, err := blake2s.NewXOF(32+16, nil)
                if err != nil {
                        log.Fatalln(err)
                }
                if err != nil {
                        log.Fatalln(err)
                }
-               h.Write([]byte(vors.NoisePrologue))
-               peer.key = h.Sum(nil)
+               xof.Write([]byte(vors.NoisePrologue))
+               xof.Write(hs.ChannelBinding())
+               peer.key = make([]byte, 32+16)
+               if _, err = io.ReadFull(xof, peer.key); err != nil {
+                       log.Fatalln(err)
+               }
+               peer.mac = siphash.New(peer.key[32:])
        }
 
        {
        }
 
        {
@@ -379,11 +383,7 @@ func main() {
                var err error
                var sid byte
                var peer *Peer
                var err error
                var sid byte
                var peer *Peer
-               var ciph *chacha20.Cipher
-               var macKey [32]byte
-               var mac *poly1305.MAC
-               tag := make([]byte, poly1305.TagSize)
-               nonce := make([]byte, 12)
+               tag := make([]byte, siphash.Size)
                for {
                        n, from, err = lnUDP.ReadFromUDP(buf)
                        if err != nil {
                for {
                        n, from, err = lnUDP.ReadFromUDP(buf)
                        if err != nil {
@@ -422,27 +422,19 @@ func main() {
                        if n == 1 {
                                continue
                        }
                        if n == 1 {
                                continue
                        }
-                       if n <= 4+vors.TagLen {
+                       if n <= 4+siphash.Size {
                                peer.stats.bads++
                                continue
                        }
 
                                peer.stats.bads++
                                continue
                        }
 
-                       copy(nonce[len(nonce)-4:], buf)
-                       ciph, err = chacha20.NewUnauthenticatedCipher(peer.key, nonce)
-                       if err != nil {
-                               log.Fatal(err)
-                       }
-                       clear(macKey[:])
-                       ciph.XORKeyStream(macKey[:], macKey[:])
-                       ciph.SetCounter(1)
-                       mac = poly1305.New(&macKey)
-                       if _, err = mac.Write(buf[4 : n-vors.TagLen]); err != nil {
+                       peer.mac.Reset()
+                       if _, err = peer.mac.Write(buf[: n-siphash.Size]); err != nil {
                                log.Fatal(err)
                        }
                                log.Fatal(err)
                        }
-                       mac.Sum(tag[:0])
+                       peer.mac.Sum(tag[:0])
                        if subtle.ConstantTimeCompare(
                        if subtle.ConstantTimeCompare(
-                               tag[:vors.TagLen],
-                               buf[n-vors.TagLen:n],
+                               tag[:siphash.Size],
+                               buf[n-siphash.Size:n],
                        ) != 1 {
                                peer.stats.bads++
                                continue
                        ) != 1 {
                                peer.stats.bads++
                                continue
index d1e078a9491a8bca2a67896c008b2ffa89e0c8312148fb3500c647d1277418c5..82c569df65155da64be4d930b3d37dba3b478c467d40ed409b4f9a867fbc6f85 100644 (file)
@@ -1,6 +1,7 @@
 package main
 
 import (
 package main
 
 import (
+       "hash"
        "log/slog"
        "net"
        "sync"
        "log/slog"
        "net"
        "sync"
@@ -29,6 +30,7 @@ type Peer struct {
        sid   byte
        addr  *net.UDPAddr
        key   []byte
        sid   byte
        addr  *net.UDPAddr
        key   []byte
+       mac   hash.Hash
        stats *Stats
        room  *Room
 
        stats *Stats
        room  *Room
 
index 8987dedcff866f3689ea95fc872a4f25bd51e8baffceb729943c26add984e176..27f58819f8d8c81d6ccf90ad10c9a63b0d086804b1e32df585a62a77581e7ae1 100644 (file)
@@ -11,9 +11,11 @@ is single client speaking at one time.
 Concealment) and DTX (discontinuous transmission) features enabled.
 Optional VAD (voice activity detection).
 
 Concealment) and DTX (discontinuous transmission) features enabled.
 Optional VAD (voice activity detection).
 
-@item Noise protocol-based handshake over TCP between client and server
-for creating authenticated encrypted channel and authentication based on
-server's public key knowledge.
+@item Noise protocol-based 0-RTT handshake over TCP between client and
+server for creating authenticated encrypted channel and authentication
+based on server's public key knowledge.
+
+@item Fast ChaCha20 encryption with SipHash24 message authentication.
 
 @item Rooms, optionally password protected.
 
 
 @item Rooms, optionally password protected.
 
index b1b19ee60221ae3161dcfbf206e7223fcd9c774b3bc61c7bf103cf2ca58cbaf3..b1c2fb92c984dcac6778daf581001651506cd0356798a9218df06f612e2cb6c7 100644 (file)
@@ -43,7 +43,7 @@ stop trying to use and revive legacy obsolete IPv4. Either use some
 overlay network on top of it, or VPN, whatever.
 
 @item Mono-cypher, mono-codec protocol. The @url{https://opus-codec.org/, Opus}
 overlay network on top of it, or VPN, whatever.
 
 @item Mono-cypher, mono-codec protocol. The @url{https://opus-codec.org/, Opus}
-audio codec is perfect for VoIP tasks. ChaCha20-Poly1305 is more than
+audio codec is perfect for VoIP tasks. ChaCha20 is more than
 appropriate and satisfies as fast and secure encryption solution.
 
 @float
 appropriate and satisfies as fast and secure encryption solution.
 
 @float
index e26a34d49c048b52789d14ac36d81f97088edb991c26606dca0ebfe2ac87105a..59815f8cd94a046ed2b93e35ab47dd130f49f3c9e5116b846c5794c6d999e701 100644 (file)
@@ -6,6 +6,6 @@ that you retrieved trusted and untampered software.
 Its fingerprint: @code{SHA256:qmlbyzvDRNXGJNxteapAWOmJRrBrZ7afLsEqr36M6kA}.
 
 @example
 Its fingerprint: @code{SHA256:qmlbyzvDRNXGJNxteapAWOmJRrBrZ7afLsEqr36M6kA}.
 
 @example
-$ ssh-keygen -Y verify -f PUBKEY-SSH.pub -I vors@@cypherpunks.ru -n file \
-    -s vors-@value{VERSION}.tar.zst.sig < vors-@value{VERSION}.tar.zst
+$ ssh-keygen -Y verify -f PUBKEY-SSH.pub -I vors@@stargrave.org -n file \
+    -s vors-@value{VERSION}.tar.zst.sig <vors-@value{VERSION}.tar.zst
 @end example
 @end example
index b420984d2c2766e9f8fb1a8dbe35018aca78469bca628b663a975344230829d0..a1af4d43a52e65b4a9eb9b6e30a2a7e718c84f2164a7fe8e68bd90e003641b61 100644 (file)
@@ -10,10 +10,10 @@ Each frame has a single byte stream identifier (unique identifier of the
 participant) and 24-bit big-endian packet counter. Reordered packets are
 dropped. 24-bit counter is long enough for very long talk sessions.
 
 participant) and 24-bit big-endian packet counter. Reordered packets are
 dropped. 24-bit counter is long enough for very long talk sessions.
 
-Each packet is encrypted with ChaCha20-Poly1305. The key is generated
-during the handshake procedure with the server and is shared among the
-other participants. The stream identifier together with the packet
-counter is used as a nonce. Only 64-bits of Poly1305 are used.
+Each packet is encrypted with ChaCha20 and authenticated with SipHash24.
+The keys are generated during the handshake procedure with the server
+and is shared among the other participants. The stream identifier
+together with the packet counter is used as a nonce.
 
 It is tuned for 24Kbps bandwidth. But remember that it has additional 8B
 of MAC tag, 4B VoRS, 8B UDP and 40B IPv6 headers.
 
 It is tuned for 24Kbps bandwidth. But remember that it has additional 8B
 of MAC tag, 4B VoRS, 8B UDP and 40B IPv6 headers.
diff --git a/go.mod b/go.mod
index 0276bb781f818eb307bf3c65fa65c8ac8a58673b754e31ff215fbdec368ee4fc..563ffc7c424648d3d4a0ab62ce68d2f92d12d9492d99798a19a5e9f8147323a8 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module go.stargrave.org/vors
 go 1.21
 
 require (
 go 1.21
 
 require (
+       github.com/dchest/siphash v1.2.3
        github.com/dustin/go-humanize v1.0.1
        github.com/flynn/noise v1.1.0
        github.com/jroimartin/gocui v0.5.0
        github.com/dustin/go-humanize v1.0.1
        github.com/flynn/noise v1.1.0
        github.com/jroimartin/gocui v0.5.0
diff --git a/go.sum b/go.sum
index c4ccfe2880830bedbf387a9c3aef57d7fac0b9e0a984955f9572638eed1a8033..4646fd1f78488ac8abef15cd3f3ae72889f346e9fd21d6f1624dce9b36cae45d 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
+github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
index 5e4b8837942d17de6fc415f378d2badb528b10924810d3ac824d9b44366dec78..365a39c1f41c1635456e363865faa7a7ca223f7c0b5b6ac9b13092434dc7c045 100644 (file)
@@ -8,7 +8,7 @@ import (
        "github.com/flynn/noise"
 )
 
        "github.com/flynn/noise"
 )
 
-const NoisePrologue = "VoRS v1"
+const NoisePrologue = "VoRS v2"
 
 var NoiseCipherSuite = noise.NewCipherSuite(
        noise.DH25519,
 
 var NoiseCipherSuite = noise.NewCipherSuite(
        noise.DH25519,
index 8236a4d77bfcf305a1e86c1c3201cb42a27b3bf287ce5c3a35b4d883dd030402..0ef1d5a837b85209d6db4e3c6d5f898138241699d5a1e4459a93e8c3942efe7e 100644 (file)
@@ -6,8 +6,6 @@ import (
 )
 
 const (
 )
 
 const (
-       TagLen = 8
-
        CmdPing = "PING"
        CmdPong = "PONG"
        CmdAdd  = "ADD"
        CmdPing = "PING"
        CmdPong = "PONG"
        CmdAdd  = "ADD"
index 28dd1a0bedc5aec0dea375558fceb4fe41f23596d6c2e94c05f62573a58c5966..f454ea5866d033e35b58d627c47d4b70e07fcb278a62b663ebece3b6d9496d1f 100644 (file)
@@ -3,7 +3,7 @@ package internal
 import "runtime"
 
 const (
 import "runtime"
 
 const (
-       Version  = "1.0.0"
+       Version  = "2.0.0"
        Warranty = `Copyright (C) 2024 Sergey Matveev
 
 This program is free software: you can redistribute it and/or modify
        Warranty = `Copyright (C) 2024 Sergey Matveev
 
 This program is free software: you can redistribute it and/or modify