001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.security; 019 020import java.io.ByteArrayInputStream; 021import java.io.ByteArrayOutputStream; 022import java.io.IOException; 023import java.security.Key; 024import java.security.KeyException; 025import java.security.SecureRandom; 026import java.util.Properties; 027import javax.crypto.spec.SecretKeySpec; 028import org.apache.commons.crypto.cipher.CryptoCipherFactory; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.HConstants; 031import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 032import org.apache.hadoop.hbase.io.crypto.Cipher; 033import org.apache.hadoop.hbase.io.crypto.Encryption; 034import org.apache.hadoop.hbase.io.crypto.aes.CryptoAES; 035import org.apache.hadoop.hbase.util.Bytes; 036import org.apache.yetus.audience.InterfaceAudience; 037import org.apache.yetus.audience.InterfaceStability; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; 042import org.apache.hadoop.hbase.shaded.protobuf.generated.EncryptionProtos; 043import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos; 044 045/** 046 * Some static utility methods for encryption uses in hbase-client. 047 */ 048@InterfaceAudience.Private 049@InterfaceStability.Evolving 050public final class EncryptionUtil { 051 static private final Logger LOG = LoggerFactory.getLogger(EncryptionUtil.class); 052 053 static private final SecureRandom RNG = new SecureRandom(); 054 055 /** 056 * Private constructor to keep this class from being instantiated. 057 */ 058 private EncryptionUtil() { 059 } 060 061 /** 062 * Protect a key by encrypting it with the secret key of the given subject. 063 * The configuration must be set up correctly for key alias resolution. 064 * @param conf configuration 065 * @param key the raw key bytes 066 * @param algorithm the algorithm to use with this key material 067 * @return the encrypted key bytes 068 * @throws IOException 069 */ 070 public static byte[] wrapKey(Configuration conf, byte[] key, String algorithm) 071 throws IOException { 072 return wrapKey(conf, 073 conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()), 074 new SecretKeySpec(key, algorithm)); 075 } 076 077 /** 078 * Protect a key by encrypting it with the secret key of the given subject. 079 * The configuration must be set up correctly for key alias resolution. 080 * @param conf configuration 081 * @param subject subject key alias 082 * @param key the key 083 * @return the encrypted key bytes 084 */ 085 public static byte[] wrapKey(Configuration conf, String subject, Key key) 086 throws IOException { 087 // Wrap the key with the configured encryption algorithm. 088 String algorithm = 089 conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 090 Cipher cipher = Encryption.getCipher(conf, algorithm); 091 if (cipher == null) { 092 throw new RuntimeException("Cipher '" + algorithm + "' not available"); 093 } 094 EncryptionProtos.WrappedKey.Builder builder = EncryptionProtos.WrappedKey.newBuilder(); 095 builder.setAlgorithm(key.getAlgorithm()); 096 byte[] iv = null; 097 if (cipher.getIvLength() > 0) { 098 iv = new byte[cipher.getIvLength()]; 099 RNG.nextBytes(iv); 100 builder.setIv(UnsafeByteOperations.unsafeWrap(iv)); 101 } 102 byte[] keyBytes = key.getEncoded(); 103 builder.setLength(keyBytes.length); 104 builder.setHashAlgorithm(Encryption.getConfiguredHashAlgorithm(conf)); 105 builder.setHash( 106 UnsafeByteOperations.unsafeWrap(Encryption.computeCryptoKeyHash(conf, keyBytes))); 107 ByteArrayOutputStream out = new ByteArrayOutputStream(); 108 Encryption.encryptWithSubjectKey(out, new ByteArrayInputStream(keyBytes), subject, 109 conf, cipher, iv); 110 builder.setData(UnsafeByteOperations.unsafeWrap(out.toByteArray())); 111 // Build and return the protobuf message 112 out.reset(); 113 builder.build().writeDelimitedTo(out); 114 return out.toByteArray(); 115 } 116 117 /** 118 * Unwrap a key by decrypting it with the secret key of the given subject. 119 * The configuration must be set up correctly for key alias resolution. 120 * @param conf configuration 121 * @param subject subject key alias 122 * @param value the encrypted key bytes 123 * @return the raw key bytes 124 * @throws IOException 125 * @throws KeyException 126 */ 127 public static Key unwrapKey(Configuration conf, String subject, byte[] value) 128 throws IOException, KeyException { 129 EncryptionProtos.WrappedKey wrappedKey = EncryptionProtos.WrappedKey.PARSER 130 .parseDelimitedFrom(new ByteArrayInputStream(value)); 131 String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, 132 HConstants.CIPHER_AES); 133 Cipher cipher = Encryption.getCipher(conf, algorithm); 134 if (cipher == null) { 135 throw new RuntimeException("Cipher '" + algorithm + "' not available"); 136 } 137 return getUnwrapKey(conf, subject, wrappedKey, cipher); 138 } 139 140 private static Key getUnwrapKey(Configuration conf, String subject, 141 EncryptionProtos.WrappedKey wrappedKey, Cipher cipher) throws IOException, KeyException { 142 String configuredHashAlgorithm = Encryption.getConfiguredHashAlgorithm(conf); 143 String wrappedHashAlgorithm = wrappedKey.getHashAlgorithm().trim(); 144 if(!configuredHashAlgorithm.equalsIgnoreCase(wrappedHashAlgorithm)) { 145 String msg = String.format("Unexpected encryption key hash algorithm: %s (expecting: %s)", 146 wrappedHashAlgorithm, configuredHashAlgorithm); 147 if(Encryption.failOnHashAlgorithmMismatch(conf)) { 148 throw new KeyException(msg); 149 } 150 LOG.debug(msg); 151 } 152 ByteArrayOutputStream out = new ByteArrayOutputStream(); 153 byte[] iv = wrappedKey.hasIv() ? wrappedKey.getIv().toByteArray() : null; 154 Encryption.decryptWithSubjectKey(out, wrappedKey.getData().newInput(), 155 wrappedKey.getLength(), subject, conf, cipher, iv); 156 byte[] keyBytes = out.toByteArray(); 157 if (wrappedKey.hasHash()) { 158 if (!Bytes.equals(wrappedKey.getHash().toByteArray(), 159 Encryption.hashWithAlg(wrappedHashAlgorithm, keyBytes))) { 160 throw new KeyException("Key was not successfully unwrapped"); 161 } 162 } 163 return new SecretKeySpec(keyBytes, wrappedKey.getAlgorithm()); 164 } 165 166 /** 167 * Unwrap a wal key by decrypting it with the secret key of the given subject. The configuration 168 * must be set up correctly for key alias resolution. 169 * @param conf configuration 170 * @param subject subject key alias 171 * @param value the encrypted key bytes 172 * @return the raw key bytes 173 * @throws IOException if key is not found for the subject, or if some I/O error occurs 174 * @throws KeyException if fail to unwrap the key 175 */ 176 public static Key unwrapWALKey(Configuration conf, String subject, byte[] value) 177 throws IOException, KeyException { 178 EncryptionProtos.WrappedKey wrappedKey = 179 EncryptionProtos.WrappedKey.PARSER.parseDelimitedFrom(new ByteArrayInputStream(value)); 180 String algorithm = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 181 Cipher cipher = Encryption.getCipher(conf, algorithm); 182 if (cipher == null) { 183 throw new RuntimeException("Cipher '" + algorithm + "' not available"); 184 } 185 return getUnwrapKey(conf, subject, wrappedKey, cipher); 186 } 187 188 /** 189 * Helper to create an encyption context. 190 * 191 * @param conf The current configuration. 192 * @param family The current column descriptor. 193 * @return The created encryption context. 194 * @throws IOException if an encryption key for the column cannot be unwrapped 195 * @throws IllegalStateException in case of encryption related configuration errors 196 */ 197 public static Encryption.Context createEncryptionContext(Configuration conf, 198 ColumnFamilyDescriptor family) throws IOException { 199 Encryption.Context cryptoContext = Encryption.Context.NONE; 200 String cipherName = family.getEncryptionType(); 201 if (cipherName != null) { 202 if(!Encryption.isEncryptionEnabled(conf)) { 203 throw new IllegalStateException("Encryption for family '" + family.getNameAsString() 204 + "' configured with type '" + cipherName + "' but the encryption feature is disabled"); 205 } 206 Cipher cipher; 207 Key key; 208 byte[] keyBytes = family.getEncryptionKey(); 209 if (keyBytes != null) { 210 // Family provides specific key material 211 key = unwrapKey(conf, keyBytes); 212 // Use the algorithm the key wants 213 cipher = Encryption.getCipher(conf, key.getAlgorithm()); 214 if (cipher == null) { 215 throw new IllegalStateException("Cipher '" + key.getAlgorithm() + "' is not available"); 216 } 217 // Fail if misconfigured 218 // We use the encryption type specified in the column schema as a sanity check on 219 // what the wrapped key is telling us 220 if (!cipher.getName().equalsIgnoreCase(cipherName)) { 221 throw new IllegalStateException("Encryption for family '" + family.getNameAsString() 222 + "' configured with type '" + cipherName + "' but key specifies algorithm '" 223 + cipher.getName() + "'"); 224 } 225 } else { 226 // Family does not provide key material, create a random key 227 cipher = Encryption.getCipher(conf, cipherName); 228 if (cipher == null) { 229 throw new IllegalStateException("Cipher '" + cipherName + "' is not available"); 230 } 231 key = cipher.getRandomKey(); 232 } 233 cryptoContext = Encryption.newContext(conf); 234 cryptoContext.setCipher(cipher); 235 cryptoContext.setKey(key); 236 } 237 return cryptoContext; 238 } 239 240 /** 241 * Helper for {@link #unwrapKey(Configuration, String, byte[])} which automatically uses the 242 * configured master and alternative keys, rather than having to specify a key type to unwrap 243 * with. 244 * 245 * The configuration must be set up correctly for key alias resolution. 246 * 247 * @param conf the current configuration 248 * @param keyBytes the key encrypted by master (or alternative) to unwrap 249 * @return the key bytes, decrypted 250 * @throws IOException if the key cannot be unwrapped 251 */ 252 public static Key unwrapKey(Configuration conf, byte[] keyBytes) throws IOException { 253 Key key; 254 String masterKeyName = conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, 255 User.getCurrent().getShortName()); 256 try { 257 // First try the master key 258 key = unwrapKey(conf, masterKeyName, keyBytes); 259 } catch (KeyException e) { 260 // If the current master key fails to unwrap, try the alternate, if 261 // one is configured 262 if (LOG.isDebugEnabled()) { 263 LOG.debug("Unable to unwrap key with current master key '" + masterKeyName + "'"); 264 } 265 String alternateKeyName = 266 conf.get(HConstants.CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY); 267 if (alternateKeyName != null) { 268 try { 269 key = unwrapKey(conf, alternateKeyName, keyBytes); 270 } catch (KeyException ex) { 271 throw new IOException(ex); 272 } 273 } else { 274 throw new IOException(e); 275 } 276 } 277 return key; 278 } 279 280 /** 281 * Helper to create an instance of CryptoAES. 282 * 283 * @param conf The current configuration. 284 * @param cryptoCipherMeta The metadata for create CryptoAES. 285 * @return The instance of CryptoAES. 286 * @throws IOException if create CryptoAES failed 287 */ 288 public static CryptoAES createCryptoAES(RPCProtos.CryptoCipherMeta cryptoCipherMeta, 289 Configuration conf) throws IOException { 290 Properties properties = new Properties(); 291 // the property for cipher class 292 properties.setProperty(CryptoCipherFactory.CLASSES_KEY, 293 conf.get("hbase.rpc.crypto.encryption.aes.cipher.class", 294 "org.apache.commons.crypto.cipher.JceCipher")); 295 // create SaslAES for client 296 return new CryptoAES(cryptoCipherMeta.getTransformation(), properties, 297 cryptoCipherMeta.getInKey().toByteArray(), 298 cryptoCipherMeta.getOutKey().toByteArray(), 299 cryptoCipherMeta.getInIv().toByteArray(), 300 cryptoCipherMeta.getOutIv().toByteArray()); 301 } 302}