This is an automated email from the ASF dual-hosted git repository.

busbey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/master by this push:
     new 6a5c928  HBASE-25181 Add options for disabling column family 
encryption and choosing hash algorithm for wrapped encryption keys.
6a5c928 is described below

commit 6a5c9285390c0ae66adfb07f9dafbad69f89fa32
Author: Mate Szalay-Beko <[email protected]>
AuthorDate: Tue Oct 13 11:40:01 2020 +0200

    HBASE-25181 Add options for disabling column family encryption and choosing 
hash algorithm for wrapped encryption keys.
    
    Prior to this patch hbase always used the MD5 hash algorithm to store a 
hash for encryption keys.
    This hash is needed to verify the secret key of the subject. (e.g. making
    sure that the same secrey key is used during encrypted HFile read and 
write).
    The MD5 algorithm is considered weak, and can not be used in some
    (e.g. FIPS compliant) clusters.
    
    In this patch we:
    - add a config parameter to globally enable/disable column family 
encryption (def enabled)
    - introduce a backward compatible way of specifying the hash algorithm.
      This enable us to use newer and more secure hash algorithms like SHA-384
      or SHA-512 (which are FIPS compliant).
    - add a config parameter to fail if an hfile is encountered that uses a
      different hash algorithm than the one currently configured to ease 
validation after
      migrating key hash algorithms (def disabled)
    
    Closes #2539
    
    Signed-off-by: Sean Busbey <[email protected]>
    Signed-off-by: Esteban Gutierrez <[email protected]>
---
 .../hadoop/hbase/security/EncryptionUtil.java      |  34 +++--
 .../hadoop/hbase/security/TestEncryptionUtil.java  | 135 +++++++++++++++--
 .../org/apache/hadoop/hbase/io/crypto/Context.java |   4 +-
 .../apache/hadoop/hbase/io/crypto/Encryption.java  | 159 ++++++++++++++-------
 .../src/main/protobuf/client/Encryption.proto      |   1 +
 .../apache/hadoop/hbase/util/EncryptionTest.java   |  15 +-
 .../hbase/regionserver/TestEncryptionDisabled.java | 110 ++++++++++++++
 .../hadoop/hbase/util/TestEncryptionTest.java      |  30 ++++
 8 files changed, 406 insertions(+), 82 deletions(-)

diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java
 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java
index 72fd0c8..ddd4e52 100644
--- 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java
+++ 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java
@@ -24,24 +24,23 @@ import java.security.Key;
 import java.security.KeyException;
 import java.security.SecureRandom;
 import java.util.Properties;
-
 import javax.crypto.spec.SecretKeySpec;
-
 import org.apache.commons.crypto.cipher.CryptoCipherFactory;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
+import org.apache.hadoop.hbase.io.crypto.Cipher;
+import org.apache.hadoop.hbase.io.crypto.Encryption;
+import org.apache.hadoop.hbase.io.crypto.aes.CryptoAES;
+import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.yetus.audience.InterfaceStability;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
-import org.apache.hadoop.hbase.io.crypto.Cipher;
-import org.apache.hadoop.hbase.io.crypto.Encryption;
+
 import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.EncryptionProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos;
