View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to you under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package org.apache.hadoop.hbase.io.crypto;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.security.DigestException;
23  import java.security.Key;
24  import java.security.MessageDigest;
25  import java.security.NoSuchAlgorithmException;
26  import java.security.spec.InvalidKeySpecException;
27  import java.util.Arrays;
28  import java.util.Map;
29  import java.util.concurrent.ConcurrentHashMap;
30  
31  import javax.crypto.SecretKeyFactory;
32  import javax.crypto.spec.PBEKeySpec;
33  import javax.crypto.spec.SecretKeySpec;
34  
35  import org.apache.commons.io.IOUtils;
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.hadoop.hbase.classification.InterfaceAudience;
39  import org.apache.hadoop.hbase.classification.InterfaceStability;
40  import org.apache.hadoop.conf.Configuration;
41  import org.apache.hadoop.hbase.HBaseConfiguration;
42  import org.apache.hadoop.hbase.HConstants;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.hbase.util.Pair;
45  import org.apache.hadoop.util.ReflectionUtils;
46  
47  /**
48   * A facade for encryption algorithms and related support.
49   */
50  @InterfaceAudience.Public
51  @InterfaceStability.Evolving
52  public final class Encryption {
53  
54    private static final Log LOG = LogFactory.getLog(Encryption.class);
55  
56    /**
57     * Crypto context
58     */
59    @InterfaceAudience.Public
60    @InterfaceStability.Evolving
61    public static class Context extends org.apache.hadoop.hbase.io.crypto.Context {
62  
63      /** The null crypto context */
64      public static final Context NONE = new Context();
65  
66      private Context() {
67        super();
68      }
69  
70      private Context(Configuration conf) {
71        super(conf);
72      }
73  
74      @Override
75      public Context setCipher(Cipher cipher) {
76        super.setCipher(cipher);
77        return this;
78      }
79  
80      @Override
81      public Context setKey(Key key) {
82        super.setKey(key);
83        return this;
84      }
85  
86      public Context setKey(byte[] key) {
87        super.setKey(new SecretKeySpec(key, getCipher().getName()));
88        return this;
89      }
90    }
91  
92    public static Context newContext() {
93      return new Context();
94    }
95  
96    public static Context newContext(Configuration conf) {
97      return new Context(conf);
98    }
99  
100   // Prevent instantiation
101   private Encryption() {
102     super();
103   }
104 
105   /**
106    * Get an cipher given a name
107    * @param name the cipher name
108    * @return the cipher, or null if a suitable one could not be found
109    */
110   public static Cipher getCipher(Configuration conf, String name) {
111     return getCipherProvider(conf).getCipher(name);
112   }
113 
114   /**
115    * Get names of supported encryption algorithms
116    *
117    * @return Array of strings, each represents a supported encryption algorithm
118    */
119   public static String[] getSupportedCiphers() {
120     return getSupportedCiphers(HBaseConfiguration.create());
121   }
122 
123   /**
124    * Get names of supported encryption algorithms
125    *
126    * @return Array of strings, each represents a supported encryption algorithm
127    */
128   public static String[] getSupportedCiphers(Configuration conf) {
129     return getCipherProvider(conf).getSupportedCiphers();
130   }
131 
132   /**
133    * Return the MD5 digest of the concatenation of the supplied arguments.
134    */
135   public static byte[] hash128(String... args) {
136     byte[] result = new byte[16];
137     try {
138       MessageDigest md = MessageDigest.getInstance("MD5");
139       for (String arg: args) {
140         md.update(Bytes.toBytes(arg));
141       }
142       md.digest(result, 0, result.length);
143       return result;
144     } catch (NoSuchAlgorithmException e) {
145       throw new RuntimeException(e);
146     } catch (DigestException e) {
147       throw new RuntimeException(e);
148     }
149   }
150 
151   /**
152    * Return the MD5 digest of the concatenation of the supplied arguments.
153    */
154   public static byte[] hash128(byte[]... args) {
155     byte[] result = new byte[16];
156     try {
157       MessageDigest md = MessageDigest.getInstance("MD5");
158       for (byte[] arg: args) {
159         md.update(arg);
160       }
161       md.digest(result, 0, result.length);
162       return result;
163     } catch (NoSuchAlgorithmException e) {
164       throw new RuntimeException(e);
165     } catch (DigestException e) {
166       throw new RuntimeException(e);
167     }
168   }
169 
170   /**
171    * Return the SHA-256 digest of the concatenation of the supplied arguments.
172    */
173   public static byte[] hash256(String... args) {
174     byte[] result = new byte[32];
175     try {
176       MessageDigest md = MessageDigest.getInstance("SHA-256");
177       for (String arg: args) {
178         md.update(Bytes.toBytes(arg));
179       }
180       md.digest(result, 0, result.length);
181       return result;
182     } catch (NoSuchAlgorithmException e) {
183       throw new RuntimeException(e);
184     } catch (DigestException e) {
185       throw new RuntimeException(e);
186     }
187   }
188 
189   /**
190    * Return the SHA-256 digest of the concatenation of the supplied arguments.
191    */
192   public static byte[] hash256(byte[]... args) {
193     byte[] result = new byte[32];
194     try {
195       MessageDigest md = MessageDigest.getInstance("SHA-256");
196       for (byte[] arg: args) {
197         md.update(arg);
198       }
199       md.digest(result, 0, result.length);
200       return result;
201     } catch (NoSuchAlgorithmException e) {
202       throw new RuntimeException(e);
203     } catch (DigestException e) {
204       throw new RuntimeException(e);
205     }
206   }
207 
208   /**
209    * Return a 128 bit key derived from the concatenation of the supplied
210    * arguments using PBKDF2WithHmacSHA1 at 10,000 iterations.
211    * 
212    */
213   public static byte[] pbkdf128(String... args) {
214     byte[] salt = new byte[128];
215     Bytes.random(salt);
216     StringBuilder sb = new StringBuilder();
217     for (String s: args) {
218       sb.append(s);
219     }
220     PBEKeySpec spec = new PBEKeySpec(sb.toString().toCharArray(), salt, 10000, 128);
221     try {
222       return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
223         .generateSecret(spec).getEncoded();
224     } catch (NoSuchAlgorithmException e) {
225       throw new RuntimeException(e);
226     } catch (InvalidKeySpecException e) {
227       throw new RuntimeException(e);
228     }
229   }
230 
231   /**
232    * Return a 128 bit key derived from the concatenation of the supplied
233    * arguments using PBKDF2WithHmacSHA1 at 10,000 iterations.
234    * 
235    */
236   public static byte[] pbkdf128(byte[]... args) {
237     byte[] salt = new byte[128];
238     Bytes.random(salt);
239     StringBuilder sb = new StringBuilder();
240     for (byte[] b: args) {
241       sb.append(Arrays.toString(b));
242     }
243     PBEKeySpec spec = new PBEKeySpec(sb.toString().toCharArray(), salt, 10000, 128);
244     try {
245       return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
246         .generateSecret(spec).getEncoded();
247     } catch (NoSuchAlgorithmException e) {
248       throw new RuntimeException(e);
249     } catch (InvalidKeySpecException e) {
250       throw new RuntimeException(e);
251     }
252   }
253 
254   /**
255    * Encrypt a block of plaintext
256    * <p>
257    * The encryptor's state will be finalized. It should be reinitialized or
258    * returned to the pool.
259    * @param out ciphertext
260    * @param src plaintext
261    * @param offset
262    * @param length
263    * @param e
264    * @throws IOException
265     */
266   public static void encrypt(OutputStream out, byte[] src, int offset,
267       int length, Encryptor e) throws IOException {
268     OutputStream cout = e.createEncryptionStream(out);
269     try {
270       cout.write(src, offset, length);
271     } finally {
272       cout.close();
273     }
274   }
275 
276   /**
277    * Encrypt a block of plaintext
278    * @param out ciphertext
279    * @param src plaintext
280    * @param offset
281    * @param length
282    * @param context
283    * @param iv
284    * @throws IOException
285     */
286   public static void encrypt(OutputStream out, byte[] src, int offset,
287       int length, Context context, byte[] iv) throws IOException {
288     Encryptor e = context.getCipher().getEncryptor();
289     e.setKey(context.getKey());
290     e.setIv(iv); // can be null
291     e.reset();
292     encrypt(out, src, offset, length, e);
293   }
294 
295   /**
296    * Encrypt a stream of plaintext given an encryptor
297    * <p>
298    * The encryptor's state will be finalized. It should be reinitialized or
299    * returned to the pool.
300    * @param out ciphertext
301    * @param in plaintext
302    * @param e
303    * @throws IOException
304    */
305   public static void encrypt(OutputStream out, InputStream in, Encryptor e)
306       throws IOException {
307     OutputStream cout = e.createEncryptionStream(out);
308     try {
309       IOUtils.copy(in, cout);
310     } finally {
311       cout.close();
312     }
313   }
314 
315   /**
316    * Encrypt a stream of plaintext given a context and IV
317    * @param out ciphertext
318    * @param in plaintet
319    * @param context
320    * @param iv
321    * @throws IOException
322    */
323   public static void encrypt(OutputStream out, InputStream in, Context context,
324       byte[] iv) throws IOException {
325     Encryptor e = context.getCipher().getEncryptor();
326     e.setKey(context.getKey());
327     e.setIv(iv); // can be null
328     e.reset();
329     encrypt(out, in, e);
330   }
331 
332   /**
333    * Decrypt a block of ciphertext read in from a stream with the given
334    * cipher and context
335    * <p>
336    * The decryptor's state will be finalized. It should be reinitialized or
337    * returned to the pool.
338    * @param dest
339    * @param destOffset
340    * @param in
341    * @param destSize
342    * @param d
343    * @throws IOException
344    */
345   public static void decrypt(byte[] dest, int destOffset, InputStream in,
346       int destSize, Decryptor d) throws IOException {
347     InputStream cin = d.createDecryptionStream(in);
348     try {
349       IOUtils.readFully(cin, dest, destOffset, destSize);
350     } finally {
351       cin.close();
352     }
353   }
354 
355   /**
356    * Decrypt a block of ciphertext from a stream given a context and IV
357    * @param dest
358    * @param destOffset
359    * @param in
360    * @param destSize
361    * @param context
362    * @param iv
363    * @throws IOException
364    */
365   public static void decrypt(byte[] dest, int destOffset, InputStream in,
366       int destSize, Context context, byte[] iv) throws IOException {
367     Decryptor d = context.getCipher().getDecryptor();
368     d.setKey(context.getKey());
369     d.setIv(iv); // can be null
370     decrypt(dest, destOffset, in, destSize, d);
371   }
372 
373   /**
374    * Decrypt a stream of ciphertext given a decryptor
375    * @param out
376    * @param in
377    * @param outLen
378    * @param d
379    * @throws IOException
380    */
381   public static void decrypt(OutputStream out, InputStream in, int outLen,
382       Decryptor d) throws IOException {
383     InputStream cin = d.createDecryptionStream(in);
384     byte buf[] = new byte[8*1024];
385     long remaining = outLen;
386     try {
387       while (remaining > 0) {
388         int toRead = (int)(remaining < buf.length ? remaining : buf.length);
389         int read = cin.read(buf, 0, toRead);
390         if (read < 0) {
391           break;
392         }
393         out.write(buf, 0, read);
394         remaining -= read;
395       }
396     } finally {
397       cin.close();
398     }
399   }
400 
401   /**
402    * Decrypt a stream of ciphertext given a context and IV
403    * @param out
404    * @param in
405    * @param outLen
406    * @param context
407    * @param iv
408    * @throws IOException
409    */
410   public static void decrypt(OutputStream out, InputStream in, int outLen,
411       Context context, byte[] iv) throws IOException {
412     Decryptor d = context.getCipher().getDecryptor();
413     d.setKey(context.getKey());
414     d.setIv(iv); // can be null
415     decrypt(out, in, outLen, d);
416   }
417 
418   /**
419    * Resolves a key for the given subject
420    * @param subject
421    * @param conf
422    * @return a key for the given subject
423    * @throws IOException if the key is not found
424    */
425   public static Key getSecretKeyForSubject(String subject, Configuration conf)
426       throws IOException {
427     KeyProvider provider = (KeyProvider)getKeyProvider(conf);
428     if (provider != null) try {
429       Key[] keys = provider.getKeys(new String[] { subject });
430       if (keys != null && keys.length > 0) {
431         return keys[0];
432       }
433     } catch (Exception e) {
434       throw new IOException(e);
435     }
436     throw new IOException("No key found for subject '" + subject + "'");
437   }
438 
439   /**
440    * Encrypts a block of plaintext with the symmetric key resolved for the given subject
441    * @param out ciphertext
442    * @param in plaintext
443    * @param conf configuration
444    * @param cipher the encryption algorithm
445    * @param iv the initialization vector, can be null
446    * @throws IOException
447    */
448   public static void encryptWithSubjectKey(OutputStream out, InputStream in,
449       String subject, Configuration conf, Cipher cipher, byte[] iv)
450       throws IOException {
451     Key key = getSecretKeyForSubject(subject, conf);
452     if (key == null) {
453       throw new IOException("No key found for subject '" + subject + "'");
454     }
455     Encryptor e = cipher.getEncryptor();
456     e.setKey(key);
457     e.setIv(iv); // can be null
458     encrypt(out, in, e);
459   }
460 
461   /**
462    * Decrypts a block of ciphertext with the symmetric key resolved for the given subject
463    * @param out plaintext
464    * @param in ciphertext
465    * @param outLen the expected plaintext length
466    * @param subject the subject's key alias
467    * @param conf configuration
468    * @param cipher the encryption algorithm
469    * @param iv the initialization vector, can be null
470    * @throws IOException
471    */
472   public static void decryptWithSubjectKey(OutputStream out, InputStream in, int outLen,
473       String subject, Configuration conf, Cipher cipher, byte[] iv) throws IOException {
474     Key key = getSecretKeyForSubject(subject, conf);
475     if (key == null) {
476       throw new IOException("No key found for subject '" + subject + "'");
477     }
478     Decryptor d = cipher.getDecryptor();
479     d.setKey(key);
480     d.setIv(iv); // can be null
481     try {
482       decrypt(out, in, outLen, d);
483     } catch (IOException e) {
484       // If the current cipher algorithm fails to unwrap, try the alternate cipher algorithm, if one
485       // is configured
486       String alternateAlgorithm = conf.get(HConstants.CRYPTO_ALTERNATE_KEY_ALGORITHM_CONF_KEY);
487       if (alternateAlgorithm != null) {
488         if (LOG.isDebugEnabled()) {
489           LOG.debug("Unable to decrypt data with current cipher algorithm '"
490               + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES)
491               + "'. Trying with the alternate cipher algorithm '" + alternateAlgorithm
492               + "' configured.");
493         }
494         Cipher alterCipher = Encryption.getCipher(conf, alternateAlgorithm);
495         if (alterCipher == null) {
496           throw new RuntimeException("Cipher '" + alternateAlgorithm + "' not available");
497         }
498         d = alterCipher.getDecryptor();
499         d.setKey(key);
500         d.setIv(iv); // can be null
501         decrypt(out, in, outLen, d);
502       } else {
503         throw new IOException(e);
504       }
505     }
506   }
507 
508   private static ClassLoader getClassLoaderForClass(Class<?> c) {
509     ClassLoader cl = Thread.currentThread().getContextClassLoader();
510     if (cl == null) {
511       cl = c.getClassLoader();
512     }
513     if (cl == null) {
514       cl = ClassLoader.getSystemClassLoader();
515     }
516     if (cl == null) {
517       throw new RuntimeException("A ClassLoader to load the Cipher could not be determined");
518     }
519     return cl;
520   }
521 
522   public static CipherProvider getCipherProvider(Configuration conf) {
523     String providerClassName = conf.get(HConstants.CRYPTO_CIPHERPROVIDER_CONF_KEY,
524       DefaultCipherProvider.class.getName());
525     try {
526       CipherProvider provider = (CipherProvider)
527         ReflectionUtils.newInstance(getClassLoaderForClass(CipherProvider.class)
528           .loadClass(providerClassName),
529         conf);
530       return provider;
531     } catch (Exception e) {
532       throw new RuntimeException(e);
533     }
534   }
535 
536   static final Map<Pair<String,String>,KeyProvider> keyProviderCache =
537       new ConcurrentHashMap<Pair<String,String>,KeyProvider>();
538 
539   public static KeyProvider getKeyProvider(Configuration conf) {
540     String providerClassName = conf.get(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY,
541       KeyStoreKeyProvider.class.getName());
542     String providerParameters = conf.get(HConstants.CRYPTO_KEYPROVIDER_PARAMETERS_KEY, "");
543     try {
544       Pair<String,String> providerCacheKey = new Pair<String,String>(providerClassName,
545         providerParameters);
546       KeyProvider provider = keyProviderCache.get(providerCacheKey);
547       if (provider != null) {
548         return provider;
549       }
550       provider = (KeyProvider) ReflectionUtils.newInstance(
551         getClassLoaderForClass(KeyProvider.class).loadClass(providerClassName),
552         conf);
553       provider.init(providerParameters);
554       if (LOG.isDebugEnabled()) {
555         LOG.debug("Installed " + providerClassName + " into key provider cache");
556       }
557       keyProviderCache.put(providerCacheKey, provider);
558       return provider;
559     } catch (Exception e) {
560       throw new RuntimeException(e);
561     }
562   }
563 
564   public static void incrementIv(byte[] iv) {
565     incrementIv(iv, 1);
566   }
567 
568   public static void incrementIv(byte[] iv, int v) {
569     int length = iv.length;
570     boolean carry = true;
571     // TODO: Optimize for v > 1, e.g. 16, 32
572     do {
573       for (int i = 0; i < length; i++) {
574         if (carry) {
575           iv[i] = (byte) ((iv[i] + 1) & 0xFF);
576           carry = 0 == iv[i];
577         } else {
578           break;
579         }
580       }
581       v--;
582     } while (v > 0);
583   }
584 
585 }