diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2025-09-29 21:10:00 -0700 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2025-09-29 21:54:36 -0700 |
| commit | 36e28b20fb771b3750ee6adeabd93c89ccc53148 (patch) | |
| tree | f17878543259b4e9f6bca4c77a09f248631777e4 /internal/cryptoutil/cryptoutil.go | |
| parent | Fix vendor hash (diff) | |
| download | roseh.moe-36e28b20fb771b3750ee6adeabd93c89ccc53148.tar.zst | |
Implement "online authenticated encryption"
Diffstat (limited to 'internal/cryptoutil/cryptoutil.go')
| -rw-r--r-- | internal/cryptoutil/cryptoutil.go | 151 |
1 files changed, 1 insertions, 150 deletions
diff --git a/internal/cryptoutil/cryptoutil.go b/internal/cryptoutil/cryptoutil.go index 09ed996..b269803 100644 --- a/internal/cryptoutil/cryptoutil.go +++ b/internal/cryptoutil/cryptoutil.go @@ -3,8 +3,6 @@ package cryptoutil import ( - "crypto/aes" - "crypto/cipher" "crypto/hkdf" "crypto/hmac" "crypto/pbkdf2" @@ -13,10 +11,6 @@ import ( "crypto/subtle" "encoding/hex" "errors" - "fmt" - "hash" - "io" - "slices" ) const ( @@ -25,8 +19,6 @@ const ( oneSaltSize = 64 hashSize = 64 certSize = sha512.Size - aesKeySize = 32 - nonceSize = aes.BlockSize ) // SaltSize is the expected salt length for HashIter. @@ -59,13 +51,6 @@ type HMACKey []byte // with HMAC-SHA512. type SignedMessage []byte -// An EncryptionKey must be EncryptionKeySize bytes. -const EncryptionKeySize = aesKeySize + HMACKeySize - -// An EncryptionKey is used for encrypting and decrypting data. An EncryptionKey -// must be EncryptionKeySize bytes. -type EncryptionKey []byte - // An EncryptedMessage is encrypted with AES-256-CTR-HMAC-SHA512. type EncryptedMessage []byte @@ -140,139 +125,5 @@ func (k HMACKey) Verify(msg SignedMessage) ([]byte, bool) { // EncryptionKey derives an EncryptionKey. func (k RawKey) EncryptionKey() (EncryptionKey, error) { - return hkdf.Expand(sha512.New, k.key, "encrypt", EncryptionKeySize) -} - -// Encrypt encrypts a message with AES-256-CTR-HMAC-SHA512. -func (k EncryptionKey) Encrypt(msg []byte) (EncryptedMessage, error) { - aesKey, hmacKey := k[:aesKeySize], HMACKey(k[aesKeySize:]) - block, err := aes.NewCipher(aesKey) - if err != nil { - return nil, err - } - cipherText := make([]byte, len(msg)+nonceSize, len(msg)+nonceSize+certSize) - nonce := cipherText[len(msg) : len(msg)+nonceSize] - rand.Read(nonce) - cipher.NewCTR(block, nonce).XORKeyStream(cipherText, msg) - return EncryptedMessage(hmacKey.Sign(cipherText)), nil -} - -// Decrypt verifies and decrypts an encrypted message. It overwrites msg with -// the resulting plain text. -func (k EncryptionKey) Decrypt(msg EncryptedMessage) ([]byte, error) { - aesKey, hmacKey := k[:aesKeySize], HMACKey(k[aesKeySize:]) - msg, ok := hmacKey.Verify(SignedMessage(msg)) - if !ok { - return nil, errors.New("bad signature") - } - block, err := aes.NewCipher(aesKey) - if err != nil { - return nil, err - } - msg, nonce := msg[:len(msg)-nonceSize], msg[len(msg)-nonceSize:] - cipher.NewCTR(block, nonce).XORKeyStream(msg, msg) - return msg, nil -} - -// An EncryptingWriter encrypts the output and writes it to the underlying -// writer. It's very important to call .Flush() to write the MAC after the data -// has been written. -type EncryptingWriter struct { - w io.Writer - stream cipher.Stream - mac hash.Hash - buf []byte -} - -// Writer returns a new writer that encrypts its output. Don't forget to call -// .Flush() to write the MAC. -func (k EncryptionKey) Writer(w io.Writer) (*EncryptingWriter, error) { - aesKey, hmacKey := k[:aesKeySize], k[aesKeySize:] - block, err := aes.NewCipher(aesKey) - if err != nil { - return nil, err - } - mac := hmac.New(sha512.New, hmacKey) - nonce := make([]byte, nonceSize) - rand.Read(nonce) - mac.Write(nonce) - if _, err := w.Write(nonce); err != nil { - return nil, err - } - return &EncryptingWriter{ - w: w, - stream: cipher.NewCTR(block, nonce), - mac: mac, - }, nil -} - -// Write encrypts buf and writes it to the underlying writer. -func (w *EncryptingWriter) Write(buf []byte) (int, error) { - if len(w.buf) < len(buf) { - w.buf = slices.Grow(w.buf, len(buf)-len(w.buf)) - } - w.buf = w.buf[:len(buf)] - // Encrypt-then-MAC - w.stream.XORKeyStream(w.buf, buf) - w.mac.Write(w.buf) - return w.w.Write(w.buf) -} - -// Flush writes the MAC for the encrypted message. Flush must be called after -// all data has been written. -func (w *EncryptingWriter) Flush() error { - _, err := w.w.Write(w.mac.Sum(nil)) - return err -} - -// A DecryptingReader decrypts data from an underlying reader. -type DecryptingReader struct { - r cipher.StreamReader -} - -// Reader returns a new DecryptingReader. The data is processed in two passes, -// first to verify the MAC, then the io.Seeker interface is used to reset the -// reader for decryption. -func (k EncryptionKey) Reader(r io.ReadSeeker) (*DecryptingReader, error) { - aesKey, hmacKey := k[:aesKeySize], k[aesKeySize:] - block, err := aes.NewCipher(aesKey) - if err != nil { - return nil, err - } - totalLen, err := r.Seek(0, io.SeekEnd) - if err != nil { - return nil, err - } - if totalLen < certSize { - return nil, errors.New("file too short") - } - dataLen := totalLen - certSize - if _, err := r.Seek(0, io.SeekStart); err != nil { - return nil, err - } - mac := hmac.New(sha512.New, hmacKey) - if _, err := io.Copy(mac, &io.LimitedReader{R: r, N: dataLen}); err != nil { - return nil, err - } - expectedMAC := mac.Sum(nil) - sig := make([]byte, certSize) - if _, err := io.ReadFull(r, sig); err != nil { - return nil, err - } - if !hmac.Equal(sig, expectedMAC) { - return nil, fmt.Errorf("invalid mac (got %x, want %x)", sig, expectedMAC) - } - if _, err := r.Seek(0, io.SeekStart); err != nil { - return nil, err - } - nonce := make([]byte, nonceSize) - if _, err := io.ReadFull(r, nonce); err != nil { - return nil, err - } - return &DecryptingReader{cipher.StreamReader{S: cipher.NewCTR(block, nonce), R: &io.LimitedReader{R: r, N: dataLen - nonceSize}}}, nil -} - -// Read reads and decrypts data from the underlying reader. -func (r *DecryptingReader) Read(buf []byte) (int, error) { - return r.r.Read(buf) + return hkdf.Expand(sha512.New, k.key, "encrypt", sha512.Size) } |
