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}