blob: 6805870df4781cd0b3ae06cfcbede8814dec7427 [file] [log] [blame]
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package com.google.crypto.tink;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;
/**
* An interface for streaming authenticated encryption with associated data.
*
* <p>Streaming encryption is typically used for encrypting large plaintexts such as large files.
* Tink may eventually contain multiple interfaces for streaming encryption depending on the
* supported properties. This interface supports a streaming interface for symmetric encryption with
* authentication. The underlying encryption modes are selected so that partial plaintext can be
* obtained fast by decrypting and authenticating just a part of the ciphertext.
*
* <h3>Security guarantees</h3>
*
* <p>Instances of StreamingAead must follow the OAE2 definition as proposed in the paper "Online
* Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance" by Hoang, Reyhanitabar, Rogaway
* and Vizár https://eprint.iacr.org/2015/189.pdf
*
* <h3>Restrictions</h3>
*
* <p>Encryption must be done in one session. There is no possibility to modify an existing
* ciphertext or append to it (other than reencrypt the whole file again). One reason for this
* restriction is the use of AES-GCM as one cipher to implement this interface. If single segments
* are modified then this is equivalent to reusing the same IV twice, but reusing an IV twice leaks
* an AES-GCM key. Another reason is that implementations of this interface have no protection
* against roll-back attacks: an attacker can always try to restore a previous version of the file
* without detection.
*
* <h3>Blocking vs non-blocking I/O</h3>
*
* <p>A channel can be in a blocking mode (i.e. always waits until the requested number of bytes
* have been processed) or non-blocking mode (i.e. I/O operation will never block and may transfer
* fewer bytes than were requested or possibly no bytes at all).
*
* <p>If the channel provided to the streaming encryption is in blocking mode then encryption and
* decryption have the same property. That is, encryption always processes all the plaintext passed
* in, and waits until complete segments have been written to the ciphertext channel (incomplete
* segment, if any, is buffered). Similarly, decryption blocks until sufficiently many bytes have
* been read from the ciphertext channel so that all the requested plaintext can be decrypted and
* authenticated, or until the end of the plaintext has been reached, or an IOException occurred.
*
* <p>If the channel provided to the streaming encryption is in non-blocking mode, then encryption
* and decryption are also non-blocking. Since encryption and decryption is done in segments it is
* possible that for example a call attempting to read() returns no plaintext at all even if partial
* ciphertext was read from the underlying channel.
*
* <h3>Sample encryption</h3>
*
* <pre>{@code
* StreamingAead s = ...
* java.nio.channels.FileChannel ciphertextDestination =
* new FileOutputStream(ciphertextFile).getChannel();
* byte[] aad = ...
* WritableByteChannel encryptingChannel = s.newEncryptingChannel(ciphertextDestination, aad);
* while ( ... ) {
* int r = encryptingChannel.write(buffer);
* ...
* }
* encryptingChannel.close();
* }</pre>
*
* <h3>Sample full decryption</h3>
*
* <pre>{@code
* StreamingAead s = ...
* java.nio.channels.FileChannel ciphertextSource =
* new FileInputStream(ciphertextFile).getChannel();
* byte[] aad = ...
* ReadableByteChannel decryptingChannel = s.newDecryptingChannel(ciphertextSource, aad);
* int chunkSize = ...
* ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
* do {
* buffer.clear();
* int cnt = decryptingChannel.read(buffer);
* if (cnt > 0) {
* // Process cnt bytes of plaintext.
* } else if (read == -1) {
* // End of plaintext detected.
* break;
* } else if (read == 0) {
* // No ciphertext is available at the moment.
* }
* }
* }</pre>
*
* @since 1.1.0
*/
public interface StreamingAead {
/**
* Returns a WritableByteChannel for plaintext. Any data written to the returned
* channel will be encrypted and the resulting ciphertext written to the provided
* {@code ciphertextDestination}
*
* @param ciphertextDestination the channel to which the ciphertext is written.
* @param associatedData data associated with the plaintext. This data is authenticated
* but not encrypted. It must be passed into the decryption.
*/
WritableByteChannel newEncryptingChannel(
WritableByteChannel ciphertextDestination, byte[] associatedData)
throws GeneralSecurityException, IOException;
/**
* Returns a SeekableByteChannel that allows to access the plaintext.
*
* <p>This method does not work on Android Marshmallow (API level 23) or older because these
* Android versions don't have the java.nio.channels.SeekableByteChannel interface.
*
* @param ciphertextSource the ciphertext
* @param associatedData the data associated with the ciphertext.
* @return {@link SeekableByteChannel} that allows random read access to the plaintext. The
* following methods of SeekableByteChannel are implemented:
* <ul>
* <li>{@code long position()} Returns the channel's position in the plaintext.
* <li>{@code SeekableByteChannel position(long newPosition)} Sets the channel's position.
* Setting the position to a value greater than the plaintext size is legal. A later
* attempt to read byte will immediately return an end-of-file indication.
* <li>{@code int read(ByteBuffer dst)} Bytes are read starting at the channel's position,
* and then the position is updated with the number of bytes actually read. All bytes
* returned have been authenticated. If the end of the stream has been reached -1 is
* returned. A result of -1 is authenticated (e.g. by checking the MAC of the last
* ciphertext chunk.) A call to this function attempts to fill dst, but it may return
* fewer bytes than requested, e.g. if the underlying ciphertextSource does not provide
* the requested number of bytes or if the plaintext ended.
* <p>Throws {@link IOException} if a MAC verification failed. TODO: Should we extend
* the interface with read(ByteBuffer dst, long position) to avoid race conditions?
* <li>{@code long size()} Returns the size of the plaintext. TODO: Decide whether the
* result should be authenticated)
* <li>{@code SeekableByteChannel truncate(long size)} throws {@link
* java.nio.channels.NonWritableChannelException } because the channel is read-only.
* <li>{@code int write(ByteBuffer src)} throws {@link
* java.nio.channels.NonWritableChannelException } because the channel is read-only.
* <li>{@code close()} closes the channel
* <li>{@code isOpen()}
* </ul>
*
* @throws GeneralSecurityException if the header of the ciphertext is corrupt or if
* associatedData is not correct.
* @throws IOException if an IOException occurred while reading from ciphertextDestination.
*/
SeekableByteChannel newSeekableDecryptingChannel(
SeekableByteChannel ciphertextSource, byte[] associatedData)
throws GeneralSecurityException, IOException;
ReadableByteChannel newDecryptingChannel(
ReadableByteChannel ciphertextSource, byte[] associatedData)
throws GeneralSecurityException, IOException;
/**
* Returns a wrapper around {@code ciphertextDestination}, such that any write-operation via
* the wrapper results in AEAD-encryption of the written data, using {@code associatedData}
* as associated authenticated data. The associated data is not included in the ciphertext
* and has to be passed in as parameter for decryption.
*/
OutputStream newEncryptingStream(OutputStream ciphertextDestination, byte[] associatedData)
throws GeneralSecurityException, IOException;
/**
* Returns a wrapper around {@code ciphertextSource}, such that any read-operation
* via the wrapper results in AEAD-decryption of the underlying ciphertext,
* using {@code associatedData} as associated authenticated data.
*
* <p>The returned InputStream may support {@code mark()}/{@code reset()},
* but does not have to do it -- {@code markSupported()} provides the corresponding info.
*
* <p>The returned InputStream supports {@code skip()}, yet possibly in an inefficient way,
* i.e. by reading a sequence of blocks until the desired position. If a more efficient
* {@code skip()}-functionality is needed, the Channel-based API can be used.
*/
InputStream newDecryptingStream(InputStream ciphertextSource, byte[] associatedData)
throws GeneralSecurityException, IOException;
}