diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2026-01-25 06:36:52 -0800 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2026-01-25 06:36:52 -0800 |
| commit | 1743be7b30655fe13ae0e60e3480f25d31eaea8f (patch) | |
| tree | da3067522c846568e8d1dcd072b6c7e31f9dca5a /oae2.go | |
| parent | 207148233be7f30161dbbbd7d6d5a5966d1fbc36 (diff) | |
| download | oae2-1743be7b30655fe13ae0e60e3480f25d31eaea8f.tar.zst | |
Add some more comments and documentation
Diffstat (limited to 'oae2.go')
| -rw-r--r-- | oae2.go | 35 |
1 files changed, 32 insertions, 3 deletions
@@ -1,11 +1,22 @@ -// Pacakage oae2 implements “Online Authenticated-Encryption” based on the -// influential paper +// Pacakage oae2 implements “Online Authenticated-Encryption” (also known as +// streaming AEAD) based on the influential paper // “[Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance]” by // Hoang et al. // // Please do not use this package; it likely has critical // security vulnerabilities. // +// # Encrypted stream format +// +// The encrypted streams written by this package start with a 32 byte random +// salt, followed by a number of encrypted segments each of length +// segmentSize + 16. That is, each encrypted segment has an overhead of 16 +// bytes for the authentication tag. Each segment is encrypted using +// AES-256-GCM, using a key derived from the given key and the random salt +// using HKDF-HMAC-SHA256. +// +// The encrypted stream format may change in the future. +// // [Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance]: https://eprint.iacr.org/2015/189.pdf package oae2 @@ -202,6 +213,7 @@ func (w *Writer) Close() error { return nil } +// We avoid bufio.Reader to minimize extra allocations. type bufReader struct { r io.Reader nextByte byte @@ -247,7 +259,8 @@ type Reader struct { } // NewReader returns a Reader that wraps r and decrypts the data using key in -// chunks of size segmentSize. NewReader panics if segmentSize <= 0. +// chunks of size segmentSize. segmentSize must match the segment size that was +// used to write the encrypted stream. NewReader panics if segmentSize <= 0. func NewReader(r io.Reader, key []byte, segmentSize int) *Reader { if segmentSize <= 0 { panic("oae2.NewReader: segmentSize must be strictly greater than 0") @@ -262,6 +275,16 @@ func NewReader(r io.Reader, key []byte, segmentSize int) *Reader { func (r *Reader) initialize() error { r.initialized = true + // N.B. there's a subtle bug lurking here: for an input stream of + // exactly 32 bytes, it's important that we reject this stream since it + // doesn't have an authentication tag. So if we naively read exactly 32 + // bytes inside initialize, then Read will see that the underlying + // reader is at EOF, and won't be able to distinguish this from a valid + // EOF case. Fortunately we can easily work around this by reading one + // extra byte here: since the shortest possible encrypted stream is + // 32 + 16 bytes, every valid stream will have an extra byte for us + // here, and other than on the first segment, Read always knows + // precisely whether the stream is at EOF. buf := make([]byte, saltSize+1) if _, r.err = io.ReadFull(&r.r, buf); r.err != nil { if r.err == io.EOF { @@ -282,8 +305,14 @@ func (r *Reader) init() error { } func (r *Reader) fillBuf() error { + // It's important that we know whether this is the last segment, to set + // the appropriate byte in the IV. Some other implementations just try + // both options, but I think we can be a little bit more precise and + // read an extra byte here to be sure if we're at the end. n, err := io.ReadFull(&r.r, r.buf[:r.segmentSize+aeadOverhead+1]) if err != nil && err != io.ErrUnexpectedEOF { + // Don't set r.err to EOF here, since we might Seek and reset + // the stream, but r.err is unrecoverable. if err != io.EOF { r.err = err } |