-import org.apache.hadoop.hbase.io.crypto.aes.CryptoAES;
-import org.apache.hadoop.hbase.util.Bytes;
 
 /**
  * Some static utility methods for encryption uses in hbase-client.
@@ -102,7 +101,9 @@ public final class EncryptionUtil {
     }
     byte[] keyBytes = key.getEncoded();
     builder.setLength(keyBytes.length);
-    
builder.setHash(UnsafeByteOperations.unsafeWrap(Encryption.hash128(keyBytes)));
+    builder.setHashAlgorithm(Encryption.getConfiguredHashAlgorithm(conf));
+    builder.setHash(
+      UnsafeByteOperations.unsafeWrap(Encryption.computeCryptoKeyHash(conf, 
keyBytes)));
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     Encryption.encryptWithSubjectKey(out, new ByteArrayInputStream(keyBytes), 
subject,
       conf, cipher, iv);
@@ -138,13 +139,24 @@ public final class EncryptionUtil {
 
   private static Key getUnwrapKey(Configuration conf, String subject,
       EncryptionProtos.WrappedKey wrappedKey, Cipher cipher) throws 
IOException, KeyException {
+    String configuredHashAlgorithm = 
Encryption.getConfiguredHashAlgorithm(conf);
+    String wrappedHashAlgorithm = wrappedKey.getHashAlgorithm().trim();
+    if(!configuredHashAlgorithm.equalsIgnoreCase(wrappedHashAlgorithm)) {
+      String msg = String.format("Unexpected encryption key hash algorithm: %s 
(expecting: %s)",
+        wrappedHashAlgorithm, configuredHashAlgorithm);
+      if(Encryption.failOnHashAlgorithmMismatch(conf)) {
+        throw new KeyException(msg);
+      }
+      LOG.debug(msg);
+    }
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     byte[] iv = wrappedKey.hasIv() ? wrappedKey.getIv().toByteArray() : null;
     Encryption.decryptWithSubjectKey(out, wrappedKey.getData().newInput(),
       wrappedKey.getLength(), subject, conf, cipher, iv);
     byte[] keyBytes = out.toByteArray();
     if (wrappedKey.hasHash()) {
-      if (!Bytes.equals(wrappedKey.getHash().toByteArray(), 
Encryption.hash128(keyBytes))) {
+      if (!Bytes.equals(wrappedKey.getHash().toByteArray(),
+                        Encryption.hashWithAlg(wrappedHashAlgorithm, 
keyBytes))) {
         throw new KeyException("Key was not successfully unwrapped");
       }
     }
@@ -186,6 +198,10 @@ public final class EncryptionUtil {
     Encryption.Context cryptoContext = Encryption.Context.NONE;
     String cipherName = family.getEncryptionType();
     if (cipherName != null) {
+      if(!Encryption.isEncryptionEnabled(conf)) {
+        throw new RuntimeException("Encryption for family '" + 
family.getNameAsString()
+          + "' configured with type '" + cipherName + "' but the encryption 
feature is disabled");
+      }
       Cipher cipher;
       Key key;
       byte[] keyBytes = family.getEncryptionKey();
diff --git 
a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java
 
b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java
index 59bebcf..9275dc9 100644
--- 
a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java
+++ 
b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java
@@ -28,6 +28,7 @@ import javax.crypto.spec.SecretKeySpec;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.io.crypto.Encryption;
 import org.apache.hadoop.hbase.io.crypto.KeyProviderForTesting;
 import org.apache.hadoop.hbase.io.crypto.aes.AES;
 import org.apache.hadoop.hbase.testclassification.ClientTests;
@@ -40,6 +41,9 @@ import org.junit.experimental.categories.Category;
 @Category({ClientTests.class, SmallTests.class})
 public class TestEncryptionUtil {
 
+  private static final String INVALID_HASH_ALG = 
"this-hash-algorithm-not-exists hopefully... :)";
+  private static final String DEFAULT_HASH_ALGORITHM = "use-default";
+
   @ClassRule
   public static final HBaseClassTestRule CLASS_RULE =
       HBaseClassTestRule.forClass(TestEncryptionUtil.class);
@@ -49,16 +53,108 @@ public class TestEncryptionUtil {
   // untested.  Not ideal!
 
   @Test
-  public void testKeyWrapping() throws Exception {
+  public void testKeyWrappingUsingHashAlgDefault() throws Exception {
+    testKeyWrapping(DEFAULT_HASH_ALGORITHM);
+  }
+
+  @Test
+  public void testKeyWrappingUsingHashAlgMD5() throws Exception {
+    testKeyWrapping("MD5");
+  }
+
+  @Test
+  public void testKeyWrappingUsingHashAlgSHA256() throws Exception {
+    testKeyWrapping("SHA-256");
+  }
+
+  @Test
+  public void testKeyWrappingUsingHashAlgSHA384() throws Exception {
+    testKeyWrapping("SHA-384");
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testKeyWrappingWithInvalidHashAlg() throws Exception {
+    testKeyWrapping(INVALID_HASH_ALG);
+  }
+
+  @Test
+  public void testWALKeyWrappingUsingHashAlgDefault() throws Exception {
+    testWALKeyWrapping(DEFAULT_HASH_ALGORITHM);
+  }
+
+  @Test
+  public void testWALKeyWrappingUsingHashAlgMD5() throws Exception {
+    testWALKeyWrapping("MD5");
+  }
+
+  @Test
+  public void testWALKeyWrappingUsingHashAlgSHA256() throws Exception {
+    testWALKeyWrapping("SHA-256");
+  }
+
+  @Test
+  public void testWALKeyWrappingUsingHashAlgSHA384() throws Exception {
+    testWALKeyWrapping("SHA-384");
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testWALKeyWrappingWithInvalidHashAlg() throws Exception {
+    testWALKeyWrapping(INVALID_HASH_ALG);
+  }
+
+  @Test(expected = KeyException.class)
+  public void testWALKeyWrappingWithIncorrectKey() throws Exception {
+    // set up the key provider for testing to resolve a key for our test 
subject
+    Configuration conf = new Configuration(); // we don't need 
HBaseConfiguration for this
+    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, 
KeyProviderForTesting.class.getName());
+
+    // generate a test key
+    byte[] keyBytes = new byte[AES.KEY_LENGTH];
+    new SecureRandom().nextBytes(keyBytes);
+    String algorithm = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, 
HConstants.CIPHER_AES);
+    Key key = new SecretKeySpec(keyBytes, algorithm);
+
+    // wrap the test key
+    byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key);
+    assertNotNull(wrappedKeyBytes);
+
+    // unwrap with an incorrect key
+    EncryptionUtil.unwrapWALKey(conf, "other", wrappedKeyBytes);
+  }
+
+  @Test(expected = KeyException.class)
+  public void testHashAlgorithmMismatchWhenFailExpected() throws Exception {
+    Configuration conf = new Configuration(); // we don't need 
HBaseConfiguration for this
+    conf.setBoolean(Encryption.CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY, 
true);
+    testKeyWrappingWithMismatchingAlgorithms(conf);
+  }
+
+  @Test
+  public void testHashAlgorithmMismatchWhenFailNotExpected() throws Exception {
+    Configuration conf = new Configuration(); // we don't need 
HBaseConfiguration for this
+    conf.setBoolean(Encryption.CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY, 
false);
+    testKeyWrappingWithMismatchingAlgorithms(conf);
+  }
+
+  @Test
+  public void testHashAlgorithmMismatchShouldNotFailWithDefaultConfig() throws 
Exception {
+    Configuration conf = new Configuration(); // we don't need 
HBaseConfiguration for this
+    testKeyWrappingWithMismatchingAlgorithms(conf);
+  }
+
+  private void testKeyWrapping(String hashAlgorithm) throws Exception {
     // set up the key provider for testing to resolve a key for our test 
subject
     Configuration conf = new Configuration(); // we don't need 
HBaseConfiguration for this
     conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, 
KeyProviderForTesting.class.getName());
+    if(!hashAlgorithm.equals(DEFAULT_HASH_ALGORITHM)) {
+      conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, hashAlgorithm);
+    }
 
     // generate a test key
     byte[] keyBytes = new byte[AES.KEY_LENGTH];
     new SecureRandom().nextBytes(keyBytes);
     String algorithm =
-        conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, 
HConstants.CIPHER_AES);
+      conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, 
HConstants.CIPHER_AES);
     Key key = new SecretKeySpec(keyBytes, algorithm);
 
     // wrap the test key
@@ -72,7 +168,7 @@ public class TestEncryptionUtil {
     assertTrue(unwrappedKey instanceof SecretKeySpec);
     // did we get back what we wrapped?
     assertTrue("Unwrapped key bytes do not match original",
-      Bytes.equals(keyBytes, unwrappedKey.getEncoded()));
+               Bytes.equals(keyBytes, unwrappedKey.getEncoded()));
 
     // unwrap with an incorrect key
     try {
@@ -83,11 +179,13 @@ public class TestEncryptionUtil {
     }
   }
 
-  @Test
-  public void testWALKeyWrapping() throws Exception {
+  private void testWALKeyWrapping(String hashAlgorithm) throws Exception {
     // set up the key provider for testing to resolve a key for our test 
subject
     Configuration conf = new Configuration(); // we don't need 
HBaseConfiguration for this
     conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, 
KeyProviderForTesting.class.getName());
+    if(!hashAlgorithm.equals(DEFAULT_HASH_ALGORITHM)) {
+      conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, hashAlgorithm);
+    }
 
     // generate a test key
     byte[] keyBytes = new byte[AES.KEY_LENGTH];
@@ -106,26 +204,37 @@ public class TestEncryptionUtil {
     assertTrue(unwrappedKey instanceof SecretKeySpec);
     // did we get back what we wrapped?
     assertTrue("Unwrapped key bytes do not match original",
-      Bytes.equals(keyBytes, unwrappedKey.getEncoded()));
+               Bytes.equals(keyBytes, unwrappedKey.getEncoded()));
   }
 
-  @Test(expected = KeyException.class)
-  public void testWALKeyWrappingWithIncorrectKey() throws Exception {
-    // set up the key provider for testing to resolve a key for our test 
subject
-    Configuration conf = new Configuration(); // we don't need 
HBaseConfiguration for this
+  private void testKeyWrappingWithMismatchingAlgorithms(Configuration conf) 
throws Exception {
+    // we use MD5 to hash the encryption key during wrapping
     conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, 
KeyProviderForTesting.class.getName());
+    conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, "MD5");
 
     // generate a test key
     byte[] keyBytes = new byte[AES.KEY_LENGTH];
     new SecureRandom().nextBytes(keyBytes);
-    String algorithm = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, 
HConstants.CIPHER_AES);
+    String algorithm =
+      conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, 
HConstants.CIPHER_AES);
     Key key = new SecretKeySpec(keyBytes, algorithm);
 
     // wrap the test key
     byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key);
     assertNotNull(wrappedKeyBytes);
 
-    // unwrap with an incorrect key
-    EncryptionUtil.unwrapWALKey(conf, "other", wrappedKeyBytes);
+    // we set the default hash algorithm to SHA-384 during unwrapping
+    conf.set(Encryption.CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY, "SHA-384");
+
+    // unwrap
+    // we expect to fail, if CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY == 
true
+    // otherwise we will use the algorithm written during wrapping
+    Key unwrappedKey = EncryptionUtil.unwrapKey(conf, "hbase", 
wrappedKeyBytes);
+    assertNotNull(unwrappedKey);
+
+    // did we get back what we wrapped?
+    assertTrue("Unwrapped key bytes do not match original",
+      Bytes.equals(keyBytes, unwrappedKey.getEncoded()));
   }
+
 }
diff --git 
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Context.java 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Context.java
index f556ed2..f3c9cab 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Context.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Context.java
@@ -18,10 +18,10 @@ package org.apache.hadoop.hbase.io.crypto;
 
 import java.security.Key;
 
+import org.apache.commons.codec.binary.Hex;
 import org.apache.hadoop.conf.Configurable;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseConfiguration;
-import org.apache.hadoop.hbase.util.MD5Hash;
 import org.apache.yetus.audience.InterfaceAudience;
 
 import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
@@ -94,7 +94,7 @@ public class Context implements Configurable {
         ", want=" + cipher.getKeyLength());
     }
     this.key = key;
-    this.keyHash = MD5Hash.getMD5AsHex(encoded);
+    this.keyHash = new 
String(Hex.encodeHex(Encryption.computeCryptoKeyHash(conf, encoded)));
     return this;
   }
 }
diff --git 
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java
index af0089d0..24e4768 100644
--- 
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java
+++ 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java
@@ -16,10 +16,11 @@
  */
 package org.apache.hadoop.hbase.io.crypto;
 
