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 */ 018 019package org.apache.hadoop.crypto.key; 020 021import java.io.IOException; 022import java.nio.ByteBuffer; 023import java.security.GeneralSecurityException; 024import java.security.SecureRandom; 025 026import javax.crypto.Cipher; 027import javax.crypto.spec.IvParameterSpec; 028import javax.crypto.spec.SecretKeySpec; 029 030import com.google.common.base.Preconditions; 031 032import org.apache.hadoop.classification.InterfaceAudience; 033import org.apache.hadoop.crypto.CryptoCodec; 034import org.apache.hadoop.crypto.Decryptor; 035import org.apache.hadoop.crypto.Encryptor; 036 037/** 038 * A KeyProvider with Cryptographic Extensions specifically for generating 039 * and decrypting encrypted encryption keys. 040 * 041 */ 042@InterfaceAudience.Private 043public class KeyProviderCryptoExtension extends 044 KeyProviderExtension<KeyProviderCryptoExtension.CryptoExtension> { 045 046 /** 047 * Designates an encrypted encryption key, or EEK. 048 */ 049 public static final String EEK = "EEK"; 050 /** 051 * Designates a decrypted encrypted encryption key, that is, an encryption key 052 * (EK). 053 */ 054 public static final String EK = "EK"; 055 056 /** 057 * An encrypted encryption key (EEK) and related information. An EEK must be 058 * decrypted using the key's encryption key before it can be used. 059 */ 060 public static class EncryptedKeyVersion { 061 private String encryptionKeyName; 062 private String encryptionKeyVersionName; 063 private byte[] encryptedKeyIv; 064 private KeyVersion encryptedKeyVersion; 065 066 /** 067 * Create a new EncryptedKeyVersion. 068 * 069 * @param keyName Name of the encryption key used to 070 * encrypt the encrypted key. 071 * @param encryptionKeyVersionName Version name of the encryption key used 072 * to encrypt the encrypted key. 073 * @param encryptedKeyIv Initialization vector of the encrypted 074 * key. The IV of the encryption key used to 075 * encrypt the encrypted key is derived from 076 * this IV. 077 * @param encryptedKeyVersion The encrypted encryption key version. 078 */ 079 protected EncryptedKeyVersion(String keyName, 080 String encryptionKeyVersionName, byte[] encryptedKeyIv, 081 KeyVersion encryptedKeyVersion) { 082 this.encryptionKeyName = keyName; 083 this.encryptionKeyVersionName = encryptionKeyVersionName; 084 this.encryptedKeyIv = encryptedKeyIv; 085 this.encryptedKeyVersion = encryptedKeyVersion; 086 } 087 088 /** 089 * Factory method to create a new EncryptedKeyVersion that can then be 090 * passed into {@link #decryptEncryptedKey}. Note that the fields of the 091 * returned EncryptedKeyVersion will only partially be populated; it is not 092 * necessarily suitable for operations besides decryption. 093 * 094 * @param keyName Key name of the encryption key use to encrypt the 095 * encrypted key. 096 * @param encryptionKeyVersionName Version name of the encryption key used 097 * to encrypt the encrypted key. 098 * @param encryptedKeyIv Initialization vector of the encrypted 099 * key. The IV of the encryption key used to 100 * encrypt the encrypted key is derived from 101 * this IV. 102 * @param encryptedKeyMaterial Key material of the encrypted key. 103 * @return EncryptedKeyVersion suitable for decryption. 104 */ 105 public static EncryptedKeyVersion createForDecryption(String keyName, 106 String encryptionKeyVersionName, byte[] encryptedKeyIv, 107 byte[] encryptedKeyMaterial) { 108 KeyVersion encryptedKeyVersion = new KeyVersion(null, EEK, 109 encryptedKeyMaterial); 110 return new EncryptedKeyVersion(keyName, encryptionKeyVersionName, 111 encryptedKeyIv, encryptedKeyVersion); 112 } 113 114 /** 115 * @return Name of the encryption key used to encrypt the encrypted key. 116 */ 117 public String getEncryptionKeyName() { 118 return encryptionKeyName; 119 } 120 121 /** 122 * @return Version name of the encryption key used to encrypt the encrypted 123 * key. 124 */ 125 public String getEncryptionKeyVersionName() { 126 return encryptionKeyVersionName; 127 } 128 129 /** 130 * @return Initialization vector of the encrypted key. The IV of the 131 * encryption key used to encrypt the encrypted key is derived from this 132 * IV. 133 */ 134 public byte[] getEncryptedKeyIv() { 135 return encryptedKeyIv; 136 } 137 138 /** 139 * @return The encrypted encryption key version. 140 */ 141 public KeyVersion getEncryptedKeyVersion() { 142 return encryptedKeyVersion; 143 } 144 145 /** 146 * Derive the initialization vector (IV) for the encryption key from the IV 147 * of the encrypted key. This derived IV is used with the encryption key to 148 * decrypt the encrypted key. 149 * <p/> 150 * The alternative to this is using the same IV for both the encryption key 151 * and the encrypted key. Even a simple symmetric transformation like this 152 * improves security by avoiding IV re-use. IVs will also be fairly unique 153 * among different EEKs. 154 * 155 * @param encryptedKeyIV of the encrypted key (i.e. {@link 156 * #getEncryptedKeyIv()}) 157 * @return IV for the encryption key 158 */ 159 protected static byte[] deriveIV(byte[] encryptedKeyIV) { 160 byte[] rIv = new byte[encryptedKeyIV.length]; 161 // Do a simple XOR transformation to flip all the bits 162 for (int i = 0; i < encryptedKeyIV.length; i++) { 163 rIv[i] = (byte) (encryptedKeyIV[i] ^ 0xff); 164 } 165 return rIv; 166 } 167 } 168 169 /** 170 * CryptoExtension is a type of Extension that exposes methods to generate 171 * EncryptedKeys and to decrypt the same. 172 */ 173 public interface CryptoExtension extends KeyProviderExtension.Extension { 174 175 /** 176 * Calls to this method allows the underlying KeyProvider to warm-up any 177 * implementation specific caches used to store the Encrypted Keys. 178 * @param keyNames Array of Key Names 179 */ 180 public void warmUpEncryptedKeys(String... keyNames) 181 throws IOException; 182 183 /** 184 * Drains the Queue for the provided key. 185 * 186 * @param keyName the key to drain the Queue for 187 */ 188 public void drain(String keyName); 189 190 /** 191 * Generates a key material and encrypts it using the given key version name 192 * and initialization vector. The generated key material is of the same 193 * length as the <code>KeyVersion</code> material of the latest key version 194 * of the key and is encrypted using the same cipher. 195 * <p/> 196 * NOTE: The generated key is not stored by the <code>KeyProvider</code> 197 * 198 * @param encryptionKeyName 199 * The latest KeyVersion of this key's material will be encrypted. 200 * @return EncryptedKeyVersion with the generated key material, the version 201 * name is 'EEK' (for Encrypted Encryption Key) 202 * @throws IOException 203 * thrown if the key material could not be generated 204 * @throws GeneralSecurityException 205 * thrown if the key material could not be encrypted because of a 206 * cryptographic issue. 207 */ 208 public EncryptedKeyVersion generateEncryptedKey( 209 String encryptionKeyName) throws IOException, 210 GeneralSecurityException; 211 212 /** 213 * Decrypts an encrypted byte[] key material using the given a key version 214 * name and initialization vector. 215 * 216 * @param encryptedKeyVersion 217 * contains keyVersionName and IV to decrypt the encrypted key 218 * material 219 * @return a KeyVersion with the decrypted key material, the version name is 220 * 'EK' (For Encryption Key) 221 * @throws IOException 222 * thrown if the key material could not be decrypted 223 * @throws GeneralSecurityException 224 * thrown if the key material could not be decrypted because of a 225 * cryptographic issue. 226 */ 227 public KeyVersion decryptEncryptedKey( 228 EncryptedKeyVersion encryptedKeyVersion) throws IOException, 229 GeneralSecurityException; 230 } 231 232 private static class DefaultCryptoExtension implements CryptoExtension { 233 234 private final KeyProvider keyProvider; 235 private static final ThreadLocal<SecureRandom> RANDOM = 236 new ThreadLocal<SecureRandom>() { 237 @Override 238 protected SecureRandom initialValue() { 239 return new SecureRandom(); 240 } 241 }; 242 243 private DefaultCryptoExtension(KeyProvider keyProvider) { 244 this.keyProvider = keyProvider; 245 } 246 247 @Override 248 public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName) 249 throws IOException, GeneralSecurityException { 250 // Fetch the encryption key 251 KeyVersion encryptionKey = keyProvider.getCurrentKey(encryptionKeyName); 252 Preconditions.checkNotNull(encryptionKey, 253 "No KeyVersion exists for key '%s' ", encryptionKeyName); 254 // Generate random bytes for new key and IV 255 256 CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf()); 257 final byte[] newKey = new byte[encryptionKey.getMaterial().length]; 258 cc.generateSecureRandom(newKey); 259 final byte[] iv = new byte[cc.getCipherSuite().getAlgorithmBlockSize()]; 260 cc.generateSecureRandom(iv); 261 // Encryption key IV is derived from new key's IV 262 final byte[] encryptionIV = EncryptedKeyVersion.deriveIV(iv); 263 Encryptor encryptor = cc.createEncryptor(); 264 encryptor.init(encryptionKey.getMaterial(), encryptionIV); 265 int keyLen = newKey.length; 266 ByteBuffer bbIn = ByteBuffer.allocateDirect(keyLen); 267 ByteBuffer bbOut = ByteBuffer.allocateDirect(keyLen); 268 bbIn.put(newKey); 269 bbIn.flip(); 270 encryptor.encrypt(bbIn, bbOut); 271 bbOut.flip(); 272 byte[] encryptedKey = new byte[keyLen]; 273 bbOut.get(encryptedKey); 274 return new EncryptedKeyVersion(encryptionKeyName, 275 encryptionKey.getVersionName(), iv, 276 new KeyVersion(encryptionKey.getName(), EEK, encryptedKey)); 277 } 278 279 @Override 280 public KeyVersion decryptEncryptedKey( 281 EncryptedKeyVersion encryptedKeyVersion) throws IOException, 282 GeneralSecurityException { 283 // Fetch the encryption key material 284 final String encryptionKeyVersionName = 285 encryptedKeyVersion.getEncryptionKeyVersionName(); 286 final KeyVersion encryptionKey = 287 keyProvider.getKeyVersion(encryptionKeyVersionName); 288 Preconditions.checkNotNull(encryptionKey, 289 "KeyVersion name '%s' does not exist", encryptionKeyVersionName); 290 Preconditions.checkArgument( 291 encryptedKeyVersion.getEncryptedKeyVersion().getVersionName() 292 .equals(KeyProviderCryptoExtension.EEK), 293 "encryptedKey version name must be '%s', is '%s'", 294 KeyProviderCryptoExtension.EEK, 295 encryptedKeyVersion.getEncryptedKeyVersion().getVersionName() 296 ); 297 298 // Encryption key IV is determined from encrypted key's IV 299 final byte[] encryptionIV = 300 EncryptedKeyVersion.deriveIV(encryptedKeyVersion.getEncryptedKeyIv()); 301 302 CryptoCodec cc = CryptoCodec.getInstance(keyProvider.getConf()); 303 Decryptor decryptor = cc.createDecryptor(); 304 decryptor.init(encryptionKey.getMaterial(), encryptionIV); 305 final KeyVersion encryptedKV = 306 encryptedKeyVersion.getEncryptedKeyVersion(); 307 int keyLen = encryptedKV.getMaterial().length; 308 ByteBuffer bbIn = ByteBuffer.allocateDirect(keyLen); 309 ByteBuffer bbOut = ByteBuffer.allocateDirect(keyLen); 310 bbIn.put(encryptedKV.getMaterial()); 311 bbIn.flip(); 312 decryptor.decrypt(bbIn, bbOut); 313 bbOut.flip(); 314 byte[] decryptedKey = new byte[keyLen]; 315 bbOut.get(decryptedKey); 316 return new KeyVersion(encryptionKey.getName(), EK, decryptedKey); 317 } 318 319 @Override 320 public void warmUpEncryptedKeys(String... keyNames) 321 throws IOException { 322 // NO-OP since the default version does not cache any keys 323 } 324 325 @Override 326 public void drain(String keyName) { 327 // NO-OP since the default version does not cache any keys 328 } 329 } 330 331 /** 332 * This constructor is to be used by sub classes that provide 333 * delegating/proxying functionality to the {@link KeyProviderCryptoExtension} 334 * @param keyProvider 335 * @param extension 336 */ 337 protected KeyProviderCryptoExtension(KeyProvider keyProvider, 338 CryptoExtension extension) { 339 super(keyProvider, extension); 340 } 341 342 /** 343 * Notifies the Underlying CryptoExtension implementation to warm up any 344 * implementation specific caches for the specified KeyVersions 345 * @param keyNames Arrays of key Names 346 */ 347 public void warmUpEncryptedKeys(String... keyNames) 348 throws IOException { 349 getExtension().warmUpEncryptedKeys(keyNames); 350 } 351 352 /** 353 * Generates a key material and encrypts it using the given key version name 354 * and initialization vector. The generated key material is of the same 355 * length as the <code>KeyVersion</code> material and is encrypted using the 356 * same cipher. 357 * <p/> 358 * NOTE: The generated key is not stored by the <code>KeyProvider</code> 359 * 360 * @param encryptionKeyName The latest KeyVersion of this key's material will 361 * be encrypted. 362 * @return EncryptedKeyVersion with the generated key material, the version 363 * name is 'EEK' (for Encrypted Encryption Key) 364 * @throws IOException thrown if the key material could not be generated 365 * @throws GeneralSecurityException thrown if the key material could not be 366 * encrypted because of a cryptographic issue. 367 */ 368 public EncryptedKeyVersion generateEncryptedKey(String encryptionKeyName) 369 throws IOException, 370 GeneralSecurityException { 371 return getExtension().generateEncryptedKey(encryptionKeyName); 372 } 373 374 /** 375 * Decrypts an encrypted byte[] key material using the given a key version 376 * name and initialization vector. 377 * 378 * @param encryptedKey contains keyVersionName and IV to decrypt the encrypted 379 * key material 380 * @return a KeyVersion with the decrypted key material, the version name is 381 * 'EK' (For Encryption Key) 382 * @throws IOException thrown if the key material could not be decrypted 383 * @throws GeneralSecurityException thrown if the key material could not be 384 * decrypted because of a cryptographic issue. 385 */ 386 public KeyVersion decryptEncryptedKey(EncryptedKeyVersion encryptedKey) 387 throws IOException, GeneralSecurityException { 388 return getExtension().decryptEncryptedKey(encryptedKey); 389 } 390 391 /** 392 * Creates a <code>KeyProviderCryptoExtension</code> using a given 393 * {@link KeyProvider}. 394 * <p/> 395 * If the given <code>KeyProvider</code> implements the 396 * {@link CryptoExtension} interface the <code>KeyProvider</code> itself 397 * will provide the extension functionality, otherwise a default extension 398 * implementation will be used. 399 * 400 * @param keyProvider <code>KeyProvider</code> to use to create the 401 * <code>KeyProviderCryptoExtension</code> extension. 402 * @return a <code>KeyProviderCryptoExtension</code> instance using the 403 * given <code>KeyProvider</code>. 404 */ 405 public static KeyProviderCryptoExtension createKeyProviderCryptoExtension( 406 KeyProvider keyProvider) { 407 CryptoExtension cryptoExtension = (keyProvider instanceof CryptoExtension) 408 ? (CryptoExtension) keyProvider 409 : new DefaultCryptoExtension(keyProvider); 410 return new KeyProviderCryptoExtension(keyProvider, cryptoExtension); 411 } 412 413 @Override 414 public void close() throws IOException { 415 KeyProvider provider = getKeyProvider(); 416 if (provider != null && provider != this) { 417 provider.close(); 418 } 419 } 420 421}