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