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