Skip to content

Commit

Permalink
Improved handling of cipher list
Browse files Browse the repository at this point in the history
  • Loading branch information
riobard committed Feb 5, 2017
1 parent e9e7977 commit f51e6b5
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 41 deletions.
148 changes: 110 additions & 38 deletions cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,84 +5,146 @@ import (
"crypto/cipher"
"errors"
"fmt"
"io"
"net"
"sort"
"strings"

"golang.org/x/crypto/chacha20poly1305"

"github.com/Yawning/chacha20"
"github.com/riobard/go-shadowsocks2/core"
"github.com/riobard/go-shadowsocks2/shadowaead"
"github.com/riobard/go-shadowsocks2/shadowstream"
"golang.org/x/crypto/chacha20poly1305"
)

// ErrKeySize means the supplied key size does not meet the requirement of cipher choosed.
// ErrKeySize means the key size does not meet the requirement of cipher.
var ErrKeySize = errors.New("key size error")

func pickCipher(name string, key []byte) (core.StreamConnCipher, core.PacketConnCipher, error) {
// ErrCipherNotSupported means the cipher has not been implemented.
var ErrCipherNotSupported = errors.New("cipher not supported")

// List of AEAD ciphers: key size in bytes and constructor
var aeadList = map[string]struct {
KeySize int
New func(key []byte) (cipher.AEAD, error)
}{
"aes-128-gcm": {16, aesGCM},
"aes-192-gcm": {24, aesGCM},
"aes-256-gcm": {32, aesGCM},
"aes-128-gcm-16": {16, aesGCM16},
"aes-192-gcm-16": {24, aesGCM16},
"aes-256-gcm-16": {32, aesGCM16},
"chacha20-ietf-poly1305": {32, chacha20poly1305.New},
}

switch strings.ToLower(name) {
case "aes-128-gcm", "aes-192-gcm", "aes-256-gcm":
aead, err := aesGCM(key, 0) // 0 for standard 12-byte nonce
return aeadStream(aead), aeadPacket(aead), err
// List of stream ciphers: key size in bytes and constructor
var streamList = map[string]struct {
KeySize int
New func(key []byte) (shadowstream.Cipher, error)
}{
"aes-128-ctr": {16, aesCTR},
"aes-192-ctr": {24, aesCTR},
"aes-256-ctr": {32, aesCTR},
"aes-128-cfb": {16, aesCFB},
"aes-192-cfb": {24, aesCFB},
"aes-256-cfb": {32, aesCFB},
"chacha20-ietf": {32, newChacha20ietf},
}

case "aes-128-gcm-16", "aes-192-gcm-16", "aes-256-gcm-16":
aead, err := aesGCM(key, 16) // 16-byte nonce for better collision avoidance
return aeadStream(aead), aeadPacket(aead), err
// Return two lists of sorted cipher names.
func availableCiphers() (aead []string, stream []string) {
for k := range aeadList {
aead = append(aead, k)
}

case "chacha20-ietf-poly1305":
aead, err := chacha20poly1305.New(key)
return aeadStream(aead), aeadPacket(aead), err
for k := range streamList {
stream = append(stream, k)
}

case "aes-128-ctr", "aes-192-ctr", "aes-256-ctr":
ciph, err := aesCTR(key)
return streamStream(ciph), streamPacket(ciph), err
sort.Strings(aead)
sort.Strings(stream)
return
}

case "aes-128-cfb", "aes-192-cfb", "aes-256-cfb":
ciph, err := aesCFB(key)
return streamStream(ciph), streamPacket(ciph), err
// Print available ciphers to w
func printCiphers(w io.Writer) {
fmt.Fprintf(w, "## Available AEAD ciphers (recommended)\n\n")

aead, stream := availableCiphers()
for _, name := range aead {
fmt.Fprintf(w, "%s\n", name)
}

fmt.Fprintf(w, "\n## Available stream ciphers\n\n")
for _, name := range stream {
fmt.Fprintf(w, "%s\n", name)
}
}

func pickCipher(name string, key []byte) (core.StreamConnCipher, core.PacketConnCipher, error) {
name = strings.ToLower(name)

case "chacha20-ietf":
if len(key) != chacha20.KeySize {
if choice, ok := aeadList[name]; ok {
if len(key) != choice.KeySize {
return nil, nil, ErrKeySize
}
k := chacha20ietfkey(key)
return streamStream(k), streamPacket(k), nil
aead, err := choice.New(key)
return aeadStream(aead), aeadPacket(aead), err
}

case "dummy": // only for benchmarking and debugging
return dummyStream(), dummyPacket(), nil
if choice, ok := streamList[name]; ok {
if len(key) != choice.KeySize {
return nil, nil, ErrKeySize
}
ciph, err := choice.New(key)
return streamStream(ciph), streamPacket(ciph), err
}

default:
err := fmt.Errorf("cipher not supported: %s", name)
return nil, nil, err
if name == "dummy" {
return dummyStream(), dummyPacket(), nil
}
return nil, nil, ErrCipherNotSupported
}

// Dummy ciphers (no encryption)

func dummyStream() core.StreamConnCipher {
return func(c net.Conn) net.Conn { return c }
}
func dummyPacket() core.PacketConnCipher {
return func(c net.PacketConn) net.PacketConn { return c }
}

// AEAD ciphers

func aeadStream(aead cipher.AEAD) core.StreamConnCipher {
return func(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
}

func aeadPacket(aead cipher.AEAD) core.PacketConnCipher {
return func(c net.PacketConn) net.PacketConn { return shadowaead.NewPacketConn(c, aead) }
}

func aesGCM(key []byte, nonceSize int) (cipher.AEAD, error) {
// AES-GCM with standard 12-byte nonce
func aesGCM(key []byte) (cipher.AEAD, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if nonceSize > 0 {
return cipher.NewGCMWithNonceSize(blk, nonceSize)
return cipher.NewGCM(blk)
}

// AES-GCM with 16-byte nonce for better collision avoidance
func aesGCM16(key []byte) (cipher.AEAD, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(blk) // standard 12-byte nonce
return cipher.NewGCMWithNonceSize(blk, 16)
}

// Stream ciphers

func streamStream(ciph shadowstream.Cipher) core.StreamConnCipher {
return func(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
}
Expand All @@ -91,11 +153,12 @@ func streamPacket(ciph shadowstream.Cipher) core.PacketConnCipher {
return func(c net.PacketConn) net.PacketConn { return shadowstream.NewPacketConn(c, ciph) }
}

// CTR mode
type ctrStream struct{ cipher.Block }

func (b *ctrStream) IVSize() int { return b.BlockSize() }
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }

func aesCTR(key []byte) (shadowstream.Cipher, error) {
blk, err := aes.NewCipher(key)
Expand All @@ -105,11 +168,12 @@ func aesCTR(key []byte) (shadowstream.Cipher, error) {
return &ctrStream{blk}, nil
}

// CFB mode
type cfbStream struct{ cipher.Block }

func (b *cfbStream) IVSize() int { return b.BlockSize() }
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }

func aesCFB(key []byte) (shadowstream.Cipher, error) {
blk, err := aes.NewCipher(key)
Expand All @@ -119,14 +183,22 @@ func aesCFB(key []byte) (shadowstream.Cipher, error) {
return &ctrStream{blk}, nil
}

// IETF-variant of chacha20
type chacha20ietfkey []byte

func (k chacha20ietfkey) IVSize() int { return chacha20.INonceSize }
func (k chacha20ietfkey) IVSize() int { return chacha20.INonceSize }
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewCipher(k, iv)
if err != nil {
panic(err)
panic(err) // should never happen
}
return ciph
}
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }

func newChacha20ietf(key []byte) (shadowstream.Cipher, error) {
if len(key) != chacha20.KeySize {
return nil, ErrKeySize
}
return chacha20ietfkey(key), nil
}
11 changes: 8 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func main() {
}

flag.BoolVar(&config.Verbose, "verbose", false, "verbose mode")
flag.StringVar(&flags.Cipher, "cipher", "aes-128-gcm", "cipher to encrypt/decrypt")
flag.StringVar(&flags.Cipher, "cipher", "", "cipher")
flag.StringVar(&flags.Key, "key", "", "secret key in hexadecimal")
flag.StringVar(&flags.Server, "s", "", "server listen address")
flag.StringVar(&flags.Client, "c", "", "client connect address")
Expand All @@ -49,14 +49,19 @@ func main() {
flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout")
flag.Parse()

if flags.Cipher == "" {
printCiphers(os.Stderr)
return
}

key, err := hex.DecodeString(flags.Key)
if err != nil {
log.Fatalf("failed to parse key: %v", err)
log.Fatalf("key: %v", err)
}

streamCipher, packetCipher, err := pickCipher(flags.Cipher, key)
if err != nil {
log.Fatalf("failed to create cipher %s: %v", flags.Cipher, err)
log.Fatalf("cipher: %v", err)
}

if flags.Client != "" { // client mode
Expand Down

0 comments on commit f51e6b5

Please sign in to comment.