--- /dev/null
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package chacha20poly1305 implements the ChaCha20-Poly1305 AEAD and its
+// extended nonce variant XChaCha20-Poly1305, as specified in RFC 8439 and
+// draft-irtf-cfrg-xchacha-01.
+package chacha20poly1305
+
+import (
+ "crypto/cipher"
+ "errors"
+)
+
+const (
+ // KeySize is the size of the key used by this AEAD, in bytes.
+ KeySize = 32
+
+ // NonceSize is the size of the nonce used with the standard variant of this
+ // AEAD, in bytes.
+ //
+ // Note that this is too short to be safely generated at random if the same
+ // key is reused more than 2³² times.
+ NonceSize = 12
+
+ // NonceSizeX is the size of the nonce used with the XChaCha20-Poly1305
+ // variant of this AEAD, in bytes.
+ NonceSizeX = 24
+
+ // Overhead is the size of the Poly1305 authentication tag, and the
+ // difference between a ciphertext length and its plaintext.
+ Overhead = 16
+)
+
+type chacha20poly1305 struct {
+ key [KeySize]byte
+}
+
+// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
+func New(key []byte) (cipher.AEAD, error) {
+ if len(key) != KeySize {
+ return nil, errors.New("chacha20poly1305: bad key length")
+ }
+ ret := new(chacha20poly1305)
+ copy(ret.key[:], key)
+ return ret, nil
+}
+
+func (c *chacha20poly1305) NonceSize() int {
+ return NonceSize
+}
+
+func (c *chacha20poly1305) Overhead() int {
+ return Overhead
+}
+
+func (c *chacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
+ if len(nonce) != NonceSize {
+ panic("chacha20poly1305: bad nonce length passed to Seal")
+ }
+
+ if uint64(len(plaintext)) > (1<<38)-64 {
+ panic("chacha20poly1305: plaintext too large")
+ }
+
+ return c.seal(dst, nonce, plaintext, additionalData)
+}
+
+var errOpen = errors.New("chacha20poly1305: message authentication failed")
+
+func (c *chacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ if len(nonce) != NonceSize {
+ panic("chacha20poly1305: bad nonce length passed to Open")
+ }
+ if len(ciphertext) < 16 {
+ return nil, errOpen
+ }
+ if uint64(len(ciphertext)) > (1<<38)-48 {
+ panic("chacha20poly1305: ciphertext too large")
+ }
+
+ return c.open(dst, nonce, ciphertext, additionalData)
+}
+
+// sliceForAppend takes a slice and a requested number of bytes. It returns a
+// slice with the contents of the given slice followed by that many bytes and a
+// second slice that aliases into it and contains only the extra bytes. If the
+// original slice has sufficient capacity then no allocation is performed.
+func sliceForAppend(in []byte, n int) (head, tail []byte) {
+ if total := len(in) + n; cap(in) >= total {
+ head = in[:total]
+ } else {
+ head = make([]byte, total)
+ copy(head, in)
+ }
+ tail = head[len(in):]
+ return
+}
--- /dev/null
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build gc && !purego
+
+package chacha20poly1305
+
+import (
+ "encoding/binary"
+
+ "golang.org/x/sys/cpu"
+)
+
+//go:noescape
+func chacha20Poly1305Open(dst []byte, key []uint32, src, ad []byte) bool
+
+//go:noescape
+func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)
+
+var (
+ useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2
+)
+
+// setupState writes a ChaCha20 input matrix to state. See
+// https://tools.ietf.org/html/rfc7539#section-2.3.
+func setupState(state *[16]uint32, key *[32]byte, nonce []byte) {
+ state[0] = 0x61707865
+ state[1] = 0x3320646e
+ state[2] = 0x79622d32
+ state[3] = 0x6b206574
+
+ state[4] = binary.LittleEndian.Uint32(key[0:4])
+ state[5] = binary.LittleEndian.Uint32(key[4:8])
+ state[6] = binary.LittleEndian.Uint32(key[8:12])
+ state[7] = binary.LittleEndian.Uint32(key[12:16])
+ state[8] = binary.LittleEndian.Uint32(key[16:20])
+ state[9] = binary.LittleEndian.Uint32(key[20:24])
+ state[10] = binary.LittleEndian.Uint32(key[24:28])
+ state[11] = binary.LittleEndian.Uint32(key[28:32])
+
+ state[12] = 0
+ state[13] = binary.LittleEndian.Uint32(nonce[0:4])
+ state[14] = binary.LittleEndian.Uint32(nonce[4:8])
+ state[15] = binary.LittleEndian.Uint32(nonce[8:12])
+}
+
+func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
+ if !cpu.X86.HasSSSE3 {
+ return c.sealGeneric(dst, nonce, plaintext, additionalData)
+ }
+
+ var state [16]uint32
+ setupState(&state, &c.key, nonce)
+
+ ret, out := sliceForAppend(dst, len(plaintext)+16)
+ chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
+ return ret
+}
+
+func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ if !cpu.X86.HasSSSE3 {
+ return c.openGeneric(dst, nonce, ciphertext, additionalData)
+ }
+
+ var state [16]uint32
+ setupState(&state, &c.key, nonce)
+
+ ciphertext = ciphertext[:len(ciphertext)-16]
+ ret, out := sliceForAppend(dst, len(ciphertext))
+ chacha20Poly1305Open(out, state[:], ciphertext, additionalData)
+ return ret, nil
+}
--- /dev/null
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package chacha20poly1305
+
+import (
+ "golang.org/x/crypto/chacha20"
+)
+
+func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []byte) []byte {
+ ret, out := sliceForAppend(dst, len(plaintext)+16)
+ ciphertext, _ := out[:len(plaintext)], out[len(plaintext):]
+ s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
+ s.SetCounter(1) // set the counter to 1, skipping 32 bytes
+ s.XORKeyStream(ciphertext, plaintext)
+ return ret
+}
+
+func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ ciphertext = ciphertext[:len(ciphertext)-16]
+ s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
+ s.SetCounter(1) // set the counter to 1, skipping 32 bytes
+ ret, out := sliceForAppend(dst, len(ciphertext))
+ s.XORKeyStream(out, ciphertext)
+ return ret, nil
+}
--- /dev/null
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !amd64 || !gc || purego
+
+package chacha20poly1305
+
+func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
+ return c.sealGeneric(dst, nonce, plaintext, additionalData)
+}
+
+func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ return c.openGeneric(dst, nonce, ciphertext, additionalData)
+}
// udpobfs -- simple point-to-point UDP obfuscation proxy
-// Copyright (C) 2023-2025 Sergey Matveev <stargrave@stargrave.org>
+// Copyright (C) 2023-2026 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// udpobfs -- simple point-to-point UDP obfuscation proxy
-// Copyright (C) 2023-2025 Sergey Matveev <stargrave@stargrave.org>
+// Copyright (C) 2023-2026 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// udpobfs -- simple point-to-point UDP obfuscation proxy
-// Copyright (C) 2023-2025 Sergey Matveev <stargrave@stargrave.org>
+// Copyright (C) 2023-2026 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
--- /dev/null
+package main
+
+import (
+ "crypto/cipher"
+ "crypto/rand"
+ "io"
+ "os"
+
+ "github.com/dchest/threefish"
+)
+
+func main() {
+ key := make([]byte, 64)
+ tweak := make([]byte, 16)
+ iv := make([]byte, 64)
+ rand.Read(key)
+ rand.Read(tweak)
+ rand.Read(iv)
+ c, err := threefish.NewCipher(key, tweak)
+ if err != nil {
+ panic(err)
+ }
+ var b cipher.BlockMode
+ buf := make([]byte, 1024*1024)
+ for {
+ if _, err = io.ReadFull(os.Stdin, buf); err != nil {
+ panic(err)
+ }
+ b = cipher.NewCBCEncrypter(c, iv)
+ b.CryptBlocks(buf, buf)
+ os.Stdout.Write(buf)
+ }
+}
--- /dev/null
+package udpobfs
+
+import (
+ "crypto/cipher"
+ "crypto/rand"
+ "crypto/rc4"
+ "encoding/binary"
+ "io"
+ "sync"
+ "testing"
+
+ "github.com/dchest/threefish"
+ "golang.org/x/crypto/blowfish"
+ "golang.org/x/crypto/cast5"
+ "golang.org/x/crypto/chacha20"
+ "golang.org/x/crypto/chacha20poly1305"
+ "golang.org/x/crypto/xtea"
+ "lukechampine.com/blake3"
+)
+
+func BenchmarkChaCha20Poly1305(b *testing.B) {
+ var key [32]byte
+ rand.Read(key[:])
+ var nonce [12]byte
+ rand.Read(nonce[:])
+ dst := make([]byte, 1500)
+ src := make([]byte, 500)
+ rand.Read(src)
+ c, err := chacha20poly1305.New(key[:])
+ if err != nil {
+ panic(err)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ c.Seal(dst[:0], nonce[:], src, nil)
+ }
+}
+
+func BenchmarkChaCha20(b *testing.B) {
+ var key [32]byte
+ rand.Read(key[:])
+ var nonce [12]byte
+ rand.Read(nonce[:])
+ dst := make([]byte, 1500)
+ src := make([]byte, 500)
+ rand.Read(src)
+ c, err := chacha20.NewUnauthenticatedCipher(key[:], nonce[:])
+ if err != nil {
+ panic(err)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ c.XORKeyStream(dst, src)
+ }
+}
+
+func BenchmarkBLAKE3(b *testing.B) {
+ var key [32]byte
+ rand.Read(key[:])
+ h := blake3.New(32, key[:])
+ var nonce [8]byte
+ rand.Read(nonce[:])
+ dst := make([]byte, 500)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ h.Reset()
+ MustWrite(h, nonce[:])
+ if _, err := io.ReadFull(h.XOF(), dst); err != nil {
+ panic(err)
+ }
+ h.Reset()
+ MustWrite(h, dst)
+ }
+}
+
+func BenchmarkBLAKE3Seek(b *testing.B) {
+ var key [32]byte
+ rand.Read(key[:])
+ h := blake3.New(32, key[:])
+ var nonce [8]byte
+ rand.Read(nonce[:])
+ n := int64(binary.BigEndian.Uint64(nonce[:]))
+ if n < 0 {
+ n = -n
+ }
+ dst := make([]byte, 1500)
+ b.ResetTimer()
+ var xof *blake3.OutputReader
+ var err error
+ for i := 0; i < b.N; i++ {
+ h.Reset()
+ xof = h.XOF()
+ if _, err = xof.Seek(n, io.SeekStart); err != nil {
+ panic(err)
+ }
+ // MustWrite(h, nonce[:])
+ if _, err := io.ReadFull(h.XOF(), dst); err != nil {
+ panic(err)
+ }
+ h.Reset()
+ MustWrite(h, dst)
+ }
+}
+
+func BenchmarkBlowfish(b *testing.B) {
+ var key [32]byte
+ rand.Read(key[:])
+ dst := make([]byte, 8)
+ src := make([]byte, 8)
+ rand.Read(src)
+ c, err := blowfish.NewCipher(key[:])
+ if err != nil {
+ panic(err)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ c.Encrypt(dst, src)
+ }
+}
+
+func BenchmarkXTEA(b *testing.B) {
+ var key [16]byte
+ rand.Read(key[:])
+ dst := make([]byte, 8)
+ src := make([]byte, 8)
+ rand.Read(src)
+ c, err := xtea.NewCipher(key[:])
+ if err != nil {
+ panic(err)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ c.Encrypt(dst, src)
+ }
+}
+
+func BenchmarkCAST5(b *testing.B) {
+ var key [16]byte
+ rand.Read(key[:])
+ dst := make([]byte, 8)
+ src := make([]byte, 8)
+ rand.Read(src)
+ c, err := cast5.NewCipher(key[:])
+ if err != nil {
+ panic(err)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ c.Encrypt(dst, src)
+ }
+}
+
+func BenchmarkRLock(b *testing.B) {
+ var l sync.RWMutex
+ b.ResetTimer()
+ a := 0
+ for i := 0; i < b.N; i++ {
+ l.RLock()
+ a++
+ l.RUnlock()
+ }
+}
+
+func BenchmarkBigBlow(b *testing.B) {
+ var key [32]byte
+ rand.Read(key[:])
+ dst := make([]byte, 1500)
+ src := make([]byte, 504)
+ rand.Read(src)
+ c, err := blowfish.NewCipher(key[:])
+ if err != nil {
+ panic(err)
+ }
+ for b.Loop() {
+ for i := 0; i < len(src); i += 8 {
+ c.Encrypt(dst[i:], src[i:])
+ }
+ }
+}
+
+func BenchmarkRC4(b *testing.B) {
+ var key [32]byte
+ rand.Read(key[:])
+ dst := make([]byte, 1500)
+ src := make([]byte, 504)
+ rand.Read(src)
+ c, err := rc4.NewCipher(key[:])
+ if err != nil {
+ panic(err)
+ }
+ for b.Loop() {
+ c.XORKeyStream(dst, src)
+ }
+}
+
+func BenchmarkThreefish(b *testing.B) {
+ var tweak [16]byte
+ var key [64]byte
+ rand.Read(key[:])
+ dst := make([]byte, 1500)
+ src := make([]byte, 512)
+ rand.Read(src)
+ c, err := threefish.NewCipher(key[:], tweak[:])
+ if err != nil {
+ panic(err)
+ }
+ for b.Loop() {
+ for i := 0; i < len(src); i += 64 {
+ c.Encrypt(dst[i:i+64], src[i:i+64])
+ }
+ }
+}
+
+func BenchmarkThreefishCFB(b *testing.B) {
+ var tweak [16]byte
+ var iv [64]byte
+ var key [64]byte
+ rand.Read(key[:])
+ rand.Read(iv[:])
+ dst := make([]byte, 1500)
+ src := make([]byte, 500)
+ rand.Read(src)
+ c, err := threefish.NewCipher(key[:], tweak[:])
+ if err != nil {
+ panic(err)
+ }
+ for b.Loop() {
+ s := cipher.NewCFBDecrypter(c, iv[:])
+ s.XORKeyStream(dst, src)
+ }
+}
+
+func BenchmarkThreefishCBC(b *testing.B) {
+ var tweak [16]byte
+ var iv [64]byte
+ var key [64]byte
+ rand.Read(key[:])
+ rand.Read(iv[:])
+ dst := make([]byte, 1500)
+ src := make([]byte, 512)
+ rand.Read(src)
+ c, err := threefish.NewCipher(key[:], tweak[:])
+ if err != nil {
+ panic(err)
+ }
+ for b.Loop() {
+ s := cipher.NewCBCDecrypter(c, iv[:])
+ s.CryptBlocks(dst, src)
+ }
+}
+
+
+func BenchmarkThreefishCBCE(b *testing.B) {
+ var tweak [16]byte
+ var iv [64]byte
+ var key [64]byte
+ rand.Read(key[:])
+ rand.Read(iv[:])
+ dst := make([]byte, 1500)
+ src := make([]byte, 512)
+ rand.Read(src)
+ c, err := threefish.NewCipher(key[:], tweak[:])
+ if err != nil {
+ panic(err)
+ }
+ for b.Loop() {
+ s := cipher.NewCBCEncrypter(c, iv[:])
+ s.CryptBlocks(dst, src)
+ }
+}
// udpobfs -- simple point-to-point UDP obfuscation proxy
-// Copyright (C) 2023-2025 Sergey Matveev <stargrave@stargrave.org>
+// Copyright (C) 2023-2026 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// udpobfs -- simple point-to-point UDP obfuscation proxy
-// Copyright (C) 2023-2025 Sergey Matveev <stargrave@stargrave.org>
+// Copyright (C) 2023-2026 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by