Files
safebin/internal/crypto/reader.go
T
x aca7267301 refactor: internals
Signed-off-by: skidoodle <contact@albert.lol>
2026-01-17 22:58:38 +01:00

108 lines
2.2 KiB
Go

package crypto
import (
"crypto/cipher"
"encoding/binary"
"errors"
"fmt"
"io"
)
var ErrInvalidWhence = errors.New("invalid whence")
var ErrNegativeBias = errors.New("negative bias")
type Decryptor struct {
readSeeker io.ReadSeeker
aead cipher.AEAD
size int64
offset int64
}
func NewDecryptor(readSeeker io.ReadSeeker, aead cipher.AEAD, encryptedSize int64) *Decryptor {
overhead := int64(aead.Overhead())
chunkWithOverhead := int64(GCMChunkSize) + overhead
fullBlocks := encryptedSize / chunkWithOverhead
remainder := encryptedSize % chunkWithOverhead
plainSize := fullBlocks * GCMChunkSize
if remainder > overhead {
plainSize += (remainder - overhead)
}
return &Decryptor{
readSeeker: readSeeker,
aead: aead,
size: plainSize,
offset: 0,
}
}
func (d *Decryptor) Read(buf []byte) (int, error) {
if d.offset >= d.size {
return 0, io.EOF
}
chunkIdx := d.offset / GCMChunkSize
overhang := d.offset % GCMChunkSize
overhead := int64(d.aead.Overhead())
actualChunkSize := int64(GCMChunkSize) + overhead
_, err := d.readSeeker.Seek(chunkIdx*actualChunkSize, io.SeekStart)
if err != nil {
return 0, fmt.Errorf("failed to seek: %w", err)
}
encrypted := make([]byte, actualChunkSize)
bytesRead, err := io.ReadFull(d.readSeeker, encrypted)
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
return 0, fmt.Errorf("failed to read encrypted data: %w", err)
}
nonce := make([]byte, NonceSize)
if chunkIdx < 0 {
return 0, fmt.Errorf("invalid chunk index")
}
binary.BigEndian.PutUint64(nonce[4:], uint64(chunkIdx))
plaintext, err := d.aead.Open(nil, nonce, encrypted[:bytesRead], nil)
if err != nil {
return 0, fmt.Errorf("failed to decrypt: %w", err)
}
if overhang >= int64(len(plaintext)) {
return 0, io.EOF
}
available := plaintext[overhang:]
nCopied := copy(buf, available)
d.offset += int64(nCopied)
return nCopied, nil
}
func (d *Decryptor) Seek(offset int64, whence int) (int64, error) {
var abs int64
switch whence {
case io.SeekStart:
abs = offset
case io.SeekCurrent:
abs = d.offset + offset
case io.SeekEnd:
abs = d.size + offset
default:
return 0, ErrInvalidWhence
}
if abs < 0 {
return 0, ErrNegativeBias
}
d.offset = abs
return abs, nil
}