Files
monibuca/plugin/crypto/pkg/method/stream.go
2024-12-31 14:18:07 +08:00

185 lines
3.9 KiB
Go
Executable File

package method
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"hash"
"io"
)
type Key struct {
Key string
Iv string
EncKey string
MacKey string
}
func init() {
RegisterCryptor("stream", newStream)
}
type StreamCryptor struct {
enckey []byte
macKey []byte
encrypter *StreamEncrypter `yaml:"-"`
decrypter *StreamDecrypter `json:"-"`
}
func NewStreamEncrypter(encKey, macKey []byte) (*StreamEncrypter, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
iv := make([]byte, block.BlockSize())
_, err = rand.Read(iv)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(block, iv)
mac := hmac.New(sha256.New, macKey)
return &StreamEncrypter{
Block: block,
Stream: stream,
Mac: mac,
IV: iv,
}, nil
}
func NewStreamDecrypter(encKey, macKey []byte, meta StreamMeta) (*StreamDecrypter, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(block, meta.IV)
mac := hmac.New(sha256.New, macKey)
return &StreamDecrypter{
Block: block,
Stream: stream,
Mac: mac,
Meta: meta,
}, nil
}
type StreamMeta struct {
// IV is the initial value for the crypto function
IV []byte
// Hash is the sha256 hmac of the stream
Hash []byte
}
type StreamEncrypter struct {
Source io.Reader
Block cipher.Block
Stream cipher.Stream
Mac hash.Hash
IV []byte
}
// StreamDecrypter is a decrypter for a stream of data with authentication
type StreamDecrypter struct {
Source io.Reader
Block cipher.Block
Stream cipher.Stream
Mac hash.Hash
Meta StreamMeta
}
// Read encrypts the bytes of the inner reader and places them into p
func (s *StreamEncrypter) Read(p []byte) (int, error) {
n, readErr := s.Source.Read(p)
if n > 0 {
s.Stream.XORKeyStream(p[:n], p[:n])
err := writeHash(s.Mac, p[:n])
if err != nil {
return n, err
}
return n, readErr
}
return 0, io.EOF
}
// Meta returns the encrypted stream metadata for use in decrypting. This should only be called after the stream is finished
func (s *StreamEncrypter) Meta() StreamMeta {
return StreamMeta{IV: s.IV, Hash: s.Mac.Sum(nil)}
}
// Read reads bytes from the underlying reader and then decrypts them
func (s *StreamDecrypter) Read(p []byte) (int, error) {
n, readErr := s.Source.Read(p)
if n > 0 {
err := writeHash(s.Mac, p[:n])
if err != nil {
return n, err
}
s.Stream.XORKeyStream(p[:n], p[:n])
return n, readErr
}
return 0, io.EOF
}
func newStream(cfg Key) (ICryptor, error) {
var cryptor *StreamCryptor
if (cfg.EncKey == "") || (cfg.MacKey == "") {
return nil, errors.New("stream cryptor config not enckey or mackey")
} else {
encKey := []byte(cfg.EncKey)
macKey := []byte(cfg.MacKey)
encrypter, err := NewStreamEncrypter(encKey, macKey)
if err != nil {
return nil, err
}
decrypter, err := NewStreamDecrypter(encKey, macKey, encrypter.Meta())
if err != nil {
return nil, err
}
cryptor = &StreamCryptor{
enckey: encKey,
macKey: macKey,
encrypter: encrypter,
decrypter: decrypter,
}
}
return cryptor, nil
}
func (c *StreamCryptor) Encrypt(origin []byte) ([]byte, error) {
c.encrypter.Source = bytes.NewReader(origin)
return io.ReadAll(c.encrypter)
}
func (c *StreamCryptor) Decrypt(encrypted []byte) ([]byte, error) {
c.decrypter.Source = bytes.NewReader(encrypted)
return io.ReadAll(c.decrypter)
}
func (c *StreamCryptor) GetKey() string {
b64 := base64.RawStdEncoding
return fmt.Sprintf("%s.%s.%s.%s",
b64.EncodeToString(c.enckey),
b64.EncodeToString(c.macKey),
b64.EncodeToString(c.encrypter.IV),
b64.EncodeToString(c.encrypter.Mac.Sum(nil)),
)
}
func writeHash(mac hash.Hash, p []byte) error {
m, err := mac.Write(p)
if err != nil {
return err
}
if m != len(p) {
return errors.New("could not write all bytes to hmac")
}
return nil
}