+import static java.lang.String.format;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.security.DigestException;
 import java.security.Key;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
@@ -51,6 +52,50 @@ public final class Encryption {
 
   private static final Logger LOG = LoggerFactory.getLogger(Encryption.class);
 
+
+  /**
+   * Configuration key for globally enable / disable column family encryption
+   */
+  public static final String CRYPTO_ENABLED_CONF_KEY = "hbase.crypto.enabled";
+
+  /**
+   * Default value for globally enable / disable column family encryption
+   * (set to "true" for backward compatibility)
+   */
+  public static final boolean CRYPTO_ENABLED_CONF_DEFAULT = true;
+
+  /**
+   * Configuration key for the hash algorithm used for generating key hash in 
encrypted HFiles.
+   * This is a MessageDigest algorithm identifier string, like "MD5", 
"SHA-256" or "SHA-384".
+   * (default: "MD5" for backward compatibility reasons)
+   */
+  public static final String CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY = 
"hbase.crypto.key.hash.algorithm";
+
+  /**
+   * Default hash algorithm used for generating key hash in encrypted HFiles.
+   * (we use "MD5" for backward compatibility reasons)
+   */
+  public static final String CRYPTO_KEY_HASH_ALGORITHM_CONF_DEFAULT = "MD5";
+
+  /**
+   * Configuration key for specifying the behaviour if the configured hash 
algorithm
+   * differs from the one used for generating key hash in encrypted HFiles 
currently being read.
+   *
+   * - "false" (default): we won't fail but use the hash algorithm stored in 
the HFile
+   * - "true": we throw an exception (this can be useful if regulations are 
enforcing the usage
+   *           of certain algorithms, e.g. on FIPS compliant clusters)
+   */
+  public static final String CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY =
+    "hbase.crypto.key.hash.algorithm.failOnMismatch";
+
+  /**
+   * Default behaviour is not to fail if the hash algorithm configured differs 
from the one
+   * used in the HFile. (this is the more fail-safe approach, allowing us to 
read
+   * encrypted HFiles written using a different encryption key hash algorithm)
+   */
+  public static final boolean 
CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_DEFAULT = false;
+
+
   /**
    * Crypto context
    */
@@ -99,6 +144,14 @@ public final class Encryption {
     super();
   }
 
+
+  /**
+   * Returns true if the column family encryption feature is enabled globally.
+   */
+  public static boolean isEncryptionEnabled(Configuration conf) {
+    return conf.getBoolean(CRYPTO_ENABLED_CONF_KEY, 
CRYPTO_ENABLED_CONF_DEFAULT);
+  }
+
   /**
    * Get an cipher given a name
    * @param name the cipher name
@@ -127,79 +180,63 @@ public final class Encryption {
   }
 
   /**
-   * Return the MD5 digest of the concatenation of the supplied arguments.
+   * Returns the Hash Algorithm defined in the crypto configuration.
    */
-  public static byte[] hash128(String... args) {
-    byte[] result = new byte[16];
+  public static String getConfiguredHashAlgorithm(Configuration conf) {
+    return conf.getTrimmed(CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY,
+                    CRYPTO_KEY_HASH_ALGORITHM_CONF_DEFAULT);
+  }
+
+  /**
+   * Returns the Hash Algorithm mismatch behaviour defined in the crypto 
configuration.
+   */
+  public static boolean failOnHashAlgorithmMismatch(Configuration conf) {
+    return conf.getBoolean(CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_KEY,
+                           CRYPTO_KEY_FAIL_ON_ALGORITHM_MISMATCH_CONF_DEFAULT);
+  }
+
+  /**
+   * Returns the hash of the supplied argument, using the hash algorithm
+   * specified in the given config.
+   */
+  public static byte[] computeCryptoKeyHash(Configuration conf, byte[] arg) {
+    String algorithm = getConfiguredHashAlgorithm(conf);
     try {
-      MessageDigest md = MessageDigest.getInstance("MD5");
-      for (String arg: args) {
-        md.update(Bytes.toBytes(arg));
-      }
-      md.digest(result, 0, result.length);
-      return result;
-    } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException(e);
-    } catch (DigestException e) {
-      throw new RuntimeException(e);
+      return hashWithAlg(algorithm, arg);
+    } catch (RuntimeException e) {
+      String message = format("Error in computeCryptoKeyHash (please check 
your configuration " +
+                              "parameter %s and the security provider 
configuration of the JVM)",
+                              CRYPTO_KEY_HASH_ALGORITHM_CONF_KEY);
+      throw new RuntimeException(message, e);
     }
   }
 
   /**
    * Return the MD5 digest of the concatenation of the supplied arguments.
    */
