refactor: internals

Signed-off-by: skidoodle <contact@albert.lol>
This commit is contained in:
2026-01-17 22:58:38 +01:00
parent 5bc9497fa0
commit aca7267301
9 changed files with 476 additions and 242 deletions
+37 -21
View File
@@ -6,27 +6,34 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"io"
)
const (
GCMChunkSize = 64 * 1024
NonceSize = 12
KeySize = 16
IDSize = 9
)
func DeriveKey(r io.Reader) ([]byte, error) {
h := sha256.New()
if _, err := io.Copy(h, r); err != nil {
return nil, err
func DeriveKey(reader io.Reader) ([]byte, error) {
hasher := sha256.New()
if _, err := io.Copy(hasher, reader); err != nil {
return nil, fmt.Errorf("failed to copy to hasher: %w", err)
}
return h.Sum(nil)[:16], nil
return hasher.Sum(nil)[:KeySize], nil
}
func GetID(key []byte, ext string) string {
h := sha256.New()
h.Write(key)
h.Write([]byte(ext))
return base64.RawURLEncoding.EncodeToString(h.Sum(nil)[:9])
hasher := sha256.New()
hasher.Write(key)
hasher.Write([]byte(ext))
return base64.RawURLEncoding.EncodeToString(hasher.Sum(nil)[:IDSize])
}
type GCMStreamer struct {
@@ -34,37 +41,46 @@ type GCMStreamer struct {
}
func NewGCMStreamer(key []byte) (*GCMStreamer, error) {
b, err := aes.NewCipher(key)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to create cipher: %w", err)
}
g, err := cipher.NewGCM(b)
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to create GCM: %w", err)
}
return &GCMStreamer{AEAD: g}, nil
return &GCMStreamer{AEAD: gcm}, nil
}
func (g *GCMStreamer) EncryptStream(dst io.Writer, src io.Reader) error {
buf := make([]byte, GCMChunkSize)
var chunkIdx uint64 = 0
var chunkIdx uint64
for {
n, err := io.ReadFull(src, buf)
if n > 0 {
bytesRead, err := io.ReadFull(src, buf)
if bytesRead > 0 {
nonce := make([]byte, NonceSize)
binary.BigEndian.PutUint64(nonce[4:], chunkIdx)
ciphertext := g.AEAD.Seal(nil, nonce, buf[:n], nil)
ciphertext := g.AEAD.Seal(nil, nonce, buf[:bytesRead], nil)
if _, werr := dst.Write(ciphertext); werr != nil {
return werr
return fmt.Errorf("failed to write ciphertext: %w", werr)
}
chunkIdx++
}
if err == io.EOF || err == io.ErrUnexpectedEOF {
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
break
}
if err != nil {
return err
return fmt.Errorf("failed to read source: %w", err)
}
}
return nil
}
+38 -23
View File
@@ -4,34 +4,41 @@ import (
"crypto/cipher"
"encoding/binary"
"errors"
"fmt"
"io"
)
var ErrInvalidWhence = errors.New("invalid whence")
var ErrNegativeBias = errors.New("negative bias")
type Decryptor struct {
rs io.ReadSeeker
aead cipher.AEAD
size int64
offset int64
readSeeker io.ReadSeeker
aead cipher.AEAD
size int64
offset int64
}
func NewDecryptor(rs io.ReadSeeker, aead cipher.AEAD, encryptedSize int64) *Decryptor {
func NewDecryptor(readSeeker io.ReadSeeker, aead cipher.AEAD, encryptedSize int64) *Decryptor {
overhead := int64(aead.Overhead())
fullBlocks := encryptedSize / (GCMChunkSize + overhead)
remainder := encryptedSize % (GCMChunkSize + overhead)
chunkWithOverhead := int64(GCMChunkSize) + overhead
plainSize := (fullBlocks * GCMChunkSize)
fullBlocks := encryptedSize / chunkWithOverhead
remainder := encryptedSize % chunkWithOverhead
plainSize := fullBlocks * GCMChunkSize
if remainder > overhead {
plainSize += (remainder - overhead)
}
return &Decryptor{
rs: rs,
aead: aead,
size: plainSize,
readSeeker: readSeeker,
aead: aead,
size: plainSize,
offset: 0,
}
}
func (d *Decryptor) Read(p []byte) (int, error) {
func (d *Decryptor) Read(buf []byte) (int, error) {
if d.offset >= d.size {
return 0, io.EOF
}
@@ -40,25 +47,29 @@ func (d *Decryptor) Read(p []byte) (int, error) {
overhang := d.offset % GCMChunkSize
overhead := int64(d.aead.Overhead())
actualChunkSize := int64(GCMChunkSize + overhead)
actualChunkSize := int64(GCMChunkSize) + overhead
_, err := d.rs.Seek(chunkIdx*actualChunkSize, io.SeekStart)
_, err := d.readSeeker.Seek(chunkIdx*actualChunkSize, io.SeekStart)
if err != nil {
return 0, err
return 0, fmt.Errorf("failed to seek: %w", err)
}
encrypted := make([]byte, actualChunkSize)
n, err := io.ReadFull(d.rs, encrypted)
if err != nil && err != io.ErrUnexpectedEOF {
return 0, err
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[:n], nil)
plaintext, err := d.aead.Open(nil, nonce, encrypted[:bytesRead], nil)
if err != nil {
return 0, err
return 0, fmt.Errorf("failed to decrypt: %w", err)
}
if overhang >= int64(len(plaintext)) {
@@ -66,7 +77,7 @@ func (d *Decryptor) Read(p []byte) (int, error) {
}
available := plaintext[overhang:]
nCopied := copy(p, available)
nCopied := copy(buf, available)
d.offset += int64(nCopied)
return nCopied, nil
@@ -74,6 +85,7 @@ func (d *Decryptor) Read(p []byte) (int, error) {
func (d *Decryptor) Seek(offset int64, whence int) (int64, error) {
var abs int64
switch whence {
case io.SeekStart:
abs = offset
@@ -82,11 +94,14 @@ func (d *Decryptor) Seek(offset int64, whence int) (int64, error) {
case io.SeekEnd:
abs = d.size + offset
default:
return 0, errors.New("invalid whence")
return 0, ErrInvalidWhence
}
if abs < 0 {
return 0, errors.New("negative bias")
return 0, ErrNegativeBias
}
d.offset = abs
return abs, nil
}