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
019 package org.apache.hadoop.crypto.key;
020
021 import java.io.IOException;
022 import java.nio.ByteBuffer;
023 import java.security.GeneralSecurityException;
024 import java.security.SecureRandom;
025
026 import javax.crypto.Cipher;
027 import javax.crypto.spec.IvParameterSpec;
028 import javax.crypto.spec.SecretKeySpec;
029
030 import com.google.common.base.Preconditions;
031
032 import org.apache.hadoop.classification.InterfaceAudience;
033 import org.apache.hadoop.crypto.CryptoCodec;
034 import org.apache.hadoop.crypto.Decryptor;
035 import 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
043 public 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 if (getKeyProvider() != null) {
416 getKeyProvider().close();
417 }
418 }
419
420 }