From 878265bcf7a9f90c8fc1aa2cc53ef861e9ac8940 Mon Sep 17 00:00:00 2001 From: Steven Selph Date: Sat, 21 May 2016 22:20:51 -0400 Subject: [PATCH] end-to-end: encode scrypt iterations in to file header. Updates #543 --- src/dcrypto/v1/v1.go | 53 ++++++++++++++++++++++++++++++++------- src/dcrypto/v1/v1_test.go | 4 +-- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/dcrypto/v1/v1.go b/src/dcrypto/v1/v1.go index a94420a5..b9337639 100644 --- a/src/dcrypto/v1/v1.go +++ b/src/dcrypto/v1/v1.go @@ -30,6 +30,7 @@ import ( "crypto/hmac" "crypto/rand" "crypto/sha512" + "encoding/binary" "errors" "hash" "io" @@ -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 @@ -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") @@ -91,12 +99,15 @@ 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") @@ -104,6 +115,7 @@ func (h *hashReadWriter) Write(p []byte) (int, error) { return h.hash.Write(p) } +// Read implements io.Reader. func (h *hashReadWriter) Read(p []byte) (int, error) { if !h.done { h.done = true @@ -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 @@ -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 } @@ -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 @@ -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) @@ -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{ diff --git a/src/dcrypto/v1/v1_test.go b/src/dcrypto/v1/v1_test.go index bf19e2c2..ec483118 100644 --- a/src/dcrypto/v1/v1_test.go +++ b/src/dcrypto/v1/v1_test.go @@ -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) } @@ -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