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; 027 028import javax.crypto.spec.SecretKeySpec; 029 030import org.apache.commons.crypto.cipher.CryptoCipherFactory; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.hbase.HColumnDescriptor; 033import org.apache.hadoop.hbase.HConstants; 034import org.apache.yetus.audience.InterfaceAudience; 035import org.apache.yetus.audience.InterfaceStability; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 039import org.apache.hadoop.hbase.io.crypto.Cipher; 040import org.apache.hadoop.hbase.io.crypto.Encryption; 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; 044import org.apache.hadoop.hbase.io.crypto.aes.CryptoAES; 045import org.apache.hadoop.hbase.util.Bytes; 046 047/** 048 * Some static utility methods for encryption uses in hbase-client. 049 */ 050@InterfaceAudience.Private 051@InterfaceStability.Evolving 052public final class EncryptionUtil { 053 static private final Logger LOG = LoggerFactory.getLogger(EncryptionUtil.class); 054 055 static private final SecureRandom RNG = new SecureRandom(); 056 057 /** 058 * Private constructor to keep this class from being instantiated. 059 */ 060 private EncryptionUtil() { 061 } 062 063 /** 064 * Protect a key by encrypting it with the secret key of the given subject. 065 * The configuration must be set up correctly for key alias resolution. 066 * @param conf configuration 067 * @param key the raw key bytes 068 * @param algorithm the algorithm to use with this key material 069 * @return the encrypted key bytes 070 * @throws IOException 071 */ 072 public static byte[] wrapKey(Configuration conf, byte[] key, String algorithm) 073 throws IOException { 074 return wrapKey(conf, 075 conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()), 076 new SecretKeySpec(key, algorithm)); 077 } 078 079 /** 080 * Protect a key by encrypting it with the secret key of the given subject. 081 * The configuration must be set up correctly for key alias resolution. 082 * @param conf configuration 083 * @param subject subject key alias 084 * @param key the key 085 * @return the encrypted key bytes 086 */ 087 public static byte[] wrapKey(Configuration conf, String subject, Key key) 088 throws IOException { 089 // Wrap the key with the configured encryption algorithm. 090 String algorithm = 091 conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 092 Cipher cipher = Encryption.getCipher(conf, algorithm); 093 if (cipher == null) { 094 throw new RuntimeException("Cipher '" + algorithm + "' not available"); 095 } 096 EncryptionProtos.WrappedKey.Builder builder = EncryptionProtos.WrappedKey.newBuilder(); 097 builder.setAlgorithm(key.getAlgorithm()); 098 byte[] iv = null; 099 if (cipher.getIvLength() > 0) { 100 iv = new byte[cipher.getIvLength()]; 101 RNG.nextBytes(iv); 102 builder.setIv(UnsafeByteOperations.unsafeWrap(iv)); 103 } 104 byte[] keyBytes = key.getEncoded(); 105 builder.setLength(keyBytes.length); 106 builder.setHash(UnsafeByteOperations.unsafeWrap(Encryption.hash128(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 ByteArrayOutputStream out = new ByteArrayOutputStream(); 143 byte[] iv = wrappedKey.hasIv() ? wrappedKey.getIv().toByteArray() : null; 144 Encryption.decryptWithSubjectKey(out, wrappedKey.getData().newInput(), 145 wrappedKey.getLength(), subject, conf, cipher, iv); 146 byte[] keyBytes = out.toByteArray(); 147 if (wrappedKey.hasHash()) { 148 if (!Bytes.equals(wrappedKey.getHash().toByteArray(), Encryption.hash128(keyBytes))) { 149 throw new KeyException("Key was not successfully unwrapped"); 150 } 151 } 152 return new SecretKeySpec(keyBytes, wrappedKey.getAlgorithm()); 153 } 154 155 /** 156 * Unwrap a wal key by decrypting it with the secret key of the given subject. The configuration 157 * must be set up correctly for key alias resolution. 158 * @param conf configuration 159 * @param subject subject key alias 160 * @param value the encrypted key bytes 161 * @return the raw key bytes 162 * @throws IOException if key is not found for the subject, or if some I/O error occurs 163 * @throws KeyException if fail to unwrap the key 164 */ 165 public static Key unwrapWALKey(Configuration conf, String subject, byte[] value) 166 throws IOException, KeyException { 167 EncryptionProtos.WrappedKey wrappedKey = 168 EncryptionProtos.WrappedKey.PARSER.parseDelimitedFrom(new ByteArrayInputStream(value)); 169 String algorithm = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 170 Cipher cipher = Encryption.getCipher(conf, algorithm); 171 if (cipher == null) { 172 throw new RuntimeException("Cipher '" + algorithm + "' not available"); 173 } 174 return getUnwrapKey(conf, subject, wrappedKey, cipher); 175 } 176 177 /** 178 * Helper to create an encyption context. 179 * 180 * @param conf The current configuration. 181 * @param family The current column descriptor. 182 * @return The created encryption context. 183 * @throws IOException if an encryption key for the column cannot be unwrapped 184 */ 185 public static Encryption.Context createEncryptionContext(Configuration conf, 186 ColumnFamilyDescriptor family) throws IOException { 187 Encryption.Context cryptoContext = Encryption.Context.NONE; 188 String cipherName = family.getEncryptionType(); 189 if (cipherName != null) { 190 Cipher cipher; 191 Key key; 192 byte[] keyBytes = family.getEncryptionKey(); 193 if (keyBytes != null) { 194 // Family provides specific key material 195 key = unwrapKey(conf, keyBytes); 196 // Use the algorithm the key wants 197 cipher = Encryption.getCipher(conf, key.getAlgorithm()); 198 if (cipher == null) { 199 throw new RuntimeException("Cipher '" + key.getAlgorithm() + "' is not available"); 200 } 201 // Fail if misconfigured 202 // We use the encryption type specified in the column schema as a sanity check on 203 // what the wrapped key is telling us 204 if (!cipher.getName().equalsIgnoreCase(cipherName)) { 205 throw new RuntimeException("Encryption for family '" + family.getNameAsString() 206 + "' configured with type '" + cipherName + "' but key specifies algorithm '" 207 + cipher.getName() + "'"); 208 } 209 } else { 210 // Family does not provide key material, create a random key 211 cipher = Encryption.getCipher(conf, cipherName); 212 if (cipher == null) { 213 throw new RuntimeException("Cipher '" + cipherName + "' is not available"); 214 } 215 key = cipher.getRandomKey(); 216 } 217 cryptoContext = Encryption.newContext(conf); 218 cryptoContext.setCipher(cipher); 219 cryptoContext.setKey(key); 220 } 221 return cryptoContext; 222 } 223 224 /** 225 * Helper for {@link #unwrapKey(Configuration, String, byte[])} which automatically uses the 226 * configured master and alternative keys, rather than having to specify a key type to unwrap 227 * with. 228 * 229 * The configuration must be set up correctly for key alias resolution. 230 * 231 * @param conf the current configuration 232 * @param keyBytes the key encrypted by master (or alternative) to unwrap 233 * @return the key bytes, decrypted 234 * @throws IOException if the key cannot be unwrapped 235 */ 236 public static Key unwrapKey(Configuration conf, byte[] keyBytes) throws IOException { 237 Key key; 238 String masterKeyName = conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, 239 User.getCurrent().getShortName()); 240 try { 241 // First try the master key 242 key = unwrapKey(conf, masterKeyName, keyBytes); 243 } catch (KeyException e) { 244 // If the current master key fails to unwrap, try the alternate, if 245 // one is configured 246 if (LOG.isDebugEnabled()) { 247 LOG.debug("Unable to unwrap key with current master key '" + masterKeyName + "'"); 248 } 249 String alternateKeyName = 250 conf.get(HConstants.CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY); 251 if (alternateKeyName != null) { 252 try { 253 key = unwrapKey(conf, alternateKeyName, keyBytes); 254 } catch (KeyException ex) { 255 throw new IOException(ex); 256 } 257 } else { 258 throw new IOException(e); 259 } 260 } 261 return key; 262 } 263 264 /** 265 * Helper to create an instance of CryptoAES. 266 * 267 * @param conf The current configuration. 268 * @param cryptoCipherMeta The metadata for create CryptoAES. 269 * @return The instance of CryptoAES. 270 * @throws IOException if create CryptoAES failed 271 */ 272 public static CryptoAES createCryptoAES(RPCProtos.CryptoCipherMeta cryptoCipherMeta, 273 Configuration conf) throws IOException { 274 Properties properties = new Properties(); 275 // the property for cipher class 276 properties.setProperty(CryptoCipherFactory.CLASSES_KEY, 277 conf.get("hbase.rpc.crypto.encryption.aes.cipher.class", 278 "org.apache.commons.crypto.cipher.JceCipher")); 279 // create SaslAES for client 280 return new CryptoAES(cryptoCipherMeta.getTransformation(), properties, 281 cryptoCipherMeta.getInKey().toByteArray(), 282 cryptoCipherMeta.getOutKey().toByteArray(), 283 cryptoCipherMeta.getInIv().toByteArray(), 284 cryptoCipherMeta.getOutIv().toByteArray()); 285 } 286}