%PDF- %PDF-
| Direktori : /home/waritko/go/src/github.com/odeke-em/drive/src/dcrypto/v1/ |
| Current File : //home/waritko/go/src/github.com/odeke-em/drive/src/dcrypto/v1/v1.go |
// Copyright 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package v1 implements the first version of encryption for drive
// It uses AES-256 in CTR mode for encryption and uses authenticates
// with an HMAC using SHA-512.
//
// This package should always be able to decrypt files that were encrypted
// using this package. If there is a change that needs to be made that would
// prevent decryption of old files, it should be done in a new version.
package v1
import (
"bufio"
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"encoding/binary"
"errors"
"hash"
"io"
"os"
"github.com/odeke-em/go-utils/tmpfile"
"golang.org/x/crypto/scrypt"
)
const (
// The size of the HMAC sum.
hmacSize = sha512.Size
// The size of the HMAC key.
hmacKeySize = 32 // 256 bits
// The size of the random salt.
saltSize = 32 // 256 bits
// The size of the AES key.
aesKeySize = 32 // 256 bits
// The size of the AES block.
blockSize = aes.BlockSize
// 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 int32 = 262144 // 2^18
)
const _16KB = 16 * 1024
var (
// The underlying hash function to use for HMAC.
hashFunc = sha512.New
// 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")
// randBytes returns random bytes in a byte slice of size.
func randBytes(size int) ([]byte, error) {
b := make([]byte, size)
_, err := rand.Read(b)
return b, err
}
// keys derives AES and HMAC keys from a password and salt.
func keys(pass, salt []byte, iterations int) (aesKey, hmacKey []byte, err error) {
key, err := scrypt.Key(pass, salt, iterations, 8, 1, keySize)
if err != nil {
return nil, nil, err
}
aesKey = append(aesKey, key[:aesKeySize]...)
hmacKey = append(hmacKey, key[aesKeySize:keySize]...)
return aesKey, hmacKey, nil
}
// Make sure we implement io.ReadWriter.
var _ io.ReadWriter = &hashReadWriter{}
// 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
h.sum = bytes.NewReader(h.hash.Sum(nil))
}
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
// is no recovery.
func NewEncryptReader(r io.Reader, pass []byte) (io.Reader, error) {
salt, err := randBytes(saltSize)
if err != nil {
return nil, err
}
return newEncryptReader(r, pass, salt, scryptIterations)
}
// 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 int32) (io.Reader, error) {
itersAsBytes, err := encInt32(iterations)
if err != nil {
return nil, err
}
aesKey, hmacKey, err := keys(pass, salt, int(iterations))
if err != nil {
return nil, err
}
iv, err := randBytes(blockSize)
if err != nil {
return nil, err
}
var header []byte
header = append(header, itersAsBytes...)
header = append(header, salt...)
header = append(header, iv...)
return encrypter(r, aesKey, hmacKey, iv, header)
}
// encrypter returns the encrypted reader passed on the keys and IV provided.
func encrypter(r io.Reader, aesKey, hmacKey, iv, header []byte) (io.Reader, error) {
b, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
h := hmac.New(hashFunc, hmacKey)
hr := &hashReadWriter{hash: h}
sr := &cipher.StreamReader{R: r, S: cipher.NewCTR(b, iv)}
return io.MultiReader(io.TeeReader(io.MultiReader(bytes.NewReader(header), sr), hr), hr), nil
}
// decodeHeader decodes the header of the reader.
// It returns the keys, IV, and original header using the password and iterations in the reader.
func decodeHeader(r io.Reader, password []byte) (aesKey, hmacKey, iv, header []byte, err error) {
itersAsBytes, iterations, err := decInt32(r)
if err != nil {
return nil, nil, nil, nil, err
}
salt := make([]byte, saltSize)
iv = make([]byte, blockSize)
_, err = io.ReadFull(r, salt)
if err != nil {
return nil, nil, nil, nil, err
}
_, err = io.ReadFull(r, iv)
if err != nil {
return nil, nil, nil, nil, err
}
aesKey, hmacKey, err = keys(password, salt, int(iterations))
if err != nil {
return nil, nil, nil, nil, err
}
header = append(header, itersAsBytes...)
header = append(header, salt...)
header = append(header, iv...)
return aesKey, hmacKey, iv, header, err
}
// decryptReader wraps a io.Reader decrypting its content.
type decryptReader struct {
tmpFile *tmpfile.TmpFile
sReader *cipher.StreamReader
}
// NewDecryptReader creates an io.ReadCloser wrapping an io.Reader.
// It has to read the entire io.Reader to disk using a temp file so that it can
// 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) (d io.ReadCloser, err error) {
mac := make([]byte, hmacSize)
aesKey, hmacKey, iv, header, err := decodeHeader(r, pass)
h := hmac.New(hashFunc, hmacKey)
h.Write(header)
if err != nil {
return nil, err
}
dst, err := tmpfile.New(&tmpfile.Context{
Dir: os.TempDir(),
Suffix: "drive-encrypted-",
})
if err != nil {
return nil, err
}
// If there is an error, try to delete the temp file.
defer func() {
if err != nil {
dst.Done()
}
}()
b, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
d = &decryptReader{
tmpFile: dst,
sReader: &cipher.StreamReader{R: dst, S: cipher.NewCTR(b, iv)},
}
w := io.MultiWriter(h, dst)
buf := bufio.NewReaderSize(r, _16KB)
for {
b, err := buf.Peek(_16KB)
if err != nil && err != io.EOF {
return nil, err
}
if err == io.EOF {
left := buf.Buffered()
if left < hmacSize {
return nil, DecryptErr
}
copy(mac, b[left-hmacSize:left])
_, err = io.CopyN(w, buf, int64(left-hmacSize))
if err != nil {
return nil, err
}
break
}
_, err = io.CopyN(w, buf, _16KB-hmacSize)
if err != nil {
return nil, err
}
}
if !hmac.Equal(mac, h.Sum(nil)) {
return nil, DecryptErr
}
dst.Seek(0, 0)
return d, nil
}
// Read implements io.Reader.
func (d *decryptReader) Read(dst []byte) (int, error) {
return d.sReader.Read(dst)
}
// Close implements io.Closer.
func (d *decryptReader) Close() error {
return d.tmpFile.Done()
}
// Hash hashes the plaintext based on the header of the encrypted file and returns the hash Sum.
func Hash(plainTextR io.Reader, headerR io.Reader, password []byte, h hash.Hash) ([]byte, error) {
aesKey, hmacKey, iv, eHeader, err := decodeHeader(headerR, password)
if err != nil {
return nil, err
}
encReader, err := encrypter(plainTextR, aesKey, hmacKey, iv, eHeader)
if err != nil {
return nil, err
}
if _, err := io.Copy(h, encReader); err != nil {
return nil, err
}
return h.Sum(nil), nil
}