138 lines
3.4 KiB
Go
138 lines
3.4 KiB
Go
package stream
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
)
|
|
|
|
const (
|
|
MaxBlobSize = 2097152 // 2mb, or 2 * 2^20
|
|
BlobHashSize = sha512.Size384
|
|
BlobHashHexLength = BlobHashSize * 2 // in hex, each byte is 2 chars
|
|
)
|
|
|
|
type Blob []byte
|
|
|
|
var ErrBlobTooBig = errors.Newf("blob must be at most %d bytes", MaxBlobSize)
|
|
var ErrBlobEmpty = errors.New("blob is empty")
|
|
|
|
func (b Blob) Size() int {
|
|
return len(b)
|
|
}
|
|
|
|
// Hash returns a hash of the blob data
|
|
func (b Blob) Hash() []byte {
|
|
if b.Size() == 0 {
|
|
return nil
|
|
}
|
|
hashBytes := sha512.Sum384(b)
|
|
return hashBytes[:]
|
|
}
|
|
|
|
// HashHex returns the blob hash as a hex string
|
|
func (b Blob) HashHex() string {
|
|
return hex.EncodeToString(b.Hash())
|
|
}
|
|
|
|
// ValidForSend returns true if the blob size is within the limits
|
|
func (b Blob) ValidForSend() error {
|
|
if b.Size() > MaxBlobSize {
|
|
return errors.WithStack(ErrBlobTooBig)
|
|
}
|
|
if b.Size() == 0 {
|
|
return errors.WithStack(ErrBlobEmpty)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewBlob(data, key, iv []byte) (Blob, error) {
|
|
if len(data) == 0 {
|
|
// this is here to match python behavior. in theory we could encrypt an empty blob
|
|
return nil, errors.WithStack(errors.New("cannot encrypt empty slice"))
|
|
}
|
|
blockCipher, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if len(iv) != blockCipher.BlockSize() {
|
|
return nil, errors.WithStack(errors.New("IV length must equal to block size"))
|
|
}
|
|
|
|
cbc := cipher.NewCBCEncrypter(blockCipher, iv)
|
|
plaintext, err := pkcs7Pad(data, blockCipher.BlockSize())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ciphertext := make([]byte, len(plaintext))
|
|
cbc.CryptBlocks(ciphertext, plaintext)
|
|
return ciphertext, nil
|
|
}
|
|
|
|
// DecryptBlob decrypts a blob
|
|
func DecryptBlob(b Blob, key, iv []byte) ([]byte, error) {
|
|
return b.Plaintext(key, iv)
|
|
}
|
|
|
|
func (b Blob) Plaintext(key, iv []byte) ([]byte, error) {
|
|
blockCipher, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if len(iv) != blockCipher.BlockSize() {
|
|
return nil, errors.WithStack(errors.New("IV length must equal to block size"))
|
|
}
|
|
|
|
cbc := cipher.NewCBCDecrypter(blockCipher, iv)
|
|
plaintext := make([]byte, len(b))
|
|
cbc.CryptBlocks(plaintext, b)
|
|
|
|
plaintext, err = pkcs7Unpad(plaintext, blockCipher.BlockSize())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return plaintext, nil
|
|
}
|
|
|
|
// https://github.com/fullsailor/pkcs7/blob/master/pkcs7.go#L468
|
|
func pkcs7Pad(data []byte, blockLen int) ([]byte, error) {
|
|
if blockLen < 1 {
|
|
return nil, errors.WithStack(errors.Newf("invalid block length %d", blockLen))
|
|
}
|
|
padLen := blockLen - (len(data) % blockLen)
|
|
if padLen == 0 {
|
|
padLen = blockLen
|
|
}
|
|
padded := make([]byte, len(data)+padLen)
|
|
copy(padded, data)
|
|
copy(padded[len(padded)-padLen:], bytes.Repeat([]byte{byte(padLen)}, padLen))
|
|
return padded, nil
|
|
}
|
|
|
|
func pkcs7Unpad(data []byte, blockLen int) ([]byte, error) {
|
|
if blockLen < 1 {
|
|
return nil, errors.WithStack(errors.Newf("invalid block length %d", blockLen))
|
|
}
|
|
if len(data)%blockLen != 0 || len(data) == 0 {
|
|
return nil, errors.WithStack(errors.Newf("invalid data length %d", len(data)))
|
|
}
|
|
|
|
// the last byte is the length of padding
|
|
padLen := int(data[len(data)-1])
|
|
|
|
// check padding integrity, all bytes should be the same
|
|
pad := data[len(data)-padLen:]
|
|
for _, padbyte := range pad {
|
|
if padbyte != byte(padLen) {
|
|
return nil, errors.WithStack(errors.New("invalid padding"))
|
|
}
|
|
}
|
|
|
|
return data[:len(data)-padLen], nil
|
|
}
|