+  public static byte[] hash128(String... args) {
+    return hashWithAlg("MD5", Bytes.toByteArrays(args));
+  }
+
+  /**
+   * Return the MD5 digest of the concatenation of the supplied arguments.
+   */
   public static byte[] hash128(byte[]... args) {
-    byte[] result = new byte[16];
-    try {
-      MessageDigest md = MessageDigest.getInstance("MD5");
-      for (byte[] arg: args) {
-        md.update(arg);
-      }
-      md.digest(result, 0, result.length);
-      return result;
-    } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException(e);
-    } catch (DigestException e) {
-      throw new RuntimeException(e);
-    }
+    return hashWithAlg("MD5", args);
   }
 
   /**
    * Return the SHA-256 digest of the concatenation of the supplied arguments.
    */
   public static byte[] hash256(String... args) {
-    byte[] result = new byte[32];
-    try {
-      MessageDigest md = MessageDigest.getInstance("SHA-256");
-      for (String arg: args) {
-        md.update(Bytes.toBytes(arg));
-      }
-      md.digest(result, 0, result.length);
-      return result;
-    } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException(e);
-    } catch (DigestException e) {
-      throw new RuntimeException(e);
-    }
+    return hashWithAlg("SHA-256", Bytes.toByteArrays(args));
   }
 
   /**
    * Return the SHA-256 digest of the concatenation of the supplied arguments.
    */
   public static byte[] hash256(byte[]... args) {
-    byte[] result = new byte[32];
-    try {
-      MessageDigest md = MessageDigest.getInstance("SHA-256");
-      for (byte[] arg: args) {
-        md.update(arg);
-      }
-      md.digest(result, 0, result.length);
-      return result;
-    } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException(e);
-    } catch (DigestException e) {
-      throw new RuntimeException(e);
-    }
+    return hashWithAlg("SHA-256", args);
   }
 
   /**
@@ -578,4 +615,20 @@ public final class Encryption {
     } while (v > 0);
   }
 
+  /**
+   * Return the hash of the concatenation of the supplied arguments, using the 
+   * hash algorithm provided.
+   */
+  public static byte[] hashWithAlg(String algorithm, byte[]... args) {
+    try {
+      MessageDigest md = MessageDigest.getInstance(algorithm);
+      for (byte[] arg: args) {
+        md.update(arg);
+      }
+      return md.digest();
+    } catch (NoSuchAlgorithmException e) {
+      throw new RuntimeException("unable to use hash algorithm: " + algorithm, 
e);
+    }
+  }
+
 }
