326 lines
8.2 KiB
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])
|
||
|
|
}
|
||
|
|
}
|