starcrypto/symm/mode.go

326 lines
8.2 KiB
Go

package symm
import (
"crypto/cipher"
"errors"
"io"
"strings"
"b612.me/starcrypto/paddingx"
)
const (
MODEECB = "ECB"
MODECBC = "CBC"
MODECFB = "CFB"
MODEOFB = "OFB"
MODECTR = "CTR"
MODEGCM = "GCM"
)
var ErrUnsupportedCipherMode = errors.New("cipher mode not supported")
func normalizeCipherMode(mode string) string {
return strings.ToUpper(strings.TrimSpace(mode))
}
func encryptWithBlockMode(block cipher.Block, data, iv []byte, mode, paddingType, defaultPadding string) ([]byte, error) {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODECBC
}
switch mode {
case MODEECB:
if paddingType == "" {
paddingType = defaultPadding
}
content, err := paddingx.Pad(data, block.BlockSize(), paddingType)
if err != nil {
return nil, err
}
out := make([]byte, len(content))
ecbEncryptBlocks(block, out, content)
return out, nil
case MODECBC:
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
content, err := paddingx.Pad(data, block.BlockSize(), paddingType)
if err != nil {
return nil, err
}
out := make([]byte, len(content))
cipher.NewCBCEncrypter(block, iv).CryptBlocks(out, content)
return out, nil
case MODECFB, MODEOFB, MODECTR:
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
stream, err := newCipherStream(block, iv, mode, false)
if err != nil {
return nil, err
}
out := make([]byte, len(data))
stream.XORKeyStream(out, data)
return out, nil
default:
return nil, ErrUnsupportedCipherMode
}
}
func decryptWithBlockMode(block cipher.Block, src, iv []byte, mode, paddingType, defaultPadding string) ([]byte, error) {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODECBC
}
switch mode {
case MODEECB:
if len(src) == 0 || len(src)%block.BlockSize() != 0 {
return nil, errors.New("ciphertext is not a full block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
decrypted := make([]byte, len(src))
ecbDecryptBlocks(block, decrypted, src)
return paddingx.Unpad(decrypted, block.BlockSize(), paddingType)
case MODECBC:
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
if len(src) == 0 || len(src)%block.BlockSize() != 0 {
return nil, errors.New("ciphertext is not a full block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
decrypted := make([]byte, len(src))
cipher.NewCBCDecrypter(block, iv).CryptBlocks(decrypted, src)
return paddingx.Unpad(decrypted, block.BlockSize(), paddingType)
case MODECFB, MODEOFB, MODECTR:
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
stream, err := newCipherStream(block, iv, mode, true)
if err != nil {
return nil, err
}
out := make([]byte, len(src))
stream.XORKeyStream(out, src)
return out, nil
default:
return nil, ErrUnsupportedCipherMode
}
}
func encryptWithBlockModeStream(block cipher.Block, dst io.Writer, src io.Reader, iv []byte, mode, paddingType, defaultPadding string) error {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODECBC
}
switch mode {
case MODEECB:
if paddingType == "" {
paddingType = defaultPadding
}
return encryptPaddedBlockStream(dst, src, block.BlockSize(), paddingType, func(out, in []byte) {
ecbEncryptBlocks(block, out, in)
})
case MODECBC:
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
modeEnc := cipher.NewCBCEncrypter(block, iv)
return encryptPaddedBlockStream(dst, src, block.BlockSize(), paddingType, modeEnc.CryptBlocks)
case MODECFB, MODEOFB, MODECTR:
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
stream, err := newCipherStream(block, iv, mode, false)
if err != nil {
return err
}
return xorStreamCopy(dst, src, stream)
default:
return ErrUnsupportedCipherMode
}
}
func decryptWithBlockModeStream(block cipher.Block, dst io.Writer, src io.Reader, iv []byte, mode, paddingType, defaultPadding string) error {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODECBC
}
switch mode {
case MODEECB:
if paddingType == "" {
paddingType = defaultPadding
}
return decryptPaddedBlockStream(dst, src, block.BlockSize(), paddingType, func(out, in []byte) {
ecbDecryptBlocks(block, out, in)
})
case MODECBC:
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
modeDec := cipher.NewCBCDecrypter(block, iv)
return decryptPaddedBlockStream(dst, src, block.BlockSize(), paddingType, modeDec.CryptBlocks)
case MODECFB, MODEOFB, MODECTR:
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
stream, err := newCipherStream(block, iv, mode, true)
if err != nil {
return err
}
return xorStreamCopy(dst, src, stream)
default:
return ErrUnsupportedCipherMode
}
}
func newCipherStream(block cipher.Block, iv []byte, mode string, decrypt bool) (cipher.Stream, error) {
switch mode {
case MODECFB:
if decrypt {
return cipher.NewCFBDecrypter(block, iv), nil
}
return cipher.NewCFBEncrypter(block, iv), nil
case MODEOFB:
return cipher.NewOFB(block, iv), nil
case MODECTR:
return cipher.NewCTR(block, iv), nil
default:
return nil, ErrUnsupportedCipherMode
}
}
func xorStreamCopy(dst io.Writer, src io.Reader, stream cipher.Stream) error {
buf := make([]byte, 32*1024)
out := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
stream.XORKeyStream(out[:n], buf[:n])
if _, werr := dst.Write(out[:n]); werr != nil {
return werr
}
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
func encryptPaddedBlockStream(dst io.Writer, src io.Reader, blockSize int, paddingType string, cryptBlocks func(dst, src []byte)) error {
pending := make([]byte, 0, blockSize*2)
buf := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
pending = append(pending, buf[:n]...)
processLen := len(pending) - blockSize
if processLen > 0 {
processLen -= processLen % blockSize
if processLen > 0 {
out := make([]byte, processLen)
cryptBlocks(out, pending[:processLen])
if _, werr := dst.Write(out); werr != nil {
return werr
}
pending = append([]byte(nil), pending[processLen:]...)
}
}
}
if err != nil {
if err == io.EOF {
break
}
return err
}
}
content, err := paddingx.Pad(pending, blockSize, paddingType)
if err != nil {
return err
}
out := make([]byte, len(content))
cryptBlocks(out, content)
_, err = dst.Write(out)
return err
}
func decryptPaddedBlockStream(dst io.Writer, src io.Reader, blockSize int, paddingType string, cryptBlocks func(dst, src []byte)) error {
pending := make([]byte, 0, blockSize*2)
buf := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
pending = append(pending, buf[:n]...)
processLen := len(pending) - blockSize
if processLen > 0 {
processLen -= processLen % blockSize
if processLen > 0 {
out := make([]byte, processLen)
cryptBlocks(out, pending[:processLen])
if _, werr := dst.Write(out); werr != nil {
return werr
}
pending = append([]byte(nil), pending[processLen:]...)
}
}
}
if err != nil {
if err == io.EOF {
break
}
return err
}
}
if len(pending) == 0 || len(pending)%blockSize != 0 {
return errors.New("ciphertext is not a full block size")
}
decrypted := make([]byte, len(pending))
cryptBlocks(decrypted, pending)
out, err := paddingx.Unpad(decrypted, blockSize, paddingType)
if err != nil {
return err
}
_, err = dst.Write(out)
return err
}
func ecbEncryptBlocks(block cipher.Block, dst, src []byte) {
blockSize := block.BlockSize()
for i := 0; i < len(src); i += blockSize {
block.Encrypt(dst[i:i+blockSize], src[i:i+blockSize])
}
}
func ecbDecryptBlocks(block cipher.Block, dst, src []byte) {
blockSize := block.BlockSize()
for i := 0; i < len(src); i += blockSize {
block.Decrypt(dst[i:i+blockSize], src[i:i+blockSize])
}
}