From 078a382963f530db77577db45f77f8c27f99ca38 Mon Sep 17 00:00:00 2001 From: Qian Wang Date: Fri, 14 Jun 2019 19:48:59 +1000 Subject: [PATCH] Improve the security of header obfuscation --- internal/multiplex/crypto.go | 25 ++++++++------- internal/multiplex/obfs.go | 60 ++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/internal/multiplex/crypto.go b/internal/multiplex/crypto.go index 2674f3e..67c5f23 100644 --- a/internal/multiplex/crypto.go +++ b/internal/multiplex/crypto.go @@ -7,17 +7,19 @@ import ( ) type Crypto interface { - encrypt([]byte) ([]byte, error) - decrypt([]byte) ([]byte, error) + encrypt([]byte, []byte) ([]byte, error) + decrypt([]byte, []byte) ([]byte, error) } type Plain struct{} -func (p *Plain) encrypt(plaintext []byte) ([]byte, error) { - return plaintext, nil +func (p *Plain) encrypt(plaintext []byte, nonce []byte) ([]byte, error) { + salt := make([]byte, 16) + rand.Read(salt) + return append(plaintext, salt...), nil } -func (p *Plain) decrypt(buf []byte) ([]byte, error) { +func (p *Plain) decrypt(buf []byte, nonce []byte) ([]byte, error) { return buf, nil } @@ -36,26 +38,23 @@ func MakeAESCipher(key []byte) (*AES, error) { return &ret, nil } -func (a *AES) encrypt(plaintext []byte) ([]byte, error) { - nonce := make([]byte, 12) - rand.Read(nonce) +func (a *AES) encrypt(plaintext []byte, nonce []byte) ([]byte, error) { aesgcm, err := cipher.NewGCM(a.cipher) if err != nil { return nil, err } ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) - ret := make([]byte, 12+len(plaintext)+16) - copy(ret[:12], nonce) - copy(ret[12:], ciphertext) + ret := make([]byte, len(plaintext)+16) + copy(ret, ciphertext) return ret, nil } -func (a *AES) decrypt(buf []byte) ([]byte, error) { +func (a *AES) decrypt(ciphertext []byte, nonce []byte) ([]byte, error) { aesgcm, err := cipher.NewGCM(a.cipher) if err != nil { return nil, err } - plain, err := aesgcm.Open(nil, buf[:12], buf[12:], nil) + plain, err := aesgcm.Open(nil, nonce, ciphertext, nil) if err != nil { return nil, err } diff --git a/internal/multiplex/obfs.go b/internal/multiplex/obfs.go index 68fac11..429785e 100644 --- a/internal/multiplex/obfs.go +++ b/internal/multiplex/obfs.go @@ -5,37 +5,46 @@ import ( "crypto/sha1" "encoding/binary" "errors" - "io" ) type Obfser func(*Frame) ([]byte, error) type Deobfser func([]byte) (*Frame, error) var u32 = binary.BigEndian.Uint32 +var putU32 = binary.BigEndian.PutUint32 const headerLen = 12 -func genXorKeys(key, nonce []byte) (i uint32, ii uint32, iii uint8) { +func genXorKey(key, salt []byte) []byte { h := sha1.New() - hashed := h.Sum(append(key, nonce...)) - return u32(hashed[0:4]), u32(hashed[4:8]), hashed[8] + h.Write(append(key, salt...)) + return h.Sum(nil)[:12] +} + +func xor(a []byte, b []byte) { + for i := range a { + a[i] ^= b[i] + } } func MakeObfs(key []byte, algo Crypto) Obfser { obfs := func(f *Frame) ([]byte, error) { - obfsedHeader := make([]byte, headerLen) - // header: [StreamID 4 bytes][Seq 4 bytes][Closing 1 byte][Nonce 3 bytes] - io.ReadFull(rand.Reader, obfsedHeader[9:12]) - i, ii, iii := genXorKeys(key, obfsedHeader[9:12]) - binary.BigEndian.PutUint32(obfsedHeader[0:4], f.StreamID^i) - binary.BigEndian.PutUint32(obfsedHeader[4:8], f.Seq^ii) - obfsedHeader[8] = f.Closing ^ iii + // header: [StreamID 4 bytes][Seq 4 bytes][Closing 1 byte][random 3 bytes] + header := make([]byte, headerLen) + putU32(header[0:4], f.StreamID) + putU32(header[4:8], f.Seq) + header[8] = f.Closing + rand.Read(header[9:12]) - encryptedPayload, err := algo.encrypt(f.Payload) + encryptedPayload, err := algo.encrypt(f.Payload, header) if err != nil { return nil, err } + salt := encryptedPayload[len(encryptedPayload)-16:] + xorKey := genXorKey(key, salt) + xor(header, xorKey) + // Composing final obfsed message // We don't use util.AddRecordLayer here to avoid unnecessary malloc obfsed := make([]byte, 5+headerLen+len(encryptedPayload)) @@ -43,9 +52,9 @@ func MakeObfs(key []byte, algo Crypto) Obfser { obfsed[1] = 0x03 obfsed[2] = 0x03 binary.BigEndian.PutUint16(obfsed[3:5], uint16(headerLen+len(encryptedPayload))) - copy(obfsed[5:5+headerLen], obfsedHeader) + copy(obfsed[5:5+headerLen], header) copy(obfsed[5+headerLen:], encryptedPayload) - // obfsed: [record layer 5 bytes][cipherheader 12 bytes][payload] + // obfsed: [record layer 5 bytes][obfsedheader 12 bytes][payload] return obfsed, nil } return obfs @@ -53,18 +62,23 @@ func MakeObfs(key []byte, algo Crypto) Obfser { func MakeDeobfs(key []byte, algo Crypto) Deobfser { deobfs := func(in []byte) (*Frame, error) { - if len(in) < 5+headerLen { - return nil, errors.New("Input cannot be shorter than 17 bytes") + if len(in) < 5+headerLen+16 { + return nil, errors.New("Input cannot be shorter than 33 bytes") } peeled := in[5:] - i, ii, iii := genXorKeys(key, peeled[9:12]) - streamID := u32(peeled[0:4]) ^ i - seq := u32(peeled[4:8]) ^ ii - closing := peeled[8] ^ iii - rawPayload := make([]byte, len(peeled)-headerLen) - copy(rawPayload, peeled[headerLen:]) - decryptedPayload, err := algo.decrypt(rawPayload) + header := peeled[0:12] + payload := peeled[12:] + salt := peeled[len(peeled)-16:] + + xorKey := genXorKey(key, salt) + xor(header, xorKey) + + streamID := u32(header[0:4]) + seq := u32(header[4:8]) + closing := header[8] + + decryptedPayload, err := algo.decrypt(payload, header) if err != nil { return nil, err }