/* * Copyright 2013-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file 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.amazonaws.services.s3.internal.crypto; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.NullCipher; import javax.crypto.SecretKey; /** * Functions like a {@link Cipher} but provides only a subset of all the * interface methods of {@link Cipher}. This class is intended to be used in * lieu of the underlying Cipher directly whenever applicable. For example, this * class makes it easy to generate an inverse cipher, or to create an * "auxiliary" cipher for use with get-range or multi-part upload operations. A * subclass may also support the mark and reset operations to enable parts of a * plaintext to be re-processed which is useful for error recovery typical when * network transmission is involved. *
* However a cipher lite, unlike a {@link Cipher}, can only be used once, and * cannot be reused after the {@link #doFinal()} methods have been invoked. In * other words, it is NOT true that, upon finishing, the doFinal method will * reset the cipher lite object to the state it was in when first constructed. * * @author Hanson Char * * @see GCMCipherLite */ public class CipherLite { /** * A no-op implementation. */ public static final CipherLite Null = new CipherLite() { @Override public CipherLite createAuxiliary(long startingBytePos) { return this; } @Override public CipherLite createInverse() { return this; } }; private final Cipher cipher; private final ContentCryptoScheme scheme; private final SecretKey secreteKey; private final int cipherMode; private CipherLite() { this.cipher = new NullCipher(); this.scheme = null; this.secreteKey = null; this.cipherMode = -1; } CipherLite(Cipher cipher, ContentCryptoScheme scheme, SecretKey secreteKey, int cipherMode) { this.cipher = cipher; this.scheme = scheme; this.secreteKey = secreteKey; this.cipherMode = cipherMode; } /** * Recreates a new instance of CipherLite from the current one. */ public CipherLite recreate() { return scheme.createCipherLite(secreteKey, cipher.getIV(), this.cipherMode, cipher.getProvider(), true); } /** * Creates a new instance of CipherLite from the current one, but using * the given IV. */ public CipherLite createUsingIV(byte[] iv) { return scheme.createCipherLite(secreteKey, iv, this.cipherMode, cipher.getProvider(), true); } /** * Returns an auxiliary {@link CipherLite} for partial plaintext * re-encryption (or re-decryption) purposes. * * @param startingBytePos * the starting byte position of the plaintext. Must be a * multiple of the cipher block size. */ public CipherLite createAuxiliary(long startingBytePos) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidAlgorithmParameterException { return scheme.createAuxillaryCipher(secreteKey, cipher.getIV(), cipherMode, cipher.getProvider(), startingBytePos); } /** * Returns the inverse of the current {@link CipherLite}. */ public CipherLite createInverse() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidAlgorithmParameterException { int inversedMode; if (cipherMode == Cipher.DECRYPT_MODE) inversedMode = Cipher.ENCRYPT_MODE; else if (cipherMode == Cipher.ENCRYPT_MODE) inversedMode = Cipher.DECRYPT_MODE; else throw new UnsupportedOperationException(); return scheme.createCipherLite(secreteKey, cipher.getIV(), inversedMode, cipher.getProvider(), true); } /** * Finishes a multiple-part encryption or decryption operation, depending on * how the underlying cipher was initialized. * *
* Input data that may have been buffered during a previous
* update
operation is processed, with padding (if requested)
* being applied. If an AEAD mode such as GCM/CCM is being used, the
* authentication tag is appended in the case of encryption, or verified in
* the case of decryption. The result is stored in a new buffer.
*
*
* Note: if any exception is thrown, a new instance of this cipher lite * object may need to be constructed before it can be used again. be * reconstructed before it can be used again. * * @return the new buffer with the result * * @exception IllegalStateException * if this cipher is in a wrong state (e.g., has not been * initialized) * @exception IllegalBlockSizeException * if this cipher is a block cipher, no padding has been * requested (only in encryption mode), and the total input * length of the data processed by this cipher is not a * multiple of block size; or if this encryption algorithm is * unable to process the input data provided. * @exception BadPaddingException * if this cipher is in decryption mode, and (un)padding has * been requested, but the decrypted data is not bounded by * the appropriate padding bytes * @exception javax.crypto.AEADBadTagException * if this cipher is decrypting in an AEAD mode (such as * GCM/CCM), and the received authentication tag does not * match the calculated value */ public byte[] doFinal() throws IllegalBlockSizeException, BadPaddingException { return cipher.doFinal(); } /** * Encrypts or decrypts data in a single-part operation, or finishes a * multiple-part operation. The data is encrypted or decrypted, depending on * how the underlying cipher was initialized. * *
* The bytes in the input
buffer, and any input bytes that may
* have been buffered during a previous update
operation, are
* processed, with padding (if requested) being applied. If an AEAD mode
* such as GCM/CCM is being used, the authentication tag is appended in the
* case of encryption, or verified in the case of decryption. The result is
* stored in a new buffer.
*
*
* Note: if any exception is thrown, a new instance of this cipher lite * object may need to be constructed before it can be used again. * * @param input * the input buffer * * @return the new buffer with the result * * @exception IllegalStateException * if this cipher is in a wrong state (e.g., has not been * initialized) * @exception IllegalBlockSizeException * if this cipher is a block cipher, no padding has been * requested (only in encryption mode), and the total input * length of the data processed by this cipher is not a * multiple of block size; or if this encryption algorithm is * unable to process the input data provided. * @exception BadPaddingException * if this cipher is in decryption mode, and (un)padding has * been requested, but the decrypted data is not bounded by * the appropriate padding bytes; or if this cipher is * decrypting in an AEAD mode (such as GCM/CCM), and the * received authentication tag does not match the calculated * value */ public byte[] doFinal(byte[] input) throws IllegalBlockSizeException, BadPaddingException { return cipher.doFinal(input); } /** * Encrypts or decrypts data in a single-part operation, or finishes a * multiple-part operation. The data is encrypted or decrypted, depending on * how the underlying cipher was initialized. * *
* The first inputLen
bytes in the input
buffer,
* starting at inputOffset
inclusive, and any input bytes that
* may have been buffered during a previous update
operation,
* are processed, with padding (if requested) being applied. If an AEAD mode
* such as GCM/CCM is being used, the authentication tag is appended in the
* case of encryption, or verified in the case of decryption. The result is
* stored in a new buffer.
*
*
* Note: if any exception is thrown, a new instance of this cipher lite
* object may need to be constructed before it can be used again.
*
* @param input
* the input buffer
* @param inputOffset
* the offset in input
where the input starts
* @param inputLen
* the input length
*
* @return the new buffer with the result
*
* @exception IllegalStateException
* if this cipher is in a wrong state (e.g., has not been
* initialized)
* @exception IllegalBlockSizeException
* if this cipher is a block cipher, no padding has been
* requested (only in encryption mode), and the total input
* length of the data processed by this cipher is not a
* multiple of block size; or if this encryption algorithm is
* unable to process the input data provided.
* @exception BadPaddingException
* if this cipher is in decryption mode, and (un)padding has
* been requested, but the decrypted data is not bounded by
* the appropriate padding bytes; or if this cipher is
* decrypting in an AEAD mode (such as GCM/CCM), and the
* received authentication tag does not match the calculated
* value
*/
public byte[] doFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
return cipher.doFinal(input, inputOffset, inputLen);
}
/**
* Continues a multiple-part encryption or decryption operation (depending
* on how the underlying cipher was initialized), processing another data
* part.
*
*
* The first inputLen
bytes in the input
buffer,
* starting at inputOffset
inclusive, are processed, and the
* result is stored in a new buffer.
*
*
* If inputLen
is zero, this method returns null
.
*
* @param input
* the input buffer
* @param inputOffset
* the offset in input
where the input starts
* @param inputLen
* the input length
*
* @return the new buffer with the result, or null if the underlying cipher
* is a block cipher and the input data is too short to result in a
* new block.
*
* @exception IllegalStateException
* if the underlying cipher is in a wrong state (e.g., has
* not been initialized)
*/
public byte[] update(byte[] input, int inputOffset, int inputLen) {
return cipher.update(input, inputOffset, inputLen);
}
/**
* Returns the algorithm name of the underlying cipher.
*/
public final String getCipherAlgorithm() {
return cipher.getAlgorithm();
}
/**
* Returns the provider of the underlying cipher.
*/
public final Provider getCipherProvider() {
return cipher.getProvider();
}
/**
* Returns the standard algorithm name for the secret key. For example,
* "DSA" would indicate that this key is a DSA key. See Appendix A in the Java Cryptography Architecture API Specification & Reference
* for information about standard algorithm names.
*/
public final String getSecretKeyAlgorithm() {
return secreteKey.getAlgorithm();
}
/**
* This method is provided only for testing purposes. The {@link CipherLite}
* is intended to be used in lieu of the underlying Cipher.
*/
public final Cipher getCipher() {
return cipher;
}
public final ContentCryptoScheme getContentCryptoScheme() {
return scheme;
}
/**
* Returns the initialization vector (IV) in a new buffer.
*
*
This is useful in the case where a random IV was created,
* or in the context of password-based encryption or
* decryption, where the IV is derived from a user-supplied password.
*
* @return the initialization vector in a new buffer, or null if the
* underlying algorithm does not use an IV, or if the IV has not yet
* been set.
*/
public final byte[] getIV() {
return cipher.getIV();
}
/**
* Returns the block size (in bytes).
*
* @return the block size (in bytes), or 0 if the underlying algorithm is
* not a block cipher
*/
public final int getBlockSize() {
return cipher.getBlockSize();
}
final int getCipherMode() {
return cipherMode;
}
/**
* Tests if this cipher lite supports the mark
* and reset
methods. Returns false by default, but subclass
* may override.
*/
public boolean markSupported() { return false; }
/**
* Marks the current position in this cipher lite. A subsequent call to the
* reset
method repositions this cipher lite at the last marked
* position so that subsequent crypto operations will be logically performed
* in an idempotent manner as if the cipher has been rewinded back to the
* marked position.
*
*
* The general contract of mark
is that, if the method
* markSupported
returns true
, the cipher lite
* somehow remembers the internal state after the call to mark
* and stands ready to restore to the internal state so that it would be
* able to produce the same output given the same input again if and
* whenever the method reset
is called.
*
* @return the current position marked or -1 if mark/reset is not supported.
*/
public long mark() { return -1; }
/**
* Repositions this cipher lite to the position at the time the
* mark
method was last called.
*
*
* The general contract of reset
is:
*
*
*
markSupported
returns true
,
* then the cipher lite is reset to the internal state since the most recent
* call to mark
(or since the start of the input data, if
* mark
has not been called), so that subsequent callers of the
* udpate
or doFinal
method would produce the same
* output given the same input data identical to the input data after the
* mark
method was last called..markSupported
returns false
,
* then the call to reset
may throw an
* IllegalStateException
.