001/*
002 * Copyright 2018-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2018-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2018-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.io.IOException;
041import java.io.OutputStream;
042import java.security.SecureRandom;
043import java.security.GeneralSecurityException;
044import javax.crypto.Cipher;
045import javax.crypto.CipherOutputStream;
046
047
048
049/**
050 * This class provides an {@code OutputStream} implementation that will encrypt
051 * all data written to it with a key generated from a passphrase.  Details about
052 * the encryption will be encapsulated in a
053 * {@link PassphraseEncryptedStreamHeader}, which will typically be written to
054 * the underlying stream before any of the encrypted data, so that the
055 * {@link PassphraseEncryptedInputStream} can read it to determine how to
056 * decrypt that data when provided with the same passphrase.  However, it is
057 * also possible to store the encryption header elsewhere and provide it to the
058 * {@code PassphraseEncryptedInputStream} constructor so that that the
059 * underlying stream will only include encrypted data.
060 * <BR><BR>
061 * The specific details of the encryption performed may change over time, but
062 * the information in the header should ensure that data encrypted with
063 * different settings can still be decrypted (as long as the JVM provides the
064 * necessary support for that encryption).  The current implementation uses a
065 * baseline of 128-bit AES/CBC/PKCS5Padding using a key generated from the
066 * provided passphrase using the PBKDF2WithHmacSHA1 key factory algorithm
067 * (unfortunately, PBKDF2WithHmacSHA256 isn't available on Java 7, which was
068 * still a supported Java version for the LDAP SDK at the time this class was
069 * created) with 16,384 iterations and a 128-bit (16-byte) salt.  However, if
070 * the  output stream is configured to use strong encryption, then it will
071 * attempt to use 256-bit AES/CBC/PKCS5Padding with a PBKDF2WithHmacSHA512 key
072 * factory algorithm with 131,072 iterations and a 128-bit salt.  If the JVM
073 * does not support this level of encryption, then it will fall back to a key
074 * size of 128 bits and a key factory algorithm of PBKDF2WithHmacSHA1.
075 * <BR><BR>
076 * Note that the use of strong encryption may require special configuration for
077 * some versions of the JVM (for example, installation of JCE unlimited strength
078 * jurisdiction policy files).  If data encrypted on one system may need to be
079 * decrypted on another system, then you should make sure that all systems will
080 * support the stronger encryption option before choosing to use it over the
081 * baseline encryption option.
082 */
083@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
084public final class PassphraseEncryptedOutputStream
085     extends OutputStream
086{
087  /**
088   * The default PBKDF2 iteration count that should be used for the
089   * {@link PassphraseEncryptionCipherType#AES_128} cipher type.
090   */
091  public static final int DEFAULT_AES_128_CIPHER_TYPE_ITERATION_COUNT;
092
093
094
095  /**
096   * The default PBKDF2 iteration count that should be used for the
097   * {@link PassphraseEncryptionCipherType#AES_256} cipher type.
098   */
099  public static final int DEFAULT_AES_256_CIPHER_TYPE_ITERATION_COUNT;
100
101
102
103  /**
104   * The name of a system property that can be used to override the default
105   * PBKDF2 iteration count for the
106   * {@link PassphraseEncryptionCipherType#AES_128} cipher type.
107   */
108  @NotNull public static final String
109       PROPERTY_DEFAULT_AES_128_CIPHER_TYPE_ITERATION_COUNT =
110            PassphraseEncryptedOutputStream.class.getName() +
111                 ".defaultAES128CipherTypeIterationCount";
112
113
114
115  /**
116   * The name of a system property that can be used to override the default
117   * PBKDF2 iteration count for the
118   * {@link PassphraseEncryptionCipherType#AES_256} cipher type.
119   */
120  @NotNull public static final String
121       PROPERTY_DEFAULT_AES_256_CIPHER_TYPE_ITERATION_COUNT =
122            PassphraseEncryptedOutputStream.class.getName() +
123                 ".defaultAES256CipherTypeIterationCount";
124
125
126
127  static
128  {
129    int defaultAES128IterationCount = 100_000;
130    final String defaultAES128IterationCountPropertyValue =
131         StaticUtils.setSystemProperty(
132              PROPERTY_DEFAULT_AES_128_CIPHER_TYPE_ITERATION_COUNT, null);
133    if (defaultAES128IterationCountPropertyValue != null)
134    {
135      try
136      {
137        defaultAES128IterationCount =
138             Integer.parseInt(defaultAES128IterationCountPropertyValue);
139      }
140      catch (final Exception e)
141      {
142        Debug.debugException(e);
143      }
144    }
145
146    DEFAULT_AES_128_CIPHER_TYPE_ITERATION_COUNT = defaultAES128IterationCount;
147
148
149    int defaultAES256IterationCount = 600_000;
150    final String defaultAES256IterationCountPropertyValue =
151         StaticUtils.setSystemProperty(
152              PROPERTY_DEFAULT_AES_256_CIPHER_TYPE_ITERATION_COUNT, null);
153    if (defaultAES256IterationCountPropertyValue != null)
154    {
155      try
156      {
157        defaultAES256IterationCount =
158             Integer.parseInt(defaultAES256IterationCountPropertyValue);
159      }
160      catch (final Exception e)
161      {
162        Debug.debugException(e);
163      }
164    }
165
166    DEFAULT_AES_256_CIPHER_TYPE_ITERATION_COUNT = defaultAES256IterationCount;
167  }
168
169
170
171  // The cipher output stream that will be used to actually write the
172  // encrypted output.
173  @NotNull private final CipherOutputStream cipherOutputStream;
174
175  // A header containing the encoded encryption details.
176  @NotNull private final PassphraseEncryptedStreamHeader encryptionHeader;
177
178
179
180  /**
181   * Creates a new passphrase-encrypted output stream with the provided
182   * information.  It will not use a key identifier, will use the baseline
183   * encryption strength rather than attempting to use strong encryption, and it
184   * will write the generated {@link PassphraseEncryptedStreamHeader} to the
185   * underlying stream before writing any encrypted data.
186   *
187   * @param  passphrase
188   *              The passphrase that will be used to generate the encryption
189   *              key.  It must not be {@code null}.
190   * @param  wrappedOutputStream
191   *              The output stream to which the encrypted data (optionally
192   *              preceded by a header with details about the encryption) will
193   *              be written.  It must not be {@code null}.
194   *
195   * @throws  GeneralSecurityException  If a problem is encountered while
196   *                                    initializing the encryption.
197   *
198   * @throws  IOException  If a problem is encountered while writing the
199   *                       encryption header to the underlying output stream.
200   */
201  public PassphraseEncryptedOutputStream(@NotNull final String passphrase,
202              @NotNull final OutputStream wrappedOutputStream)
203         throws GeneralSecurityException, IOException
204  {
205    this(passphrase.toCharArray(), wrappedOutputStream);
206  }
207
208
209
210  /**
211   * Creates a new passphrase-encrypted output stream with the provided
212   * information.  It will not use a key identifier, will use the baseline
213   * encryption strength rather than attempting to use strong encryption, and it
214   * will write the generated {@link PassphraseEncryptedStreamHeader} to the
215   * underlying stream before writing any encrypted data.
216   *
217   * @param  passphrase
218   *              The passphrase that will be used to generate the encryption
219   *              key.  It must not be {@code null}.
220   * @param  wrappedOutputStream
221   *              The output stream to which the encrypted data (optionally
222   *              preceded by a header with details about the encryption) will
223   *              be written.  It must not be {@code null}.
224   *
225   * @throws  GeneralSecurityException  If a problem is encountered while
226   *                                    initializing the encryption.
227   *
228   * @throws  IOException  If a problem is encountered while writing the
229   *                       encryption header to the underlying output stream.
230   */
231  public PassphraseEncryptedOutputStream(@NotNull final char[] passphrase,
232              @NotNull final OutputStream wrappedOutputStream)
233         throws GeneralSecurityException, IOException
234  {
235    this(passphrase, wrappedOutputStream, null, false, true);
236  }
237
238
239
240  /**
241   * Creates a new passphrase-encrypted output stream with the provided
242   * information.
243   *
244   * @param  passphrase
245   *              The passphrase that will be used to generate the encryption
246   *              key.  It must not be {@code null}.
247   * @param  wrappedOutputStream
248   *              The output stream to which the encrypted data (optionally
249   *              preceded by a header with details about the encryption) will
250   *              be written.  It must not be {@code null}.
251   * @param  keyIdentifier
252   *              An optional identifier that may be used to associate the
253   *              encryption details with information in another system.  This
254   *              is primarily intended for use in conjunction with
255   *              UnboundID/Ping Identity products, but may be useful in other
256   *              systems.  It may be {@code null} if no key identifier is
257   *              needed.
258   * @param  useStrongEncryption
259   *              Indicates whether to attempt to use strong encryption, if it
260   *              is available.  If this is {@code true} and the JVM supports
261   *              the stronger level of encryption, then that encryption will be
262   *              used.  If this is {@code false}, or if the JVM does not
263   *              support the attempted stronger level of encryption, then the
264   *              baseline configuration will be used.
265   * @param  writeHeaderToStream
266   *              Indicates whether to write the generated
267   *              {@link PassphraseEncryptedStreamHeader} to the provided
268   *              {@code wrappedOutputStream} before any encrypted data so that
269   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
270   *              information necessary for decrypting the data.  If this is
271   *              {@code false}, then the {@link #getEncryptionHeader()} method
272   *              must be used to obtain the encryption header so that it can be
273   *              stored elsewhere and provided to the
274   *              {@code PassphraseEncryptedInputStream} constructor.
275   *
276   * @throws  GeneralSecurityException  If a problem is encountered while
277   *                                    initializing the encryption.
278   *
279   * @throws  IOException  If a problem is encountered while writing the
280   *                       encryption header to the underlying output stream.
281   */
282  public PassphraseEncryptedOutputStream(@NotNull final String passphrase,
283              @NotNull final OutputStream wrappedOutputStream,
284              @Nullable final String keyIdentifier,
285              final boolean useStrongEncryption,
286              final boolean writeHeaderToStream)
287         throws GeneralSecurityException, IOException
288  {
289    this(passphrase.toCharArray(), wrappedOutputStream, keyIdentifier,
290         useStrongEncryption, writeHeaderToStream);
291  }
292
293
294
295  /**
296   * Creates a new passphrase-encrypted output stream with the provided
297   * information.
298   *
299   * @param  passphrase
300   *              The passphrase that will be used to generate the encryption
301   *              key.  It must not be {@code null}.
302   * @param  wrappedOutputStream
303   *              The output stream to which the encrypted data (optionally
304   *              preceded by a header with details about the encryption) will
305   *              be written.  It must not be {@code null}.
306   * @param  keyIdentifier
307   *              An optional identifier that may be used to associate the
308   *              encryption details with information in another system.  This
309   *              is primarily intended for use in conjunction with
310   *              UnboundID/Ping Identity products, but may be useful in other
311   *              systems.  It may be {@code null} if no key identifier is
312   *              needed.
313   * @param  useStrongEncryption
314   *              Indicates whether to attempt to use strong encryption, if it
315   *              is available.  If this is {@code true} and the JVM supports
316   *              the stronger level of encryption, then that encryption will be
317   *              used.  If this is {@code false}, or if the JVM does not
318   *              support the attempted stronger level of encryption, then the
319   *              baseline configuration will be used.
320   * @param  writeHeaderToStream
321   *              Indicates whether to write the generated
322   *              {@link PassphraseEncryptedStreamHeader} to the provided
323   *              {@code wrappedOutputStream} before any encrypted data so that
324   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
325   *              information necessary for decrypting the data.  If this is
326   *              {@code false}, then the {@link #getEncryptionHeader()} method
327   *              must be used to obtain the encryption header so that it can be
328   *              stored elsewhere and provided to the
329   *              {@code PassphraseEncryptedInputStream} constructor.
330   *
331   * @throws  GeneralSecurityException  If a problem is encountered while
332   *                                    initializing the encryption.
333   *
334   * @throws  IOException  If a problem is encountered while writing the
335   *                       encryption header to the underlying output stream.
336   */
337  public PassphraseEncryptedOutputStream(@NotNull final char[] passphrase,
338              @NotNull final OutputStream wrappedOutputStream,
339              @Nullable final String keyIdentifier,
340              final boolean useStrongEncryption,
341              final boolean writeHeaderToStream)
342         throws GeneralSecurityException, IOException
343  {
344    this(passphrase, wrappedOutputStream,
345         generateProperties(keyIdentifier, useStrongEncryption, null,
346              writeHeaderToStream));
347  }
348
349
350
351  /**
352   * Creates a new passphrase-encrypted output stream with the provided
353   * information.
354   *
355   * @param  passphrase
356   *              The passphrase that will be used to generate the encryption
357   *              key.  It must not be {@code null}.
358   * @param  wrappedOutputStream
359   *              The output stream to which the encrypted data (optionally
360   *              preceded by a header with details about the encryption) will
361   *              be written.  It must not be {@code null}.
362   * @param  keyIdentifier
363   *              An optional identifier that may be used to associate the
364   *              encryption details with information in another system.  This
365   *              is primarily intended for use in conjunction with
366   *              UnboundID/Ping Identity products, but may be useful in other
367   *              systems.  It may be {@code null} if no key identifier is
368   *              needed.
369   * @param  useStrongEncryption
370   *              Indicates whether to attempt to use strong encryption, if it
371   *              is available.  If this is {@code true} and the JVM supports
372   *              the stronger level of encryption, then that encryption will be
373   *              used.  If this is {@code false}, or if the JVM does not
374   *              support the attempted stronger level of encryption, then the
375   *              baseline configuration will be used.
376   * @param  keyFactoryIterationCount
377   *              The iteration count to use when generating the encryption key
378   *              from the provided passphrase.
379   * @param  writeHeaderToStream
380   *              Indicates whether to write the generated
381   *              {@link PassphraseEncryptedStreamHeader} to the provided
382   *              {@code wrappedOutputStream} before any encrypted data so that
383   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
384   *              information necessary for decrypting the data.  If this is
385   *              {@code false}, then the {@link #getEncryptionHeader()} method
386   *              must be used to obtain the encryption header so that it can be
387   *              stored elsewhere and provided to the
388   *              {@code PassphraseEncryptedInputStream} constructor.
389   *
390   * @throws  GeneralSecurityException  If a problem is encountered while
391   *                                    initializing the encryption.
392   *
393   * @throws  IOException  If a problem is encountered while writing the
394   *                       encryption header to the underlying output stream.
395   */
396  public PassphraseEncryptedOutputStream(@NotNull final String passphrase,
397              @NotNull final OutputStream wrappedOutputStream,
398              @Nullable final String keyIdentifier,
399              final boolean useStrongEncryption,
400              final int keyFactoryIterationCount,
401              final boolean writeHeaderToStream)
402         throws GeneralSecurityException, IOException
403  {
404    this(passphrase.toCharArray(), wrappedOutputStream, keyIdentifier,
405         useStrongEncryption, keyFactoryIterationCount, writeHeaderToStream);
406  }
407
408
409
410  /**
411   * Creates a new passphrase-encrypted output stream with the provided
412   * information.
413   *
414   * @param  passphrase
415   *              The passphrase that will be used to generate the encryption
416   *              key.  It must not be {@code null}.
417   * @param  wrappedOutputStream
418   *              The output stream to which the encrypted data (optionally
419   *              preceded by a header with details about the encryption) will
420   *              be written.  It must not be {@code null}.
421   * @param  keyIdentifier
422   *              An optional identifier that may be used to associate the
423   *              encryption details with information in another system.  This
424   *              is primarily intended for use in conjunction with
425   *              UnboundID/Ping Identity products, but may be useful in other
426   *              systems.  It may be {@code null} if no key identifier is
427   *              needed.
428   * @param  useStrongEncryption
429   *              Indicates whether to attempt to use strong encryption, if it
430   *              is available.  If this is {@code true} and the JVM supports
431   *              the stronger level of encryption, then that encryption will be
432   *              used.  If this is {@code false}, or if the JVM does not
433   *              support the attempted stronger level of encryption, then the
434   *              baseline configuration will be used.
435   * @param  keyFactoryIterationCount
436   *              The iteration count to use when generating the encryption key
437   *              from the provided passphrase.
438   * @param  writeHeaderToStream
439   *              Indicates whether to write the generated
440   *              {@link PassphraseEncryptedStreamHeader} to the provided
441   *              {@code wrappedOutputStream} before any encrypted data so that
442   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
443   *              information necessary for decrypting the data.  If this is
444   *              {@code false}, then the {@link #getEncryptionHeader()} method
445   *              must be used to obtain the encryption header so that it can be
446   *              stored elsewhere and provided to the
447   *              {@code PassphraseEncryptedInputStream} constructor.
448   *
449   * @throws  GeneralSecurityException  If a problem is encountered while
450   *                                    initializing the encryption.
451   *
452   * @throws  IOException  If a problem is encountered while writing the
453   *                       encryption header to the underlying output stream.
454   */
455  public PassphraseEncryptedOutputStream(@NotNull final char[] passphrase,
456              @NotNull final OutputStream wrappedOutputStream,
457              @Nullable final String keyIdentifier,
458              final boolean useStrongEncryption,
459              final int keyFactoryIterationCount,
460              final boolean writeHeaderToStream)
461         throws GeneralSecurityException, IOException
462  {
463    this(passphrase, wrappedOutputStream,
464         generateProperties(keyIdentifier, useStrongEncryption,
465              keyFactoryIterationCount, writeHeaderToStream));
466  }
467
468
469
470  /**
471   * Generates an appropriate {@link PassphraseEncryptedOutputStreamProperties}
472   * object from the provided information.
473   *
474   * @param  keyIdentifier
475   *              An optional identifier that may be used to associate the
476   *              encryption details with information in another system.  This
477   *              is primarily intended for use in conjunction with
478   *              UnboundID/Ping Identity products, but may be useful in other
479   *              systems.  It may be {@code null} if no key identifier is
480   *              needed.
481   * @param  useStrongEncryption
482   *              Indicates whether to attempt to use strong encryption, if it
483   *              is available.  If this is {@code true} and the JVM supports
484   *              the stronger level of encryption, then that encryption will be
485   *              used.  If this is {@code false}, or if the JVM does not
486   *              support the attempted stronger level of encryption, then the
487   *              baseline configuration will be used.
488   * @param  keyFactoryIterationCount
489   *              The iteration count to use when generating the encryption key
490   *              from the provided passphrase.
491   * @param  writeHeaderToStream
492   *              Indicates whether to write the generated
493   *              {@link PassphraseEncryptedStreamHeader} to the provided
494   *              {@code wrappedOutputStream} before any encrypted data so that
495   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
496   *              information necessary for decrypting the data.  If this is
497   *              {@code false}, then the {@link #getEncryptionHeader()} method
498   *              must be used to obtain the encryption header so that it can be
499   *              stored elsewhere and provided to the
500   *              {@code PassphraseEncryptedInputStream} constructor.
501   *
502   * @return  The generated properties object.
503   */
504  @NotNull()
505  private static PassphraseEncryptedOutputStreamProperties generateProperties(
506               @Nullable final String keyIdentifier,
507               final boolean useStrongEncryption,
508               @Nullable final Integer keyFactoryIterationCount,
509               final boolean writeHeaderToStream)
510  {
511    final PassphraseEncryptedOutputStreamProperties properties;
512    if (useStrongEncryption)
513    {
514      properties = new PassphraseEncryptedOutputStreamProperties(
515           PassphraseEncryptionCipherType.getStrongestAvailableCipherType());
516    }
517    else
518    {
519      properties = new PassphraseEncryptedOutputStreamProperties(
520           PassphraseEncryptionCipherType.AES_128);
521    }
522
523    properties.setKeyIdentifier(keyIdentifier);
524    properties.setWriteHeaderToStream(writeHeaderToStream);
525
526    if (keyFactoryIterationCount != null)
527    {
528      properties.setKeyFactoryIterationCount(keyFactoryIterationCount);
529    }
530
531    return properties;
532  }
533
534
535
536  /**
537   * Creates a new passphrase-encrypted output stream with the provided
538   * information.
539   *
540   * @param  passphrase
541   *              The passphrase that will be used to generate the encryption
542   *              key.  It must not be {@code null}.
543   * @param  wrappedOutputStream
544   *              The output stream to which the encrypted data (optionally
545   *              preceded by a header with details about the encryption) will
546   *              be written.  It must not be {@code null}.
547   * @param  properties
548   *              The properties to use when encrypting data.  It must not be
549   *              {@code null}.
550   *
551   * @throws  GeneralSecurityException  If a problem is encountered while
552   *                                    initializing the encryption.
553   *
554   * @throws  IOException  If a problem is encountered while writing the
555   *                       encryption header to the underlying output stream.
556   */
557  public PassphraseEncryptedOutputStream(@NotNull final String passphrase,
558       @NotNull final OutputStream wrappedOutputStream,
559       @NotNull final PassphraseEncryptedOutputStreamProperties properties)
560       throws GeneralSecurityException, IOException
561  {
562    this(passphrase.toCharArray(), wrappedOutputStream, properties);
563  }
564
565
566
567  /**
568   * Creates a new passphrase-encrypted output stream with the provided
569   * information.
570   *
571   * @param  passphrase
572   *              The passphrase that will be used to generate the encryption
573   *              key.  It must not be {@code null}.
574   * @param  wrappedOutputStream
575   *              The output stream to which the encrypted data (optionally
576   *              preceded by a header with details about the encryption) will
577   *              be written.  It must not be {@code null}.
578   * @param  properties
579   *              The properties to use when encrypting data.  It must not be
580   *              {@code null}.
581   *
582   * @throws  GeneralSecurityException  If a problem is encountered while
583   *                                    initializing the encryption.
584   *
585   * @throws  IOException  If a problem is encountered while writing the
586   *                       encryption header to the underlying output stream.
587   */
588  public PassphraseEncryptedOutputStream(@NotNull final char[] passphrase,
589       @NotNull final OutputStream wrappedOutputStream,
590       @NotNull final PassphraseEncryptedOutputStreamProperties properties)
591       throws GeneralSecurityException, IOException
592  {
593    final SecureRandom random = ThreadLocalSecureRandom.get();
594
595    final PassphraseEncryptionCipherType cipherType =
596         properties.getCipherType();
597    final byte[] keyFactorySalt =
598         new byte[cipherType.getKeyFactorySaltLengthBytes()];
599    random.nextBytes(keyFactorySalt);
600
601    final byte[] cipherInitializationVector =
602         new byte[cipherType.getInitializationVectorLengthBytes()];
603    random.nextBytes(cipherInitializationVector);
604
605    encryptionHeader = new PassphraseEncryptedStreamHeader(passphrase,
606              cipherType.getKeyFactoryAlgorithm(),
607              properties.getKeyFactoryIterationCount(), keyFactorySalt,
608              cipherType.getKeyLengthBits(),
609              cipherType.getCipherTransformation(), cipherInitializationVector,
610              properties.getKeyIdentifier(), cipherType.getMacAlgorithm());
611    final Cipher cipher = encryptionHeader.createCipher(Cipher.ENCRYPT_MODE);
612
613    if (properties.writeHeaderToStream())
614    {
615      encryptionHeader.writeTo(wrappedOutputStream);
616    }
617
618    cipherOutputStream = new CipherOutputStream(wrappedOutputStream, cipher);
619  }
620
621
622
623  /**
624   * Creates a new passphrase-encrypted output stream that wraps the provided
625   * output stream and reuses the same derived secret key as the given
626   * stream header (although with a newly computed initialization vector).  This
627   * can dramatically reduce the cost of creating a new passphrase-encrypted
628   * output stream with the same underlying password and settings without the
629   * need to recompute the key.
630   *
631   * @param  header
632   *              The existing passphrase-encrypted stream header that contains
633   *              the details to use for the encryption.  It must not be
634   *              {@code null}, and it must have an associated secret key.
635   * @param  wrappedOutputStream
636   *              The output stream to which the encrypted data (optionally
637   *              preceded by a header with details about the encryption) will
638   *              be written.  It must not be {@code null}.
639   * @param  writeHeaderToStream
640   *              Indicates whether to write the generated
641   *              {@link PassphraseEncryptedStreamHeader} to the provided
642   *              {@code wrappedOutputStream} before any encrypted data so that
643   *              a {@link PassphraseEncryptedInputStream} can read it to obtain
644   *              information necessary for decrypting the data.  If this is
645   *              {@code false}, then the {@link #getEncryptionHeader()} method
646   *              must be used to obtain the encryption header so that it can be
647   *              stored elsewhere and provided to the
648   *              {@code PassphraseEncryptedInputStream} constructor.
649   *
650   * @throws  GeneralSecurityException  If a problem is encountered while
651   *                                    initializing the encryption.
652   *
653   * @throws  IOException  If a problem is encountered while writing the
654   *                       encryption header to the underlying output stream.
655   */
656  public PassphraseEncryptedOutputStream(
657              @NotNull final PassphraseEncryptedStreamHeader header,
658              @NotNull final OutputStream wrappedOutputStream,
659final boolean writeHeaderToStream)
660         throws GeneralSecurityException, IOException
661  {
662    encryptionHeader = header.withNewCipherInitializationVector();
663
664    final Cipher cipher = encryptionHeader.createCipher(Cipher.ENCRYPT_MODE);
665    if (writeHeaderToStream)
666    {
667      encryptionHeader.writeTo(wrappedOutputStream);
668    }
669
670    cipherOutputStream = new CipherOutputStream(wrappedOutputStream, cipher);
671  }
672
673
674
675  /**
676   * Writes an encrypted representation of the provided byte to the underlying
677   * output stream.
678   *
679   * @param  b  The byte of data to be written.  Only the least significant 8
680   *            bits of the value will be used, and the most significant 24 bits
681   *            will be ignored.
682   *
683   * @throws  IOException  If a problem is encountered while encrypting the data
684   *                       or writing to the underlying output stream.
685   */
686  @Override()
687  public void write(final int b)
688         throws IOException
689  {
690    cipherOutputStream.write(b);
691  }
692
693
694
695  /**
696   * Writes an encrypted representation of the contents of the provided byte
697   * array to the underlying output stream.
698   *
699   * @param  b  The array containing the data to be written.  It must not be
700   *            {@code null}.  All bytes in the array will be written.
701   *
702   * @throws  IOException  If a problem is encountered while encrypting the data
703   *                       or writing to the underlying output stream.
704   */
705  @Override()
706  public void write(@NotNull final byte[] b)
707         throws IOException
708  {
709    cipherOutputStream.write(b);
710  }
711
712
713
714  /**
715   * Writes an encrypted representation of the specified portion of the provided
716   * byte array to the underlying output stream.
717   *
718   * @param  b       The array containing the data to be written.  It must not
719   *                 be {@code null}.
720   * @param  offset  The index in the array of the first byte to be written.
721   *                 It must be greater than or equal to zero, and less than the
722   *                 length of the provided array.
723   * @param  length  The number of bytes to be written.  It must be greater than
724   *                 or equal to zero, and the sum of the {@code offset} and
725   *                 {@code length} values must be less than or equal to the
726   *                 length of the provided array.
727   *
728   * @throws  IOException  If a problem is encountered while encrypting the data
729   *                       or writing to the underlying output stream.
730   */
731  @Override()
732  public void write(@NotNull final byte[] b, final int offset, final int length)
733         throws IOException
734  {
735    cipherOutputStream.write(b, offset, length);
736  }
737
738
739
740  /**
741   * Flushes the underlying output stream so that any buffered encrypted output
742   * will be written to the underlying output stream, and also flushes the
743   * underlying output stream.  Note that this call may not flush any data that
744   * has yet to be encrypted (for example, because the encryption uses a block
745   * cipher and the associated block is not yet full).
746   *
747   * @throws  IOException  If a problem is encountered while flushing data to
748   *                       the underlying output stream.
749   */
750  @Override()
751  public void flush()
752         throws IOException
753  {
754    cipherOutputStream.flush();
755  }
756
757
758
759  /**
760   * Closes this output stream, along with the underlying output stream.  Any
761   * remaining buffered data will be processed (including generating any
762   * necessary padding) and flushed to the underlying output stream before the
763   * streams are closed.
764   *
765   * @throws  IOException  If a problem is encountered while closing the stream.
766   */
767  @Override()
768  public void close()
769         throws IOException
770  {
771    cipherOutputStream.close();
772  }
773
774
775
776  /**
777   * Retrieves an encryption header with details about the encryption being
778   * used.  If this header was not automatically written to the beginning of the
779   * underlying output stream before any encrypted data, then it must be stored
780   * somewhere else so that it can be provided to the
781   * {@link PassphraseEncryptedInputStream} constructor.
782   *
783   * @return  An encryption header with details about the encryption being used.
784   */
785  @NotNull()
786  public PassphraseEncryptedStreamHeader getEncryptionHeader()
787  {
788    return encryptionHeader;
789  }
790}