~tsileo/blobstash

29c63fc53cd653b943af998aea6ce1afdc378438 — Thomas Sileo 6 months ago a462ff8
crypto: add new helper pkg
2 files changed, 185 insertions(+), 0 deletions(-)

A pkg/crypto/crypto.go
A pkg/crypto/crypto_test.go
A pkg/crypto/crypto.go => pkg/crypto/crypto.go +129 -0
@@ 0,0 1,129 @@
package crypto // import "a4.io/blobstash/pkg/crypto"

import (
	"bytes"
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"golang.org/x/crypto/nacl/secretbox"
)

// The length of the nonce used for the secretbox implementation.
const nonceLength = 24

// The length of the encryption key for the secretbox implementation.
const keyLength = 32

var (
	header = []byte("#blobstash/encrypted_blobsfile\n")
)

func Seal(nkey *[32]byte, path string) (string, error) {
	var nonce [nonceLength]byte
	if _, err := rand.Reader.Read(nonce[:]); err != nil {
		return "", err
	}
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	tmpfile, err := ioutil.TempFile("", "blobstash_secretbox")
	if err != nil {
		return "", err
	}
	if _, err := tmpfile.Write(header); err != nil {
		return "", err
	}

	buf := make([]byte, 16*1024)
	chunklen := make([]byte, 4)
L:
	for {
		n, err := f.Read(buf)
		switch err {
		case nil:
		case io.EOF:
			break L
		default:
			return "", err
		}
		dat := secretbox.Seal(nonce[:], buf[:n], &nonce, nkey)
		binary.BigEndian.PutUint32(chunklen[:], uint32(len(dat)))
		if _, err := tmpfile.Write(chunklen); err != nil {
			return "", err
		}
		if _, err := tmpfile.Write(dat); err != nil {
			return "", err
		}
	}
	if err := tmpfile.Close(); err != nil {
		return "", err
	}

	return tmpfile.Name(), nil
}

func Open(nkey *[32]byte, path string) (string, error) {
	var nonce [nonceLength]byte
	// Actually decrypt the cipher text

	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()
	h := make([]byte, len(header))
	if _, err := f.Read(h); err != nil {
		return "", err
	}
	if !bytes.Equal(header, h) {
		return "", fmt.Errorf("invalid header %v", h)
	}
	tmpfile, err := ioutil.TempFile("", "blobstash_secretbox")
	if err != nil {
		return "", err
	}

	chunklenBytes := make([]byte, 4)
	var chunklen uint32
L:
	for {
		_, err = f.Read(chunklenBytes)
		switch err {
		case nil:
		case io.EOF:
			break L
		default:
			return "", err
		}
		chunklen = binary.BigEndian.Uint32(chunklenBytes[:])
		chunk := make([]byte, chunklen)
		_, err = f.Read(chunk)
		if err != nil {
			return "", err
		}

		copy(nonce[:], chunk[:24])
		decrypted, ok := secretbox.Open(nil, chunk[24:], &nonce, nkey)
		if !ok {
			panic("decryption error")
		}

		if _, err = tmpfile.Write(decrypted); err != nil {
			return "", err
		}

	}

	if err = tmpfile.Close(); err != nil {
		return "", err
	}

	return tmpfile.Name(), nil
}

A pkg/crypto/crypto_test.go => pkg/crypto/crypto_test.go +56 -0
@@ 0,0 1,56 @@
package crypto

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"io/ioutil"
	"os"
	"testing"
)

func TestCrypto(t *testing.T) {
	secretKeyBytes, err := hex.DecodeString("6368616e676520746869732070617373776f726420746f206120736563726574")
	if err != nil {
		panic(err)
	}

	dat := make([]byte, 3<<20)
	if _, err := rand.Reader.Read(dat[:]); err != nil {
		panic(err)
	}
	tmpfile, err := ioutil.TempFile("", "blobstash_secretbox")
	if err != nil {
		panic(err)
	}
	if _, err := tmpfile.Write(dat); err != nil {
		panic(err)
	}
	if err := tmpfile.Close(); err != nil {
		panic(err)
	}
	defer os.Remove(tmpfile.Name())

	var secretKey [32]byte
	copy(secretKey[:], secretKeyBytes)

	sealed, err := Seal(&secretKey, tmpfile.Name())
	if err != nil {
		panic(err)
	}
	defer os.Remove(sealed)
	t.Logf("sealed=%v", sealed)
	unsealed, err := Open(&secretKey, sealed)
	if err != nil {
		panic(err)
	}
	defer os.Remove(unsealed)
	t.Logf("unsealed=%v", unsealed)
	dat2, err := ioutil.ReadFile(unsealed)
	if err != nil {
		panic(err)
	}
	if !bytes.Equal(dat, dat2) {
		t.Errorf("failed to decrypt input")
	}
}