1// Copyright 2020 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14// 15//////////////////////////////////////////////////////////////////////////////// 16 17// Package noncebased provides a reusable streaming AEAD framework. 18// 19// It tackles the segment handling portions of the nonce based online 20// encryption scheme proposed in "Online Authenticated-Encryption and its 21// Nonce-Reuse Misuse-Resistance" by Hoang, Reyhanitabar, Rogaway and Vizár 22// (https://eprint.iacr.org/2015/189.pdf). 23// 24// In this scheme, the format of a ciphertext is: 25// 26// header || segment_0 || segment_1 || ... || segment_k. 27// 28// The format of header is: 29// 30// headerLength || salt || nonce_prefix 31// 32// headerLength is 1 byte which documents the size of the header and can be 33// obtained via HeaderLength(). In principle, headerLength is redundant 34// information, since the length of the header can be determined from the key 35// size. 36// 37// salt is a salt used in the key derivation. 38// 39// nonce_prefix is a prefix for all per-segment nonces. 40// 41// segment_i is the i-th segment of the ciphertext. The size of segment_1 .. 42// segment_{k-1} is ciphertextSegmentSize. segment_0 is shorter, so that 43// segment_0 plus additional data of size firstCiphertextSegmentOffset (e.g. 44// the header) aligns with ciphertextSegmentSize. 45// 46// The first segment size will be: 47// 48// ciphertextSegmentSize - HeaderLength() - firstCiphertextSegmentOffset. 49package noncebased 50 51import ( 52 "encoding/binary" 53 "errors" 54 "io" 55 "math" 56) 57 58var ( 59 // ErrNonceSizeTooShort indicates that the specified nonce size isn't large 60 // enough to hold the nonce prefix, counter and last segment flag. 61 ErrNonceSizeTooShort = errors.New("nonce size too short") 62 63 // ErrCiphertextSegmentTooShort indicates the the ciphertext segment being 64 // processed is too short. 65 ErrCiphertextSegmentTooShort = errors.New("ciphertext segment too short") 66 67 // ErrTooManySegments indicates that the ciphertext has too many segments. 68 ErrTooManySegments = errors.New("too many segments") 69) 70 71// SegmentEncrypter facilitates implementing various streaming AEAD encryption 72// modes. 73type SegmentEncrypter interface { 74 EncryptSegment(segment, nonce []byte) ([]byte, error) 75} 76 77// Writer provides a framework for ingesting plaintext data and 78// writing encrypted data to the wrapped io.Writer. The scheme used for 79// encrypting segments is specified by providing a SegmentEncrypter 80// implementation. 81type Writer struct { 82 w io.Writer 83 segmentEncrypter SegmentEncrypter 84 encryptedSegmentCnt uint64 85 firstCiphertextSegmentOffset int 86 nonceSize int 87 noncePrefix []byte 88 plaintext []byte 89 plaintextPos int 90 ciphertext []byte 91 closed bool 92} 93 94// WriterParams contains the options for instantiating a Writer via NewWriter(). 95type WriterParams struct { 96 // W is the underlying writer being wrapped. 97 W io.Writer 98 99 // SegmentEncrypter provides a method for encrypting segments. 100 SegmentEncrypter SegmentEncrypter 101 102 // NonceSize is the length of generated nonces. It must be at least 5 + 103 // len(NoncePrefix). It can be longer, but longer nonces introduce more 104 // overhead in the resultant ciphertext. 105 NonceSize int 106 107 // NoncePrefix is a constant that all nonces throughout the ciphertext will 108 // start with. It's length must be at least 5 bytes shorter than NonceSize. 109 NoncePrefix []byte 110 111 // The size of the segments which the plaintext will be split into. 112 PlaintextSegmentSize int 113 114 // FirstCiphertexSegmentOffset indicates where the ciphertext should begin in 115 // W. This allows for the existence of overhead in the stream unrelated to 116 // this encryption scheme. 117 FirstCiphertextSegmentOffset int 118} 119 120// NewWriter creates a new Writer instance. 121func NewWriter(params WriterParams) (*Writer, error) { 122 if params.NonceSize-len(params.NoncePrefix) < 5 { 123 return nil, ErrNonceSizeTooShort 124 } 125 return &Writer{ 126 w: params.W, 127 segmentEncrypter: params.SegmentEncrypter, 128 nonceSize: params.NonceSize, 129 noncePrefix: params.NoncePrefix, 130 firstCiphertextSegmentOffset: params.FirstCiphertextSegmentOffset, 131 plaintext: make([]byte, params.PlaintextSegmentSize), 132 }, nil 133} 134 135// Write encrypts passed data and passes the encrypted data to the underlying writer. 136func (w *Writer) Write(p []byte) (int, error) { 137 if w.closed { 138 return 0, errors.New("write on closed writer") 139 } 140 141 pos := 0 142 for { 143 ptLim := len(w.plaintext) 144 if w.encryptedSegmentCnt == 0 { 145 ptLim -= w.firstCiphertextSegmentOffset 146 } 147 n := copy(w.plaintext[w.plaintextPos:ptLim], p[pos:]) 148 w.plaintextPos += n 149 pos += n 150 if pos == len(p) { 151 break 152 } 153 154 nonce, err := generateSegmentNonce(w.nonceSize, w.noncePrefix, w.encryptedSegmentCnt, false) 155 if err != nil { 156 return pos, err 157 } 158 159 w.ciphertext, err = w.segmentEncrypter.EncryptSegment(w.plaintext[:ptLim], nonce) 160 if err != nil { 161 return pos, err 162 } 163 164 if _, err := w.w.Write(w.ciphertext); err != nil { 165 return pos, err 166 } 167 168 w.plaintextPos = 0 169 w.encryptedSegmentCnt++ 170 } 171 return pos, nil 172} 173 174// Close encrypts the remaining data, flushes it to the underlying writer and 175// closes this writer. 176func (w *Writer) Close() error { 177 if w.closed { 178 return nil 179 } 180 181 nonce, err := generateSegmentNonce(w.nonceSize, w.noncePrefix, w.encryptedSegmentCnt, true) 182 if err != nil { 183 return err 184 } 185 186 w.ciphertext, err = w.segmentEncrypter.EncryptSegment(w.plaintext[:w.plaintextPos], nonce) 187 if err != nil { 188 return err 189 } 190 191 if _, err := w.w.Write(w.ciphertext); err != nil { 192 return err 193 } 194 195 w.plaintextPos = 0 196 w.encryptedSegmentCnt++ 197 w.closed = true 198 return nil 199} 200 201// SegmentDecrypter facilitates implementing various streaming AEAD encryption modes. 202type SegmentDecrypter interface { 203 DecryptSegment(segment, nonce []byte) ([]byte, error) 204} 205 206// Reader facilitates the decryption of ciphertexts created using a Writer. 207// 208// The scheme used for decrypting segments is specified by providing a 209// SegmentDecrypter implementation. The implementation must align 210// with the SegmentEncrypter used in the Writer. 211type Reader struct { 212 r io.Reader 213 segmentDecrypter SegmentDecrypter 214 decryptedSegmentCnt uint64 215 firstCiphertextSegmentOffset int 216 nonceSize int 217 noncePrefix []byte 218 plaintext []byte 219 plaintextPos int 220 ciphertext []byte 221 ciphertextPos int 222} 223 224// ReaderParams contains the options for instantiating a Reader via NewReader(). 225type ReaderParams struct { 226 // R is the underlying reader being wrapped. 227 R io.Reader 228 229 // SegmentDecrypter provides a method for decrypting segments. 230 SegmentDecrypter SegmentDecrypter 231 232 // NonceSize is the length of generated nonces. It must match the NonceSize 233 // of the Writer used to create the ciphertext. 234 NonceSize int 235 236 // NoncePrefix is a constant that all nocnes throughout the ciphertext start 237 // with. It's extracted from the header of the ciphertext. 238 NoncePrefix []byte 239 240 // The size of the ciphertext segments. 241 CiphertextSegmentSize int 242 243 // FirstCiphertexSegmentOffset indicates where the ciphertext actually begins 244 // in R. This allows for the existence of overhead in the stream unrelated to 245 // this encryption scheme. 246 FirstCiphertextSegmentOffset int 247} 248 249// NewReader creates a new Reader instance. 250func NewReader(params ReaderParams) (*Reader, error) { 251 if params.NonceSize-len(params.NoncePrefix) < 5 { 252 return nil, ErrNonceSizeTooShort 253 } 254 return &Reader{ 255 r: params.R, 256 segmentDecrypter: params.SegmentDecrypter, 257 nonceSize: params.NonceSize, 258 noncePrefix: params.NoncePrefix, 259 firstCiphertextSegmentOffset: params.FirstCiphertextSegmentOffset, 260 261 // Allocate an extra byte to detect the last segment. 262 ciphertext: make([]byte, params.CiphertextSegmentSize+1), 263 }, nil 264} 265 266// Read decrypts data from underlying reader and passes it to p. 267func (r *Reader) Read(p []byte) (int, error) { 268 if r.plaintextPos < len(r.plaintext) { 269 n := copy(p, r.plaintext[r.plaintextPos:]) 270 r.plaintextPos += n 271 return n, nil 272 } 273 274 r.plaintextPos = 0 275 276 ctLim := len(r.ciphertext) 277 if r.decryptedSegmentCnt == 0 { 278 ctLim -= r.firstCiphertextSegmentOffset 279 } 280 n, err := io.ReadFull(r.r, r.ciphertext[r.ciphertextPos:ctLim]) 281 if err != nil && err != io.ErrUnexpectedEOF { 282 return 0, err 283 } 284 285 var ( 286 lastSegment bool 287 segment int 288 ) 289 if err != nil { 290 lastSegment = true 291 segment = r.ciphertextPos + n 292 } else { 293 segment = r.ciphertextPos + n - 1 294 } 295 296 if segment < 0 { 297 return 0, ErrCiphertextSegmentTooShort 298 } 299 300 nonce, err := generateSegmentNonce(r.nonceSize, r.noncePrefix, r.decryptedSegmentCnt, lastSegment) 301 if err != nil { 302 return 0, err 303 } 304 305 r.plaintext, err = r.segmentDecrypter.DecryptSegment(r.ciphertext[:segment], nonce) 306 if err != nil { 307 return 0, err 308 } 309 310 // Copy 1 byte remainder to the beginning of ciphertext. 311 if !lastSegment { 312 remainderOffset := segment 313 r.ciphertext[0] = r.ciphertext[remainderOffset] 314 r.ciphertextPos = 1 315 } 316 317 r.decryptedSegmentCnt++ 318 319 n = copy(p, r.plaintext) 320 r.plaintextPos = n 321 return n, nil 322} 323 324// generateSegmentNonce returns a nonce for a segment. 325// 326// The format of the nonce is: 327// 328// nonce_prefix || ctr || last_block. 329// 330// nonce_prefix is a constant prefix used throughout the whole ciphertext. 331// 332// The ctr is a 32 bit counter. 333// 334// last_block is 1 byte which is set to 1 for the last segment and 0 335// otherwise. 336func generateSegmentNonce(size int, prefix []byte, segmentNum uint64, last bool) ([]byte, error) { 337 if segmentNum >= math.MaxUint32 { 338 return nil, ErrTooManySegments 339 } 340 341 nonce := make([]byte, size) 342 copy(nonce, prefix) 343 offset := len(prefix) 344 binary.BigEndian.PutUint32(nonce[offset:], uint32(segmentNum)) 345 offset += 4 346 if last { 347 nonce[offset] = 1 348 } 349 return nonce, nil 350} 351