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/subtle"
"errors"
"hash"
"io"
"io/ioutil"
@ -121,46 +122,96 @@ func xorBytes(dst, a, b []byte) int {
type authReader struct {
data io.Reader // data to be authenticated
adata io.Reader // the authentication code to read
akey []byte // authentication key
buf *bytes.Buffer // buffer to store data to authenticate
mac hash.Hash // hmac hash
err error
auth bool
}
func newAuthReader(akey []byte, data, adata io.Reader) io.Reader {
return &authReader{
// Streaming authentication
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,
adata: adata,
akey: akey,
buf: new(bytes.Buffer),
mac: hmac.New(sha1.New, akey),
err: nil,
auth: false,
}
if streaming {
return &ar
}
return &bufferedAuthReader{
ar,
new(bytes.Buffer),
}
}
// Read will fully buffer the file data payload to authenticate first.
// If authentication fails, returns ErrDecryption immediately.
// Else, sends data along for decryption.
func (a *authReader) Read(b []byte) (int, error) {
type bufferedAuthReader struct {
authReader
buf *bytes.Buffer // buffer to store data to authenticate
}
// buffered authentication
func (a *bufferedAuthReader) Read(b []byte) (int, error) {
// check for sticky error
if a.err != nil {
return 0, a.err
}
// make sure we have auth'ed before we send any data
if !a.auth {
nn, err := io.Copy(a.buf, a.data)
_, err := io.Copy(a.buf, a.data)
if err != nil {
a.err = ErrDecryption
return 0, a.err
}
ab := new(bytes.Buffer)
nn, err = io.Copy(ab, a.adata)
nn, err := io.Copy(ab, a.adata)
if err != nil || nn != 10 {
a.err = ErrDecryption
return 0, a.err
}
a.auth = checkAuthentication(a.buf.Bytes(), ab.Bytes(), a.akey)
if !a.auth {
a.mac.Write(a.buf.Bytes())
if !a.checkAuthentication(ab.Bytes()) {
a.err = ErrDecryption
return 0, a.err
}
@ -173,15 +224,13 @@ func (a *authReader) Read(b []byte) (int, error) {
return n, a.err
}
func checkAuthentication(message, authcode, key []byte) bool {
mac := hmac.New(sha1.New, key)
mac.Write(message)
expectedAuthCode := mac.Sum(nil)
func (a *authReader) checkAuthentication(authcode []byte) bool {
expectedAuthCode := a.mac.Sum(nil)
// Truncate at the first 10 bytes
expectedAuthCode = expectedAuthCode[:10]
// Change to use crypto/subtle for constant time comparison
b := subtle.ConstantTimeCompare(expectedAuthCode, authcode) > 0
return b
a.auth = subtle.ConstantTimeCompare(expectedAuthCode, authcode) > 0
return a.auth
}
func checkPasswordVerification(pwvv, pwv []byte) bool {
@ -205,8 +254,7 @@ func newDecryptionReader(r *io.SectionReader, f *File) (io.ReadCloser, error) {
return nil, ErrDecryption
}
// Change to a streaming implementation
// Maybe not such a good idea after all.
// See:
// Maybe not such a good idea after all. See:
// https://www.imperialviolet.org/2014/06/27/streamingencryption.html
// https://www.imperialviolet.org/2015/05/16/aeads.html
// grab the salt, pwvv, data, and authcode
@ -216,11 +264,6 @@ func newDecryptionReader(r *io.SectionReader, f *File) (io.ReadCloser, error) {
}
salt := saltpwvv[:saltLen]
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
decKey, authKey, pwv := generateKeys(f.password, salt, keyLen)
// check password verifier (pwv)
@ -228,8 +271,13 @@ func newDecryptionReader(r *io.SectionReader, f *File) (io.ReadCloser, error) {
if !checkPasswordVerification(pwv, pwvv) {
return nil, ErrDecryption
}
// setup auth reader
ar := newAuthReader(authKey, data, authcode)
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)
// setup auth reader, (buffered)/streaming
ar := newAuthReader(authKey, data, authcode, false)
// return decryption reader
dr := decryptStream(decKey, ar)
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 tampered data payload, use messWith
// Test streaming vs buffered reading