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

org.apache.hadoop.crypto.key.KeyProviderCryptoExtension Maven / Gradle / Ivy

There is a newer version: 3.4.1
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.hadoop.crypto.key;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.List;
import java.util.ListIterator;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.google.common.base.Preconditions;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.crypto.CryptoCodec;
import org.apache.hadoop.crypto.Decryptor;
import org.apache.hadoop.crypto.Encryptor;

/**
 * A KeyProvider with Cryptographic Extensions specifically for generating
 * and decrypting encrypted encryption keys.
 * 
 */
@InterfaceAudience.Private
public class KeyProviderCryptoExtension extends
    KeyProviderExtension {

  /**
   * Designates an encrypted encryption key, or EEK.
   */
  public static final String EEK = "EEK";
  /**
   * Designates a decrypted encrypted encryption key, that is, an encryption key
   * (EK).
   */
  public static final String EK = "EK";

  /**
   * An encrypted encryption key (EEK) and related information. An EEK must be
   * decrypted using the key's encryption key before it can be used.
   */
  public static class EncryptedKeyVersion {
    private String encryptionKeyName;
    private String encryptionKeyVersionName;
    private byte[] encryptedKeyIv;
    private KeyVersion encryptedKeyVersion;

    /**
     * Create a new EncryptedKeyVersion.
     *
     * @param keyName                  Name of the encryption key used to
     *                                 encrypt the encrypted key.
     * @param encryptionKeyVersionName Version name of the encryption key used
     *                                 to encrypt the encrypted key.
     * @param encryptedKeyIv           Initialization vector of the encrypted
     *                                 key. The IV of the encryption key used to
     *                                 encrypt the encrypted key is derived from
     *                                 this IV.
     * @param encryptedKeyVersion      The encrypted encryption key version.
     */
    protected EncryptedKeyVersion(String keyName,
        String encryptionKeyVersionName, byte[] encryptedKeyIv,
        KeyVersion encryptedKeyVersion) {
      this.encryptionKeyName = keyName == null ? null : keyName.intern();
      this.encryptionKeyVersionName = encryptionKeyVersionName == null ?
          null : encryptionKeyVersionName.intern();
      this.encryptedKeyIv = encryptedKeyIv;
      this.encryptedKeyVersion = encryptedKeyVersion;
    }

    /**
     * Factory method to create a new EncryptedKeyVersion that can then be
     * passed into {@link #decryptEncryptedKey}. Note that the fields of the
     * returned EncryptedKeyVersion will only partially be populated; it is not
     * necessarily suitable for operations besides decryption.
     *
     * @param keyName Key name of the encryption key use to encrypt the
     *                encrypted key.
     * @param encryptionKeyVersionName Version name of the encryption key used
     *                                 to encrypt the encrypted key.
     * @param encryptedKeyIv           Initialization vector of the encrypted
     *                                 key. The IV of the encryption key used to
     *                                 encrypt the encrypted key is derived from
     *                                 this IV.
     * @param encryptedKeyMaterial     Key material of the encrypted key.
     * @return EncryptedKeyVersion suitable for decryption.
     */
    public static EncryptedKeyVersion createForDecryption(String keyName,
        String encryptionKeyVersionName, byte[] encryptedKeyIv,
        byte[] encryptedKeyMaterial) {
      KeyVersion encryptedKeyVersion = new KeyVersion(null, EEK,
          encryptedKeyMaterial);
      return new EncryptedKeyVersion(keyName, encryptionKeyVersionName,
          encryptedKeyIv, encryptedKeyVersion);
    }

    /**
     * @return Name of the encryption key used to encrypt the encrypted key.
     */
    public String getEncryptionKeyName() {
      return encryptionKeyName;
    }

    /**
     * @return Version name of the encryption key used to encrypt the encrypted
     * key.
     */
    public String getEncryptionKeyVersionName() {
      return encryptionKeyVersionName;
    }

    /**
     * @return Initialization vector of the encrypted key. The IV of the
     * encryption key used to encrypt the encrypted key is derived from this
     * IV.
     */
    public byte[] getEncryptedKeyIv() {
      return encryptedKeyIv;
    }

    /**
     * @return The encrypted encryption key version.
     */
    public KeyVersion getEncryptedKeyVersion() {
      return encryptedKeyVersion;
    }

    /**
     * Derive the initialization vector (IV) for the encryption key from the IV
     * of the encrypted key. This derived IV is used with the encryption key to
     * decrypt the encrypted key.
     * 

* The alternative to this is using the same IV for both the encryption key * and the encrypted key. Even a simple symmetric transformation like this * improves security by avoiding IV re-use. IVs will also be fairly unique * among different EEKs. * * @param encryptedKeyIV of the encrypted key (i.e. {@link * #getEncryptedKeyIv()}) * @return IV for the encryption key */ protected static byte[] deriveIV(byte[] encryptedKeyIV) { byte[] rIv = new byte[encryptedKeyIV.length]; // Do a simple XOR transformation to flip all the bits for (int i = 0; i < encryptedKeyIV.length; i++) { rIv[i] = (byte) (encryptedKeyIV[i] ^ 0xff); } return rIv; } } /** * CryptoExtension is a type of Extension that exposes methods to generate * EncryptedKeys and to decrypt the same. */ public interface CryptoExtension extends KeyProviderExtension.Extension { /** * Calls to this method allows the underlying KeyProvider to warm-up any * implementation specific caches used to store the Encrypted Keys. * @param keyNames Array of Key Names */ public void warmUpEncryptedKeys(String... keyNames) throws IOException; /** * Drains the Queue for the provided key. * * @param keyName the key to drain the Queue for */ public void drain(String keyName); /** * Generates a key material and encrypts it using the given key name. * The generated key material is of the same * length as the KeyVersion material of the latest key version * of the key and is encrypted using the same cipher. *

* NOTE: The generated key is not stored by the KeyProvider * * @param encryptionKeyName * The latest KeyVersion of this key's material will be encrypted. * @return EncryptedKeyVersion with the generated key material, the version * name is 'EEK' (for Encrypted Encryption Key) * @throws IOException * thrown if the key material could not be generated * @throws GeneralSecurityException * thrown if the key material could not be encrypted because of a * cryptographic issue. */ public EncryptedKeyVersion generateEncryptedKey( String encryptionKeyName) throws IOException, GeneralSecurityException; /** * Decrypts an encrypted byte[] key material using the given key version * name and initialization vector. * * @param encryptedKeyVersion * contains keyVersionName and IV to decrypt the encrypted key * material * @return a KeyVersion with the decrypted key material, the version name is * 'EK' (For Encryption Key) * @throws IOException * thrown if the key material could not be decrypted * @throws GeneralSecurityException * thrown if the key material could not be decrypted because of a * cryptographic issue. */ public KeyVersion decryptEncryptedKey( EncryptedKeyVersion encryptedKeyVersion) throws IOException, GeneralSecurityException; /** * Re-encrypts an encrypted key version, using its initialization vector * and key material, but with the latest key version name of its key name * in the key provider. *

* If the latest key version name in the provider is the * same as the one encrypted the passed-in encrypted key version, the same * encrypted key version is returned. *

* NOTE: The generated key is not stored by the KeyProvider * * @param ekv The EncryptedKeyVersion containing keyVersionName and IV. * @return The re-encrypted EncryptedKeyVersion. * @throws IOException If the key material could not be re-encrypted. * @throws GeneralSecurityException If the key material could not be * re-encrypted because of a cryptographic issue. */ EncryptedKeyVersion reencryptEncryptedKey(EncryptedKeyVersion ekv) throws IOException, GeneralSecurityException; /** * Batched version of {@link #reencryptEncryptedKey(EncryptedKeyVersion)}. *

* For each encrypted key version, re-encrypts an encrypted key version, * using its initialization vector and key material, but with the latest * key version name of its key name. If the latest key version name in the * provider is the same as the one encrypted the passed-in encrypted key * version, the same encrypted key version is returned. *

* NOTE: The generated key is not stored by the KeyProvider * * @param ekvs List containing the EncryptedKeyVersion's * @throws IOException If any EncryptedKeyVersion could not be re-encrypted * @throws GeneralSecurityException If any EncryptedKeyVersion could not be * re-encrypted because of a cryptographic issue. */ void reencryptEncryptedKeys(List ekvs) throws IOException, GeneralSecurityException; } private static class DefaultCryptoExtension implements CryptoExtension { private final KeyProvider keyProvider; private static final ThreadLocal RANDOM = new ThreadLocal() { @Override protected SecureRandom initialValue() { return new SecureRandom(); } }; private DefaultCryptoExtension(KeyProvider keyProvider) { this.keyProvider = keyProvider; } @Override public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName) throws IOException, GeneralSecurityException { // Fetch the encryption key KeyVersion encryptionKey = keyProvider.getCurrentKey(encryptionKeyName); Preconditions.checkNotNull(encryptionKey, "No KeyVersion exists for key '%s' ", encryptionKeyName); // Generate random bytes for new key and IV CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf()); try { final byte[] newKey = new byte[encryptionKey.getMaterial().length]; cc.generateSecureRandom(newKey); final byte[] iv = new byte[cc.getCipherSuite().getAlgorithmBlockSize()]; cc.generateSecureRandom(iv); Encryptor encryptor = cc.createEncryptor(); return generateEncryptedKey(encryptor, encryptionKey, newKey, iv); } finally { cc.close(); } } private EncryptedKeyVersion generateEncryptedKey(final Encryptor encryptor, final KeyVersion encryptionKey, final byte[] key, final byte[] iv) throws IOException, GeneralSecurityException { // Encryption key IV is derived from new key's IV final byte[] encryptionIV = EncryptedKeyVersion.deriveIV(iv); encryptor.init(encryptionKey.getMaterial(), encryptionIV); final int keyLen = key.length; ByteBuffer bbIn = ByteBuffer.allocateDirect(keyLen); ByteBuffer bbOut = ByteBuffer.allocateDirect(keyLen); bbIn.put(key); bbIn.flip(); encryptor.encrypt(bbIn, bbOut); bbOut.flip(); byte[] encryptedKey = new byte[keyLen]; bbOut.get(encryptedKey); return new EncryptedKeyVersion(encryptionKey.getName(), encryptionKey.getVersionName(), iv, new KeyVersion(encryptionKey.getName(), EEK, encryptedKey)); } @Override public EncryptedKeyVersion reencryptEncryptedKey(EncryptedKeyVersion ekv) throws IOException, GeneralSecurityException { final String ekName = ekv.getEncryptionKeyName(); final KeyVersion ekNow = keyProvider.getCurrentKey(ekName); Preconditions .checkNotNull(ekNow, "KeyVersion name '%s' does not exist", ekName); Preconditions.checkArgument(ekv.getEncryptedKeyVersion().getVersionName() .equals(KeyProviderCryptoExtension.EEK), "encryptedKey version name must be '%s', but found '%s'", KeyProviderCryptoExtension.EEK, ekv.getEncryptedKeyVersion().getVersionName()); if (ekv.getEncryptedKeyVersion().equals(ekNow)) { // no-op if same key version return ekv; } final KeyVersion dek = decryptEncryptedKey(ekv); final CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf()); try { final Encryptor encryptor = cc.createEncryptor(); return generateEncryptedKey(encryptor, ekNow, dek.getMaterial(), ekv.getEncryptedKeyIv()); } finally { cc.close(); } } @Override public void reencryptEncryptedKeys(List ekvs) throws IOException, GeneralSecurityException { Preconditions.checkNotNull(ekvs, "Input list is null"); KeyVersion ekNow = null; Decryptor decryptor = null; Encryptor encryptor = null; try (CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf())) { decryptor = cc.createDecryptor(); encryptor = cc.createEncryptor(); ListIterator iter = ekvs.listIterator(); while (iter.hasNext()) { final EncryptedKeyVersion ekv = iter.next(); Preconditions.checkNotNull(ekv, "EncryptedKeyVersion is null"); final String ekName = ekv.getEncryptionKeyName(); Preconditions.checkNotNull(ekName, "Key name is null"); Preconditions.checkNotNull(ekv.getEncryptedKeyVersion(), "EncryptedKeyVersion is null"); Preconditions.checkArgument( ekv.getEncryptedKeyVersion().getVersionName() .equals(KeyProviderCryptoExtension.EEK), "encryptedKey version name must be '%s', but found '%s'", KeyProviderCryptoExtension.EEK, ekv.getEncryptedKeyVersion().getVersionName()); if (ekNow == null) { ekNow = keyProvider.getCurrentKey(ekName); Preconditions .checkNotNull(ekNow, "Key name '%s' does not exist", ekName); } else { Preconditions.checkArgument(ekNow.getName().equals(ekName), "All keys must have the same key name. Expected '%s' " + "but found '%s'", ekNow.getName(), ekName); } final String encryptionKeyVersionName = ekv.getEncryptionKeyVersionName(); final KeyVersion encryptionKey = keyProvider.getKeyVersion(encryptionKeyVersionName); Preconditions.checkNotNull(encryptionKey, "KeyVersion name '%s' does not exist", encryptionKeyVersionName); if (encryptionKey.equals(ekNow)) { // no-op if same key version continue; } final KeyVersion ek = decryptEncryptedKey(decryptor, encryptionKey, ekv); iter.set(generateEncryptedKey(encryptor, ekNow, ek.getMaterial(), ekv.getEncryptedKeyIv())); } } } private KeyVersion decryptEncryptedKey(final Decryptor decryptor, final KeyVersion encryptionKey, final EncryptedKeyVersion encryptedKeyVersion) throws IOException, GeneralSecurityException { // Encryption key IV is determined from encrypted key's IV final byte[] encryptionIV = EncryptedKeyVersion.deriveIV(encryptedKeyVersion.getEncryptedKeyIv()); decryptor.init(encryptionKey.getMaterial(), encryptionIV); final KeyVersion encryptedKV = encryptedKeyVersion.getEncryptedKeyVersion(); int keyLen = encryptedKV.getMaterial().length; ByteBuffer bbIn = ByteBuffer.allocateDirect(keyLen); ByteBuffer bbOut = ByteBuffer.allocateDirect(keyLen); bbIn.put(encryptedKV.getMaterial()); bbIn.flip(); decryptor.decrypt(bbIn, bbOut); bbOut.flip(); byte[] decryptedKey = new byte[keyLen]; bbOut.get(decryptedKey); return new KeyVersion(encryptionKey.getName(), EK, decryptedKey); } @Override public KeyVersion decryptEncryptedKey( EncryptedKeyVersion encryptedKeyVersion) throws IOException, GeneralSecurityException { // Fetch the encryption key material final String encryptionKeyVersionName = encryptedKeyVersion.getEncryptionKeyVersionName(); final KeyVersion encryptionKey = keyProvider.getKeyVersion(encryptionKeyVersionName); Preconditions .checkNotNull(encryptionKey, "KeyVersion name '%s' does not exist", encryptionKeyVersionName); Preconditions.checkArgument( encryptedKeyVersion.getEncryptedKeyVersion().getVersionName() .equals(KeyProviderCryptoExtension.EEK), "encryptedKey version name must be '%s', but found '%s'", KeyProviderCryptoExtension.EEK, encryptedKeyVersion.getEncryptedKeyVersion().getVersionName()); try (CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf())) { final Decryptor decryptor = cc.createDecryptor(); return decryptEncryptedKey(decryptor, encryptionKey, encryptedKeyVersion); } } @Override public void warmUpEncryptedKeys(String... keyNames) throws IOException { // NO-OP since the default version does not cache any keys } @Override public void drain(String keyName) { // NO-OP since the default version does not cache any keys } } /** * This constructor is to be used by sub classes that provide * delegating/proxying functionality to the {@link KeyProviderCryptoExtension} * @param keyProvider * @param extension */ protected KeyProviderCryptoExtension(KeyProvider keyProvider, CryptoExtension extension) { super(keyProvider, extension); } /** * Notifies the Underlying CryptoExtension implementation to warm up any * implementation specific caches for the specified KeyVersions * @param keyNames Arrays of key Names */ public void warmUpEncryptedKeys(String... keyNames) throws IOException { getExtension().warmUpEncryptedKeys(keyNames); } /** * Generates a key material and encrypts it using the given key version name * and initialization vector. The generated key material is of the same * length as the KeyVersion material and is encrypted using the * same cipher. *

* NOTE: The generated key is not stored by the KeyProvider * * @param encryptionKeyName The latest KeyVersion of this key's material will * be encrypted. * @return EncryptedKeyVersion with the generated key material, the version * name is 'EEK' (for Encrypted Encryption Key) * @throws IOException thrown if the key material could not be generated * @throws GeneralSecurityException thrown if the key material could not be * encrypted because of a cryptographic issue. */ public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName) throws IOException, GeneralSecurityException { return getExtension().generateEncryptedKey(encryptionKeyName); } /** * Decrypts an encrypted byte[] key material using the given a key version * name and initialization vector. * * @param encryptedKey contains keyVersionName and IV to decrypt the encrypted * key material * @return a KeyVersion with the decrypted key material, the version name is * 'EK' (For Encryption Key) * @throws IOException thrown if the key material could not be decrypted * @throws GeneralSecurityException thrown if the key material could not be * decrypted because of a cryptographic issue. */ public KeyVersion decryptEncryptedKey(EncryptedKeyVersion encryptedKey) throws IOException, GeneralSecurityException { return getExtension().decryptEncryptedKey(encryptedKey); } /** * Re-encrypts an encrypted key version, using its initialization vector * and key material, but with the latest key version name of its key name * in the key provider. *

* If the latest key version name in the provider is the * same as the one encrypted the passed-in encrypted key version, the same * encrypted key version is returned. *

* NOTE: The generated key is not stored by the KeyProvider * * @param ekv The EncryptedKeyVersion containing keyVersionName and IV. * @return The re-encrypted EncryptedKeyVersion. * @throws IOException If the key material could not be re-encrypted * @throws GeneralSecurityException If the key material could not be * re-encrypted because of a cryptographic issue. */ public EncryptedKeyVersion reencryptEncryptedKey(EncryptedKeyVersion ekv) throws IOException, GeneralSecurityException { return getExtension().reencryptEncryptedKey(ekv); } /** * Calls {@link CryptoExtension#drain(String)} for the given key name on the * underlying {@link CryptoExtension}. * * @param keyName */ public void drain(String keyName) { getExtension().drain(keyName); } /** * Batched version of {@link #reencryptEncryptedKey(EncryptedKeyVersion)}. *

* For each encrypted key version, re-encrypts an encrypted key version, * using its initialization vector and key material, but with the latest * key version name of its key name. If the latest key version name in the * provider is the same as the one encrypted the passed-in encrypted key * version, the same encrypted key version is returned. *

* NOTE: The generated key is not stored by the KeyProvider * * @param ekvs List containing the EncryptedKeyVersion's * @return The re-encrypted EncryptedKeyVersion's, in the same order. * @throws IOException If any EncryptedKeyVersion could not be re-encrypted * @throws GeneralSecurityException If any EncryptedKeyVersion could not be * re-encrypted because of a cryptographic issue. */ public void reencryptEncryptedKeys(List ekvs) throws IOException, GeneralSecurityException { getExtension().reencryptEncryptedKeys(ekvs); } /** * Creates a KeyProviderCryptoExtension using a given * {@link KeyProvider}. *

* If the given KeyProvider implements the * {@link CryptoExtension} interface the KeyProvider itself * will provide the extension functionality. * If the given KeyProvider implements the * {@link KeyProviderExtension} interface and the KeyProvider being * extended by the KeyProvider implements the * {@link CryptoExtension} interface, the KeyProvider being extended will * provide the extension functionality. Otherwise, a default extension * implementation will be used. * * @param keyProvider KeyProvider to use to create the * KeyProviderCryptoExtension extension. * @return a KeyProviderCryptoExtension instance using the * given KeyProvider. */ public static KeyProviderCryptoExtension createKeyProviderCryptoExtension( KeyProvider keyProvider) { CryptoExtension cryptoExtension = null; if (keyProvider instanceof CryptoExtension) { cryptoExtension = (CryptoExtension) keyProvider; } else if (keyProvider instanceof KeyProviderExtension && ((KeyProviderExtension)keyProvider).getKeyProvider() instanceof KeyProviderCryptoExtension.CryptoExtension) { KeyProviderExtension keyProviderExtension = (KeyProviderExtension)keyProvider; cryptoExtension = (CryptoExtension)keyProviderExtension.getKeyProvider(); } else { cryptoExtension = new DefaultCryptoExtension(keyProvider); } return new KeyProviderCryptoExtension(keyProvider, cryptoExtension); } @Override public void close() throws IOException { KeyProvider provider = getKeyProvider(); if (provider != null && provider != this) { provider.close(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy