Skip to content

Commit

Permalink
Encryption async api (ethereum#17603)
Browse files Browse the repository at this point in the history
* swarm/storage/encryption: async segmentwise encryption/decryption

* swarm/storage: adapt hasherstore to encryption API change

* swarm/api: adapt RefEncryption for AC to new Encryption API

* swarm/storage/encryption: address review comments
  • Loading branch information
zelig authored and gbalint committed Sep 11, 2018
1 parent 10bac36 commit 6dd8748
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 125 deletions.
22 changes: 12 additions & 10 deletions swarm/api/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,27 @@ import (
)

type RefEncryption struct {
spanEncryption encryption.Encryption
dataEncryption encryption.Encryption
span []byte
refSize int
span []byte
}

func NewRefEncryption(refSize int) *RefEncryption {
span := make([]byte, 8)
binary.LittleEndian.PutUint64(span, uint64(refSize))
return &RefEncryption{
spanEncryption: encryption.New(0, uint32(refSize/32), sha3.NewKeccak256),
dataEncryption: encryption.New(refSize, 0, sha3.NewKeccak256),
span: span,
refSize: refSize,
span: span,
}
}

func (re *RefEncryption) Encrypt(ref []byte, key []byte) ([]byte, error) {
encryptedSpan, err := re.spanEncryption.Encrypt(re.span, key)
spanEncryption := encryption.New(key, 0, uint32(re.refSize/32), sha3.NewKeccak256)
encryptedSpan, err := spanEncryption.Encrypt(re.span)
if err != nil {
return nil, err
}
encryptedData, err := re.dataEncryption.Encrypt(ref, key)
dataEncryption := encryption.New(key, re.refSize, 0, sha3.NewKeccak256)
encryptedData, err := dataEncryption.Encrypt(ref)
if err != nil {
return nil, err
}
Expand All @@ -57,7 +57,8 @@ func (re *RefEncryption) Encrypt(ref []byte, key []byte) ([]byte, error) {
}

func (re *RefEncryption) Decrypt(ref []byte, key []byte) ([]byte, error) {
decryptedSpan, err := re.spanEncryption.Decrypt(ref[:8], key)
spanEncryption := encryption.New(key, 0, uint32(re.refSize/32), sha3.NewKeccak256)
decryptedSpan, err := spanEncryption.Decrypt(ref[:8])
if err != nil {
return nil, err
}
Expand All @@ -67,7 +68,8 @@ func (re *RefEncryption) Decrypt(ref []byte, key []byte) ([]byte, error) {
return nil, errors.New("invalid span in encrypted reference")
}

decryptedRef, err := re.dataEncryption.Decrypt(ref[8:], key)
dataEncryption := encryption.New(key, re.refSize, 0, sha3.NewKeccak256)
decryptedRef, err := dataEncryption.Decrypt(ref[8:])
if err != nil {
return nil, err
}
Expand Down
126 changes: 81 additions & 45 deletions swarm/storage/encryption/encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,91 +21,127 @@ import (
"encoding/binary"
"fmt"
"hash"
"sync"
)

const KeyLength = 32

type Key []byte

type Encryption interface {
Encrypt(data []byte, key Key) ([]byte, error)
Decrypt(data []byte, key Key) ([]byte, error)
Encrypt(data []byte) ([]byte, error)
Decrypt(data []byte) ([]byte, error)
}

type encryption struct {
padding int
initCtr uint32
hashFunc func() hash.Hash
key Key // the encryption key (hashSize bytes long)
keyLen int // length of the key = length of blockcipher block
padding int // encryption will pad the data upto this if > 0
initCtr uint32 // initial counter used for counter mode blockcipher
hashFunc func() hash.Hash // hasher constructor function
}

func New(padding int, initCtr uint32, hashFunc func() hash.Hash) *encryption {
// New constructs a new encryptor/decryptor
func New(key Key, padding int, initCtr uint32, hashFunc func() hash.Hash) *encryption {
return &encryption{
key: key,
keyLen: len(key),
padding: padding,
initCtr: initCtr,
hashFunc: hashFunc,
}
}

func (e *encryption) Encrypt(data []byte, key Key) ([]byte, error) {
// Encrypt encrypts the data and does padding if specified
func (e *encryption) Encrypt(data []byte) ([]byte, error) {
length := len(data)
outLength := length
isFixedPadding := e.padding > 0
if isFixedPadding && length > e.padding {
return nil, fmt.Errorf("Data length longer than padding, data length %v padding %v", length, e.padding)
}

paddedData := data
if isFixedPadding && length < e.padding {
paddedData = make([]byte, e.padding)
copy(paddedData[:length], data)
rand.Read(paddedData[length:])
if isFixedPadding {
if length > e.padding {
return nil, fmt.Errorf("Data length longer than padding, data length %v padding %v", length, e.padding)
}
outLength = e.padding
}
return e.transform(paddedData, key), nil
out := make([]byte, outLength)
e.transform(data, out)
return out, nil
}

func (e *encryption) Decrypt(data []byte, key Key) ([]byte, error) {
// Decrypt decrypts the data, if padding was used caller must know original length and truncate
func (e *encryption) Decrypt(data []byte) ([]byte, error) {
length := len(data)
if e.padding > 0 && length != e.padding {
return nil, fmt.Errorf("Data length different than padding, data length %v padding %v", length, e.padding)
}
out := make([]byte, length)
e.transform(data, out)
return out, nil
}

return e.transform(data, key), nil
//
func (e *encryption) transform(in, out []byte) {
inLength := len(in)
wg := sync.WaitGroup{}
wg.Add((inLength-1)/e.keyLen + 1)
for i := 0; i < inLength; i += e.keyLen {
l := min(e.keyLen, inLength-i)
// call transformations per segment (asyncronously)
go func(i int, x, y []byte) {
defer wg.Done()
e.Transcrypt(i, x, y)
}(i/e.keyLen, in[i:i+l], out[i:i+l])
}
// pad the rest if out is longer
pad(out[inLength:])
wg.Wait()
}

func (e *encryption) transform(data []byte, key Key) []byte {
dataLength := len(data)
transformedData := make([]byte, dataLength)
// used for segmentwise transformation
// if in is shorter than out, padding is used
func (e *encryption) Transcrypt(i int, in []byte, out []byte) {
// first hash key with counter (initial counter + i)
hasher := e.hashFunc()
ctr := e.initCtr
hashSize := hasher.Size()
for i := 0; i < dataLength; i += hashSize {
hasher.Write(key)
hasher.Write(e.key)

ctrBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(ctrBytes, ctr)
ctrBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(ctrBytes, uint32(i)+e.initCtr)
hasher.Write(ctrBytes)

hasher.Write(ctrBytes)
ctrHash := hasher.Sum(nil)
hasher.Reset()

ctrHash := hasher.Sum(nil)
hasher.Reset()
hasher.Write(ctrHash)
// second round of hashing for selective disclosure
hasher.Write(ctrHash)
segmentKey := hasher.Sum(nil)
hasher.Reset()

segmentKey := hasher.Sum(nil)

hasher.Reset()
// XOR bytes uptil length of in (out must be at least as long)
inLength := len(in)
for j := 0; j < inLength; j++ {
out[j] = in[j] ^ segmentKey[j]
}
// insert padding if out is longer
pad(out[inLength:])
}

segmentSize := min(hashSize, dataLength-i)
for j := 0; j < segmentSize; j++ {
transformedData[i+j] = data[i+j] ^ segmentKey[j]
}
ctr++
func pad(b []byte) {
l := len(b)
for total := 0; total < l; {
read, _ := rand.Read(b[total:])
total += read
}
return transformedData
}

func GenerateRandomKey() (Key, error) {
key := make([]byte, KeyLength)
_, err := rand.Read(key)
return key, err
// GenerateRandomKey generates a random key of length l
func GenerateRandomKey(l int) Key {
key := make([]byte, l)
var total int
for total < l {
read, _ := rand.Read(key[total:])
total += read
}
return key
}

func min(x, y int) int {
Expand Down
Loading

0 comments on commit 6dd8748

Please sign in to comment.