Skip to content

Commit

Permalink
end-to-end: encode scrypt iterations in to file header.
Browse files Browse the repository at this point in the history
Updates odeke-em#543
  • Loading branch information
sselph committed May 22, 2016
1 parent 0685494 commit 878265b
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 11 deletions.
53 changes: 44 additions & 9 deletions src/dcrypto/v1/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"encoding/binary"
"errors"
"hash"
"io"
Expand Down Expand Up @@ -58,7 +59,7 @@ const (
// The number of iterations to use in for key generation
// See N value in https://godoc.org/golang.org/x/crypto/scrypt#Key
// Must be a power of 2.
scryptIterations = 262144 // 2^18
scryptIterations int32 = 262144 // 2^18
)

const _16KB = 16 * 1024
Expand All @@ -69,6 +70,13 @@ var (

// The amount of key material we need.
keySize = hmacKeySize + aesKeySize

// The size of the Header.
HeaderSize = 4 + saltSize + blockSize

// The overhead added to the file by using this library.
// OverHead + len(plaintext) == len(ciphertext)
OverHead = HeaderSize + hmacSize
)

var DecryptErr = errors.New("message corrupt or incorrect password")
Expand All @@ -91,19 +99,23 @@ func keys(pass, salt []byte, iterations int) (aesKey, hmacKey []byte, err error)
return aesKey, hmacKey, nil
}

// hashReadWriter hashes on write and on read finalizes the hash and returns it.
// Writes after a Read will return an error.
type hashReadWriter struct {
hash hash.Hash
done bool
sum io.Reader
}

// Write implements io.Writer
func (h *hashReadWriter) Write(p []byte) (int, error) {
if h.done {
return 0, errors.New("writing to hashReadWriter after read is not allowed")
}
return h.hash.Write(p)
}

// Read implements io.Reader.
func (h *hashReadWriter) Read(p []byte) (int, error) {
if !h.done {
h.done = true
Expand All @@ -112,6 +124,23 @@ func (h *hashReadWriter) Read(p []byte) (int, error) {
return h.sum.Read(p)
}

// encInt32 will encode a int32 in to a byte slice.
func encInt32(i int32) ([]byte, error) {
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.LittleEndian, i); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

// decInt32 will read an int32 from a reader and return the byte slice and the int32.
func decInt32(r io.Reader) (b []byte, i int32, err error) {
buf := new(bytes.Buffer)
tr := io.TeeReader(r, buf)
err = binary.Read(tr, binary.LittleEndian, &i)
return buf.Bytes(), i, err
}

// NewEncryptReader returns an io.Reader wrapping the provided io.Reader.
// It uses a user provided password and a random salt to derive keys.
// If the key is provided interactively, it should be verified since there
Expand All @@ -127,8 +156,12 @@ func NewEncryptReader(r io.Reader, pass []byte) (io.Reader, error) {
// newEncryptReader returns a encryptReader wrapping an io.Reader.
// It uses a user provided password and the provided salt iterated the
// provided number of times to derive keys.
func newEncryptReader(r io.Reader, pass, salt []byte, iterations int) (io.Reader, error) {
aesKey, hmacKey, err := keys(pass, salt, iterations)
func newEncryptReader(r io.Reader, pass, salt []byte, iterations int32) (io.Reader, error) {
iByte, err := encInt32(iterations)
if err != nil {
return nil, err
}
aesKey, hmacKey, err := keys(pass, salt, int(iterations))
if err != nil {
return nil, err
}
Expand All @@ -144,6 +177,7 @@ func newEncryptReader(r io.Reader, pass, salt []byte, iterations int) (io.Reader
hr := &hashReadWriter{hash: h}
sr := &cipher.StreamReader{R: r, S: cipher.NewCTR(b, iv)}
var header []byte
header = append(header, iByte...)
header = append(header, salt...)
header = append(header, iv...)
return io.MultiReader(io.TeeReader(io.MultiReader(bytes.NewBuffer(header), sr), hr), hr), nil
Expand All @@ -160,11 +194,11 @@ type decryptReader struct {
// hash the contents to verify that it is safe to decrypt.
// If the file is athenticated, the DecryptReader will be returned and
// the resulting bytes will be the plaintext.
func NewDecryptReader(r io.Reader, pass []byte) (io.ReadCloser, error) {
return newDecryptReader(r, pass, scryptIterations)
}

func newDecryptReader(r io.Reader, pass []byte, iterations int) (d *decryptReader, err error) {
func NewDecryptReader(r io.Reader, pass []byte) (d io.ReadCloser, err error) {
iByte, iterations, err := decInt32(r)
if err != nil {
return nil, err
}
salt := make([]byte, saltSize)
iv := make([]byte, blockSize)
mac := make([]byte, hmacSize)
Expand All @@ -176,12 +210,13 @@ func newDecryptReader(r io.Reader, pass []byte, iterations int) (d *decryptReade
if err != nil {
return nil, err
}
aesKey, hmacKey, err := keys(pass, salt, iterations)
aesKey, hmacKey, err := keys(pass, salt, int(iterations))
if err != nil {
return nil, err
}
// Start Verifying the HMAC of the message.
h := hmac.New(hashFunc, hmacKey)
h.Write(iByte)
h.Write(salt)
h.Write(iv)
dst, err := tmpfile.New(&tmpfile.Context{
Expand Down
4 changes: 2 additions & 2 deletions src/dcrypto/v1/v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var aesKey = []byte{122, 207, 56, 234, 212, 67, 99, 125, 149, 229, 186, 218, 134

// TestKeys tests that the keys function hasn't changed and produces the expected keys.
func TestKeys(t *testing.T) {
a, b, err := keys(password, salt, scryptIterations)
a, b, err := keys(password, salt, int(scryptIterations))
if err != nil {
t.Errorf("keys(%v, %v) => %q; want nil", password, salt, err)
}
Expand Down Expand Up @@ -75,7 +75,7 @@ func TestRoundTrip(t *testing.T) {
t.Errorf("ioutil.ReadAll(*EncryptReader) => %q; want nil", err)
continue
}
dr, err := newDecryptReader(bytes.NewBuffer(cipher), password, 1024)
dr, err := NewDecryptReader(bytes.NewBuffer(cipher), password)
if err != nil {
t.Errorf("NewDecryptReader() => %q; want nil", err)
continue
Expand Down

0 comments on commit 878265b

Please sign in to comment.