package ecookie
import (
"bytes"
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"errors"
"io"
"golang.org/x/crypto/blake2b"
"snix.ir/rabbitio"
)
var (
ErrAUTHCOK = errors.New("ecookie: cookie is corrupted")
ErrENDDATA = errors.New("ecookie: cookie length is too short")
ErrBadKlen = errors.New("ecookie: key must be 16 byte len")
)
const (
lenhashfnc = 32
lenkrabbit = 16
lenivahash = lenhashfnc + lenivforkt
lenivforkt = 8
)
type Encryptor struct {
key []byte
}
type Decryptor struct {
key []byte
}
func NewEncryptor(key []byte) (*Encryptor, error) {
if len(key) != lenkrabbit {
return nil, ErrBadKlen
}
copyKey := make([]byte, lenkrabbit)
copy(copyKey, key)
return &Encryptor{key: copyKey}, nil
}
func NewDecryptor(key []byte) (*Decryptor, error) {
if len(key) != lenkrabbit {
return nil, ErrBadKlen
}
copyKey := make([]byte, lenkrabbit)
copy(copyKey, key)
return &Decryptor{key: copyKey}, nil
}
func randomIVGen() ([]byte, error) {
iv := make([]byte, lenivforkt)
if _, err := rand.Read(iv); err != nil {
return nil, err
}
return iv, nil
}
func (h *Encryptor) Encrypt(src []byte) ([]byte, error) {
ivx, err := randomIVGen()
if err != nil {
return nil, err
}
cip, _ := rabbitio.NewCipher(h.key, ivx)
dst := make([]byte, len(src))
cip.XORKeyStream(dst, src)
hash, err := blake2bHash(multiReader(h.key, ivx, dst))
if err != nil {
return nil, err
}
buff := new(bytes.Buffer)
_, err = io.Copy(hex.NewEncoder(buff), multiReader(hash, ivx, dst))
return buff.Bytes(), err
}
func multiReader(vs ...[]byte) io.Reader {
var rds []io.Reader
for _, x := range vs {
r := bytes.NewReader(x)
rds = append(rds, r)
}
return io.MultiReader(rds...)
}
func blake2bHash(r io.Reader) ([]byte, error) {
hasher, _ := blake2b.New256(nil)
if _, err := io.Copy(hasher, r); err != nil {
return nil, err
}
return hasher.Sum(nil), nil
}
func (h *Decryptor) Decrypt(raw []byte) ([]byte, error) {
bf := new(bytes.Buffer)
if _, err := io.Copy(bf,
hex.NewDecoder(bytes.NewReader(raw))); err != nil {
return nil, err
}
if bf.Len() < lenivahash {
return nil, ErrENDDATA
}
u := bf.Bytes()
cl, err := blake2bHash(multiReader(h.key, u[lenhashfnc:]))
if err != nil {
return nil, err
}
eq := subtle.ConstantTimeCompare(cl, u[:lenhashfnc])
if eq != 1 {
return nil, ErrAUTHCOK
}
cip, _ := rabbitio.NewCipher(h.key, u[lenhashfnc:lenivahash])
pln := make([]byte, bf.Len()-lenivahash)
cip.XORKeyStream(pln, u[lenivahash:])
return pln, err
}