diff options
Diffstat (limited to 'internal/cryptoutil')
| -rw-r--r-- | internal/cryptoutil/cryptoutil.go | 139 | ||||
| -rw-r--r-- | internal/cryptoutil/cryptoutil_test.go | 159 | ||||
| -rw-r--r-- | internal/cryptoutil/oae2.go | 297 |
3 files changed, 0 insertions, 595 deletions
diff --git a/internal/cryptoutil/cryptoutil.go b/internal/cryptoutil/cryptoutil.go deleted file mode 100644 index 0429b02..0000000 --- a/internal/cryptoutil/cryptoutil.go +++ /dev/null @@ -1,139 +0,0 @@ -// Package cryptoutil contains friendly wrappers around the algorithms from the -// standard library's crypto package. -package cryptoutil - -import ( - "crypto/hkdf" - "crypto/hmac" - "crypto/pbkdf2" - "crypto/rand" - "crypto/sha512" - "crypto/subtle" - "encoding/binary" - "encoding/hex" - "errors" - "io" -) - -const ( - defaultIterations = 4718580 // from cmd/finditers - - oneSaltSize = 64 - hashSize = 64 - certSize = sha512.Size -) - -// SaltSize is the expected salt length for HashIter. -const SaltSize = 2 * oneSaltSize - -// A PasswordHash must be PasswordHashSize bytes. -const PasswordHashSize = SaltSize + hashSize - -// A PasswordHash is derived from the user's password and can be passed to -// CheckPassword to verify if two passwords match. A PasswordHash must be -// PasswordHashSize bytes. -type PasswordHash []byte - -func (h PasswordHash) String() string { return hex.EncodeToString(h) } - -// A RawKey is generated from a password hash and can be used to derive further -// key material. -type RawKey struct { - key []byte -} - -// An HMACKey must be HMACKeySize bytes. -const HMACKeySize = sha512.BlockSize - -// An HMAC key can be used to symmetrically sign and verify messages -// using HMAC-SHA512. An HMACKey must be HMACKeyLen bytes. -type HMACKey []byte - -// A SignedMessage is a message that has been cryptographically signed -// with HMAC-SHA512. -type SignedMessage []byte - -// An EncryptedMessage is encrypted with AES-256-CTR-HMAC-SHA512. -type EncryptedMessage []byte - -// HashIter runs PBKDF2-SHA512 for iter iterations. Useful for benchmarking. The -// salt must be SaltSize bytes. -func HashIter(password string, salt []byte, iter int) ([]byte, error) { - return pbkdf2.Key(sha512.New, password, salt, iter, sha512.Size) -} - -func hashWithSalt(password string, salt []byte) (RawKey, []byte, error) { - hash, err := HashIter(password, salt[:oneSaltSize], defaultIterations) - if err != nil { - return RawKey{}, nil, err - } - key, err := hkdf.Extract(sha512.New, hash, salt[oneSaltSize:]) - if err != nil { - return RawKey{}, nil, err - } - pwHash, err := hkdf.Expand(sha512.New, key, "pwhash", hashSize) - if err != nil { - return RawKey{}, nil, err - } - return RawKey{key}, pwHash, nil -} - -// Hash hashes a user password using PBKDF2-SHA512. -func Hash(password string) (PasswordHash, error) { - salt := make([]byte, SaltSize, SaltSize+hashSize) - rand.Read(salt) - _, pwHash, err := hashWithSalt(password, salt) - if err != nil { - return nil, err - } - return append(salt, pwHash...), nil -} - -// CheckPassword verifies password against a PasswordHash and returns an -// EncryptionKey derived from the password if successful. -func (h PasswordHash) CheckPassword(password string) (RawKey, error) { - salt, expectedHash := h[:SaltSize], h[SaltSize:] - key, pwHash, err := hashWithSalt(password, salt) - if err != nil { - return RawKey{}, err - } - if subtle.ConstantTimeCompare(pwHash, expectedHash) == 0 { - return RawKey{}, errors.New("incorrect password") - } - return key, nil -} - -func (k HMACKey) mac(out, msg []byte, info string) []byte { - mac := hmac.New(sha512.New, k) - buf := make([]byte, 0, binary.MaxVarintLen64) - mac.Write(binary.AppendUvarint(buf, uint64(len(info)))) - io.WriteString(mac, info) - mac.Write(msg) - return mac.Sum(out) -} - -// Sign generates an HMAC-SHA512 signature and appends it to msg. The info and -// additionalData are also authenticated, but are not included in the returned -// signed message. -func (k HMACKey) Sign(msg []byte, info string) SignedMessage { - return k.mac(msg, msg, info) -} - -// Verify checks whether the given message has a valid signature, and returns -// the raw message if it does. The additionalData must match the data passed -// to Sign. -func (k HMACKey) Verify(msg SignedMessage, info string) ([]byte, bool) { - if len(msg) < certSize { - return nil, false - } - msg, sig := msg[:len(msg)-certSize], msg[len(msg)-certSize:] - if !hmac.Equal(sig, k.mac(nil, msg, info)) { - return nil, false - } - return msg, true -} - -// EncryptionKey derives an EncryptionKey. -func (k RawKey) EncryptionKey() (EncryptionKey, error) { - return hkdf.Expand(sha512.New, k.key, "encrypt", sha512.Size) -} diff --git a/internal/cryptoutil/cryptoutil_test.go b/internal/cryptoutil/cryptoutil_test.go deleted file mode 100644 index 059a9e1..0000000 --- a/internal/cryptoutil/cryptoutil_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package cryptoutil - -import ( - "bytes" - "encoding/hex" - "io" - "testing" -) - -func mustHex(t testing.TB, s string) []byte { - t.Helper() - bytes, err := hex.DecodeString(s) - if err != nil { - t.Fatalf("Bad hex %q: %s", s, err) - } - return bytes -} - -func TestPassword(t *testing.T) { - const password = "eclair scroll gliding sled shining removed ascend android cheer confetti" - hash, err := Hash(password) - if err != nil { - t.Fatalf("Hash(%q) failed: %s", password, err) - } - _, err = hash.CheckPassword(password) - if err != nil { - t.Errorf("%x.CheckPassword(%q) failed: %s", hash, password, err) - } -} - -func TestSignature(t *testing.T) { - key := HMACKey(mustHex(t, "669e06ec457778b9a8133edb0a87ea82c6b141ffbbc63c038da96258175eb35c")) - msg := []byte("test message") - signedMsg := key.Sign(msg, "info") - got, ok := key.Verify(signedMsg, "info") - if !ok { - t.Fatalf("Verify(%x) rejected the message", signedMsg) - } - if !bytes.Equal(got, msg) { - t.Errorf("Verify(%x) = %x, want %x", signedMsg, got, msg) - } -} - -func TestEncrypt(t *testing.T) { - key := EncryptionKey(mustHex(t, "b6de26860e0a39aa134732e58055c06ba028675453f73a6912bc932e58d5743d0e423217f06487d3e96a59980301fcc97dfcc4c6b19765f2947de3c33ab7ef9e")) - for _, tc := range []struct { - desc string - msg []byte - }{{ - desc: "empty", - msg: nil, - }, { - desc: "short", - msg: []byte("test message"), - }, { - desc: "long", - msg: func() []byte { - msg := make([]byte, 1024*1024) - for i := range msg { - msg[i] = byte(i) - } - return msg - }(), - }} { - t.Run(tc.desc, func(t *testing.T) { - encryptedMsg := new(bytes.Buffer) - w := key.NewWriter(encryptedMsg, WithAdditionalData([]byte("additional data"))) - if _, err := w.Write(tc.msg); err != nil { - t.Fatalf("EncryptingWriter.Write(%q) failed: %s", tc.msg, err) - } - if err := w.Close(); err != nil { - t.Fatalf("EncryptingWriter.Close() failed: %s", err) - } - got, err := io.ReadAll(key.NewReader(bytes.NewReader(encryptedMsg.Bytes()), WithAdditionalData([]byte("additional data")))) - if err != nil { - t.Fatalf("DecryptingReader.Read(%x) failed: %s", encryptedMsg, err) - } - if !bytes.Equal(got, tc.msg) { - t.Errorf("DecryptingReader.Read(%x) = %x, want %x", encryptedMsg, got, tc.msg) - } - }) - } -} - -func TestDeriveKey(t *testing.T) { - const password = "automaker moisture botch tubular kelp rinse rule giggly donated stock" - hash, err := Hash(password) - if err != nil { - t.Fatalf("Hash(%q) failed: %s", password, err) - } - rawKey, err := hash.CheckPassword(password) - if err != nil { - t.Fatalf("%x.CheckPassword(%q) failed: %s", hash, password, err) - } - key, err := rawKey.EncryptionKey() - if err != nil { - t.Fatalf("EncryptionKey() failed: %s", err) - } - msg := []byte("test message") - encryptedMsg := new(bytes.Buffer) - w := key.NewWriter(encryptedMsg, WithAdditionalData([]byte("additional data"))) - if _, err := w.Write(msg); err != nil { - t.Fatalf("EncryptingWriter.Write(%q) failed: %s", msg, err) - } - if err := w.Close(); err != nil { - t.Fatalf("EncryptingWriter.Close() failed: %s", err) - } - got, err := io.ReadAll(key.NewReader(encryptedMsg, WithAdditionalData([]byte("additional data")))) - if err != nil { - t.Fatalf("DecryptingReader.Read(%x) failed: %s", encryptedMsg, err) - } - if !bytes.Equal(got, msg) { - t.Errorf("DecryptingReader.Read(%x) = %x, want %x", encryptedMsg, got, msg) - } -} - -func BenchmarkEncrypt(b *testing.B) { - key := EncryptionKey(mustHex(b, "b6de26860e0a39aa134732e58055c06ba028675453f73a6912bc932e58d5743d0e423217f06487d3e96a59980301fcc97dfcc4c6b19765f2947de3c33ab7ef9e")) - msg := make([]byte, 1024*1024) - for i := range msg { - msg[i] = byte(i) - } - encryptedMsg := bytes.NewBuffer(make([]byte, 0, 35+len(msg)+16*(len(msg)+defaultOpts.segmentSize-1)/defaultOpts.segmentSize /* ?? */)) - additionalData := []byte("additional data") - for b.Loop() { - w := key.NewWriter(encryptedMsg, WithAdditionalData(additionalData)) - if _, err := w.Write(msg); err != nil { - b.Fatal(err) - } - if err := w.Close(); err != nil { - b.Fatal(err) - } - encryptedMsg.Reset() - } -} - -func BenchmarkDecrypt(b *testing.B) { - key := EncryptionKey(mustHex(b, "b6de26860e0a39aa134732e58055c06ba028675453f73a6912bc932e58d5743d0e423217f06487d3e96a59980301fcc97dfcc4c6b19765f2947de3c33ab7ef9e")) - msg := make([]byte, 1024*1024) - for i := range msg { - msg[i] = byte(i) - } - encryptedMsg := new(bytes.Buffer) - additionalData := []byte("additional data") - w := key.NewWriter(encryptedMsg, WithAdditionalData(additionalData)) - if _, err := w.Write(msg); err != nil { - b.Fatal(err) - } - if err := w.Close(); err != nil { - b.Fatal(err) - } - decryptedMsg := make([]byte, len(msg)) - for b.Loop() { - r := key.NewReader(bytes.NewReader(encryptedMsg.Bytes()), WithAdditionalData(additionalData)) - if _, err := io.ReadFull(r, decryptedMsg); err != nil { - b.Fatal(err) - } - } -} diff --git a/internal/cryptoutil/oae2.go b/internal/cryptoutil/oae2.go deleted file mode 100644 index bc8f53e..0000000 --- a/internal/cryptoutil/oae2.go +++ /dev/null @@ -1,297 +0,0 @@ -package cryptoutil - -import ( - "bufio" - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/hkdf" - "crypto/rand" - "crypto/sha512" - "encoding/binary" - "errors" - "io" -) - -// Online Authenticated Encryption from https://eprint.iacr.org/2015/189.pdf - -const ( - aeadOverhead = 16 - aesKeySize = 32 - noncePrefixSize = 3 - gcmNonceSize = 12 - headerSize = aesKeySize + noncePrefixSize -) - -// An EncryptionKey is used for encrypting and decrypting data. A key can be any -// length, but at least 64 bytes would be recommended. -type EncryptionKey []byte - -type oae2 struct { - key EncryptionKey - additionalData []byte - - aead cipher.AEAD - i int64 - noncePrefix [noncePrefixSize]byte -} - -func (o *oae2) initialize(header []byte) error { - o.i = 1 - copy(o.noncePrefix[:], header[aesKeySize:]) - derivedKey, err := hkdf.Key(sha512.New, o.key, header[:aesKeySize], "", aesKeySize) - if err != nil { - return err - } - block, err := aes.NewCipher(derivedKey) - if err != nil { - return err - } - o.aead, err = cipher.NewGCM(block) - return err -} - -func (o *oae2) nonce(nonce []byte, lastBlock bool) error { - copy(nonce, o.noncePrefix[:]) - if o.i < 0 { - return errors.New("counter overflowed (64 bits??)") - } - binary.BigEndian.PutUint64(nonce[noncePrefixSize:], uint64(o.i)) - if lastBlock { - nonce[gcmNonceSize-1] = 1 - } - return nil -} - -func (o *oae2) encryptBlock(out, block []byte, lastBlock bool) ([]byte, error) { - nonce := make([]byte, gcmNonceSize) - if err := o.nonce(nonce, lastBlock); err != nil { - return nil, err - } - encrypted := o.aead.Seal(out, nonce, block, o.additionalData) - o.i++ - o.additionalData = nil - return encrypted, nil -} - -func (o *oae2) decryptBlock(out, block []byte, lastBlock bool) ([]byte, error) { - nonce := make([]byte, gcmNonceSize) - if err := o.nonce(nonce, lastBlock); err != nil { - return nil, err - } - decrypted, err := o.aead.Open(out, nonce, block, o.additionalData) - if err != nil { - return nil, err - } - o.i++ - o.additionalData = nil - return decrypted, nil -} - -// An EncryptingWriter encrypts data in segments using the STREAM construction -// described in https://eprint.iacr.org/2015/189.pdf. The writer buffers data up -// to the segment size, so it's important to call Close to flush the -// final segment. -type EncryptingWriter struct { - w io.Writer - oae2 oae2 - segmentSize int - - initialized bool - err error - buf []byte -} - -type opts struct { - additionalData []byte - segmentSize int -} - -var defaultOpts = opts{segmentSize: 192*1024 - 1 - aeadOverhead} - -type Option func(*opts) - -func WithAdditionalData(ad []byte) Option { - return func(opts *opts) { - opts.additionalData = ad - } -} - -func WithSegmentSize(segmentSize int) Option { - return func(opts *opts) { - opts.segmentSize = segmentSize - } -} - -// NewWriter returns a new EncryptingWriter that writes to w. The additionalData -// will be authenticated with the first segment, but is not written to w. The -// same additional data must be provided when decrypting. -func (k EncryptionKey) NewWriter(w io.Writer, options ...Option) *EncryptingWriter { - opts := defaultOpts - for _, o := range options { - o(&opts) - } - return &EncryptingWriter{ - w: w, - oae2: oae2{ - key: k, - additionalData: opts.additionalData, - }, - segmentSize: opts.segmentSize, - } -} - -func (w *EncryptingWriter) initialize() error { - w.initialized = true - w.buf = make([]byte, 0, w.segmentSize+aeadOverhead) - header := make([]byte, headerSize) - rand.Read(header) - if w.err = w.oae2.initialize(header); w.err != nil { - return w.err - } - _, w.err = w.w.Write(header) - return w.err -} - -func (w *EncryptingWriter) writeBuf() error { - var encrypted []byte - if encrypted, w.err = w.oae2.encryptBlock(w.buf[:0], w.buf, false); w.err != nil { - return w.err - } - if _, w.err = w.w.Write(encrypted); w.err != nil { - return w.err - } - w.buf = w.buf[:0] - return nil -} - -func (w *EncryptingWriter) Write(buf []byte) (int, error) { - if !w.initialized { - if err := w.initialize(); err != nil { - return 0, err - } - } - if w.err != nil { - return 0, w.err - } - r := bytes.NewReader(buf) - nn := 0 - for r.Len() > 0 { - if len(w.buf) == w.segmentSize { - if err := w.writeBuf(); err != nil { - return nn, err - } - } - n, _ := r.Read(w.buf[len(w.buf):w.segmentSize]) - w.buf = w.buf[:len(w.buf)+n] - nn += n - } - return nn, nil -} - -var errClosed = errors.New("closed") - -// Close encrypts writes the final segment. It does not close the -// underlying writer. -func (w *EncryptingWriter) Close() error { - if !w.initialized { - if err := w.initialize(); err != nil { - return err - } - } - if w.err == errClosed { - return nil - } - if w.err != nil { - return w.err - } - var encrypted []byte - if encrypted, w.err = w.oae2.encryptBlock(w.buf[:0], w.buf, true); w.err != nil { - return w.err - } - if _, w.err = w.w.Write(encrypted); w.err != nil { - return w.err - } - w.err = errClosed - return nil -} - -// A DecryptingReader decrypts data using the STREAM construction. -type DecryptingReader struct { - r *bufio.Reader - oae2 oae2 - segmentSize int - - initialized bool - buf bytes.Buffer -} - -// NewReader returns a new DecryptingWriter that decrypts data from r. The -// additionaData must be the same that was provided when encrypting. -func (k EncryptionKey) NewReader(r io.Reader, options ...Option) *DecryptingReader { - opts := defaultOpts - for _, o := range options { - o(&opts) - } - return &DecryptingReader{ - r: bufio.NewReaderSize(r, headerSize), - oae2: oae2{ - key: k, - additionalData: opts.additionalData, - }, - segmentSize: opts.segmentSize, - } -} - -func (r *DecryptingReader) initialize() error { - header, err := r.r.Peek(headerSize) - if err != nil { - return err - } - if err := r.oae2.initialize(header); err != nil { - return err - } - r.r.Discard(len(header)) - r.buf = *bytes.NewBuffer(make([]byte, 0, r.segmentSize+aeadOverhead)) - r.initialized = true - return nil -} - -func (r *DecryptingReader) fillBuf() error { - r.buf.Reset() - buf := r.buf.AvailableBuffer() - buf = buf[:cap(buf)] - n, err := io.ReadFull(r.r, buf) - if n == 0 { - if err == io.ErrUnexpectedEOF { - return io.EOF - } - return err - } - buf = buf[:n] - if n > 0 { - // Peek one extra byte to check if this is the last segment - _, readErr := r.r.Peek(1) - result, err := r.oae2.decryptBlock(buf[:0], buf, readErr == io.EOF) - if err != nil { - return err - } - r.buf.Write(result) - } - return nil -} - -func (r *DecryptingReader) Read(buf []byte) (int, error) { - if !r.initialized { - if err := r.initialize(); err != nil { - return 0, err - } - } - if r.buf.Len() == 0 { - if err := r.fillBuf(); err != nil { - return 0, err - } - } - n, _ := r.buf.Read(buf) - return n, nil -} |
