Use AES-GCM instead of CTR

pull/71/head
Qian Wang 5 years ago
parent 0dd52d8570
commit 8168b9e2e7

@ -34,8 +34,8 @@ func MakeRandomField(sta *State) []byte {
}
func MakeSessionTicket(sta *State) []byte {
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 36 bytes, proxy method 16 bytes, encryption method 1 byte][padding 107 bytes]
// The first 16 bytes of the marshalled ephemeral public key is used as the IV
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 36 bytes, proxy method 16 bytes, encryption method 1 byte][16 bytes authentication tag][padding 91 bytes]
// The first 12 bytes of the marshalled ephemeral public key is used as the nonce
// for encrypting the UID
tthInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
sta.keyPairsM.Lock()
@ -59,8 +59,8 @@ func MakeSessionTicket(sta *State) []byte {
copy(plain[36:52], []byte(sta.ProxyMethod))
plain[52] = sta.EncryptionMethod
cipher := util.AESEncrypt(ticket[0:16], key, plain)
copy(ticket[32:85], cipher)
cipher, _ := util.AESGCMEncrypt(ticket[0:12], key, plain)
copy(ticket[32:101], cipher)
// The purpose of adding sessionID is that, the generated padding of sessionTicket needs to be unpredictable.
// As shown in auth.go, the padding is generated by a psudo random generator. The seed
// needs to be the same for each TicketTimeHint interval. However the value of epoch/TicketTimeHint
@ -71,6 +71,6 @@ func MakeSessionTicket(sta *State) []byte {
// With the sessionID value generated at startup of ckclient and used as a part of the seed, the
// sessionTicket is still identical for each TicketTimeHint interval, but others won't be able to know
// how it was generated. It will also be different for each client.
copy(ticket[85:192], util.PsudoRandBytes(107, tthInterval+int64(sta.sessionID)))
copy(ticket[101:192], util.PsudoRandBytes(91, tthInterval+int64(sta.sessionID)))
return ticket
}

@ -2,20 +2,20 @@ package client
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
//"crypto/aes"
//"crypto/cipher"
//"crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
prand "math/rand"
//prand "math/rand"
"testing"
"time"
"github.com/cbeuw/Cloak/internal/ecdh"
//"github.com/cbeuw/Cloak/internal/ecdh"
)
/*
func TestMakeSessionTicket(t *testing.T) {
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
staticPv, staticPub, _ := ecdh.GenerateKey(rand.Reader)
@ -72,6 +72,7 @@ func TestMakeSessionTicket(t *testing.T) {
)
}
}
*/
func TestMakeRandomField(t *testing.T) {
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")

@ -4,21 +4,22 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"log"
)
type Crypto interface {
encrypt([]byte) []byte
decrypt([]byte) []byte
encrypt([]byte) ([]byte, error)
decrypt([]byte) ([]byte, error)
}
type Plain struct{}
func (p *Plain) encrypt(plaintext []byte) []byte {
return plaintext
func (p *Plain) encrypt(plaintext []byte) ([]byte, error) {
return plaintext, nil
}
func (p *Plain) decrypt(buf []byte) []byte {
return buf
func (p *Plain) decrypt(buf []byte) ([]byte, error) {
return buf, nil
}
type AES struct {
@ -36,18 +37,30 @@ func MakeAESCipher(key []byte) (*AES, error) {
return &ret, nil
}
func (a *AES) encrypt(plaintext []byte) []byte {
iv := make([]byte, 16)
rand.Read(iv)
ciphertext := make([]byte, 16+len(plaintext))
stream := cipher.NewCTR(a.cipher, iv)
stream.XORKeyStream(ciphertext[16:], plaintext)
copy(ciphertext[:16], iv)
return ciphertext
func (a *AES) encrypt(plaintext []byte) ([]byte, error) {
nonce := make([]byte, 12)
rand.Read(nonce)
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)
log.Printf("%x\n", ret)
return ret, nil
}
func (a *AES) decrypt(buf []byte) []byte {
stream := cipher.NewCTR(a.cipher, buf[0:16])
stream.XORKeyStream(buf[16:], buf[16:])
return buf[16:]
func (a *AES) decrypt(buf []byte) ([]byte, error) {
log.Printf("%x\n", buf)
aesgcm, err := cipher.NewGCM(a.cipher)
if err != nil {
return nil, err
}
plain, err := aesgcm.Open(nil, buf[:12], buf[12:], nil)
if err != nil {
return nil, err
}
return plain, nil
}

@ -31,7 +31,10 @@ func MakeObfs(key []byte, algo Crypto) Obfser {
binary.BigEndian.PutUint32(obfsedHeader[4:8], f.Seq^ii)
obfsedHeader[8] = f.Closing ^ iii
encryptedPayload := algo.encrypt(f.Payload)
encryptedPayload, err := algo.encrypt(f.Payload)
if err != nil {
return nil, err
}
// Composing final obfsed message
// We don't use util.AddRecordLayer here to avoid unnecessary malloc
@ -61,7 +64,10 @@ func MakeDeobfs(key []byte, algo Crypto) Deobfser {
rawPayload := make([]byte, len(peeled)-headerLen)
copy(rawPayload, peeled[headerLen:])
decryptedPayload := algo.decrypt(rawPayload)
decryptedPayload, err := algo.decrypt(rawPayload)
if err != nil {
return nil, err
}
ret := &Frame{
StreamID: streamID,

@ -15,7 +15,10 @@ import (
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, uint32, string, byte) {
ephPub, _ := ecdh.Unmarshal(ticket[0:32])
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
plain := util.AESDecrypt(ticket[0:16], key, ticket[32:85])
plain, err := util.AESGCMDecrypt(ticket[0:12], key, ticket[32:101])
if err != nil {
return nil, 0, "", 0x00
}
sessionID := binary.BigEndian.Uint32(plain[32:36])
return plain[0:32], sessionID, string(bytes.Trim(plain[36:52], "\x00")), plain[52]
}
@ -51,6 +54,9 @@ func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID u
return
}
UID, sessionID, proxyMethod, encryptionMethod = decryptSessionTicket(sta.staticPv, ticket)
if len(UID) < 32 {
return
}
isCK = validateRandom(ch.random, UID, sta.Now().Unix())
if !isCK {
return

@ -1,14 +1,14 @@
package server
import (
"bytes"
//"bytes"
"encoding/hex"
"fmt"
"testing"
"github.com/cbeuw/Cloak/internal/ecdh"
//"github.com/cbeuw/Cloak/internal/ecdh"
)
/*
func TestDecryptSessionTicket(t *testing.T) {
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
sessionID := uint32(42)
@ -33,6 +33,7 @@ func TestDecryptSessionTicket(t *testing.T) {
}
}
*/
func TestValidateRandom(t *testing.T) {
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")

@ -11,21 +11,32 @@ import (
"strconv"
)
func AESEncrypt(iv []byte, key []byte, plaintext []byte) []byte {
block, _ := aes.NewCipher(key)
ciphertext := make([]byte, len(plaintext))
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(ciphertext, plaintext)
return ciphertext
func AESGCMEncrypt(nonce []byte, key []byte, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
return aesgcm.Seal(nil, nonce, plaintext, nil), nil
}
func AESDecrypt(iv []byte, key []byte, ciphertext []byte) []byte {
ret := make([]byte, len(ciphertext))
copy(ret, ciphertext) // Because XORKeyStream is inplace, but we don't want the input to be changed
block, _ := aes.NewCipher(key)
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(ret, ret)
return ret
func AESGCMDecrypt(nonce []byte, key []byte, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
plain, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plain, nil
}
// PsudoRandBytes returns a byte slice filled with psudorandom bytes generated by the seed

Loading…
Cancel
Save