Reorganize crypto code

Move all crypto functions to package "crypto", move random generators
for tests into helper package.
This commit is contained in:
Alexander Neumann 2015-04-12 09:36:14 +02:00
parent 8e8f31d3fe
commit 3a2525809c
10 changed files with 166 additions and 187 deletions

View File

@ -4,7 +4,6 @@ import (
"bytes"
"flag"
"io"
"math/rand"
"testing"
"github.com/restic/restic"
@ -16,21 +15,6 @@ import (
var benchArchiveDirectory = flag.String("test.benchdir", ".", "benchmark archiving a real directory (default: .)")
var testPol = chunker.Pol(0x3DA3358B4DC173)
func get_random(seed, count int) []byte {
buf := make([]byte, count)
rnd := rand.New(rand.NewSource(int64(seed)))
for i := 0; i < count; i++ {
buf[i] = byte(rnd.Uint32())
}
return buf
}
func randomReader(seed, size int) *bytes.Reader {
return bytes.NewReader(get_random(seed, size))
}
const bufSize = chunker.MiB
type Rdr interface {
@ -66,7 +50,7 @@ func benchmarkChunkEncrypt(b testing.TB, buf []byte, rd Rdr, key *restic.Key) {
}
func BenchmarkChunkEncrypt(b *testing.B) {
data := get_random(23, 10<<20) // 10MiB
data := Random(23, 10<<20) // 10MiB
rd := bytes.NewReader(data)
be := setupBackend(b)
@ -110,7 +94,7 @@ func BenchmarkChunkEncryptParallel(b *testing.B) {
defer teardownBackend(b, be)
key := setupKey(b, be, "geheim")
data := get_random(23, 10<<20) // 10MiB
data := Random(23, 10<<20) // 10MiB
buf := restic.GetChunkBuf("BenchmarkChunkEncryptParallel")

19
crypto/buffer_pool.go Normal file
View File

@ -0,0 +1,19 @@
package crypto
import "sync"
const defaultBufSize = 2048
var bufPool = sync.Pool{
New: func() interface{} {
return make([]byte, defaultBufSize)
},
}
func getBuffer() []byte {
return bufPool.Get().([]byte)
}
func freeBuffer(buf []byte) {
bufPool.Put(buf)
}

View File

@ -1,4 +1,4 @@
package restic
package crypto
import (
"bytes"
@ -6,11 +6,13 @@ import (
"crypto/cipher"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"sync"
"github.com/restic/restic/chunker"
"golang.org/x/crypto/poly1305"
"golang.org/x/crypto/scrypt"
)
@ -21,8 +23,30 @@ const (
MACKeySizeR = 16 // for Poly1305
MACKeySize = MACKeySizeK + MACKeySizeR // for Poly1305-AES128
ivSize = aes.BlockSize
macSize = poly1305.TagSize // Poly1305 size is 16 byte
CiphertextExtension = ivSize + macSize
)
var (
// ErrUnauthenticated is returned when ciphertext verification has failed.
ErrUnauthenticated = errors.New("ciphertext verification failed")
// ErrBufferTooSmall is returned when the destination slice is too small
// for the ciphertext.
ErrBufferTooSmall = errors.New("destination buffer too small")
)
// MasterKeys holds signing and encryption keys for a repository. It is stored
// encrypted and signed as a JSON data structure in the Data field of the Key
// structure. For the master key, the secret random polynomial used for content
// defined chunking is included.
type MasterKeys struct {
Sign MACKey `json:"sign"`
Encrypt AESKey `json:"encrypt"`
ChunkerPolynomial chunker.Pol `json:"chunker_polynomial,omitempty"`
}
type AESKey [32]byte
type MACKey struct {
K [16]byte // for AES128
@ -118,7 +142,7 @@ func poly1305_verify(msg []byte, nonce []byte, key *MACKey, mac []byte) bool {
}
// returns new encryption and mac keys. k.MACKey.R is already masked.
func generateRandomKeys() (k *MasterKeys) {
func GenerateRandomKeys() (k *MasterKeys) {
k = &MasterKeys{}
n, err := rand.Read(k.Encrypt[:])
if n != AESKeySize || err != nil {
@ -249,16 +273,17 @@ func Decrypt(ks *MasterKeys, plaintext, ciphertext []byte) ([]byte, error) {
return plaintext, nil
}
// runs scrypt(password)
func kdf(k *Key, password string) (*MasterKeys, error) {
if len(k.Salt) == 0 {
// KDF derives encryption and signing keys from the password using the supplied
// parameters N, R and P and the Salt.
func KDF(N, R, P int, salt []byte, password string) (*MasterKeys, error) {
if len(salt) == 0 {
return nil, fmt.Errorf("scrypt() called with empty salt")
}
derKeys := &MasterKeys{}
keybytes := MACKeySize + AESKeySize
scryptKeys, err := scrypt.Key([]byte(password), k.Salt, k.N, k.R, k.P, keybytes)
scryptKeys, err := scrypt.Key([]byte(password), salt, N, R, P, keybytes)
if err != nil {
return nil, fmt.Errorf("error deriving keys from password: %v", err)
}
@ -296,7 +321,7 @@ func (e *encryptWriter) Close() error {
}
// return buffer
FreeChunkBuf("EncryptWriter", e.data.Bytes())
bufPool.Put(e.data.Bytes())
return nil
}
@ -354,7 +379,7 @@ func (e *encryptWriter) Write(p []byte) (int, error) {
func EncryptTo(ks *MasterKeys, wr io.Writer) io.WriteCloser {
ew := &encryptWriter{
iv: generateRandomIV(),
data: bytes.NewBuffer(GetChunkBuf("EncryptWriter")[:0]),
data: bytes.NewBuffer(getBuffer()[:0]),
key: ks,
origWr: wr,
}
@ -426,7 +451,7 @@ func (d *decryptReader) Close() error {
return nil
}
FreeChunkBuf("decryptReader", d.buf)
freeBuffer(d.buf)
d.buf = nil
return nil
}
@ -438,7 +463,7 @@ func (d *decryptReader) Close() error {
// afterwards. If a MAC verification failure is observed, it is returned
// immediately.
func DecryptFrom(ks *MasterKeys, rd io.Reader) (io.ReadCloser, error) {
ciphertext := GetChunkBuf("decryptReader")
ciphertext := getBuffer()
ciphertext = ciphertext[0:cap(ciphertext)]
n, err := io.ReadFull(rd, ciphertext)

View File

@ -1,4 +1,4 @@
package restic
package crypto
import (
"bytes"
@ -98,24 +98,22 @@ func should_panic(f func()) (did_panic bool) {
}
func TestCrypto(t *testing.T) {
r := &Key{}
for _, tv := range test_values {
// test encryption
r.master = &MasterKeys{
k := &MasterKeys{
Encrypt: tv.ekey,
Sign: tv.skey,
}
msg := make([]byte, maxCiphertextSize)
n, err := Encrypt(r.master, msg, tv.plaintext)
msg := make([]byte, 0, 8*1024*1024) // use 8MiB for now
n, err := Encrypt(k, msg, tv.plaintext)
if err != nil {
t.Fatal(err)
}
msg = msg[:n]
// decrypt message
_, err = Decrypt(r.master, []byte{}, msg)
_, err = Decrypt(k, []byte{}, msg)
if err != nil {
t.Fatal(err)
}
@ -123,12 +121,22 @@ func TestCrypto(t *testing.T) {
// change mac, this must fail
msg[len(msg)-8] ^= 0x23
if _, err = Decrypt(r.master, []byte{}, msg); err != ErrUnauthenticated {
t.Fatal("wrong HMAC value not detected")
if _, err = Decrypt(k, []byte{}, msg); err != ErrUnauthenticated {
t.Fatal("wrong MAC value not detected")
}
// reset mac
msg[len(msg)-8] ^= 0x23
// tamper with message, this must fail
msg[16+5] ^= 0x85
if _, err = Decrypt(k, []byte{}, msg); err != ErrUnauthenticated {
t.Fatal("tampered message not detected")
}
// test decryption
p, err := Decrypt(r.master, []byte{}, tv.ciphertext)
p, err := Decrypt(k, []byte{}, tv.ciphertext)
if err != nil {
t.Fatal(err)
}

View File

@ -1,7 +1,8 @@
package restic_test
package crypto_test
import (
"bytes"
"flag"
"io"
"io/ioutil"
"os"
@ -9,13 +10,14 @@ import (
"github.com/restic/restic"
"github.com/restic/restic/chunker"
"github.com/restic/restic/crypto"
. "github.com/restic/restic/test"
)
var testLargeCrypto = flag.Bool("test.largecrypto", false, "also test crypto functions with large payloads")
func TestEncryptDecrypt(t *testing.T) {
s := setupBackend(t)
defer teardownBackend(t, s)
k := setupKey(t, s, testPassword)
k := crypto.GenerateRandomKeys()
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
if *testLargeCrypto {
@ -24,14 +26,14 @@ func TestEncryptDecrypt(t *testing.T) {
for _, size := range tests {
data := make([]byte, size)
_, err := io.ReadFull(randomReader(42, size), data)
_, err := io.ReadFull(RandomReader(42, size), data)
OK(t, err)
ciphertext := restic.GetChunkBuf("TestEncryptDecrypt")
n, err := k.Encrypt(ciphertext, data)
n, err := crypto.Encrypt(k, ciphertext, data)
OK(t, err)
plaintext, err := k.Decrypt(nil, ciphertext[:n])
plaintext, err := crypto.Decrypt(k, nil, ciphertext[:n])
OK(t, err)
restic.FreeChunkBuf("TestEncryptDecrypt", ciphertext)
@ -41,9 +43,7 @@ func TestEncryptDecrypt(t *testing.T) {
}
func TestSmallBuffer(t *testing.T) {
s := setupBackend(t)
defer teardownBackend(t, s)
k := setupKey(t, s, testPassword)
k := crypto.GenerateRandomKeys()
size := 600
data := make([]byte, size)
@ -54,9 +54,9 @@ func TestSmallBuffer(t *testing.T) {
OK(t, err)
ciphertext := make([]byte, size/2)
_, err = k.Encrypt(ciphertext, data)
_, err = crypto.Encrypt(k, ciphertext, data)
// this must throw an error, since the target slice is too small
Assert(t, err != nil && err == restic.ErrBufferTooSmall,
Assert(t, err != nil && err == crypto.ErrBufferTooSmall,
"expected restic.ErrBufferTooSmall, got %#v", err)
}
@ -65,9 +65,7 @@ func TestLargeEncrypt(t *testing.T) {
t.SkipNow()
}
s := setupBackend(t)
defer teardownBackend(t, s)
k := setupKey(t, s, testPassword)
k := crypto.GenerateRandomKeys()
for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1, chunker.MaxSize + 1<<20} {
data := make([]byte, size)
@ -77,11 +75,11 @@ func TestLargeEncrypt(t *testing.T) {
_, err = io.ReadFull(f, data)
OK(t, err)
ciphertext := make([]byte, size+restic.CiphertextExtension)
n, err := k.Encrypt(ciphertext, data)
ciphertext := make([]byte, size+crypto.CiphertextExtension)
n, err := crypto.Encrypt(k, ciphertext, data)
OK(t, err)
plaintext, err := k.Decrypt([]byte{}, ciphertext[:n])
plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext[:n])
OK(t, err)
Equals(t, plaintext, data)
@ -90,18 +88,16 @@ func TestLargeEncrypt(t *testing.T) {
func BenchmarkEncryptWriter(b *testing.B) {
size := 8 << 20 // 8MiB
rd := randomReader(23, size)
rd := RandomReader(23, size)
be := setupBackend(b)
defer teardownBackend(b, be)
k := setupKey(b, be, testPassword)
k := crypto.GenerateRandomKeys()
b.ResetTimer()
b.SetBytes(int64(size))
for i := 0; i < b.N; i++ {
rd.Seek(0, 0)
wr := k.EncryptTo(ioutil.Discard)
wr := crypto.EncryptTo(k, ioutil.Discard)
_, err := io.Copy(wr, rd)
OK(b, err)
OK(b, wr.Close())
@ -112,31 +108,25 @@ func BenchmarkEncrypt(b *testing.B) {
size := 8 << 20 // 8MiB
data := make([]byte, size)
be := setupBackend(b)
defer teardownBackend(b, be)
k := setupKey(b, be, testPassword)
buf := make([]byte, len(data)+restic.CiphertextExtension)
k := crypto.GenerateRandomKeys()
buf := make([]byte, len(data)+crypto.CiphertextExtension)
b.ResetTimer()
b.SetBytes(int64(size))
for i := 0; i < b.N; i++ {
_, err := k.Encrypt(buf, data)
_, err := crypto.Encrypt(k, buf, data)
OK(b, err)
}
}
func BenchmarkDecryptReader(b *testing.B) {
be := setupBackend(b)
defer teardownBackend(b, be)
k := setupKey(b, be, testPassword)
size := 8 << 20 // 8MiB
buf := get_random(23, size)
buf := Random(23, size)
k := crypto.GenerateRandomKeys()
ciphertext := make([]byte, len(buf)+restic.CiphertextExtension)
_, err := k.Encrypt(ciphertext, buf)
ciphertext := make([]byte, len(buf)+crypto.CiphertextExtension)
_, err := crypto.Encrypt(k, ciphertext, buf)
OK(b, err)
rd := bytes.NewReader(ciphertext)
@ -146,7 +136,7 @@ func BenchmarkDecryptReader(b *testing.B) {
for i := 0; i < b.N; i++ {
rd.Seek(0, 0)
decRd, err := k.DecryptFrom(rd)
decRd, err := crypto.DecryptFrom(k, rd)
OK(b, err)
_, err = io.Copy(ioutil.Discard, decRd)
@ -155,12 +145,10 @@ func BenchmarkDecryptReader(b *testing.B) {
}
func BenchmarkEncryptDecryptReader(b *testing.B) {
be := setupBackend(b)
defer teardownBackend(b, be)
k := setupKey(b, be, testPassword)
k := crypto.GenerateRandomKeys()
size := 8 << 20 // 8MiB
rd := randomReader(23, size)
rd := RandomReader(23, size)
b.ResetTimer()
b.SetBytes(int64(size))
@ -169,12 +157,12 @@ func BenchmarkEncryptDecryptReader(b *testing.B) {
for i := 0; i < b.N; i++ {
rd.Seek(0, 0)
buf.Reset()
wr := k.EncryptTo(buf)
wr := crypto.EncryptTo(k, buf)
_, err := io.Copy(wr, rd)
OK(b, err)
OK(b, wr.Close())
r, err := k.DecryptFrom(buf)
r, err := crypto.DecryptFrom(k, buf)
OK(b, err)
_, err = io.Copy(ioutil.Discard, r)
@ -188,31 +176,27 @@ func BenchmarkDecrypt(b *testing.B) {
size := 8 << 20 // 8MiB
data := make([]byte, size)
s := setupBackend(b)
defer teardownBackend(b, s)
k := setupKey(b, s, testPassword)
k := crypto.GenerateRandomKeys()
ciphertext := restic.GetChunkBuf("BenchmarkDecrypt")
defer restic.FreeChunkBuf("BenchmarkDecrypt", ciphertext)
plaintext := restic.GetChunkBuf("BenchmarkDecrypt")
defer restic.FreeChunkBuf("BenchmarkDecrypt", plaintext)
n, err := k.Encrypt(ciphertext, data)
n, err := crypto.Encrypt(k, ciphertext, data)
OK(b, err)
b.ResetTimer()
b.SetBytes(int64(size))
for i := 0; i < b.N; i++ {
plaintext, err = k.Decrypt(plaintext, ciphertext[:n])
plaintext, err = crypto.Decrypt(k, plaintext, ciphertext[:n])
OK(b, err)
}
}
func TestEncryptStreamWriter(t *testing.T) {
s := setupBackend(t)
defer teardownBackend(t, s)
k := setupKey(t, s, testPassword)
k := crypto.GenerateRandomKeys()
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
if *testLargeCrypto {
@ -221,23 +205,23 @@ func TestEncryptStreamWriter(t *testing.T) {
for _, size := range tests {
data := make([]byte, size)
_, err := io.ReadFull(randomReader(42, size), data)
_, err := io.ReadFull(RandomReader(42, size), data)
OK(t, err)
ciphertext := bytes.NewBuffer(nil)
wr := k.EncryptTo(ciphertext)
wr := crypto.EncryptTo(k, ciphertext)
_, err = io.Copy(wr, bytes.NewReader(data))
OK(t, err)
OK(t, wr.Close())
l := len(data) + restic.CiphertextExtension
l := len(data) + crypto.CiphertextExtension
Assert(t, len(ciphertext.Bytes()) == l,
"wrong ciphertext length: expected %d, got %d",
l, len(ciphertext.Bytes()))
// decrypt with default function
plaintext, err := k.Decrypt([]byte{}, ciphertext.Bytes())
plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext.Bytes())
OK(t, err)
Assert(t, bytes.Equal(data, plaintext),
"wrong plaintext after decryption: expected %02x, got %02x",
@ -246,9 +230,7 @@ func TestEncryptStreamWriter(t *testing.T) {
}
func TestDecryptStreamReader(t *testing.T) {
s := setupBackend(t)
defer teardownBackend(t, s)
k := setupKey(t, s, testPassword)
k := crypto.GenerateRandomKeys()
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
if *testLargeCrypto {
@ -257,19 +239,19 @@ func TestDecryptStreamReader(t *testing.T) {
for _, size := range tests {
data := make([]byte, size)
_, err := io.ReadFull(randomReader(42, size), data)
_, err := io.ReadFull(RandomReader(42, size), data)
OK(t, err)
ciphertext := make([]byte, size+restic.CiphertextExtension)
ciphertext := make([]byte, size+crypto.CiphertextExtension)
// encrypt with default function
n, err := k.Encrypt(ciphertext, data)
n, err := crypto.Encrypt(k, ciphertext, data)
OK(t, err)
Assert(t, n == len(data)+restic.CiphertextExtension,
Assert(t, n == len(data)+crypto.CiphertextExtension,
"wrong number of bytes returned after encryption: expected %d, got %d",
len(data)+restic.CiphertextExtension, n)
len(data)+crypto.CiphertextExtension, n)
rd, err := k.DecryptFrom(bytes.NewReader(ciphertext))
rd, err := crypto.DecryptFrom(k, bytes.NewReader(ciphertext))
OK(t, err)
plaintext, err := ioutil.ReadAll(rd)
@ -282,9 +264,7 @@ func TestDecryptStreamReader(t *testing.T) {
}
func TestEncryptWriter(t *testing.T) {
s := setupBackend(t)
defer teardownBackend(t, s)
k := setupKey(t, s, testPassword)
k := crypto.GenerateRandomKeys()
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
if *testLargeCrypto {
@ -293,11 +273,11 @@ func TestEncryptWriter(t *testing.T) {
for _, size := range tests {
data := make([]byte, size)
_, err := io.ReadFull(randomReader(42, size), data)
_, err := io.ReadFull(RandomReader(42, size), data)
OK(t, err)
buf := bytes.NewBuffer(nil)
wr := k.EncryptTo(buf)
wr := crypto.EncryptTo(k, buf)
_, err = io.Copy(wr, bytes.NewReader(data))
OK(t, err)
@ -305,13 +285,13 @@ func TestEncryptWriter(t *testing.T) {
ciphertext := buf.Bytes()
l := len(data) + restic.CiphertextExtension
l := len(data) + crypto.CiphertextExtension
Assert(t, len(ciphertext) == l,
"wrong ciphertext length: expected %d, got %d",
l, len(ciphertext))
// decrypt with default function
plaintext, err := k.Decrypt([]byte{}, ciphertext)
plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext)
OK(t, err)
Assert(t, bytes.Equal(data, plaintext),
"wrong plaintext after decryption: expected %02x, got %02x",

90
key.go
View File

@ -13,24 +13,13 @@ import (
"github.com/restic/restic/backend"
"github.com/restic/restic/chunker"
"github.com/restic/restic/crypto"
"github.com/restic/restic/debug"
"golang.org/x/crypto/poly1305"
)
// max size is 8MiB, defined in chunker
const macSize = poly1305.TagSize // Poly1305 size is 16 byte
const maxCiphertextSize = ivSize + chunker.MaxSize + macSize
const CiphertextExtension = ivSize + macSize
var (
// ErrUnauthenticated is returned when ciphertext verification has failed.
ErrUnauthenticated = errors.New("ciphertext verification failed")
// ErrNoKeyFound is returned when no key for the repository could be decrypted.
ErrNoKeyFound = errors.New("no key could be found")
// ErrBufferTooSmall is returned when the destination slice is too small
// for the ciphertext.
ErrBufferTooSmall = errors.New("destination buffer too small")
)
// TODO: figure out scrypt values on the fly depending on the current
@ -55,22 +44,12 @@ type Key struct {
Salt []byte `json:"salt"`
Data []byte `json:"data"`
user *MasterKeys
master *MasterKeys
user *crypto.MasterKeys
master *crypto.MasterKeys
name string
}
// MasterKeys holds signing and encryption keys for a repository. It is stored
// encrypted and signed as a JSON data structure in the Data field of the Key
// structure. For the master key, the secret random polynomial used for content
// defined chunking is included.
type MasterKeys struct {
Sign MACKey `json:"sign"`
Encrypt AESKey `json:"encrypt"`
ChunkerPolynomial chunker.Pol `json:"chunker_polynomial,omitempty"`
}
// CreateKey initializes a master key in the given backend and encrypts it with
// the password.
func CreateKey(s Server, password string) (*Key, error) {
@ -90,19 +69,19 @@ func OpenKey(s Server, name string, password string) (*Key, error) {
}
// derive user key
k.user, err = kdf(k, password)
k.user, err = crypto.KDF(k.N, k.R, k.P, k.Salt, password)
if err != nil {
return nil, err
}
// decrypt master keys
buf, err := k.DecryptUser([]byte{}, k.Data)
buf, err := crypto.Decrypt(k.user, []byte{}, k.Data)
if err != nil {
return nil, err
}
// restore json
k.master = &MasterKeys{}
k.master = &crypto.MasterKeys{}
err = json.Unmarshal(buf, k.master)
if err != nil {
return nil, err
@ -190,14 +169,14 @@ func AddKey(s Server, password string, template *Key) (*Key, error) {
}
// call KDF to derive user key
newkey.user, err = kdf(newkey, password)
newkey.user, err = crypto.KDF(newkey.N, newkey.R, newkey.P, newkey.Salt, password)
if err != nil {
return nil, err
}
if template == nil {
// generate new random master keys
newkey.master = generateRandomKeys()
newkey.master = crypto.GenerateRandomKeys()
// generate random polynomial for cdc
p, err := chunker.RandomPolynomial()
if err != nil {
@ -218,7 +197,7 @@ func AddKey(s Server, password string, template *Key) (*Key, error) {
}
newkey.Data = GetChunkBuf("key")
n, err = newkey.EncryptUser(newkey.Data, buf)
n, err = crypto.Encrypt(newkey.user, newkey.Data, buf)
newkey.Data = newkey.Data[:n]
// dump as json
@ -254,52 +233,23 @@ func AddKey(s Server, password string, template *Key) (*Key, error) {
return newkey, nil
}
func (k *Key) newIV(buf []byte) error {
_, err := io.ReadFull(rand.Reader, buf[:ivSize])
buf = buf[:ivSize]
if err != nil {
return err
}
return nil
}
// EncryptUser encrypts and signs data with the user key. Stored in ciphertext
// is IV || Ciphertext || MAC.
func (k *Key) EncryptUser(ciphertext, plaintext []byte) (int, error) {
return Encrypt(k.user, ciphertext, plaintext)
}
// Encrypt encrypts and signs data with the master key. Stored in ciphertext is
// IV || Ciphertext || MAC. Returns the ciphertext length.
func (k *Key) Encrypt(ciphertext, plaintext []byte) (int, error) {
return Encrypt(k.master, ciphertext, plaintext)
return crypto.Encrypt(k.master, ciphertext, plaintext)
}
// EncryptTo encrypts and signs data with the master key. The returned
// io.Writer writes IV || Ciphertext || HMAC. For the hash function, SHA256 is
// used.
func (k *Key) EncryptTo(wr io.Writer) io.WriteCloser {
return EncryptTo(k.master, wr)
}
// EncryptUserTo encrypts and signs data with the user key. The returned
// io.Writer writes IV || Ciphertext || HMAC. For the hash function, SHA256 is
// used.
func (k *Key) EncryptUserTo(wr io.Writer) io.WriteCloser {
return EncryptTo(k.user, wr)
return crypto.EncryptTo(k.master, wr)
}
// Decrypt verifes and decrypts the ciphertext with the master key. Ciphertext
// must be in the form IV || Ciphertext || MAC.
func (k *Key) Decrypt(plaintext, ciphertext []byte) ([]byte, error) {
return Decrypt(k.master, plaintext, ciphertext)
}
// DecryptUser verifes and decrypts the ciphertext with the user key. Ciphertext
// must be in the form IV || Ciphertext || MAC.
func (k *Key) DecryptUser(plaintext, ciphertext []byte) ([]byte, error) {
return Decrypt(k.user, plaintext, ciphertext)
return crypto.Decrypt(k.master, plaintext, ciphertext)
}
// DecryptFrom verifies and decrypts the ciphertext read from rd and makes it
@ -309,27 +259,17 @@ func (k *Key) DecryptUser(plaintext, ciphertext []byte) ([]byte, error) {
// afterwards. If an MAC verification failure is observed, it is returned
// immediately.
func (k *Key) DecryptFrom(rd io.Reader) (io.ReadCloser, error) {
return DecryptFrom(k.master, rd)
}
// DecryptFrom verifies and decrypts the ciphertext read from rd with the user
// key and makes it available on the returned Reader. Ciphertext must be in the
// form IV || Ciphertext || MAC. In order to correctly verify the ciphertext,
// rd is drained, locally buffered and made available on the returned Reader
// afterwards. If an MAC verification failure is observed, it is returned
// immediately.
func (k *Key) DecryptUserFrom(rd io.Reader) (io.ReadCloser, error) {
return DecryptFrom(k.user, rd)
return crypto.DecryptFrom(k.master, rd)
}
// Master() returns the master keys for this repository. Only included for
// debug purposes.
func (k *Key) Master() *MasterKeys {
func (k *Key) Master() *crypto.MasterKeys {
return k.master
}
// User() returns the user keys for this key. Only included for debug purposes.
func (k *Key) User() *MasterKeys {
func (k *Key) User() *crypto.MasterKeys {
return k.user
}

View File

@ -14,7 +14,6 @@ import (
var testPassword = "foobar"
var testCleanup = flag.Bool("test.cleanup", true, "clean up after running tests (remove local backend directory with all content)")
var testLargeCrypto = flag.Bool("test.largecrypto", false, "also test crypto functions with large payloads")
var testTempDir = flag.String("test.tempdir", "", "use this directory for temporary storage (default: system temp dir)")
func setupBackend(t testing.TB) restic.Server {

View File

@ -5,6 +5,7 @@ import (
"sync"
"github.com/restic/restic/chunker"
"github.com/restic/restic/crypto"
"github.com/restic/restic/debug"
)
@ -20,6 +21,8 @@ type poolStats struct {
max int
}
const maxCiphertextSize = crypto.CiphertextExtension + chunker.MaxSize
func (s *poolStats) Get(k string) {
s.m.Lock()
defer s.m.Unlock()

View File

@ -11,6 +11,7 @@ import (
"github.com/restic/restic/backend"
"github.com/restic/restic/chunker"
"github.com/restic/restic/crypto"
"github.com/restic/restic/debug"
)
@ -158,11 +159,11 @@ func (s Server) Save(t backend.Type, data []byte, id backend.ID) (Blob, error) {
var ciphertext []byte
// if the data is small enough, use a slice from the pool
if len(data) <= maxCiphertextSize-ivSize-macSize {
if len(data) <= maxCiphertextSize-crypto.CiphertextExtension {
ciphertext = GetChunkBuf("ch.Save()")
defer FreeChunkBuf("ch.Save()", ciphertext)
} else {
l := len(data) + ivSize + macSize
l := len(data) + crypto.CiphertextExtension
debug.Log("Server.Save", "create large slice of %d bytes for ciphertext", l)

View File

@ -1,7 +1,9 @@
package test_helper
import (
"bytes"
"fmt"
"math/rand"
"path/filepath"
"reflect"
"runtime"
@ -45,3 +47,21 @@ func Str2ID(s string) backend.ID {
return id
}
// Random returns size bytes of pseudo-random data derived from the seed.
func Random(seed, count int) []byte {
buf := make([]byte, count)
rnd := rand.New(rand.NewSource(int64(seed)))
for i := 0; i < count; i++ {
buf[i] = byte(rnd.Uint32())
}
return buf
}
// RandomReader returns a reader that returns size bytes of pseudo-random data
// derived from the seed.
func RandomReader(seed, size int) *bytes.Reader {
return bytes.NewReader(Random(seed, size))
}