All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.encryptionsdk.AwsCrypto Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
/*
 * Copyright 2016 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.encryptionsdk;

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Map;

import com.amazonaws.encryptionsdk.exception.AwsCryptoException;
import com.amazonaws.encryptionsdk.exception.BadCiphertextException;
import com.amazonaws.encryptionsdk.internal.DecryptionHandler;
import com.amazonaws.encryptionsdk.internal.EncryptionHandler;
import com.amazonaws.encryptionsdk.internal.LazyMessageCryptoHandler;
import com.amazonaws.encryptionsdk.internal.MessageCryptoHandler;
import com.amazonaws.encryptionsdk.internal.ProcessingSummary;
import com.amazonaws.encryptionsdk.internal.Utils;
import com.amazonaws.encryptionsdk.model.EncryptionMaterials;
import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest;

/**
 * Provides the primary entry-point to the AWS Encryption SDK. All encryption and decryption
 * operations should start here. Most people will want to use either
 * {@link #encryptData(MasterKeyProvider, byte[], Map)} and
 * {@link #decryptData(MasterKeyProvider, byte[])} to encrypt/decrypt things.
 * 
 * 

* The core concepts (and classes) in this SDK are: *

    *
  • {@link AwsCrypto} *
  • {@link DataKey} *
  • {@link MasterKey} *
  • {@link MasterKeyProvider} *
* *

* {@link AwsCrypto} provides the primary way to encrypt/decrypt data. It can operate on * byte-arrays, streams, or {@link java.lang.String Strings}. This data is encrypted using the * specifed {@link CryptoAlgorithm} and a {@link DataKey} which is unique to each encrypted message. * This {@code DataKey} is then encrypted using one (or more) {@link MasterKey MasterKeys}. The * process is reversed on decryption with the code selecting a copy of the {@code DataKey} protected * by a usable {@code MasterKey}, decrypting the {@code DataKey}, and then decrypted the message. * *

* The main way to get a {@code MasterKey} is through the use of a {@link MasterKeyProvider}. This * provides a common interface for the AwsEncryptionSdk to find and retrieve {@code MasterKeys}. * (Some {@code MasterKeys} can also be constructed directly.) * *

* {@code AwsCrypto} uses the {@code MasterKeyProvider} to determine which {@code MasterKeys} should * be used to encrypt the {@code DataKeys} by calling * {@link MasterKeyProvider#getMasterKeysForEncryption(MasterKeyRequest)} . When more than one * {@code MasterKey} is returned, the first {@code MasterKeys} is used to create the * {@code DataKeys} by calling {@link MasterKey#generateDataKey(CryptoAlgorithm,java.util.Map)} . * All of the other {@code MasterKeys} are then used to re-encrypt that {@code DataKey} with * {@link MasterKey#encryptDataKey(CryptoAlgorithm,java.util.Map,DataKey)} . This list of * {@link EncryptedDataKey EncryptedDataKeys} (the same {@code DataKey} possibly encrypted multiple * times) is stored in the {@link com.amazonaws.encryptionsdk.model.CiphertextHeaders}. * *

* {@code AwsCrypto} also uses the {@code MasterKeyProvider} to decrypt one of the * {@link EncryptedDataKey EncryptedDataKeys} from the header to retrieve the actual {@code DataKey} * necessary to decrypt the message. * *

* Any place a {@code MasterKeyProvider} is used, a {@link MasterKey} can be used instead. The * {@code MasterKey} will behave as a {@code MasterKeyProvider} which is only capable of providing * itself. This is often useful when only one {@code MasterKey} is being used. * *

* Note regarding the use of generics: This library makes heavy use of generics to provide type * safety to advanced developers. The great majority of users should be able to just use the * provided type parameters or the {@code ?} wildcard. */ @SuppressWarnings("WeakerAccess") // this is a public API public class AwsCrypto { private static final Map EMPTY_MAP = Collections.emptyMap(); /** * Returns the {@link CryptoAlgorithm} to be used for encryption when none is explicitly * selected. Currently it is {@link CryptoAlgorithm#ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384}. */ public static CryptoAlgorithm getDefaultCryptoAlgorithm() { return CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; } /** * Returns the frame size to use for encryption when none is explicitly selected. Currently it * is 4096. */ public static int getDefaultFrameSize() { return 4096; } // These are volatile because we allow unsynchronized writes via our setters, and without setting volatile we could // see strange results - e.g. copying these to a local might give different values on subsequent reads from the // local. By setting them volatile we ensure that proper memory barriers are applied to ensure things behave in a // sensible manner. private volatile CryptoAlgorithm encryptionAlgorithm_ = null; private volatile int encryptionFrameSize_ = getDefaultFrameSize(); /** * Sets the {@link CryptoAlgorithm} to use when encrypting data. This has no impact on * decryption. */ public void setEncryptionAlgorithm(final CryptoAlgorithm alg) { encryptionAlgorithm_ = alg; } public CryptoAlgorithm getEncryptionAlgorithm() { return encryptionAlgorithm_; } /** * Sets the framing size to use when encrypting data. This has no impact on decryption. * If {@code frameSize} is 0, then framing is disabled and the entire plaintext will be encrypted * in a single block. * * Note that during encryption arrays of this size will be allocated. Using extremely large frame sizes may pose * compatibility issues when the decryptor is running on 32-bit systems. Additionally, Java VM limits may set a * platform-specific upper bound to frame sizes. */ public void setEncryptionFrameSize(final int frameSize) { if (frameSize < 0) { throw new IllegalArgumentException("frameSize must be non-negative"); } encryptionFrameSize_ = frameSize; } public int getEncryptionFrameSize() { return encryptionFrameSize_; } /** * Returns the best estimate for the output length of encrypting a plaintext with the provided * {@code plaintextSize} and {@code encryptionContext}. The actual ciphertext may be shorter. * * This method is equivalent to calling {@link #estimateCiphertextSize(CryptoMaterialsManager, int, Map)} with a * {@link DefaultCryptoMaterialsManager} based on the given provider. */ public > long estimateCiphertextSize( final MasterKeyProvider provider, final int plaintextSize, final Map encryptionContext ) { return estimateCiphertextSize(new DefaultCryptoMaterialsManager(provider), plaintextSize, encryptionContext); } /** * Returns the best estimate for the output length of encrypting a plaintext with the provided * {@code plaintextSize} and {@code encryptionContext}. The actual ciphertext may be shorter. */ public long estimateCiphertextSize( CryptoMaterialsManager materialsManager, final int plaintextSize, final Map encryptionContext ) { EncryptionMaterialsRequest request = EncryptionMaterialsRequest.newBuilder() .setContext(encryptionContext) .setRequestedAlgorithm(getEncryptionAlgorithm()) // We're not actually encrypting any data, so don't consume any bytes from the cache's limits. We do need to // pass /something/ though, or the cache will be bypassed (as it'll assume this is a streaming encrypt of // unknown size). .setPlaintextSize(0) .build(); final MessageCryptoHandler cryptoHandler = new EncryptionHandler( getEncryptionFrameSize(), checkAlgorithm(materialsManager.getMaterialsForEncrypt(request)) ); return cryptoHandler.estimateOutputSize(plaintextSize); } /** * Returns the equivalent to calling * {@link #estimateCiphertextSize(MasterKeyProvider, int, Map)} with an empty * {@code encryptionContext}. */ public > long estimateCiphertextSize( final MasterKeyProvider provider, final int plaintextSize ) { return estimateCiphertextSize(provider, plaintextSize, EMPTY_MAP); } /** * Returns the equivalent to calling * {@link #estimateCiphertextSize(CryptoMaterialsManager, int, Map)} with an empty * {@code encryptionContext}. */ public long estimateCiphertextSize( final CryptoMaterialsManager materialsManager, final int plaintextSize ) { return estimateCiphertextSize(materialsManager, plaintextSize, EMPTY_MAP); } /** * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey * DataKeys} that are in turn protected by {@link MasterKey MasterKeys} provided by * {@code provider}. * * This method is equivalent to calling {@link #encryptData(CryptoMaterialsManager, byte[], Map)} using a * {@link DefaultCryptoMaterialsManager} based on the given provider. */ public > CryptoResult encryptData( final MasterKeyProvider provider, final byte[] plaintext, final Map encryptionContext ) { //noinspection unchecked return (CryptoResult) encryptData(new DefaultCryptoMaterialsManager(provider), plaintext, encryptionContext); } /** * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey * DataKeys} that are in turn protected by the given CryptoMaterialsProvider. */ public CryptoResult encryptData( CryptoMaterialsManager materialsManager, final byte[] plaintext, final Map encryptionContext ) { EncryptionMaterialsRequest request = EncryptionMaterialsRequest.newBuilder() .setContext(encryptionContext) .setRequestedAlgorithm(getEncryptionAlgorithm()) .setPlaintext(plaintext) .build(); final MessageCryptoHandler cryptoHandler = new EncryptionHandler( getEncryptionFrameSize(), checkAlgorithm(materialsManager.getMaterialsForEncrypt(request)) ); final int outSizeEstimate = cryptoHandler.estimateOutputSize(plaintext.length); final byte[] out = new byte[outSizeEstimate]; int outLen = cryptoHandler.processBytes(plaintext, 0, plaintext.length, out, 0).getBytesWritten(); outLen += cryptoHandler.doFinal(out, outLen); final byte[] outBytes = Utils.truncate(out, outLen); //noinspection unchecked return new CryptoResult(outBytes, cryptoHandler.getMasterKeys(), cryptoHandler.getHeaders()); } /** * Returns the equivalent to calling {@link #encryptData(MasterKeyProvider, byte[], Map)} with * an empty {@code encryptionContext}. */ public > CryptoResult encryptData(final MasterKeyProvider provider, final byte[] plaintext) { return encryptData(provider, plaintext, EMPTY_MAP); } /** * Returns the equivalent to calling {@link #encryptData(CryptoMaterialsManager, byte[], Map)} with * an empty {@code encryptionContext}. */ public CryptoResult encryptData( final CryptoMaterialsManager materialsManager, final byte[] plaintext ) { return encryptData(materialsManager, plaintext, EMPTY_MAP); } /** * Calls {@link #encryptData(MasterKeyProvider, byte[], Map)} on the UTF-8 encoded bytes of * {@code plaintext} and base64 encodes the result. */ public > CryptoResult encryptString( final MasterKeyProvider provider, final String plaintext, final Map encryptionContext ) { //noinspection unchecked return (CryptoResult) encryptString(new DefaultCryptoMaterialsManager(provider), plaintext, encryptionContext); } /** * Calls {@link #encryptData(CryptoMaterialsManager, byte[], Map)} on the UTF-8 encoded bytes of * {@code plaintext} and base64 encodes the result. */ public CryptoResult encryptString( CryptoMaterialsManager materialsManager, final String plaintext, final Map encryptionContext ) { final CryptoResult ctBytes = encryptData( materialsManager, plaintext.getBytes(StandardCharsets.UTF_8), encryptionContext ); return new CryptoResult<>(Base64.getEncoder().encodeToString(ctBytes.getResult()), ctBytes.getMasterKeys(), ctBytes.getHeaders()); } /** * Returns the equivalent to calling {@link #encryptString(MasterKeyProvider, String, Map)} with * an empty {@code encryptionContext}. */ public > CryptoResult encryptString(final MasterKeyProvider provider, final String plaintext) { return encryptString(provider, plaintext, EMPTY_MAP); } /** * Returns the equivalent to calling {@link #encryptString(CryptoMaterialsManager, String, Map)} with * an empty {@code encryptionContext}. */ public CryptoResult encryptString( final CryptoMaterialsManager materialsManager, final String plaintext ) { return encryptString(materialsManager, plaintext, EMPTY_MAP); } /** * Decrypts the provided {@code ciphertext} by requesting that the {@code provider} unwrap any * usable {@link DataKey} in the ciphertext and then decrypts the ciphertext using that * {@code DataKey}. */ public > CryptoResult decryptData(final MasterKeyProvider provider, final byte[] ciphertext) { return decryptData(Utils.assertNonNull(provider, "provider"), new ParsedCiphertext(ciphertext)); } /** * Decrypts the provided ciphertext by delegating to the provided materialsManager to obtain the decrypted * {@link DataKey}. * * @param materialsManager * @param ciphertext * @return */ public CryptoResult decryptData( final CryptoMaterialsManager materialsManager, final byte[] ciphertext ) { return decryptData(Utils.assertNonNull(materialsManager, "materialsManager"), new ParsedCiphertext(ciphertext)); } /** * @see #decryptData(MasterKeyProvider, byte[]) */ @SuppressWarnings("unchecked") public > CryptoResult decryptData( final MasterKeyProvider provider, final ParsedCiphertext ciphertext) { Utils.assertNonNull(provider, "provider"); return (CryptoResult) decryptData(new DefaultCryptoMaterialsManager(provider), ciphertext); } /** * @see #decryptData(CryptoMaterialsManager, byte[]) */ public CryptoResult decryptData( final CryptoMaterialsManager materialsManager, final ParsedCiphertext ciphertext ) { Utils.assertNonNull(materialsManager, "materialsManager"); final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager, ciphertext); final byte[] ciphertextBytes = ciphertext.getCiphertext(); final int contentLen = ciphertextBytes.length - ciphertext.getOffset(); final int outSizeEstimate = cryptoHandler.estimateOutputSize(contentLen); final byte[] out = new byte[outSizeEstimate]; final ProcessingSummary processed = cryptoHandler.processBytes(ciphertextBytes, ciphertext.getOffset(), contentLen, out, 0); if (processed.getBytesProcessed() != contentLen) { throw new BadCiphertextException("Unable to process entire ciphertext. May have trailing data."); } int outLen = processed.getBytesWritten(); outLen += cryptoHandler.doFinal(out, outLen); final byte[] outBytes = Utils.truncate(out, outLen); //noinspection unchecked return new CryptoResult(outBytes, cryptoHandler.getMasterKeys(), cryptoHandler.getHeaders()); } /** * Base64 decodes the {@code ciphertext} prior to decryption and then treats the results as a * UTF-8 encoded string. * * @see #decryptData(MasterKeyProvider, byte[]) */ @SuppressWarnings("unchecked") public > CryptoResult decryptString( final MasterKeyProvider provider, final String ciphertext ) { return (CryptoResult) decryptString(new DefaultCryptoMaterialsManager(provider), ciphertext); } /** * Base64 decodes the {@code ciphertext} prior to decryption and then treats the results as a * UTF-8 encoded string. * * @see #decryptData(CryptoMaterialsManager, byte[]) */ public CryptoResult decryptString(final CryptoMaterialsManager provider, final String ciphertext) { Utils.assertNonNull(provider, "provider"); final byte[] ciphertextBytes; try { ciphertextBytes = Base64.getDecoder().decode(Utils.assertNonNull(ciphertext, "ciphertext")); } catch (final IllegalArgumentException ex) { throw new BadCiphertextException("Invalid base 64", ex); } final CryptoResult ptBytes = decryptData(provider, ciphertextBytes); //noinspection unchecked return new CryptoResult( new String(ptBytes.getResult(), StandardCharsets.UTF_8), ptBytes.getMasterKeys(), ptBytes.getHeaders()); } /** * Returns a {@link CryptoOutputStream} which encrypts the data prior to passing it onto the * underlying {@link OutputStream}. * * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherOutputStream */ public > CryptoOutputStream createEncryptingStream( final MasterKeyProvider provider, final OutputStream os, final Map encryptionContext ) { //noinspection unchecked return (CryptoOutputStream) createEncryptingStream(new DefaultCryptoMaterialsManager(provider), os, encryptionContext); } /** * Returns a {@link CryptoOutputStream} which encrypts the data prior to passing it onto the * underlying {@link OutputStream}. * * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherOutputStream */ public CryptoOutputStream createEncryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os, final Map encryptionContext ) { return new CryptoOutputStream<>(os, getEncryptingStreamHandler(materialsManager, encryptionContext)); } /** * Returns the equivalent to calling * {@link #createEncryptingStream(MasterKeyProvider, OutputStream, Map)} with an empty * {@code encryptionContext}. */ public > CryptoOutputStream createEncryptingStream( final MasterKeyProvider provider, final OutputStream os) { return createEncryptingStream(provider, os, EMPTY_MAP); } /** * Returns the equivalent to calling * {@link #createEncryptingStream(CryptoMaterialsManager, OutputStream, Map)} with an empty * {@code encryptionContext}. */ public CryptoOutputStream createEncryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os ) { return createEncryptingStream(materialsManager, os, EMPTY_MAP); } /** * Returns a {@link CryptoInputStream} which encrypts the data after reading it from the * underlying {@link InputStream}. * * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherInputStream */ public > CryptoInputStream createEncryptingStream( final MasterKeyProvider provider, final InputStream is, final Map encryptionContext ) { //noinspection unchecked return (CryptoInputStream) createEncryptingStream(new DefaultCryptoMaterialsManager(provider), is, encryptionContext); } /** * Returns a {@link CryptoInputStream} which encrypts the data after reading it from the * underlying {@link InputStream}. * * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherInputStream */ public CryptoInputStream createEncryptingStream( CryptoMaterialsManager materialsManager, final InputStream is, final Map encryptionContext ) { final MessageCryptoHandler cryptoHandler = getEncryptingStreamHandler(materialsManager, encryptionContext); return new CryptoInputStream<>(is, cryptoHandler); } /** * Returns the equivalent to calling * {@link #createEncryptingStream(MasterKeyProvider, InputStream, Map)} with an empty * {@code encryptionContext}. */ public > CryptoInputStream createEncryptingStream( final MasterKeyProvider provider, final InputStream is ) { return createEncryptingStream(provider, is, EMPTY_MAP); } /** * Returns the equivalent to calling * {@link #createEncryptingStream(CryptoMaterialsManager, InputStream, Map)} with an empty * {@code encryptionContext}. */ public CryptoInputStream createEncryptingStream( final CryptoMaterialsManager materialsManager, final InputStream is ) { return createEncryptingStream(materialsManager, is, EMPTY_MAP); } /** * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the * underlying {@link OutputStream}. * * @see #decryptData(MasterKeyProvider, byte[]) * @see javax.crypto.CipherOutputStream */ public > CryptoOutputStream createDecryptingStream( final MasterKeyProvider provider, final OutputStream os) { final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider); return new CryptoOutputStream(os, cryptoHandler); } /** * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the * underlying {@link InputStream}. * * @see #decryptData(MasterKeyProvider, byte[]) * @see javax.crypto.CipherInputStream */ public > CryptoInputStream createDecryptingStream( final MasterKeyProvider provider, final InputStream is) { final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider); return new CryptoInputStream(is, cryptoHandler); } /** * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the * underlying {@link OutputStream}. * * @see #decryptData(CryptoMaterialsManager, byte[]) * @see javax.crypto.CipherOutputStream */ public CryptoOutputStream createDecryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os ) { final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager); return new CryptoOutputStream(os, cryptoHandler); } /** * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the * underlying {@link InputStream}. * * @see #encryptData(CryptoMaterialsManager, byte[], Map) * @see javax.crypto.CipherInputStream */ public CryptoInputStream createDecryptingStream( final CryptoMaterialsManager materialsManager, final InputStream is ) { final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager); return new CryptoInputStream(is, cryptoHandler); } private MessageCryptoHandler getEncryptingStreamHandler( CryptoMaterialsManager materialsManager, Map encryptionContext ) { Utils.assertNonNull(materialsManager, "materialsManager"); Utils.assertNonNull(encryptionContext, "encryptionContext"); EncryptionMaterialsRequest.Builder requestBuilder = EncryptionMaterialsRequest.newBuilder() .setContext(encryptionContext) .setRequestedAlgorithm(getEncryptionAlgorithm()); return new LazyMessageCryptoHandler(info -> { // Hopefully we know the input size now, so we can pass it along to the CMM. if (info.getMaxInputSize() != -1) { requestBuilder.setPlaintextSize(info.getMaxInputSize()); } return new EncryptionHandler( getEncryptionFrameSize(), checkAlgorithm(materialsManager.getMaterialsForEncrypt(requestBuilder.build())) ); }); } private EncryptionMaterials checkAlgorithm(EncryptionMaterials result) { if (encryptionAlgorithm_ != null && result.getAlgorithm() != encryptionAlgorithm_) { throw new AwsCryptoException( String.format("Materials manager ignored requested algorithm; algorithm %s was set on AwsCrypto " + "but %s was selected", encryptionAlgorithm_, result.getAlgorithm()) ); } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy