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.util.Map;
27  import java.util.concurrent.ConcurrentHashMap;
28  
29  import javax.crypto.spec.SecretKeySpec;
30  
31  import org.apache.commons.io.IOUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.classification.InterfaceAudience;
35  import org.apache.hadoop.classification.InterfaceStability;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.hbase.HBaseConfiguration;
38  import org.apache.hadoop.hbase.HConstants;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.hbase.util.Pair;
41  import org.apache.hadoop.util.ReflectionUtils;
42  
43  /**
44   * A facade for encryption algorithms and related support.
45   */
46  @InterfaceAudience.Public
47  @InterfaceStability.Evolving
48  public final class Encryption {
49  
50    private static final Log LOG = LogFactory.getLog(Encryption.class);
51  
52    /**
53     * Crypto context
54     */
55    public static class Context extends org.apache.hadoop.hbase.io.crypto.Context {
56  
57      /** The null crypto context */
58      public static final Context NONE = new Context();
59  
60      private Context() {
61        super();
62      }
63  
64      private Context(Configuration conf) {
65        super(conf);
66      }
67  
68      @Override
69      public Context setCipher(Cipher cipher) {
70        super.setCipher(cipher);
71        return this;
72      }
73  
74      @Override
75      public Context setKey(Key key) {
76        super.setKey(key);
77        return this;
78      }
79  
80      public Context setKey(byte[] key) {
81        super.setKey(new SecretKeySpec(key, getCipher().getName()));
82        return this;
83      }
84    }
85  
86    public static Context newContext() {
87      return new Context();
88    }
89  
90    public static Context newContext(Configuration conf) {
91      return new Context(conf);
92    }
93  
94    // Prevent instantiation
95    private Encryption() {
96      super();
97    }
98  
99    /**
100    * Get an cipher given a name
101    * @param name the cipher name
102    * @return the cipher, or null if a suitable one could not be found
103    */
104   public static Cipher getCipher(Configuration conf, String name) {
105     return getCipherProvider(conf).getCipher(name);
106   }
107 
108   /**
109    * Get names of supported encryption algorithms
110    *
111    * @return Array of strings, each represents a supported encryption algorithm
112    */
113   public static String[] getSupportedCiphers() {
114     return getSupportedCiphers(HBaseConfiguration.create());
115   }
116 
117   /**
118    * Get names of supported encryption algorithms
119    *
120    * @return Array of strings, each represents a supported encryption algorithm
121    */
122   public static String[] getSupportedCiphers(Configuration conf) {
123     return getCipherProvider(conf).getSupportedCiphers();
124   }
125 
126   /**
127    * Return the MD5 digest of the concatenation of the supplied arguments.
128    */
129   public static byte[] hash128(String... args) {
130     byte[] result = new byte[16];
131     try {
132       MessageDigest md = MessageDigest.getInstance("MD5");
133       for (String arg: args) {
134         md.update(Bytes.toBytes(arg));
135       }
136       md.digest(result, 0, result.length);
137       return result;
138     } catch (NoSuchAlgorithmException e) {
139       throw new RuntimeException(e);
140     } catch (DigestException e) {
141       throw new RuntimeException(e);
142     }
143   }
144 
145   /**
146    * Return the MD5 digest of the concatenation of the supplied arguments.
147    */
148   public static byte[] hash128(byte[]... args) {
149     byte[] result = new byte[16];
150     try {
151       MessageDigest md = MessageDigest.getInstance("MD5");
152       for (byte[] arg: args) {
153         md.update(arg);
154       }
155       md.digest(result, 0, result.length);
156       return result;
157     } catch (NoSuchAlgorithmException e) {
158       throw new RuntimeException(e);
159     } catch (DigestException e) {
160       throw new RuntimeException(e);
161     }
162   }
163 
164   /**
165    * Return the SHA-256 digest of the concatenation of the supplied arguments.
166    */
167   public static byte[] hash256(String... args) {
168     byte[] result = new byte[32];
169     try {
170       MessageDigest md = MessageDigest.getInstance("SHA-256");
171       for (String arg: args) {
172         md.update(Bytes.toBytes(arg));
173       }
174       md.digest(result, 0, result.length);
175       return result;
176     } catch (NoSuchAlgorithmException e) {
177       throw new RuntimeException(e);
178     } catch (DigestException e) {
179       throw new RuntimeException(e);
180     }
181   }
182 
183   /**
184    * Return the SHA-256 digest of the concatenation of the supplied arguments.
185    */
186   public static byte[] hash256(byte[]... args) {
187     byte[] result = new byte[32];
188     try {
189       MessageDigest md = MessageDigest.getInstance("SHA-256");
190       for (byte[] arg: args) {
191         md.update(arg);
192       }
193       md.digest(result, 0, result.length);
194       return result;
195     } catch (NoSuchAlgorithmException e) {
196       throw new RuntimeException(e);
197     } catch (DigestException e) {
198       throw new RuntimeException(e);
199     }
200   }
201 
202   /**
203    * Encrypt a block of plaintext
204    * <p>
205    * The encryptor's state will be finalized. It should be reinitialized or
206    * returned to the pool.
207    * @param out ciphertext
208    * @param src plaintext
209    * @param offset
210    * @param length
211    * @param e
212    * @throws IOException
213     */
214   public static void encrypt(OutputStream out, byte[] src, int offset,
215       int length, Encryptor e) throws IOException {
216     OutputStream cout = e.createEncryptionStream(out);
217     try {
218       cout.write(src, offset, length);
219     } finally {
220       cout.close();
221     }
222   }
223 
224   /**
225    * Encrypt a block of plaintext
226    * @param out ciphertext
227    * @param src plaintext
228    * @param offset
229    * @param length
230    * @param context
231    * @param iv
232    * @throws IOException
233     */
234   public static void encrypt(OutputStream out, byte[] src, int offset,
235       int length, Context context, byte[] iv) throws IOException {
236     Encryptor e = context.getCipher().getEncryptor();
237     e.setKey(context.getKey());
238     e.setIv(iv); // can be null
239     e.reset();
240     encrypt(out, src, offset, length, e);
241   }
242 
243   /**
244    * Encrypt a stream of plaintext given an encryptor
245    * <p>
246    * The encryptor's state will be finalized. It should be reinitialized or
247    * returned to the pool.
248    * @param out ciphertext
249    * @param in plaintext
250    * @param e
251    * @throws IOException
252    */
253   public static void encrypt(OutputStream out, InputStream in, Encryptor e)
254       throws IOException {
255     OutputStream cout = e.createEncryptionStream(out);
256     try {
257       IOUtils.copy(in, cout);
258     } finally {
259       cout.close();
260     }
261   }
262 
263   /**
264    * Encrypt a stream of plaintext given a context and IV
265    * @param out ciphertext
266    * @param in plaintet
267    * @param context
268    * @param iv
269    * @throws IOException
270    */
271   public static void encrypt(OutputStream out, InputStream in, Context context,
272       byte[] iv) throws IOException {
273     Encryptor e = context.getCipher().getEncryptor();
274     e.setKey(context.getKey());
275     e.setIv(iv); // can be null
276     e.reset();
277     encrypt(out, in, e);
278   }
279 
280   /**
281    * Decrypt a block of ciphertext read in from a stream with the given
282    * cipher and context
283    * <p>
284    * The decryptor's state will be finalized. It should be reinitialized or
285    * returned to the pool.
286    * @param dest
287    * @param destOffset
288    * @param in
289    * @param destSize
290    * @param d
291    * @throws IOException
292    */
293   public static void decrypt(byte[] dest, int destOffset, InputStream in,
294       int destSize, Decryptor d) throws IOException {
295     InputStream cin = d.createDecryptionStream(in);
296     try {
297       IOUtils.readFully(cin, dest, destOffset, destSize);
298     } finally {
299       cin.close();
300     }
301   }
302 
303   /**
304    * Decrypt a block of ciphertext from a stream given a context and IV
305    * @param dest
306    * @param destOffset
307    * @param in
308    * @param destSize
309    * @param context
310    * @param iv
311    * @throws IOException
312    */
313   public static void decrypt(byte[] dest, int destOffset, InputStream in,
314       int destSize, Context context, byte[] iv) throws IOException {
315     Decryptor d = context.getCipher().getDecryptor();
316     d.setKey(context.getKey());
317     d.setIv(iv); // can be null
318     decrypt(dest, destOffset, in, destSize, d);
319   }
320 
321   /**
322    * Decrypt a stream of ciphertext given a decryptor
323    * @param out
324    * @param in
325    * @param outLen
326    * @param d
327    * @throws IOException
328    */
329   public static void decrypt(OutputStream out, InputStream in, int outLen,
330       Decryptor d) throws IOException {
331     InputStream cin = d.createDecryptionStream(in);
332     byte buf[] = new byte[8*1024];
333     long remaining = outLen;
334     try {
335       while (remaining > 0) {
336         int toRead = (int)(remaining < buf.length ? remaining : buf.length);
337         int read = cin.read(buf, 0, toRead);
338         if (read < 0) {
339           break;
340         }
341         out.write(buf, 0, read);
342         remaining -= read;
343       }
344     } finally {
345       cin.close();
346     }
347   }
348 
349   /**
350    * Decrypt a stream of ciphertext given a context and IV
351    * @param out
352    * @param in
353    * @param outLen
354    * @param context
355    * @param iv
356    * @throws IOException
357    */
358   public static void decrypt(OutputStream out, InputStream in, int outLen,
359       Context context, byte[] iv) throws IOException {
360     Decryptor d = context.getCipher().getDecryptor();
361     d.setKey(context.getKey());
362     d.setIv(iv); // can be null
363     decrypt(out, in, outLen, d);
364   }
365 
366   /**
367    * Resolves a key for the given subject
368    * @param subject
369    * @param conf
370    * @return a key for the given subject
371    * @throws IOException if the key is not found
372    */
373   public static Key getSecretKeyForSubject(String subject, Configuration conf)
374       throws IOException {
375     KeyProvider provider = (KeyProvider)getKeyProvider(conf);
376     if (provider != null) try {
377       Key[] keys = provider.getKeys(new String[] { subject });
378       if (keys != null && keys.length > 0) {
379         return keys[0];
380       }
381     } catch (Exception e) {
382       throw new IOException(e);
383     }
384     throw new IOException("No key found for subject '" + subject + "'");
385   }
386 
387   /**
388    * Encrypts a block of plaintext with the symmetric key resolved for the given subject
389    * @param out ciphertext
390    * @param in plaintext
391    * @param conf configuration
392    * @param cipher the encryption algorithm
393    * @param iv the initialization vector, can be null
394    * @throws IOException
395    */
396   public static void encryptWithSubjectKey(OutputStream out, InputStream in,
397       String subject, Configuration conf, Cipher cipher, byte[] iv)
398       throws IOException {
399     Key key = getSecretKeyForSubject(subject, conf);
400     if (key == null) {
401       throw new IOException("No key found for subject '" + subject + "'");
402     }
403     Encryptor e = cipher.getEncryptor();
404     e.setKey(key);
405     e.setIv(iv); // can be null
406     encrypt(out, in, e);
407   }
408 
409   /**
410    * Decrypts a block of ciphertext with the symmetric key resolved for the given subject
411    * @param out plaintext
412    * @param in ciphertext
413    * @param outLen the expected plaintext length
414    * @param subject the subject's key alias
415    * @param conf configuration
416    * @param cipher the encryption algorithm
417    * @param iv the initialization vector, can be null
418    * @throws IOException
419    */
420   public static void decryptWithSubjectKey(OutputStream out, InputStream in,
421       int outLen, String subject, Configuration conf, Cipher cipher,
422       byte[] iv) throws IOException {
423     Key key = getSecretKeyForSubject(subject, conf);
424     if (key == null) {
425       throw new IOException("No key found for subject '" + subject + "'");
426     }
427     Decryptor d = cipher.getDecryptor();
428     d.setKey(key);
429     d.setIv(iv); // can be null
430     decrypt(out, in, outLen, d);
431   }
432 
433   private static ClassLoader getClassLoaderForClass(Class<?> c) {
434     ClassLoader cl = Thread.currentThread().getContextClassLoader();
435     if (cl == null) {
436       cl = c.getClassLoader();
437     }
438     if (cl == null) {
439       cl = ClassLoader.getSystemClassLoader();
440     }
441     if (cl == null) {
442       throw new RuntimeException("A ClassLoader to load the Cipher could not be determined");
443     }
444     return cl;
445   }
446 
447   public static CipherProvider getCipherProvider(Configuration conf) {
448     String providerClassName = conf.get(HConstants.CRYPTO_CIPHERPROVIDER_CONF_KEY,
449       DefaultCipherProvider.class.getName());
450     try {
451       CipherProvider provider = (CipherProvider)
452         ReflectionUtils.newInstance(getClassLoaderForClass(CipherProvider.class)
453           .loadClass(providerClassName),
454         conf);
455       return provider;
456     } catch (Exception e) {
457       throw new RuntimeException(e);
458     }
459   }
460 
461   static final Map<Pair<String,String>,KeyProvider> keyProviderCache =
462       new ConcurrentHashMap<Pair<String,String>,KeyProvider>();
463 
464   public static KeyProvider getKeyProvider(Configuration conf) {
465     String providerClassName = conf.get(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY,
466       KeyStoreKeyProvider.class.getName());
467     String providerParameters = conf.get(HConstants.CRYPTO_KEYPROVIDER_PARAMETERS_KEY, "");
468     try {
469       Pair<String,String> providerCacheKey = new Pair<String,String>(providerClassName,
470         providerParameters);
471       KeyProvider provider = keyProviderCache.get(providerCacheKey);
472       if (provider != null) {
473         return provider;
474       }
475       provider = (KeyProvider) ReflectionUtils.newInstance(
476         getClassLoaderForClass(KeyProvider.class).loadClass(providerClassName),
477         conf);
478       provider.init(providerParameters);
479       if (LOG.isDebugEnabled()) {
480         LOG.debug("Installed " + providerClassName + " into key provider cache");
481       }
482       keyProviderCache.put(providerCacheKey, provider);
483       return provider;
484     } catch (Exception e) {
485       throw new RuntimeException(e);
486     }
487   }
488 
489   public static void incrementIv(byte[] iv) {
490     int length = iv.length;
491     boolean carry = true;
492     for (int i = 0; i < length; i++) {
493       if (carry) {
494         iv[i] = (byte) ((iv[i] + 1) & 0xFF);
495         carry = 0 == iv[i];
496       } else {
497         break;
498       }
499     }
500   }
501 
502 }