From a347fdca79aed229f2b370f0dc697dc1add62867 Mon Sep 17 00:00:00 2001 From: Sudhi Herle Date: Mon, 21 Oct 2019 13:28:27 -0700 Subject: [PATCH 1/2] Teach 'sigtool enc' to accept a user defined block size --- crypt.go | 10 ++++++- go.mod | 2 +- go.sum | 2 ++ sign/encrypt.go | 76 +++++++++++++++++++++-------------------------- sign/sign.go | 6 +--- sign/sign_test.go | 1 - version | 2 +- 7 files changed, 48 insertions(+), 51 deletions(-) diff --git a/crypt.go b/crypt.go index e02376a..2995026 100644 --- a/crypt.go +++ b/crypt.go @@ -35,17 +35,24 @@ func encrypt(args []string) { var keyfile string var envpw string var pw bool + var sblksize string fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`") fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`") fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key") fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`") + fs.StringVarP(&sblksize, "block-size", "B", "4M", "Use `S` as the encryption block size") err := fs.Parse(args) if err != nil { die("%s", err) } + blksize, err := utils.ParseSize(sblksize) + if err != nil { + die("%s", err) + } + var pws, infile string if len(envpw) > 0 { @@ -108,7 +115,7 @@ func encrypt(args []string) { outfd = outf } - en, err := sign.NewEncryptor(sk) + en, err := sign.NewEncryptor(sk, blksize) if err != nil { die("%s", err) } @@ -277,3 +284,4 @@ func mustOpen(fn string, flag int) *os.File { } return fdk } + diff --git a/go.mod b/go.mod index ef96194..39cbc19 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( github.com/gogo/protobuf v1.3.1 - github.com/opencoff/go-utils v0.3.0 + github.com/opencoff/go-utils v0.4.0 github.com/opencoff/pflag v0.3.3 golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc gopkg.in/yaml.v2 v2.2.4 diff --git a/go.sum b/go.sum index e318adb..aff4d00 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/opencoff/go-utils v0.3.0 h1:/TQXjf50o3GSB9MItog5L8Gf4GWJ4B5+rmqjB4g2RZQ= github.com/opencoff/go-utils v0.3.0/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM= +github.com/opencoff/go-utils v0.4.0 h1:pu08Om//u2+YGvLkHa2CyL6eI+/1J0bXih1Z6nuITp8= +github.com/opencoff/go-utils v0.4.0/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM= github.com/opencoff/pflag v0.3.3 h1:yohZkwYGPkB34WXvUQzU5GyLhImnjfePDARUaE8me3U= github.com/opencoff/pflag v0.3.3/go.mod h1:mTLzGGUGda1Av3d34iAJlh0JIlRxmFZtmc6qoWPspK0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/sign/encrypt.go b/sign/encrypt.go index 454200a..bb691af 100644 --- a/sign/encrypt.go +++ b/sign/encrypt.go @@ -1,4 +1,4 @@ -// cipher.go -- Ed25519 based encrypt/decrypt +// encrypt.go -- Ed25519 based encrypt/decrypt // // (c) 2016 Sudhi Herle // @@ -14,22 +14,21 @@ package sign import ( + "bytes" "crypto/aes" "crypto/cipher" "crypto/ed25519" "crypto/sha256" "crypto/sha512" "crypto/subtle" + "encoding/binary" "fmt" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" "io" "math/big" - "bytes" - "encoding/binary" ) - // Encryption chunk size = 4MB const chunkSize int = 4 * 1048576 @@ -37,28 +36,35 @@ const _Magic = "SigTool" const _MagicLen = len(_Magic) const _AEADNonceLen = 32 - // Encryptor holds the encryption context type Encryptor struct { Header - key [32]byte // file encryption key + key [32]byte // file encryption key - ae cipher.AEAD - sender *PrivateKey + ae cipher.AEAD + sender *PrivateKey started bool buf []byte } -// Create a new Encryption context and use the optional private key 'sk' for +// Create a new Encryption context and use the optional private key 'sk' for // signing any recipient keys. If 'sk' is nil, then ephmeral Curve25519 keys // are generated and used with recipient's public key. -func NewEncryptor(sk *PrivateKey) (*Encryptor, error) { +func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) { + + if blksize > (16 * 1048576) { + return nil, fmt.Errorf("encrypt: Blocksize is too large (max 16M)") + } + + if blksize == 0 { + blksize = uint64(chunkSize) + } e := &Encryptor{ Header: Header{ - ChunkSize: uint32(chunkSize), - Salt: make([]byte, _AEADNonceLen), + ChunkSize: uint32(blksize), + Salt: make([]byte, _AEADNonceLen), }, sender: sk, @@ -77,11 +83,10 @@ func NewEncryptor(sk *PrivateKey) (*Encryptor, error) { return nil, fmt.Errorf("encrypt: %s", err) } - e.buf = make([]byte, chunkSize + 4 + e.ae.Overhead()) + e.buf = make([]byte, chunkSize+4+e.ae.Overhead()) return e, nil } - // Add a new recipient to this encryption context. func (e *Encryptor) AddRecipient(pk *PublicKey) error { if e.started { @@ -104,7 +109,6 @@ func (e *Encryptor) AddRecipient(pk *PublicKey) error { return nil } - // Begin the encryption process by writing the header func (e *Encryptor) start(wr io.Writer) error { msize := e.Size() @@ -112,16 +116,16 @@ func (e *Encryptor) start(wr io.Writer) error { // marshal the header and recipients hdrlen := _MagicLen + 1 + 4 + sha256.Size - buf := make([]byte, hdrlen + msize) + buf := make([]byte, hdrlen+msize) hdrbuf := buf[hdrlen:] copy(buf[:], []byte(_Magic)) - buf[_MagicLen] = 1 // file version# + buf[_MagicLen] = 1 // file version# // The fixed header is the magic _and _ the length of the variable segment. // So, we capture the length of the variable portion first. - binary.BigEndian.PutUint32(buf[_MagicLen + 1:], uint32(sha256.Size + msize)) + binary.BigEndian.PutUint32(buf[_MagicLen+1:], uint32(sha256.Size+msize)) // Now marshal the variable portion _, err := e.MarshalToSizedBuffer(hdrbuf) @@ -130,7 +134,7 @@ func (e *Encryptor) start(wr io.Writer) error { } // and calculate the header checksum - cksum := buf[_MagicLen + 1 + 4:] + cksum := buf[_MagicLen+1+4:] h := sha256.New() h.Write(hdrbuf) h.Sum(cksum[:0]) @@ -161,7 +165,6 @@ func fullwrite(buf []byte, wr io.Writer) error { return nil } - // Encrypt the input stream 'rd' and write encrypted stream to 'wr' func (e *Encryptor) Encrypt(rd io.Reader, wr io.Writer) error { if !e.started { @@ -204,7 +207,7 @@ func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i int) error { var b [8]byte var noncebuf [32]byte - binary.BigEndian.PutUint32(b[:4], uint32(e.ae.Overhead() + len(buf))) + binary.BigEndian.PutUint32(b[:4], uint32(e.ae.Overhead()+len(buf))) binary.BigEndian.PutUint32(b[4:], uint32(i)) h := sha256.New() @@ -225,26 +228,24 @@ func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i int) error { return nil } - // Decryptor holds the decryption context type Decryptor struct { Header - ae cipher.AEAD - rd io.Reader + ae cipher.AEAD + rd io.Reader buf []byte // Decrypted key key []byte } - // Create a new decryption context and if 'pk' is given, check that it matches // the sender func NewDecryptor(rd io.Reader) (*Decryptor, error) { - var b [12]byte + var b [12]byte - _, err := io.ReadFull(rd, b[:]) + _, err := io.ReadFull(rd, b[:]) if err != nil { return nil, err } @@ -281,7 +282,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) { } d := &Decryptor{ - rd: rd, + rd: rd, } err = d.Header.Unmarshal(hdr) @@ -289,7 +290,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) { return nil, fmt.Errorf("decrypt: decode error: %s", err) } - if d.ChunkSize == 0 || d.ChunkSize > (16 * 1048576) { + if d.ChunkSize == 0 || d.ChunkSize > (16*1048576) { return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize) } @@ -343,7 +344,6 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error { return fmt.Errorf("decrypt: Can't find any public key to match the given private key") - havekey: aes, err := aes.NewCipher(d.key) if err != nil { @@ -354,7 +354,7 @@ havekey: if err != nil { return fmt.Errorf("decrypt: %s", err) } - d.buf = make([]byte, int(d.ChunkSize) + d.ae.Overhead()) + d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead()) return nil } @@ -363,7 +363,6 @@ func (d *Decryptor) WrappedKeys() []*WrappedKey { return d.Keys } - // Decrypt the file and write to 'wr' func (d *Decryptor) Decrypt(wr io.Writer) error { if d.key == nil { @@ -378,7 +377,7 @@ func (d *Decryptor) Decrypt(wr io.Writer) error { if len(c) == 0 { return nil } - + if len(c) > 0 { err = fullwrite(c, wr) if err != nil { @@ -403,7 +402,6 @@ func (d *Decryptor) decrypt(i int) ([]byte, error) { return nil, fmt.Errorf("decrypt: can't read chunk %d length: %s", i, err) } - chunklen := int(binary.BigEndian.Uint32(b[:4])) binary.BigEndian.PutUint32(b[4:], uint32(i)) h := sha256.New() @@ -448,7 +446,6 @@ func (sk *PrivateKey) WrapKey(pk *PublicKey, key []byte) (*WrappedKey, error) { return wrapKey(pk, key, &ourSK) } - func wrapKey(pk *PublicKey, k []byte, ourSK *[32]byte) (*WrappedKey, error) { var curvePK, theirPK, shared [32]byte @@ -469,8 +466,6 @@ func wrapKey(pk *PublicKey, k []byte, ourSK *[32]byte) (*WrappedKey, error) { }, nil } - - // Unwrap a wrapped key using the private key 'sk' func (w *WrappedKey) UnwrapKey(sk *PrivateKey, senderPk *PublicKey) ([]byte, error) { var shared, theirPK, ourSK [32]byte @@ -501,7 +496,6 @@ func (w *WrappedKey) UnwrapKey(sk *PrivateKey, senderPk *PublicKey) ([]byte, err return key, nil } - // Convert an Ed25519 Private Key to Curve25519 Private key func (sk *PrivateKey) toCurve25519SK() []byte { if sk.ck == nil { @@ -565,8 +559,7 @@ func expand(shared, pk []byte) ([]byte, error) { return kek, err } - -// seal the data via AEAD after suitably expanding 'shared' +// seal the data via AEAD after suitably expanding 'shared' func aeadSeal(data, shared, pk []byte) ([]byte, []byte, error) { kek, err := expand(shared[:], pk) if err != nil { @@ -586,7 +579,7 @@ func aeadSeal(data, shared, pk []byte) ([]byte, []byte, error) { noncesize := ae.NonceSize() tagsize := ae.Overhead() - buf := make([]byte, tagsize + len(kek)) + buf := make([]byte, tagsize+len(kek)) nonce := make([]byte, noncesize) randread(nonce) @@ -624,7 +617,6 @@ func aeadOpen(data, nonce, shared, pk []byte) ([]byte, error) { return c, nil } - func clamp(k []byte) []byte { k[0] &= 248 k[31] &= 127 diff --git a/sign/sign.go b/sign/sign.go index daaca11..ad70313 100644 --- a/sign/sign.go +++ b/sign/sign.go @@ -223,7 +223,6 @@ func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) { return nil, fmt.Errorf("can't decode YAML:Verify: %s", err) } - // We take short passwords and extend them pwb := sha512.Sum512([]byte(pw)) @@ -250,7 +249,6 @@ func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) { return PrivateKeyFromBytes(skb) } - // Make a private key from 64-bytes of extended Ed25519 key func PrivateKeyFromBytes(skb []byte) (*PrivateKey, error) { if len(skb) != 64 { @@ -272,7 +270,6 @@ func PrivateKeyFromBytes(skb []byte) (*PrivateKey, error) { return sk, nil } - // Given a secret key, return the corresponding Public Key func (sk *PrivateKey) PublicKey() *PublicKey { return sk.pk @@ -481,7 +478,6 @@ func MakePublicKey(yml []byte) (*PublicKey, error) { return PublicKeyFromBytes(pk) } - // Make a public key from a byte string func PublicKeyFromBytes(b []byte) (*PublicKey, error) { if len(b) != 32 { @@ -489,7 +485,7 @@ func PublicKeyFromBytes(b []byte) (*PublicKey, error) { } pk := &PublicKey{ - Pk: make([]byte, 32), + Pk: make([]byte, 32), hash: pkhash(b), } diff --git a/sign/sign_test.go b/sign/sign_test.go index b29de39..54fb509 100644 --- a/sign/sign_test.go +++ b/sign/sign_test.go @@ -90,7 +90,6 @@ r: 8 p: 1 ` - // #1. Create new key pair, and read them back. func Test0(t *testing.T) { assert := newAsserter(t) diff --git a/version b/version index 0ea3a94..0d91a54 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.2.0 +0.3.0 From 817aa7fd6a38bab8e2c8a0e56bd30b90fd22c8a0 Mon Sep 17 00:00:00 2001 From: Sudhi Herle Date: Tue, 22 Oct 2019 10:06:49 -0700 Subject: [PATCH 2/2] Added tests for encrypt/decrypt routines. Updated minor version# --- README.md | 6 +- sign/crypt_test.go | 165 +++++++++++++++++++++++++++++++++++++++++++++ version | 2 +- 3 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 sign/crypt_test.go diff --git a/README.md b/README.md index ebd56f2..51c4b35 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ e.g., to verify the signature of *archive.tar.gz* against If the sender wishes to prove to the recipient that they encrypted a file: - sigtool encrypt -s sender.key to.pub -o archive.tar.gz.enc archive.tar.gz + sigtool encrypt -s sender.key to.pub -o archive.tar.gz.enc archive.tar.gz This will create an encrypted file *archive.tar.gz.enc* such that the @@ -101,7 +101,7 @@ who they expect. If the receiver has the public key of the sender, they can verify that they indeed sent the file by cryptographically checking the output: - sigtool decrypt -o archive.tar.gz -v sender.pub to.key archive.tar.gz.enc + sigtool decrypt -o archive.tar.gz -v sender.pub to.key archive.tar.gz.enc Note that the verification is optional and if the `-v` option is not used, then decryption will proceed without verifying the sender. @@ -110,7 +110,7 @@ used, then decryption will proceed without verifying the sender. `sigtool` can generate ephemeral keys for encrypting a file such that the receiver doesn't need to authenticate the sender: - sigtool encrypt to.pub -o archive.tar.gz.enc archive.tar.gz + sigtool encrypt to.pub -o archive.tar.gz.enc archive.tar.gz This will create an encrypted file *archive.tar.gz.enc* such that the recipient in possession of *to.key* can decrypt it. diff --git a/sign/crypt_test.go b/sign/crypt_test.go new file mode 100644 index 0000000..1b9dc26 --- /dev/null +++ b/sign/crypt_test.go @@ -0,0 +1,165 @@ +// crypt_test.go -- Test harness for encrypt/decrypt bits +// +// (c) 2016 Sudhi Herle +// +// Licensing Terms: GPLv2 +// +// If you need a commercial license for this work, please contact +// the author. +// +// This software does not come with any express or implied +// warranty; it is provided "as is". No claim is made to its +// suitability for any purpose. + +package sign + +import ( + "bytes" + "testing" +) + + +// one sender, one receiver no verification of sender +func TestSimple(t *testing.T) { + assert := newAsserter(t) + + receiver, err := NewKeypair() + assert(err == nil, "receiver keypair gen failed: %s", err) + + // cleartext + buf := make([]byte, 64 * 1024) + for i := 0; i < len(buf); i++ { + buf[i] = byte(i & 0xff) + } + + ee, err := NewEncryptor(nil, 4096) + assert(err == nil, "encryptor create fail: %s", err) + + err = ee.AddRecipient(&receiver.Pub) + assert(err == nil, "can't add recipient: %s", err) + + rd := bytes.NewBuffer(buf) + wr := bytes.Buffer{} + + err = ee.Encrypt(rd, &wr) + assert(err == nil, "encrypt fail: %s", err) + + rd = bytes.NewBuffer(wr.Bytes()) + + dd, err := NewDecryptor(rd) + assert(err == nil, "decryptor create fail: %s", err) + + err = dd.SetPrivateKey(&receiver.Sec, nil) + assert(err == nil, "decryptor can't add SK: %s", err) + + wr = bytes.Buffer{} + err = dd.Decrypt(&wr) + assert(err == nil, "decrypt fail: %s", err) + + b := wr.Bytes() + assert(len(b) == len(buf), "decrypt length mismatch: exp %d, saw %d", len(buf), len(b)) + + assert(byteEq(b, buf), "decrypt content mismatch") +} + +// one sender, one receiver with verification of sender +func TestSenderVerified(t *testing.T) { + assert := newAsserter(t) + + sender, err := NewKeypair() + assert(err == nil, "sender keypair gen failed: %s", err) + + receiver, err := NewKeypair() + assert(err == nil, "receiver keypair gen failed: %s", err) + + // cleartext + buf := make([]byte, 64 * 1024) + for i := 0; i < len(buf); i++ { + buf[i] = byte(i & 0xff) + } + + ee, err := NewEncryptor(&sender.Sec, 4096) + assert(err == nil, "encryptor create fail: %s", err) + + err = ee.AddRecipient(&receiver.Pub) + assert(err == nil, "can't add recipient: %s", err) + + rd := bytes.NewBuffer(buf) + wr := bytes.Buffer{} + + err = ee.Encrypt(rd, &wr) + assert(err == nil, "encrypt fail: %s", err) + + rd = bytes.NewBuffer(wr.Bytes()) + + dd, err := NewDecryptor(rd) + assert(err == nil, "decryptor create fail: %s", err) + + err = dd.SetPrivateKey(&receiver.Sec, &sender.Pub) + assert(err == nil, "decryptor can't add SK: %s", err) + + wr = bytes.Buffer{} + err = dd.Decrypt(&wr) + assert(err == nil, "decrypt fail: %s", err) + + b := wr.Bytes() + assert(len(b) == len(buf), "decrypt length mismatch: exp %d, saw %d", len(buf), len(b)) + + assert(byteEq(b, buf), "decrypt content mismatch") +} + + +// one sender, multiple receivers, each decrypting the blob +func TestMultiReceiver(t *testing.T) { + assert := newAsserter(t) + + sender, err := NewKeypair() + assert(err == nil, "sender keypair gen failed: %s", err) + + // cleartext + buf := make([]byte, 64 * 1024) + for i := 0; i < len(buf); i++ { + buf[i] = byte(i & 0xff) + } + + ee, err := NewEncryptor(&sender.Sec, 4096) + assert(err == nil, "encryptor create fail: %s", err) + + + n := 4 + rx := make([]*Keypair, n) + for i := 0; i < n; i++ { + r, err := NewKeypair() + assert(err == nil, "can't make receiver key %d: %s", i, err) + rx[i] = r + + err = ee.AddRecipient(&r.Pub) + assert(err == nil, "can't add recipient %d: %s", i, err) + } + + rd := bytes.NewBuffer(buf) + wr := bytes.Buffer{} + + err = ee.Encrypt(rd, &wr) + assert(err == nil, "encrypt fail: %s", err) + + encBytes := wr.Bytes() + for i := 0; i < n; i++ { + rd = bytes.NewBuffer(encBytes) + + dd, err := NewDecryptor(rd) + assert(err == nil, "decryptor %d create fail: %s", i, err) + + err = dd.SetPrivateKey(&rx[i].Sec, &sender.Pub) + assert(err == nil, "decryptor can't add SK %d: %s", i, err) + + wr = bytes.Buffer{} + err = dd.Decrypt(&wr) + assert(err == nil, "decrypt %d fail: %s", i, err) + + b := wr.Bytes() + assert(len(b) == len(buf), "decrypt %d length mismatch: exp %d, saw %d", i, len(buf), len(b)) + + assert(byteEq(b, buf), "decrypt %d content mismatch", i) + } +} diff --git a/version b/version index 0d91a54..9e11b32 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.3.0 +0.3.1