diff --git a/hbase-protocol-shaded/src/main/protobuf/client/Encryption.proto 
b/hbase-protocol-shaded/src/main/protobuf/client/Encryption.proto
index d0b445c..6b98e3a 100644
--- a/hbase-protocol-shaded/src/main/protobuf/client/Encryption.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/client/Encryption.proto
@@ -31,4 +31,5 @@ message WrappedKey {
   required bytes data = 3;
   optional bytes iv = 4;
   optional bytes hash = 5;
+  optional string hash_algorithm = 6 [default = "MD5"];
 }
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/EncryptionTest.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/EncryptionTest.java
index 2687d3b..9adf84d 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/EncryptionTest.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/EncryptionTest.java
@@ -99,18 +99,23 @@ public class EncryptionTest {
   /**
    * Check that the specified cipher can be loaded and initialized, or throw
    * an exception. Verifies key and cipher provider configuration as a
-   * prerequisite for cipher verification.
+   * prerequisite for cipher verification. Also verifies if encryption is 
enabled globally.
    *
-   * @param conf
-   * @param cipher
-   * @param key
-   * @throws IOException
+   * @param conf HBase configuration
+   * @param cipher chiper algorith to use for the column family
+   * @param key encryption key
+   * @throws IOException in case of encryption configuration error
    */
   public static void testEncryption(final Configuration conf, final String 
cipher,
       byte[] key) throws IOException {
     if (cipher == null) {
       return;
     }
+    if(!Encryption.isEncryptionEnabled(conf)) {
+      String message = String.format("Cipher %s failed test: encryption is 
disabled on the cluster",
+        cipher);
+      throw new IOException(message);
+    }
     testKeyProvider(conf);
     testCipherProvider(conf);
     Boolean result = cipherResults.get(cipher);
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionDisabled.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionDisabled.java
new file mode 100644
index 0000000..07cc5f7
--- /dev/null
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionDisabled.java
@@ -0,0 +1,110 @@
+/**
+ * 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.hbase.regionserver;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.DoNotRetryIOException;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
+import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
+import org.apache.hadoop.hbase.io.crypto.Encryption;
+import org.apache.hadoop.hbase.io.crypto.KeyProviderForTesting;
+import org.apache.hadoop.hbase.testclassification.MasterTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.TableDescriptorChecker;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({MasterTests.class, MediumTests.class})
+public class TestEncryptionDisabled {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+      HBaseClassTestRule.forClass(TestEncryptionDisabled.class);
+
+  private static final HBaseTestingUtility TEST_UTIL = new 
HBaseTestingUtility();
+  private static Configuration conf = TEST_UTIL.getConfiguration();
+  private static TableDescriptorBuilder tdb;
+
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    conf.setInt("hfile.format.version", 3);
+    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, 
KeyProviderForTesting.class.getName());
+    conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase");
+    conf.set(Encryption.CRYPTO_ENABLED_CONF_KEY, "false");
+    conf.set(TableDescriptorChecker.TABLE_SANITY_CHECKS, "true");
+
+    // Start the minicluster
+    TEST_UTIL.startMiniCluster(1);
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    TEST_UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void testEncryptedTableShouldNotBeCreatedWhenEncryptionDisabled() 
throws Exception {
+    // Create the table schema
+    // Specify an encryption algorithm without a key (normally HBase would 
generate a random key)
+    tdb = TableDescriptorBuilder.newBuilder(TableName.valueOf("default",
+      "TestEncryptionDisabledFail"));
+    ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder =
+      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf"));
+    String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, 
HConstants.CIPHER_AES);
+    columnFamilyDescriptorBuilder.setEncryptionType(algorithm);
+    tdb.setColumnFamily(columnFamilyDescriptorBuilder.build());
+
+    // Create the test table, we expect to get back an exception
+    try {
+      TEST_UTIL.getAdmin().createTable(tdb.build());
+    } catch (DoNotRetryIOException e) {
+      assertTrue(e.getMessage().contains("encryption is disabled on the 
cluster"));
+      return;
+    } catch (Exception e) {
+      throw new RuntimeException("create table command failed for the wrong 
reason", e);
+    }
+    fail("create table command unexpectedly succeeded");
+  }
+
+  @Test
+  public void testNonEncryptedTableShouldBeCreatedWhenEncryptionDisabled() 
throws Exception {
+    // Create the table schema
+    tdb = TableDescriptorBuilder.newBuilder(TableName.valueOf("default",
+      "TestEncryptionDisabledSuccess"));
+    ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder =
+      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf"));
+    tdb.setColumnFamily(columnFamilyDescriptorBuilder.build());
+
+    // Create the test table, this should succeed, as we don't use encryption
+    TEST_UTIL.getAdmin().createTable(tdb.build());
+    TEST_UTIL.waitTableAvailable(tdb.build().getTableName(), 5000);
+  }
+
+}
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestEncryptionTest.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestEncryptionTest.java
index a254eb3..52a2070 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestEncryptionTest.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestEncryptionTest.java
@@ -27,6 +27,7 @@ import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.io.crypto.Cipher;
 import org.apache.hadoop.hbase.io.crypto.CipherProvider;
 import org.apache.hadoop.hbase.io.crypto.DefaultCipherProvider;
+import org.apache.hadoop.hbase.io.crypto.Encryption;
 import org.apache.hadoop.hbase.io.crypto.KeyProvider;
 import org.apache.hadoop.hbase.io.crypto.KeyProviderForTesting;
 import org.apache.hadoop.hbase.testclassification.MiscTests;
@@ -91,6 +92,35 @@ public class TestEncryptionTest {
     } catch (Exception e) { }
   }
 
+  @Test
+  public void testTestEnabled() {
+    Configuration conf = HBaseConfiguration.create();
+    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, 
KeyProviderForTesting.class.getName());
+    String algorithm =
+        conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, 
HConstants.CIPHER_AES);
+    try {
+      EncryptionTest.testEncryption(conf, algorithm, null);
+    } catch (Exception e) {
+      fail("Test for cipher " + algorithm + " should have succeeded, when " +
+        Encryption.CRYPTO_ENABLED_CONF_KEY + " is not set");
+    }
+
+    conf.setBoolean(Encryption.CRYPTO_ENABLED_CONF_KEY, true);
+    try {
+      EncryptionTest.testEncryption(conf, algorithm, null);
+    } catch (Exception e) {
+      fail("Test for cipher " + algorithm + " should have succeeded, when " +
+        Encryption.CRYPTO_ENABLED_CONF_KEY + " is set to true");
+    }
+
+    conf.setBoolean(Encryption.CRYPTO_ENABLED_CONF_KEY, false);
+    try {
+      EncryptionTest.testEncryption(conf, algorithm, null);
+      fail("Test for cipher " + algorithm + " should have failed, when " +
+        Encryption.CRYPTO_ENABLED_CONF_KEY + " is set to false");
+    } catch (Exception e) { }
+  }
+
   public static class FailingKeyProvider implements KeyProvider {
 
     @Override

Reply via email to