package security import ( "fmt" "math" "strconv" "github.com/pkg/errors" ) type IEncryptedStream interface { Write(raw_data []byte) []byte Read(raw_data []byte) ([]byte, error) } const blockSize = 4096 const headerSize = 16 type EncryptedStream struct { gcm_encrypter IEncryptor header []byte header_applied bool } type StreamOption func(EncryptedStream) (EncryptedStream, error) func NewEncryptedStream(encryptor IEncryptor, options ...StreamOption) (IEncryptedStream, error) { var err error stream := EncryptedStream{gcm_encrypter: encryptor, header: nil, header_applied: false} for _, option := range options { stream, err = option(stream) if err != nil { return nil, err } } return &stream, nil } func WithUniqueId(uniqueid []byte) StreamOption { return func(stream EncryptedStream) (EncryptedStream, error) { if len(uniqueid) == headerSize { stream.header = uniqueid stream.header_applied = false return stream, nil } return stream, errors.New("invalid file id - must be 16 bytes") } } func (s *EncryptedStream) Write(rawData []byte) []byte { var length = len(rawData) highWatermark := 0 byteStream := make([]byte, 0) index := 0 if !s.header_applied { byteStream = append(byteStream, s.header...) s.header_applied = true } for index < length { highWatermark = int(math.Min(float64(length-index), float64(blockSize))) slice := s.gcm_encrypter.EncryptChunk(rawData[index:highWatermark]) chunk_size := fmt.Sprintf("%04x", len(slice)) byteStream = append(byteStream, chunk_size...) byteStream = append(byteStream, slice...) index += highWatermark } return byteStream } func (s *EncryptedStream) Read(rawData []byte) ([]byte, error) { var length int64 = int64(len(rawData)) byteStream := make([]byte, 0) if !s.header_applied && length < headerSize { return nil, errors.New("invalid stream") } var index int64 = 0 // read header if !s.header_applied { s.header = rawData[0:headerSize] s.header_applied = true index = headerSize } for index < length { nextBlockSize := "0x" + string(rawData[index:index+4]) byte_slice_length, err := strconv.ParseInt(nextBlockSize, 0, 64) if err != nil { return nil, err } index += 4 // move index by 4 to actual data slice, err := s.gcm_encrypter.DecryptChunk(rawData[index:(index + byte_slice_length)]) if err != nil { return nil, err } byteStream = append(byteStream, slice...) index += byte_slice_length } return byteStream, nil }