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
192   *              The passphrase that will be used to generate the encryption
193   *              key.  It must not be {@code null}.
194   * @param  wrappedOutputStream
195   *              The output stream to which the encrypted data (optionally
196   *              preceded by a header with details about the encryption) will
197   *              be written.  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
222   *              The passphrase that will be used to generate the encryption
223   *              key.  It must not be {@code null}.
224   * @param  wrappedOutputStream
225   *              The output stream to which the encrypted data (optionally
226   *              preceded by a header with details about the encryption) will
227   *              be written.  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
249   *              The passphrase that will be used to generate the encryption
250   *              key.  It must not be {@code null}.
251   * @param  wrappedOutputStream
252   *              The output stream to which the encrypted data (optionally
253   *              preceded by a header with details about the encryption) will
254   *              be written.  It must not be {@code null}.
255   * @param  keyIdentifier
256   *              An optional identifier that may be used to associate the
257   *              encryption details with information in another system.  This
258   *              is primarily intended for use in conjunction with
259   *              UnboundID/Ping Identity products, but may be useful in other
260   *              systems.  It may be {@code null} if no key identifier is
261   *              needed.
262   * @param  useStrongEncryption
263   *              Indicates whether to attempt to use strong encryption, if it
264   *              is available.  If this is {@code true} and the JVM supports
265   *              the stronger level of encryption, then that encryption will be
266   *              used.  If this is {@code false}, or if the JVM does not
267   *              support the attempted stronger level of encryption, then the
268   *              baseline configuration will be used.
269   * @param  writeHeaderToStream
270   *              Indicates whether to write the generated
271   *              {@link PassphraseEncryptedStreamHeader} to the provided
272   *              {@code wrappedOutputStream} before any encrypted data so that
273   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
274   *              information necessary for decrypting the data.  If this is
275   *              {@code false}, then the {@link #getEncryptionHeader()} method
276   *              must be used to obtain the encryption header so that it can be
277   *              stored elsewhere and provided to the
278   *              {@code PassphraseEncryptedInputStream} constructor.
279   *
280   * @throws  GeneralSecurityException  If a problem is encountered while
281   *                                    initializing the encryption.
282   *
283   * @throws  IOException  If a problem is encountered while writing the
284   *                       encryption header to the underlying output stream.
285   */
286  public PassphraseEncryptedOutputStream(final String passphrase,
287                                         final OutputStream wrappedOutputStream,
288                                         final String keyIdentifier,
289                                         final boolean useStrongEncryption,
290                                         final boolean writeHeaderToStream)
291         throws GeneralSecurityException, IOException
292  {
293    this(passphrase.toCharArray(), wrappedOutputStream, keyIdentifier,
294         useStrongEncryption, writeHeaderToStream);
295  }
296
297
298
299  /**
300   * Creates a new passphrase-encrypted output stream with the provided
301   * information.
302   *
303   * @param  passphrase
304   *              The passphrase that will be used to generate the encryption
305   *              key.  It must not be {@code null}.
306   * @param  wrappedOutputStream
307   *              The output stream to which the encrypted data (optionally
308   *              preceded by a header with details about the encryption) will
309   *              be written.  It must not be {@code null}.
310   * @param  keyIdentifier
311   *              An optional identifier that may be used to associate the
312   *              encryption details with information in another system.  This
313   *              is primarily intended for use in conjunction with
314   *              UnboundID/Ping Identity products, but may be useful in other
315   *              systems.  It may be {@code null} if no key identifier is
316   *              needed.
317   * @param  useStrongEncryption
318   *              Indicates whether to attempt to use strong encryption, if it
319   *              is available.  If this is {@code true} and the JVM supports
320   *              the stronger level of encryption, then that encryption will be
321   *              used.  If this is {@code false}, or if the JVM does not
322   *              support the attempted stronger level of encryption, then the
323   *              baseline configuration will be used.
324   * @param  writeHeaderToStream
325   *              Indicates whether to write the generated
326   *              {@link PassphraseEncryptedStreamHeader} to the provided
327   *              {@code wrappedOutputStream} before any encrypted data so that
328   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
329   *              information necessary for decrypting the data.  If this is
330   *              {@code false}, then the {@link #getEncryptionHeader()} method
331   *              must be used to obtain the encryption header so that it can be
332   *              stored elsewhere and provided to the
333   *              {@code PassphraseEncryptedInputStream} constructor.
334   *
335   * @throws  GeneralSecurityException  If a problem is encountered while
336   *                                    initializing the encryption.
337   *
338   * @throws  IOException  If a problem is encountered while writing the
339   *                       encryption header to the underlying output stream.
340   */
341  public PassphraseEncryptedOutputStream(final char[] passphrase,
342                                         final OutputStream wrappedOutputStream,
343                                         final String keyIdentifier,
344                                         final boolean useStrongEncryption,
345                                         final boolean writeHeaderToStream)
346         throws GeneralSecurityException, IOException
347  {
348    this(passphrase, wrappedOutputStream, keyIdentifier, useStrongEncryption,
349         (useStrongEncryption
350              ? STRONG_KEY_FACTORY_ITERATION_COUNT
351              : BASELINE_KEY_FACTORY_ITERATION_COUNT),
352         writeHeaderToStream);
353  }
354
355
356
357  /**
358   * Creates a new passphrase-encrypted output stream with the provided
359   * information.
360   *
361   * @param  passphrase
362   *              The passphrase that will be used to generate the encryption
363   *              key.  It must not be {@code null}.
364   * @param  wrappedOutputStream
365   *              The output stream to which the encrypted data (optionally
366   *              preceded by a header with details about the encryption) will
367   *              be written.  It must not be {@code null}.
368   * @param  keyIdentifier
369   *              An optional identifier that may be used to associate the
370   *              encryption details with information in another system.  This
371   *              is primarily intended for use in conjunction with
372   *              UnboundID/Ping Identity products, but may be useful in other
373   *              systems.  It may be {@code null} if no key identifier is
374   *              needed.
375   * @param  useStrongEncryption
376   *              Indicates whether to attempt to use strong encryption, if it
377   *              is available.  If this is {@code true} and the JVM supports
378   *              the stronger level of encryption, then that encryption will be
379   *              used.  If this is {@code false}, or if the JVM does not
380   *              support the attempted stronger level of encryption, then the
381   *              baseline configuration will be used.
382   * @param  keyFactoryIterationCount
383   *              The iteration count to use when generating the encryption key
384   *              from the provided passphrase.
385   * @param  writeHeaderToStream
386   *              Indicates whether to write the generated
387   *              {@link PassphraseEncryptedStreamHeader} to the provided
388   *              {@code wrappedOutputStream} before any encrypted data so that
389   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
390   *              information necessary for decrypting the data.  If this is
391   *              {@code false}, then the {@link #getEncryptionHeader()} method
392   *              must be used to obtain the encryption header so that it can be
393   *              stored elsewhere and provided to the
394   *              {@code PassphraseEncryptedInputStream} constructor.
395   *
396   * @throws  GeneralSecurityException  If a problem is encountered while
397   *                                    initializing the encryption.
398   *
399   * @throws  IOException  If a problem is encountered while writing the
400   *                       encryption header to the underlying output stream.
401   */
402  public PassphraseEncryptedOutputStream(final String passphrase,
403                                         final OutputStream wrappedOutputStream,
404                                         final String keyIdentifier,
405                                         final boolean useStrongEncryption,
406                                         final int keyFactoryIterationCount,
407                                         final boolean writeHeaderToStream)
408         throws GeneralSecurityException, IOException
409  {
410    this(passphrase.toCharArray(), wrappedOutputStream, keyIdentifier,
411         useStrongEncryption, keyFactoryIterationCount, writeHeaderToStream);
412  }
413
414
415
416  /**
417   * Creates a new passphrase-encrypted output stream with the provided
418   * information.
419   *
420   * @param  passphrase
421   *              The passphrase that will be used to generate the encryption
422   *              key.  It must not be {@code null}.
423   * @param  wrappedOutputStream
424   *              The output stream to which the encrypted data (optionally
425   *              preceded by a header with details about the encryption) will
426   *              be written.  It must not be {@code null}.
427   * @param  keyIdentifier
428   *              An optional identifier that may be used to associate the
429   *              encryption details with information in another system.  This
430   *              is primarily intended for use in conjunction with
431   *              UnboundID/Ping Identity products, but may be useful in other
432   *              systems.  It may be {@code null} if no key identifier is
433   *              needed.
434   * @param  useStrongEncryption
435   *              Indicates whether to attempt to use strong encryption, if it
436   *              is available.  If this is {@code true} and the JVM supports
437   *              the stronger level of encryption, then that encryption will be
438   *              used.  If this is {@code false}, or if the JVM does not
439   *              support the attempted stronger level of encryption, then the
440   *              baseline configuration will be used.
441   * @param  keyFactoryIterationCount
442   *              The iteration count to use when generating the encryption key
443   *              from the provided passphrase.
444   * @param  writeHeaderToStream
445   *              Indicates whether to write the generated
446   *              {@link PassphraseEncryptedStreamHeader} to the provided
447   *              {@code wrappedOutputStream} before any encrypted data so that
448   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
449   *              information necessary for decrypting the data.  If this is
450   *              {@code false}, then the {@link #getEncryptionHeader()} method
451   *              must be used to obtain the encryption header so that it can be
452   *              stored elsewhere and provided to the
453   *              {@code PassphraseEncryptedInputStream} constructor.
454   *
455   * @throws  GeneralSecurityException  If a problem is encountered while
456   *                                    initializing the encryption.
457   *
458   * @throws  IOException  If a problem is encountered while writing the
459   *                       encryption header to the underlying output stream.
460   */
461  public PassphraseEncryptedOutputStream(final char[] passphrase,
462                                         final OutputStream wrappedOutputStream,
463                                         final String keyIdentifier,
464                                         final boolean useStrongEncryption,
465                                         final int keyFactoryIterationCount,
466                                         final boolean writeHeaderToStream)
467         throws GeneralSecurityException, IOException
468  {
469    final SecureRandom random = new SecureRandom();
470
471    final byte[] keyFactorySalt = new byte[KEY_FACTORY_SALT_LENGTH_BYTES];
472    random.nextBytes(keyFactorySalt);
473
474    final byte[] cipherInitializationVector =
475         new byte[CIPHER_INITIALIZATION_VECTOR_LENGTH_BYTES];
476    random.nextBytes(cipherInitializationVector);
477
478    final String macAlgorithm;
479    PassphraseEncryptedStreamHeader header = null;
480    CipherOutputStream cipherStream = null;
481    if (useStrongEncryption)
482    {
483      macAlgorithm = STRONG_MAC_ALGORITHM;
484
485      final Boolean supportsStrongEncryption = SUPPORTS_STRONG_ENCRYPTION.get();
486      if ((supportsStrongEncryption == null) ||
487           Boolean.TRUE.equals(supportsStrongEncryption))
488      {
489        try
490        {
491          header = new PassphraseEncryptedStreamHeader(passphrase,
492               STRONG_KEY_FACTORY_ALGORITHM, keyFactoryIterationCount,
493               keyFactorySalt, STRONG_KEY_FACTORY_KEY_LENGTH_BITS,
494               CIPHER_TRANSFORMATION, cipherInitializationVector,
495               keyIdentifier, macAlgorithm);
496
497          final Cipher cipher = header.createCipher(Cipher.ENCRYPT_MODE);
498          if (writeHeaderToStream)
499          {
500            header.writeTo(wrappedOutputStream);
501          }
502
503          cipherStream = new CipherOutputStream(wrappedOutputStream, cipher);
504          SUPPORTS_STRONG_ENCRYPTION.compareAndSet(null, Boolean.TRUE);
505        }
506        catch (final Exception e)
507        {
508          Debug.debugException(e);
509          SUPPORTS_STRONG_ENCRYPTION.set(Boolean.FALSE);
510        }
511      }
512    }
513    else
514    {
515      macAlgorithm = BASELINE_MAC_ALGORITHM;
516    }
517
518    if (cipherStream == null)
519    {
520      header = new PassphraseEncryptedStreamHeader(passphrase,
521           BASELINE_KEY_FACTORY_ALGORITHM, keyFactoryIterationCount,
522           keyFactorySalt, BASELINE_KEY_FACTORY_KEY_LENGTH_BITS,
523           CIPHER_TRANSFORMATION, cipherInitializationVector, keyIdentifier,
524           macAlgorithm);
525
526      final Cipher cipher = header.createCipher(Cipher.ENCRYPT_MODE);
527      if (writeHeaderToStream)
528      {
529        header.writeTo(wrappedOutputStream);
530      }
531
532      cipherStream = new CipherOutputStream(wrappedOutputStream, cipher);
533    }
534
535    encryptionHeader = header;
536    cipherOutputStream = cipherStream;
537  }
538
539
540
541  /**
542   * Writes an encrypted representation of the provided byte to the underlying
543   * output stream.
544   *
545   * @param  b  The byte of data to be written.  Only the least significant 8
546   *            bits of the value will be used, and the most significant 24 bits
547   *            will be ignored.
548   *
549   * @throws  IOException  If a problem is encountered while encrypting the data
550   *                       or writing to the underlying output stream.
551   */
552  @Override()
553  public void write(final int b)
554         throws IOException
555  {
556    cipherOutputStream.write(b);
557  }
558
559
560
561  /**
562   * Writes an encrypted representation of the contents of the provided byte
563   * array to the underlying output stream.
564   *
565   * @param  b  The array containing the data to be written.  It must not be
566   *            {@code null}.  All bytes in the array will be written.
567   *
568   * @throws  IOException  If a problem is encountered while encrypting the data
569   *                       or writing to the underlying output stream.
570   */
571  @Override()
572  public void write(final byte[] b)
573         throws IOException
574  {
575    cipherOutputStream.write(b);
576  }
577
578
579
580  /**
581   * Writes an encrypted representation of the specified portion of the provided
582   * byte array to the underlying output stream.
583   *
584   * @param  b       The array containing the data to be written.  It must not
585   *                 be {@code null}.
586   * @param  offset  The index in the array of the first byte to be written.
587   *                 It must be greater than or equal to zero, and less than the
588   *                 length of the provided array.
589   * @param  length  The number of bytes to be written.  It must be greater than
590   *                 or equal to zero, and the sum of the {@code offset} and
591   *                 {@code length} values must be less than or equal to the
592   *                 length of the provided array.
593   *
594   * @throws  IOException  If a problem is encountered while encrypting the data
595   *                       or writing to the underlying output stream.
596   */
597  @Override()
598  public void write(final byte[] b, final int offset, final int length)
599         throws IOException
600  {
601    cipherOutputStream.write(b, offset, length);
602  }
603
604
605
606  /**
607   * Flushes the underlying output stream so that any buffered encrypted output
608   * will be written to the underlying output stream, and also flushes the
609   * underlying output stream.  Note that this call may not flush any data that
610   * has yet to be encrypted (for example, because the encryption uses a block
611   * cipher and the associated block is not yet full).
612   *
613   * @throws  IOException  If a problem is encountered while flushing data to
614   *                       the underlying output stream.
615   */
616  @Override()
617  public void flush()
618         throws IOException
619  {
620    cipherOutputStream.flush();
621  }
622
623
624
625  /**
626   * Closes this output stream, along with the underlying output stream.  Any
627   * remaining buffered data will be processed (including generating any
628   * necessary padding) and flushed to the underlying output stream before the
629   * streams are closed.
630   *
631   * @throws  IOException  If a problem is encountered while closing the stream.
632   */
633  @Override()
634  public void close()
635         throws IOException
636  {
637    cipherOutputStream.close();
638  }
639
640
641
642  /**
643   * Retrieves an encryption header with details about the encryption being
644   * used.  If this header was not automatically written to the beginning of the
645   * underlying output stream before any encrypted data, then it must be stored
646   * somewhere else so that it can be provided to the
647   * {@link PassphraseEncryptedInputStream} constructor.
648   *
649   * @return  An encryption header with details about the encryption being used.
650   */
651  public PassphraseEncryptedStreamHeader getEncryptionHeader()
652  {
653    return encryptionHeader;
654  }
655}