github.com/metacubex/mihomo@v1.18.5/transport/shadowsocks/core/cipher.go (about)

     1  package core
     2  
     3  import (
     4  	"crypto/md5"
     5  	"errors"
     6  	"net"
     7  	"sort"
     8  	"strings"
     9  
    10  	N "github.com/metacubex/mihomo/common/net"
    11  	"github.com/metacubex/mihomo/transport/shadowsocks/shadowaead"
    12  	"github.com/metacubex/mihomo/transport/shadowsocks/shadowstream"
    13  )
    14  
    15  type Cipher interface {
    16  	StreamConnCipher
    17  	PacketConnCipher
    18  }
    19  
    20  type StreamConnCipher interface {
    21  	StreamConn(net.Conn) net.Conn
    22  }
    23  
    24  type PacketConnCipher interface {
    25  	PacketConn(N.EnhancePacketConn) N.EnhancePacketConn
    26  }
    27  
    28  // ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns).
    29  var ErrCipherNotSupported = errors.New("cipher not supported")
    30  
    31  const (
    32  	aeadAes128Gcm         = "AEAD_AES_128_GCM"
    33  	aeadAes192Gcm         = "AEAD_AES_192_GCM"
    34  	aeadAes256Gcm         = "AEAD_AES_256_GCM"
    35  	aeadChacha20Poly1305  = "AEAD_CHACHA20_POLY1305"
    36  	aeadXChacha20Poly1305 = "AEAD_XCHACHA20_POLY1305"
    37  )
    38  
    39  // List of AEAD ciphers: key size in bytes and constructor
    40  var aeadList = map[string]struct {
    41  	KeySize int
    42  	New     func([]byte) (shadowaead.Cipher, error)
    43  }{
    44  	aeadAes128Gcm:         {16, shadowaead.AESGCM},
    45  	aeadAes192Gcm:         {24, shadowaead.AESGCM},
    46  	aeadAes256Gcm:         {32, shadowaead.AESGCM},
    47  	aeadChacha20Poly1305:  {32, shadowaead.Chacha20Poly1305},
    48  	aeadXChacha20Poly1305: {32, shadowaead.XChacha20Poly1305},
    49  }
    50  
    51  // List of stream ciphers: key size in bytes and constructor
    52  var streamList = map[string]struct {
    53  	KeySize int
    54  	New     func(key []byte) (shadowstream.Cipher, error)
    55  }{
    56  	"RC4-MD5":       {16, shadowstream.RC4MD5},
    57  	"AES-128-CTR":   {16, shadowstream.AESCTR},
    58  	"AES-192-CTR":   {24, shadowstream.AESCTR},
    59  	"AES-256-CTR":   {32, shadowstream.AESCTR},
    60  	"AES-128-CFB":   {16, shadowstream.AESCFB},
    61  	"AES-192-CFB":   {24, shadowstream.AESCFB},
    62  	"AES-256-CFB":   {32, shadowstream.AESCFB},
    63  	"CHACHA20":      {32, shadowstream.ChaCha20},
    64  	"CHACHA20-IETF": {32, shadowstream.Chacha20IETF},
    65  	"XCHACHA20":     {32, shadowstream.Xchacha20},
    66  }
    67  
    68  // ListCipher returns a list of available cipher names sorted alphabetically.
    69  func ListCipher() []string {
    70  	var l []string
    71  	for k := range aeadList {
    72  		l = append(l, k)
    73  	}
    74  	for k := range streamList {
    75  		l = append(l, k)
    76  	}
    77  	sort.Strings(l)
    78  	return l
    79  }
    80  
    81  // PickCipher returns a Cipher of the given name. Derive key from password if given key is empty.
    82  func PickCipher(name string, key []byte, password string) (Cipher, error) {
    83  	name = strings.ToUpper(name)
    84  
    85  	switch name {
    86  	case "DUMMY":
    87  		return &dummy{}, nil
    88  	case "CHACHA20-IETF-POLY1305":
    89  		name = aeadChacha20Poly1305
    90  	case "XCHACHA20-IETF-POLY1305":
    91  		name = aeadXChacha20Poly1305
    92  	case "AES-128-GCM":
    93  		name = aeadAes128Gcm
    94  	case "AES-192-GCM":
    95  		name = aeadAes192Gcm
    96  	case "AES-256-GCM":
    97  		name = aeadAes256Gcm
    98  	}
    99  
   100  	if choice, ok := aeadList[name]; ok {
   101  		if len(key) == 0 {
   102  			key = Kdf(password, choice.KeySize)
   103  		}
   104  		if len(key) != choice.KeySize {
   105  			return nil, shadowaead.KeySizeError(choice.KeySize)
   106  		}
   107  		aead, err := choice.New(key)
   108  		return &AeadCipher{Cipher: aead, Key: key}, err
   109  	}
   110  
   111  	if choice, ok := streamList[name]; ok {
   112  		if len(key) == 0 {
   113  			key = Kdf(password, choice.KeySize)
   114  		}
   115  		if len(key) != choice.KeySize {
   116  			return nil, shadowstream.KeySizeError(choice.KeySize)
   117  		}
   118  		ciph, err := choice.New(key)
   119  		return &StreamCipher{Cipher: ciph, Key: key}, err
   120  	}
   121  
   122  	return nil, ErrCipherNotSupported
   123  }
   124  
   125  type AeadCipher struct {
   126  	shadowaead.Cipher
   127  
   128  	Key []byte
   129  }
   130  
   131  func (aead *AeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
   132  func (aead *AeadCipher) PacketConn(c N.EnhancePacketConn) N.EnhancePacketConn {
   133  	return shadowaead.NewPacketConn(c, aead)
   134  }
   135  
   136  type StreamCipher struct {
   137  	shadowstream.Cipher
   138  
   139  	Key []byte
   140  }
   141  
   142  func (ciph *StreamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
   143  func (ciph *StreamCipher) PacketConn(c N.EnhancePacketConn) N.EnhancePacketConn {
   144  	return shadowstream.NewPacketConn(c, ciph)
   145  }
   146  
   147  // dummy cipher does not encrypt
   148  
   149  type dummy struct{}
   150  
   151  func (dummy) StreamConn(c net.Conn) net.Conn                       { return c }
   152  func (dummy) PacketConn(c N.EnhancePacketConn) N.EnhancePacketConn { return c }
   153  
   154  // key-derivation function from original Shadowsocks
   155  func Kdf(password string, keyLen int) []byte {
   156  	var b, prev []byte
   157  	h := md5.New()
   158  	for len(b) < keyLen {
   159  		h.Write(prev)
   160  		h.Write([]byte(password))
   161  		b = h.Sum(b)
   162  		prev = b[len(b)-h.Size():]
   163  		h.Reset()
   164  	}
   165  	return b[:keyLen]
   166  }