summaryrefslogtreecommitdiffstats
path: root/internal/cryptoutil/cryptoutil.go
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-09-29 21:10:00 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-09-29 21:54:36 -0700
commit36e28b20fb771b3750ee6adeabd93c89ccc53148 (patch)
treef17878543259b4e9f6bca4c77a09f248631777e4 /internal/cryptoutil/cryptoutil.go
parentFix vendor hash (diff)
downloadroseh.moe-36e28b20fb771b3750ee6adeabd93c89ccc53148.tar.zst
Implement "online authenticated encryption"
Diffstat (limited to 'internal/cryptoutil/cryptoutil.go')
-rw-r--r--internal/cryptoutil/cryptoutil.go151
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)
}