mirror of
https://github.com/skidoodle/safebin.git
synced 2026-04-28 11:17:42 +02:00
+37
-21
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user