001/*
002 * Copyright 2018-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2018-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util;
022
023
024
025import java.io.IOException;
026import java.io.OutputStream;
027import java.security.SecureRandom;
028import java.security.GeneralSecurityException;
029import java.util.concurrent.atomic.AtomicReference;
030import javax.crypto.Cipher;
031import javax.crypto.CipherOutputStream;
032
033
034
035/**
036 * This class provides an {@code OutputStream} implementation that will encrypt
037 * all data written to it with a key generated from a passphrase.  Details about
038 * the encryption will be encapsulated in a
039 * {@link PassphraseEncryptedStreamHeader}, which will typically be written to
040 * the underlying stream before any of the encrypted data, so that the
041 * {@link PassphraseEncryptedInputStream} can read it to determine how to
042 * decrypt that data when provided with the same passphrase.  However, it is
043 * also possible to store the encryption header elsewhere and provide it to the
044 * {@code PassphraseEncryptedInputStream} constructor so that that the
045 * underlying stream will only include encrypted data.
046 * <BR><BR>
047 * The specific details of the encryption performed may change over time, but
048 * the information in the header should ensure that data encrypted with
049 * different settings can still be decrypted (as long as the JVM provides the
050 * necessary support for that encryption).  The current implementation uses a
051 * baseline of 128-bit AES/CBC/PKCS5Padding using a key generated from the
052 * provided passphrase using the PBKDF2WithHmacSHA1 key factory algorithm
053 * (unfortunately, PBKDF2WithHmacSHA256 isn't available on Java 7, which is
054 * still a supported Java version for the LDAP SDK) with 16,384 iterations and a
055 * 128-bit (16-byte) salt.  However, if the  output stream is configured to use
056 * strong encryption, then it will attempt to use 256-bit AES/CBC/PKCS5Padding
057 * with a PBKDF2WithHmacSHA512 key factory algorithm with 131,072 iterations and
058 * a 128-bit salt.  If the JVM does not support this level of encryption, then
059 * it will fall back to a key size of 128 bits and a key factory algorithm of
060 * PBKDF2WithHmacSHA1.
061 * <BR><BR>
062 * Note that the use of strong encryption may require special configuration for
063 * some versions of the JVM (for example, installation of JCE unlimited strength
064 * jurisdiction policy files).  If data encrypted on one system may need to be
065 * decrypted on another system, then you should make sure that all systems will
066 * support the stronger encryption option before choosing to use it over the
067 * baseline encryption option.
068 */
069@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
070public final class PassphraseEncryptedOutputStream
071     extends OutputStream
072{
073  /**
074   * An atomic reference that indicates whether the JVM supports the stronger
075   * encryption settings.  It will be {@code null} until an attempt is made to
076   * use stronger encryption, at which point the determination will be made and
077   * a value assigned.  The cached value will be used for subsequent attempts to
078   * use the strong encryption.
079   */
080  private static final AtomicReference<Boolean> SUPPORTS_STRONG_ENCRYPTION =
081       new AtomicReference<>();
082
083
084
085  /**
086   * The length (in bytes) of the initialization vector that will be generated
087   * for the cipher.
088   */
089  private static final int CIPHER_INITIALIZATION_VECTOR_LENGTH_BYTES = 16;
090
091
092
093  /**
094   * The length (in bits) for the encryption key to generate from the password
095   * when using the baseline encryption strength.
096   */
097  private static final int BASELINE_KEY_FACTORY_KEY_LENGTH_BITS = 128;
098
099
100
101  /**
102   * The length (in bits) for the encryption key to generate from the password
103   * when using strong encryption.
104   */
105  private static final int STRONG_KEY_FACTORY_KEY_LENGTH_BITS = 256;
106
107
108
109  /**
110   * The key factory iteration count that will be used when generating the
111   * encryption key from the passphrase when using the baseline encryption
112   * strength.
113   */
114  private static final int BASELINE_KEY_FACTORY_ITERATION_COUNT = 16_384;
115
116
117
118  /**
119   * The key factory iteration count that will be used when generating the
120   * encryption key from the passphrase when using the strong encryption.
121   */
122  private static final int STRONG_KEY_FACTORY_ITERATION_COUNT = 131_072;
123
124
125
126  /**
127   * The length (in bytes) of the key factory salt that will be used when
128   * generating the encryption key from the passphrase.
129   */
130  private static final int KEY_FACTORY_SALT_LENGTH_BYTES = 16;
131
132
133
134  /**
135   * The cipher transformation that will be used for the encryption.
136   */
137  private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
138
139
140
141  /**
142   * The key factory algorithm that will be used when generating the encryption
143   * key from the passphrase when using the baseline encryption strength.
144   */
145  private static final String BASELINE_KEY_FACTORY_ALGORITHM =
146       "PBKDF2WithHmacSHA1";
147
148
149
150  /**
151   * The key factory algorithm that will be used when generating the encryption
152   * key from the passphrase when using strong encryption.
153   */
154  private static final String STRONG_KEY_FACTORY_ALGORITHM =
155       "PBKDF2WithHmacSHA512";
156
157
158
159  /**
160   * The algorithm that will be used when generating a MAC of the header
161   * contents when using the baseline encryption strength.
162   */
163  private static final String BASELINE_MAC_ALGORITHM = "HmacSHA256";
164
165
166
167  /**
168   * The algorithm that will be used when generating a MAC of the header
169   * contents when using strong encryption.
170   */
171  private static final String STRONG_MAC_ALGORITHM = "HmacSHA512";
172
173
174
175  // The cipher output stream that will be used to actually write the
176  // encrypted output.
177  private final CipherOutputStream cipherOutputStream;
178
179  // A header containing the encoded encryption details.
180  private final PassphraseEncryptedStreamHeader encryptionHeader;
181
182
183
184  /**
185   * Creates a new passphrase-encrypted output stream with the provided
186   * information.  It will not use a key identifier, will use the baseline
187   * encryption strength rather than attempting to use strong encryption, and it
188   * will write the generated {@link PassphraseEncryptedStreamHeader} to the
189   * underlying stream before writing any encrypted data.
190   *
191   * @param  passphrase           The passphrase that will be used to generate
192   *                              the encryption key.  It must not be
193   *                              {@code null}.
194   * @param  wrappedOutputStream  The output stream to which the encrypted data
195   *                              (optionally preceded by a header with
196   *                              details about the encryption) will be written.
197   *                              It must not be {@code null}.
198   *
199   * @throws  GeneralSecurityException  If a problem is encountered while
200   *                                    initializing the encryption.
201   *
202   * @throws  IOException  If a problem is encountered while writing the
203   *                       encryption header to the underlying output stream.
204   */
205  public PassphraseEncryptedOutputStream(final String passphrase,
206                                         final OutputStream wrappedOutputStream)
207         throws GeneralSecurityException, IOException
208  {
209    this(passphrase.toCharArray(), wrappedOutputStream);
210  }
211
212
213
214  /**
215   * Creates a new passphrase-encrypted output stream with the provided
216   * information.  It will not use a key identifier, will use the baseline
217   * encryption strength rather than attempting to use strong encryption, and it
218   * will write the generated {@link PassphraseEncryptedStreamHeader} to the
219   * underlying stream before writing any encrypted data.
220   *
221   * @param  passphrase           The passphrase that will be used to generate
222   *                              the encryption key.  It must not be
223   *                              {@code null}.
224   * @param  wrappedOutputStream  The output stream to which the encrypted data
225   *                              (optionally preceded by a header with
226   *                              details about the encryption) will be written.
227   *                              It must not be {@code null}.
228   *
229   * @throws  GeneralSecurityException  If a problem is encountered while
230   *                                    initializing the encryption.
231   *
232   * @throws  IOException  If a problem is encountered while writing the
233   *                       encryption header to the underlying output stream.
234   */
235  public PassphraseEncryptedOutputStream(final char[] passphrase,
236                                         final OutputStream wrappedOutputStream)
237         throws GeneralSecurityException, IOException
238  {
239    this(passphrase, wrappedOutputStream, null, false, true);
240  }
241
242
243
244  /**
245   * Creates a new passphrase-encrypted output stream with the provided
246   * information.
247   *
248   * @param  passphrase           The passphrase that will be used to generate
249   *                              the encryption key.  It must not be
250   *                              {@code null}.
251   * @param  wrappedOutputStream  The output stream to which the encrypted data
252   *                              (optionally preceded by a header with
253   *                              details about the encryption) will be written.
254   *                              It must not be {@code null}.
255   * @param  keyIdentifier        An optional identifier that may be used to
256   *                              associate the encryption details with
257   *                              information in another system.  This is
258   *                              primarily intended for use in conjunction with
259   *                              UnboundID/Ping Identity products, but may be
260   *                              useful in other systems.  It may be
261   *                              {@code null} if no key identifier is needed.
262   * @param  useStrongEncryption  Indicates whether to attempt to use strong
263   *                              encryption, if it is available.  If this is
264   *                              {@code true} and the JVM supports the stronger
265   *                              level of encryption, then that encryption will
266   *                              be used.  If this is {@code false}, or if the
267   *                              JVM does not support the attempted stronger
268   *                              level of encryption, then the baseline
269   *                              configuration will be used.
270   * @param  writeHeaderToStream  Indicates whether to write the generated
271   *                              {@link PassphraseEncryptedStreamHeader} to the
272   *                              provided {@code wrappedOutputStream} before
273   *                              any encrypted data so that a
274   *                              {@link PassphraseEncryptedInputStream} can
275   *                              read it to obtain information necessary for
276   *                              decrypting the data.  If this is
277   *                              {@code false}, then the
278   *                              {@link #getEncryptionHeader()} method must be
279   *                              used to obtain the encryption header so that
280   *                              it can be stored elsewhere and provided to the
281   *                              {@code PassphraseEncryptedInputStream}
282   *                              constructor.
283   *
284   * @throws  GeneralSecurityException  If a problem is encountered while
285   *                                    initializing the encryption.
286   *
287   * @throws  IOException  If a problem is encountered while writing the
288   *                       encryption header to the underlying output stream.
289   */
290  public PassphraseEncryptedOutputStream(final String passphrase,
291                                         final OutputStream wrappedOutputStream,
292                                         final String keyIdentifier,
293                                         final boolean useStrongEncryption,
294                                         final boolean writeHeaderToStream)
295         throws GeneralSecurityException, IOException
296  {
297    this(passphrase.toCharArray(), wrappedOutputStream, keyIdentifier,
298         useStrongEncryption, writeHeaderToStream);
299  }
300
301
302
303  /**
304   * Creates a new passphrase-encrypted output stream with the provided
305   * information.
306   *
307   * @param  passphrase           The passphrase that will be used to generate
308   *                              the encryption key.  It must not be
309   *                              {@code null}.
310   * @param  wrappedOutputStream  The output stream to which the encrypted data
311   *                              (optionally preceded by a header with
312   *                              details about the encryption) will be written.
313   *                              It must not be {@code null}.
314   * @param  keyIdentifier        An optional identifier that may be used to
315   *                              associate the encryption details with
316   *                              information in another system.  This is
317   *                              primarily intended for use in conjunction with
318   *                              UnboundID/Ping Identity products, but may be
319   *                              useful in other systems.  It may be
320   *                              {@code null} if no key identifier is needed.
321   * @param  useStrongEncryption  Indicates whether to attempt to use strong
322   *                              encryption, if it is available.  If this is
323   *                              {@code true} and the JVM supports the stronger
324   *                              level of encryption, then that encryption will
325   *                              be used.  If this is {@code false}, or if the
326   *                              JVM does not support the attempted stronger
327   *                              level of encryption, then the baseline
328   *                              configuration will be used.
329   * @param  writeHeaderToStream  Indicates whether to write the generated
330   *                              {@link PassphraseEncryptedStreamHeader} to the
331   *                              provided {@code wrappedOutputStream} before
332   *                              any encrypted data so that a
333   *                              {@link PassphraseEncryptedInputStream} can
334   *                              read it to obtain information necessary for
335   *                              decrypting the data.  If this is
336   *                              {@code false}, then the
337   *                              {@link #getEncryptionHeader()} method must be
338   *                              used to obtain the encryption header so that
339   *                              it can be stored elsewhere and provided to the
340   *                              {@code PassphraseEncryptedInputStream}
341   *                              constructor.
342   *
343   * @throws  GeneralSecurityException  If a problem is encountered while
344   *                                    initializing the encryption.
345   *
346   * @throws  IOException  If a problem is encountered while writing the
347   *                       encryption header to the underlying output stream.
348   */
349  public PassphraseEncryptedOutputStream(final char[] passphrase,
350                                         final OutputStream wrappedOutputStream,
351                                         final String keyIdentifier,
352                                         final boolean useStrongEncryption,
353                                         final boolean writeHeaderToStream)
354         throws GeneralSecurityException, IOException
355  {
356    final SecureRandom random = new SecureRandom();
357
358    final byte[] keyFactorySalt = new byte[KEY_FACTORY_SALT_LENGTH_BYTES];
359    random.nextBytes(keyFactorySalt);
360
361    final byte[] cipherInitializationVector =
362         new byte[CIPHER_INITIALIZATION_VECTOR_LENGTH_BYTES];
363    random.nextBytes(cipherInitializationVector);
364
365    final int keyFactoryIterationCount;
366    final String macAlgorithm;
367    PassphraseEncryptedStreamHeader header = null;
368    CipherOutputStream cipherStream = null;
369    if (useStrongEncryption)
370    {
371      keyFactoryIterationCount = STRONG_KEY_FACTORY_ITERATION_COUNT;
372      macAlgorithm = STRONG_MAC_ALGORITHM;
373
374      final Boolean supportsStrongEncryption = SUPPORTS_STRONG_ENCRYPTION.get();
375      if ((supportsStrongEncryption == null) ||
376           Boolean.TRUE.equals(supportsStrongEncryption))
377      {
378        try
379        {
380          header = new PassphraseEncryptedStreamHeader(passphrase,
381               STRONG_KEY_FACTORY_ALGORITHM, keyFactoryIterationCount,
382               keyFactorySalt, STRONG_KEY_FACTORY_KEY_LENGTH_BITS,
383               CIPHER_TRANSFORMATION, cipherInitializationVector,
384               keyIdentifier, macAlgorithm);
385
386          final Cipher cipher = header.createCipher(Cipher.ENCRYPT_MODE);
387          if (writeHeaderToStream)
388          {
389            header.writeTo(wrappedOutputStream);
390          }
391
392          cipherStream = new CipherOutputStream(wrappedOutputStream, cipher);
393          SUPPORTS_STRONG_ENCRYPTION.compareAndSet(null, Boolean.TRUE);
394        }
395        catch (final Exception e)
396        {
397          Debug.debugException(e);
398          SUPPORTS_STRONG_ENCRYPTION.set(Boolean.FALSE);
399        }
400      }
401    }
402    else
403    {
404      keyFactoryIterationCount = BASELINE_KEY_FACTORY_ITERATION_COUNT;
405      macAlgorithm = BASELINE_MAC_ALGORITHM;
406    }
407
408    if (cipherStream == null)
409    {
410      header = new PassphraseEncryptedStreamHeader(passphrase,
411           BASELINE_KEY_FACTORY_ALGORITHM, keyFactoryIterationCount,
412           keyFactorySalt, BASELINE_KEY_FACTORY_KEY_LENGTH_BITS,
413           CIPHER_TRANSFORMATION, cipherInitializationVector, keyIdentifier,
414           macAlgorithm);
415
416      final Cipher cipher = header.createCipher(Cipher.ENCRYPT_MODE);
417      if (writeHeaderToStream)
418      {
419        header.writeTo(wrappedOutputStream);
420      }
421
422      cipherStream = new CipherOutputStream(wrappedOutputStream, cipher);
423    }
424
425    encryptionHeader = header;
426    cipherOutputStream = cipherStream;
427  }
428
429
430
431  /**
432   * Writes an encrypted representation of the provided byte to the underlying
433   * output stream.
434   *
435   * @param  b  The byte of data to be written.  Only the least significant 8
436   *            bits of the value will be used, and the most significant 24 bits
437   *            will be ignored.
438   *
439   * @throws  IOException  If a problem is encountered while encrypting the data
440   *                       or writing to the underlying output stream.
441   */
442  @Override()
443  public void write(final int b)
444         throws IOException
445  {
446    cipherOutputStream.write(b);
447  }
448
449
450
451  /**
452   * Writes an encrypted representation of the contents of the provided byte
453   * array to the underlying output stream.
454   *
455   * @param  b  The array containing the data to be written.  It must not be
456   *            {@code null}.  All bytes in the array will be written.
457   *
458   * @throws  IOException  If a problem is encountered while encrypting the data
459   *                       or writing to the underlying output stream.
460   */
461  @Override()
462  public void write(final byte[] b)
463         throws IOException
464  {
465    cipherOutputStream.write(b);
466  }
467
468
469
470  /**
471   * Writes an encrypted representation of the specified portion of the provided
472   * byte array to the underlying output stream.
473   *
474   * @param  b       The array containing the data to be written.  It must not
475   *                 be {@code null}.
476   * @param  offset  The index in the array of the first byte to be written.
477   *                 It must be greater than or equal to zero, and less than the
478   *                 length of the provided array.
479   * @param  length  The number of bytes to be written.  It must be greater than
480   *                 or equal to zero, and the sum of the {@code offset} and
481   *                 {@code length} values must be less than or equal to the
482   *                 length of the provided array.
483   *
484   * @throws  IOException  If a problem is encountered while encrypting the data
485   *                       or writing to the underlying output stream.
486   */
487  @Override()
488  public void write(final byte[] b, final int offset, final int length)
489         throws IOException
490  {
491    cipherOutputStream.write(b, offset, length);
492  }
493
494
495
496  /**
497   * Flushes the underlying output stream so that any buffered encrypted output
498   * will be written to the underlying output stream, and also flushes the
499   * underlying output stream.  Note that this call may not flush any data that
500   * has yet to be encrypted (for example, because the encryption uses a block
501   * cipher and the associated block is not yet full).
502   *
503   * @throws  IOException  If a problem is encountered while flushing data to
504   *                       the underlying output stream.
505   */
506  @Override()
507  public void flush()
508         throws IOException
509  {
510    cipherOutputStream.flush();
511  }
512
513
514
515  /**
516   * Closes this output stream, along with the underlying output stream.  Any
517   * remaining buffered data will be processed (including generating any
518   * necessary padding) and flushed to the underlying output stream before the
519   * streams are closed.
520   *
521   * @throws  IOException  If a problem is encountered while closing the stream.
522   */
523  @Override()
524  public void close()
525         throws IOException
526  {
527    cipherOutputStream.close();
528  }
529
530
531
532  /**
533   * Retrieves an encryption header with details about the encryption being
534   * used.  If this header was not automatically written to the beginning of the
535   * underlying output stream before any encrypted data, then it must be stored
536   * somewhere else so that it can be provided to the
537   * {@link PassphraseEncryptedInputStream} constructor.
538   *
539   * @return  An encryption header with details about the encryption being used.
540   */
541  public PassphraseEncryptedStreamHeader getEncryptionHeader()
542  {
543    return encryptionHeader;
544  }
545}