mirror of
https://github.com/skidoodle/safebin.git
synced 2026-04-28 11:17:42 +02:00
2df37e9002
Signed-off-by: skidoodle <contact@albert.lol>
118 lines
2.4 KiB
Go
118 lines
2.4 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
|
|
phyOffset 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,
|
|
phyOffset: -1,
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
targetOffset := chunkIdx * actualChunkSize
|
|
|
|
if d.phyOffset != targetOffset {
|
|
if _, err := d.readSeeker.Seek(targetOffset, io.SeekStart); err != nil {
|
|
return 0, fmt.Errorf("failed to seek: %w", err)
|
|
}
|
|
d.phyOffset = targetOffset
|
|
}
|
|
|
|
encrypted := make([]byte, actualChunkSize)
|
|
|
|
bytesRead, err := io.ReadFull(d.readSeeker, encrypted)
|
|
if bytesRead > 0 {
|
|
d.phyOffset += int64(bytesRead)
|
|
}
|
|
|
|
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
|
|
}
|