mirror of https://github.com/yeka/zip.git
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:
parent
fb959e1749
commit
8d81f262c6
110
crypto.go
110
crypto.go
|
@ -12,6 +12,7 @@ import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
|
@ -119,48 +120,98 @@ 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue