|
@@ -21,52 +21,117 @@ package org.apache.hadoop.crypto.key;
|
|
|
import java.io.IOException;
|
|
|
import java.security.GeneralSecurityException;
|
|
|
import java.security.SecureRandom;
|
|
|
-
|
|
|
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;
|
|
|
|
|
|
/**
|
|
|
- * A KeyProvider with Cytographic Extensions specifically for generating
|
|
|
- * Encrypted Keys as well as decrypting them
|
|
|
+ * A KeyProvider with Cryptographic Extensions specifically for generating
|
|
|
+ * and decrypting encrypted encryption keys.
|
|
|
*
|
|
|
*/
|
|
|
+@InterfaceAudience.Private
|
|
|
public class KeyProviderCryptoExtension extends
|
|
|
KeyProviderExtension<KeyProviderCryptoExtension.CryptoExtension> {
|
|
|
|
|
|
- protected static final String EEK = "EEK";
|
|
|
- protected static final String EK = "EK";
|
|
|
+ /**
|
|
|
+ * 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";
|
|
|
|
|
|
/**
|
|
|
- * This is a holder class whose instance contains the keyVersionName, iv
|
|
|
- * used to generate the encrypted Key and the encrypted KeyVersion
|
|
|
+ * 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 keyVersionName;
|
|
|
- private byte[] iv;
|
|
|
- private KeyVersion encryptedKey;
|
|
|
-
|
|
|
- protected EncryptedKeyVersion(String keyVersionName, byte[] iv,
|
|
|
- KeyVersion encryptedKey) {
|
|
|
- this.keyVersionName = keyVersionName;
|
|
|
- this.iv = iv;
|
|
|
- this.encryptedKey = encryptedKey;
|
|
|
+ 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;
|
|
|
+ this.encryptionKeyVersionName = encryptionKeyVersionName;
|
|
|
+ this.encryptedKeyIv = encryptedKeyIv;
|
|
|
+ this.encryptedKeyVersion = encryptedKeyVersion;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return Name of the encryption key used to encrypt the encrypted key.
|
|
|
+ */
|
|
|
+ public String getEncryptionKeyName() {
|
|
|
+ return encryptionKeyName;
|
|
|
}
|
|
|
|
|
|
- public String getKeyVersionName() {
|
|
|
- return keyVersionName;
|
|
|
+ /**
|
|
|
+ * @return Version name of the encryption key used to encrypt the encrypted
|
|
|
+ * key.
|
|
|
+ */
|
|
|
+ public String getEncryptionKeyVersionName() {
|
|
|
+ return encryptionKeyVersionName;
|
|
|
}
|
|
|
|
|
|
- public byte[] getIv() {
|
|
|
- return iv;
|
|
|
+ /**
|
|
|
+ * @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;
|
|
|
}
|
|
|
|
|
|
- public KeyVersion getEncryptedKey() {
|
|
|
- return encryptedKey;
|
|
|
+ /**
|
|
|
+ * @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.
|
|
|
+ * <p/>
|
|
|
+ * 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;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -75,17 +140,24 @@ public class KeyProviderCryptoExtension extends
|
|
|
*/
|
|
|
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;
|
|
|
+
|
|
|
/**
|
|
|
* 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 <code>KeyVersion</code> material and is encrypted using the
|
|
|
- * same cipher.
|
|
|
+ * length as the <code>KeyVersion</code> material of the latest key version
|
|
|
+ * of the key and is encrypted using the same cipher.
|
|
|
* <p/>
|
|
|
* NOTE: The generated key is not stored by the <code>KeyProvider</code>
|
|
|
*
|
|
|
- * @param encryptionKeyVersion
|
|
|
- * a KeyVersion object containing the keyVersion name and material
|
|
|
- * to encrypt.
|
|
|
+ * @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
|
|
@@ -95,7 +167,7 @@ public class KeyProviderCryptoExtension extends
|
|
|
* cryptographic issue.
|
|
|
*/
|
|
|
public EncryptedKeyVersion generateEncryptedKey(
|
|
|
- KeyVersion encryptionKeyVersion) throws IOException,
|
|
|
+ String encryptionKeyName) throws IOException,
|
|
|
GeneralSecurityException;
|
|
|
|
|
|
/**
|
|
@@ -126,62 +198,87 @@ public class KeyProviderCryptoExtension extends
|
|
|
this.keyProvider = keyProvider;
|
|
|
}
|
|
|
|
|
|
- // the IV used to encrypt a EK typically will be the same IV used to
|
|
|
- // encrypt data with the EK. To avoid any chance of weakening the
|
|
|
- // encryption because the same IV is used, we simply XOR the IV thus we
|
|
|
- // are not using the same IV for 2 different encryptions (even if they
|
|
|
- // are done using different keys)
|
|
|
- private byte[] flipIV(byte[] iv) {
|
|
|
- byte[] rIv = new byte[iv.length];
|
|
|
- for (int i = 0; i < iv.length; i++) {
|
|
|
- rIv[i] = (byte) (iv[i] ^ 0xff);
|
|
|
- }
|
|
|
- return rIv;
|
|
|
- }
|
|
|
-
|
|
|
@Override
|
|
|
- public EncryptedKeyVersion generateEncryptedKey(KeyVersion keyVersion)
|
|
|
+ public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName)
|
|
|
throws IOException, GeneralSecurityException {
|
|
|
- KeyVersion keyVer =
|
|
|
- keyProvider.getKeyVersion(keyVersion.getVersionName());
|
|
|
- Preconditions.checkNotNull(keyVer, "KeyVersion name '%s' does not exist",
|
|
|
- keyVersion.getVersionName());
|
|
|
- byte[] newKey = new byte[keyVer.getMaterial().length];
|
|
|
- SecureRandom.getInstance("SHA1PRNG").nextBytes(newKey);
|
|
|
+ // 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
|
|
|
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
|
|
- byte[] iv = SecureRandom.getSeed(cipher.getBlockSize());
|
|
|
- cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyVer.getMaterial(),
|
|
|
- "AES"), new IvParameterSpec(flipIV(iv)));
|
|
|
- byte[] ek = cipher.doFinal(newKey);
|
|
|
- return new EncryptedKeyVersion(keyVersion.getVersionName(), iv,
|
|
|
- new KeyVersion(keyVer.getName(), EEK, ek));
|
|
|
+ SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
|
|
+ final byte[] newKey = new byte[encryptionKey.getMaterial().length];
|
|
|
+ random.nextBytes(newKey);
|
|
|
+ final byte[] iv = random.generateSeed(cipher.getBlockSize());
|
|
|
+ // Encryption key IV is derived from new key's IV
|
|
|
+ final byte[] encryptionIV = EncryptedKeyVersion.deriveIV(iv);
|
|
|
+ // Encrypt the new key
|
|
|
+ cipher.init(Cipher.ENCRYPT_MODE,
|
|
|
+ new SecretKeySpec(encryptionKey.getMaterial(), "AES"),
|
|
|
+ new IvParameterSpec(encryptionIV));
|
|
|
+ final byte[] encryptedKey = cipher.doFinal(newKey);
|
|
|
+ return new EncryptedKeyVersion(encryptionKeyName,
|
|
|
+ encryptionKey.getVersionName(), iv,
|
|
|
+ new KeyVersion(encryptionKey.getName(), EEK, encryptedKey));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public KeyVersion decryptEncryptedKey(
|
|
|
EncryptedKeyVersion encryptedKeyVersion) throws IOException,
|
|
|
GeneralSecurityException {
|
|
|
- KeyVersion keyVer =
|
|
|
- keyProvider.getKeyVersion(encryptedKeyVersion.getKeyVersionName());
|
|
|
- Preconditions.checkNotNull(keyVer, "KeyVersion name '%s' does not exist",
|
|
|
- encryptedKeyVersion.getKeyVersionName());
|
|
|
- KeyVersion keyVersion = encryptedKeyVersion.getEncryptedKey();
|
|
|
+ // 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);
|
|
|
+ final byte[] encryptionKeyMaterial = encryptionKey.getMaterial();
|
|
|
+ // Encryption key IV is determined from encrypted key's IV
|
|
|
+ final byte[] encryptionIV =
|
|
|
+ EncryptedKeyVersion.deriveIV(encryptedKeyVersion.getEncryptedKeyIv());
|
|
|
+ // Init the cipher with encryption key parameters
|
|
|
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
|
|
cipher.init(Cipher.DECRYPT_MODE,
|
|
|
- new SecretKeySpec(keyVersion.getMaterial(), "AES"),
|
|
|
- new IvParameterSpec(flipIV(encryptedKeyVersion.getIv())));
|
|
|
- byte[] ek =
|
|
|
- cipher.doFinal(encryptedKeyVersion.getEncryptedKey().getMaterial());
|
|
|
- return new KeyVersion(keyVer.getName(), EK, ek);
|
|
|
+ new SecretKeySpec(encryptionKeyMaterial, "AES"),
|
|
|
+ new IvParameterSpec(encryptionIV));
|
|
|
+ // Decrypt the encrypted key
|
|
|
+ final KeyVersion encryptedKV =
|
|
|
+ encryptedKeyVersion.getEncryptedKeyVersion();
|
|
|
+ final byte[] decryptedKey = cipher.doFinal(encryptedKV.getMaterial());
|
|
|
+ return new KeyVersion(encryptionKey.getName(), EK, decryptedKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void warmUpEncryptedKeys(String... keyNames)
|
|
|
+ throws IOException {
|
|
|
+ // NO-OP since the default version does not cache any keys
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
- private KeyProviderCryptoExtension(KeyProvider keyProvider,
|
|
|
+ /**
|
|
|
+ * 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
|
|
@@ -190,18 +287,18 @@ public class KeyProviderCryptoExtension extends
|
|
|
* <p/>
|
|
|
* NOTE: The generated key is not stored by the <code>KeyProvider</code>
|
|
|
*
|
|
|
- * @param encryptionKey a KeyVersion object containing the keyVersion name and
|
|
|
- * material to encrypt.
|
|
|
+ * @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(KeyVersion encryptionKey)
|
|
|
+ public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName)
|
|
|
throws IOException,
|
|
|
GeneralSecurityException {
|
|
|
- return getExtension().generateEncryptedKey(encryptionKey);
|
|
|
+ return getExtension().generateEncryptedKey(encryptionKeyName);
|
|
|
}
|
|
|
|
|
|
/**
|