Setup ground work for streaming/buffered reading.

Buffered reading is recommended as it will fully
authenticate the ciphertext before sending any data
to be decrypted. Streaming authentication is only
recommended if: 1. you buffer the data yourself
and wait for authentication before streaming to another
source such as the network, or 2. you just don't
care about authenticating unknown ciphertext before use :).
This commit is contained in:
alexmullins 2015-11-06 00:50:54 -06:00
parent fb959e1749
commit 8d81f262c6
2 changed files with 80 additions and 31 deletions

106
crypto.go
View File

@ -12,6 +12,7 @@ import (
"crypto/sha1" "crypto/sha1"
"crypto/subtle" "crypto/subtle"
"errors" "errors"
"hash"
"io" "io"
"io/ioutil" "io/ioutil"
@ -121,46 +122,96 @@ func xorBytes(dst, a, b []byte) int {
type authReader struct { type authReader struct {
data io.Reader // data to be authenticated data io.Reader // data to be authenticated
adata io.Reader // the authentication code to read adata io.Reader // the authentication code to read
akey []byte // authentication key mac hash.Hash // hmac hash
buf *bytes.Buffer // buffer to store data to authenticate
err error err error
auth bool auth bool
} }
func newAuthReader(akey []byte, data, adata io.Reader) io.Reader { // Streaming authentication
return &authReader{ func (a *authReader) Read(p []byte) (int, error) {
if a.err != nil {
return 0, a.err
}
end := false
// read underlying data
n, err := a.data.Read(p)
if err != nil && err != io.EOF {
a.err = err
return n, a.err
} else if err == io.EOF {
// if we are at the end, calculate the mac
end = true
a.err = err
}
// write any data to mac
nn, err := a.mac.Write(p[:n])
if nn != n || err != nil {
a.err = ErrDecryption
return nn, a.err
}
if end {
ab := new(bytes.Buffer)
_, err = io.Copy(ab, a.adata)
if err != nil || ab.Len() != 10 {
a.err = ErrDecryption
return n, a.err
}
if !a.checkAuthentication(ab.Bytes()) {
a.err = ErrDecryption
return n, a.err
}
}
return n, a.err
}
// newAuthReader returns either a buffered or streaming authentication reader.
// Buffered authentication is recommended. Streaming authentication is only
// recommended if: 1. you buffer the data yourself and wait for authentication
// before streaming to another source such as the network, or 2. you just don't
// care about authenticating unknown ciphertext before use :).
func newAuthReader(akey []byte, data, adata io.Reader, streaming bool) io.Reader {
ar := authReader{
data: data, data: data,
adata: adata, adata: adata,
akey: akey, mac: hmac.New(sha1.New, akey),
buf: new(bytes.Buffer),
err: nil, err: nil,
auth: false, auth: false,
} }
if streaming {
return &ar
}
return &bufferedAuthReader{
ar,
new(bytes.Buffer),
}
} }
// Read will fully buffer the file data payload to authenticate first. type bufferedAuthReader struct {
// If authentication fails, returns ErrDecryption immediately. authReader
// Else, sends data along for decryption. buf *bytes.Buffer // buffer to store data to authenticate
func (a *authReader) Read(b []byte) (int, error) { }
// buffered authentication
func (a *bufferedAuthReader) Read(b []byte) (int, error) {
// check for sticky error // check for sticky error
if a.err != nil { if a.err != nil {
return 0, a.err return 0, a.err
} }
// make sure we have auth'ed before we send any data // make sure we have auth'ed before we send any data
if !a.auth { if !a.auth {
nn, err := io.Copy(a.buf, a.data) _, err := io.Copy(a.buf, a.data)
if err != nil { if err != nil {
a.err = ErrDecryption a.err = ErrDecryption
return 0, a.err return 0, a.err
} }
ab := new(bytes.Buffer) ab := new(bytes.Buffer)
nn, err = io.Copy(ab, a.adata) nn, err := io.Copy(ab, a.adata)
if err != nil || nn != 10 { if err != nil || nn != 10 {
a.err = ErrDecryption a.err = ErrDecryption
return 0, a.err return 0, a.err
} }
a.auth = checkAuthentication(a.buf.Bytes(), ab.Bytes(), a.akey) a.mac.Write(a.buf.Bytes())
if !a.auth { if !a.checkAuthentication(ab.Bytes()) {
a.err = ErrDecryption a.err = ErrDecryption
return 0, a.err return 0, a.err
} }
@ -173,15 +224,13 @@ func (a *authReader) Read(b []byte) (int, error) {
return n, a.err return n, a.err
} }
func checkAuthentication(message, authcode, key []byte) bool { func (a *authReader) checkAuthentication(authcode []byte) bool {
mac := hmac.New(sha1.New, key) expectedAuthCode := a.mac.Sum(nil)
mac.Write(message)
expectedAuthCode := mac.Sum(nil)
// Truncate at the first 10 bytes // Truncate at the first 10 bytes
expectedAuthCode = expectedAuthCode[:10] expectedAuthCode = expectedAuthCode[:10]
// Change to use crypto/subtle for constant time comparison // Change to use crypto/subtle for constant time comparison
b := subtle.ConstantTimeCompare(expectedAuthCode, authcode) > 0 a.auth = subtle.ConstantTimeCompare(expectedAuthCode, authcode) > 0
return b return a.auth
} }
func checkPasswordVerification(pwvv, pwv []byte) bool { func checkPasswordVerification(pwvv, pwv []byte) bool {
@ -205,8 +254,7 @@ func newDecryptionReader(r *io.SectionReader, f *File) (io.ReadCloser, error) {
return nil, ErrDecryption return nil, ErrDecryption
} }
// Change to a streaming implementation // Change to a streaming implementation
// Maybe not such a good idea after all. // Maybe not such a good idea after all. See:
// See:
// https://www.imperialviolet.org/2014/06/27/streamingencryption.html // https://www.imperialviolet.org/2014/06/27/streamingencryption.html
// https://www.imperialviolet.org/2015/05/16/aeads.html // https://www.imperialviolet.org/2015/05/16/aeads.html
// grab the salt, pwvv, data, and authcode // grab the salt, pwvv, data, and authcode
@ -216,11 +264,6 @@ func newDecryptionReader(r *io.SectionReader, f *File) (io.ReadCloser, error) {
} }
salt := saltpwvv[:saltLen] salt := saltpwvv[:saltLen]
pwvv := saltpwvv[saltLen : saltLen+2] pwvv := saltpwvv[saltLen : saltLen+2]
dataOff := int64(saltLen + 2)
dataLen := int64(f.CompressedSize64 - uint64(saltLen) - 2 - 10)
data := io.NewSectionReader(r, dataOff, dataLen)
authOff := dataOff + dataLen
authcode := io.NewSectionReader(r, authOff, 10)
// generate keys // generate keys
decKey, authKey, pwv := generateKeys(f.password, salt, keyLen) decKey, authKey, pwv := generateKeys(f.password, salt, keyLen)
// check password verifier (pwv) // check password verifier (pwv)
@ -228,8 +271,13 @@ func newDecryptionReader(r *io.SectionReader, f *File) (io.ReadCloser, error) {
if !checkPasswordVerification(pwv, pwvv) { if !checkPasswordVerification(pwv, pwvv) {
return nil, ErrDecryption return nil, ErrDecryption
} }
// setup auth reader dataOff := int64(saltLen + 2)
ar := newAuthReader(authKey, data, authcode) dataLen := int64(f.CompressedSize64 - uint64(saltLen) - 2 - 10)
data := io.NewSectionReader(r, dataOff, dataLen)
authOff := dataOff + dataLen
authcode := io.NewSectionReader(r, authOff, 10)
// setup auth reader, (buffered)/streaming
ar := newAuthReader(authKey, data, authcode, false)
// return decryption reader // return decryption reader
dr := decryptStream(decKey, ar) dr := decryptStream(decKey, ar)
return ioutil.NopCloser(dr), nil return ioutil.NopCloser(dr), nil

View File

@ -100,3 +100,4 @@ func TestPasswordMacbethAct1(t *testing.T) {
// Test for AE-1 vs AE-2 // Test for AE-1 vs AE-2
// Test for tampered data payload, use messWith // Test for tampered data payload, use messWith
// Test streaming vs buffered reading