001/* 002 * Copyright 2018-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2018-2022 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-2022 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 is 068 * still a supported Java version for the LDAP SDK) with 16,384 iterations and a 069 * 128-bit (16-byte) salt. However, if the output stream is configured to use 070 * strong encryption, then it will attempt to use 256-bit AES/CBC/PKCS5Padding 071 * with a PBKDF2WithHmacSHA512 key factory algorithm with 131,072 iterations and 072 * a 128-bit salt. If the JVM does not support this level of encryption, then 073 * it will fall back to a key size of 128 bits and a key factory algorithm of 074 * 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 // The cipher output stream that will be used to actually write the 088 // encrypted output. 089 @NotNull private final CipherOutputStream cipherOutputStream; 090 091 // A header containing the encoded encryption details. 092 @NotNull private final PassphraseEncryptedStreamHeader encryptionHeader; 093 094 095 096 /** 097 * Creates a new passphrase-encrypted output stream with the provided 098 * information. It will not use a key identifier, will use the baseline 099 * encryption strength rather than attempting to use strong encryption, and it 100 * will write the generated {@link PassphraseEncryptedStreamHeader} to the 101 * underlying stream before writing any encrypted data. 102 * 103 * @param passphrase 104 * The passphrase that will be used to generate the encryption 105 * key. It must not be {@code null}. 106 * @param wrappedOutputStream 107 * The output stream to which the encrypted data (optionally 108 * preceded by a header with details about the encryption) will 109 * be written. It must not be {@code null}. 110 * 111 * @throws GeneralSecurityException If a problem is encountered while 112 * initializing the encryption. 113 * 114 * @throws IOException If a problem is encountered while writing the 115 * encryption header to the underlying output stream. 116 */ 117 public PassphraseEncryptedOutputStream(@NotNull final String passphrase, 118 @NotNull final OutputStream wrappedOutputStream) 119 throws GeneralSecurityException, IOException 120 { 121 this(passphrase.toCharArray(), wrappedOutputStream); 122 } 123 124 125 126 /** 127 * Creates a new passphrase-encrypted output stream with the provided 128 * information. It will not use a key identifier, will use the baseline 129 * encryption strength rather than attempting to use strong encryption, and it 130 * will write the generated {@link PassphraseEncryptedStreamHeader} to the 131 * underlying stream before writing any encrypted data. 132 * 133 * @param passphrase 134 * The passphrase that will be used to generate the encryption 135 * key. It must not be {@code null}. 136 * @param wrappedOutputStream 137 * The output stream to which the encrypted data (optionally 138 * preceded by a header with details about the encryption) will 139 * be written. It must not be {@code null}. 140 * 141 * @throws GeneralSecurityException If a problem is encountered while 142 * initializing the encryption. 143 * 144 * @throws IOException If a problem is encountered while writing the 145 * encryption header to the underlying output stream. 146 */ 147 public PassphraseEncryptedOutputStream(@NotNull final char[] passphrase, 148 @NotNull final OutputStream wrappedOutputStream) 149 throws GeneralSecurityException, IOException 150 { 151 this(passphrase, wrappedOutputStream, null, false, true); 152 } 153 154 155 156 /** 157 * Creates a new passphrase-encrypted output stream with the provided 158 * information. 159 * 160 * @param passphrase 161 * The passphrase that will be used to generate the encryption 162 * key. It must not be {@code null}. 163 * @param wrappedOutputStream 164 * The output stream to which the encrypted data (optionally 165 * preceded by a header with details about the encryption) will 166 * be written. It must not be {@code null}. 167 * @param keyIdentifier 168 * An optional identifier that may be used to associate the 169 * encryption details with information in another system. This 170 * is primarily intended for use in conjunction with 171 * UnboundID/Ping Identity products, but may be useful in other 172 * systems. It may be {@code null} if no key identifier is 173 * needed. 174 * @param useStrongEncryption 175 * Indicates whether to attempt to use strong encryption, if it 176 * is available. If this is {@code true} and the JVM supports 177 * the stronger level of encryption, then that encryption will be 178 * used. If this is {@code false}, or if the JVM does not 179 * support the attempted stronger level of encryption, then the 180 * baseline configuration will be used. 181 * @param writeHeaderToStream 182 * Indicates whether to write the generated 183 * {@link PassphraseEncryptedStreamHeader} to the provided 184 * {@code wrappedOutputStream} before any encrypted data so that 185 * a {@link PassphraseEncryptedInputStream} can read it to obtain 186 * information necessary for decrypting the data. If this is 187 * {@code false}, then the {@link #getEncryptionHeader()} method 188 * must be used to obtain the encryption header so that it can be 189 * stored elsewhere and provided to the 190 * {@code PassphraseEncryptedInputStream} constructor. 191 * 192 * @throws GeneralSecurityException If a problem is encountered while 193 * initializing the encryption. 194 * 195 * @throws IOException If a problem is encountered while writing the 196 * encryption header to the underlying output stream. 197 */ 198 public PassphraseEncryptedOutputStream(@NotNull final String passphrase, 199 @NotNull final OutputStream wrappedOutputStream, 200 @Nullable final String keyIdentifier, 201 final boolean useStrongEncryption, 202 final boolean writeHeaderToStream) 203 throws GeneralSecurityException, IOException 204 { 205 this(passphrase.toCharArray(), wrappedOutputStream, keyIdentifier, 206 useStrongEncryption, writeHeaderToStream); 207 } 208 209 210 211 /** 212 * Creates a new passphrase-encrypted output stream with the provided 213 * information. 214 * 215 * @param passphrase 216 * The passphrase that will be used to generate the encryption 217 * key. It must not be {@code null}. 218 * @param wrappedOutputStream 219 * The output stream to which the encrypted data (optionally 220 * preceded by a header with details about the encryption) will 221 * be written. It must not be {@code null}. 222 * @param keyIdentifier 223 * An optional identifier that may be used to associate the 224 * encryption details with information in another system. This 225 * is primarily intended for use in conjunction with 226 * UnboundID/Ping Identity products, but may be useful in other 227 * systems. It may be {@code null} if no key identifier is 228 * needed. 229 * @param useStrongEncryption 230 * Indicates whether to attempt to use strong encryption, if it 231 * is available. If this is {@code true} and the JVM supports 232 * the stronger level of encryption, then that encryption will be 233 * used. If this is {@code false}, or if the JVM does not 234 * support the attempted stronger level of encryption, then the 235 * baseline configuration will be used. 236 * @param writeHeaderToStream 237 * Indicates whether to write the generated 238 * {@link PassphraseEncryptedStreamHeader} to the provided 239 * {@code wrappedOutputStream} before any encrypted data so that 240 * a {@link PassphraseEncryptedInputStream} can read it to obtain 241 * information necessary for decrypting the data. If this is 242 * {@code false}, then the {@link #getEncryptionHeader()} method 243 * must be used to obtain the encryption header so that it can be 244 * stored elsewhere and provided to the 245 * {@code PassphraseEncryptedInputStream} constructor. 246 * 247 * @throws GeneralSecurityException If a problem is encountered while 248 * initializing the encryption. 249 * 250 * @throws IOException If a problem is encountered while writing the 251 * encryption header to the underlying output stream. 252 */ 253 public PassphraseEncryptedOutputStream(@NotNull final char[] passphrase, 254 @NotNull final OutputStream wrappedOutputStream, 255 @Nullable final String keyIdentifier, 256 final boolean useStrongEncryption, 257 final boolean writeHeaderToStream) 258 throws GeneralSecurityException, IOException 259 { 260 this(passphrase, wrappedOutputStream, 261 generateProperties(keyIdentifier, useStrongEncryption, null, 262 writeHeaderToStream)); 263 } 264 265 266 267 /** 268 * Creates a new passphrase-encrypted output stream with the provided 269 * information. 270 * 271 * @param passphrase 272 * The passphrase that will be used to generate the encryption 273 * key. It must not be {@code null}. 274 * @param wrappedOutputStream 275 * The output stream to which the encrypted data (optionally 276 * preceded by a header with details about the encryption) will 277 * be written. It must not be {@code null}. 278 * @param keyIdentifier 279 * An optional identifier that may be used to associate the 280 * encryption details with information in another system. This 281 * is primarily intended for use in conjunction with 282 * UnboundID/Ping Identity products, but may be useful in other 283 * systems. It may be {@code null} if no key identifier is 284 * needed. 285 * @param useStrongEncryption 286 * Indicates whether to attempt to use strong encryption, if it 287 * is available. If this is {@code true} and the JVM supports 288 * the stronger level of encryption, then that encryption will be 289 * used. If this is {@code false}, or if the JVM does not 290 * support the attempted stronger level of encryption, then the 291 * baseline configuration will be used. 292 * @param keyFactoryIterationCount 293 * The iteration count to use when generating the encryption key 294 * from the provided passphrase. 295 * @param writeHeaderToStream 296 * Indicates whether to write the generated 297 * {@link PassphraseEncryptedStreamHeader} to the provided 298 * {@code wrappedOutputStream} before any encrypted data so that 299 * a {@link PassphraseEncryptedInputStream} can read it to obtain 300 * information necessary for decrypting the data. If this is 301 * {@code false}, then the {@link #getEncryptionHeader()} method 302 * must be used to obtain the encryption header so that it can be 303 * stored elsewhere and provided to the 304 * {@code PassphraseEncryptedInputStream} constructor. 305 * 306 * @throws GeneralSecurityException If a problem is encountered while 307 * initializing the encryption. 308 * 309 * @throws IOException If a problem is encountered while writing the 310 * encryption header to the underlying output stream. 311 */ 312 public PassphraseEncryptedOutputStream(@NotNull final String passphrase, 313 @NotNull final OutputStream wrappedOutputStream, 314 @Nullable final String keyIdentifier, 315 final boolean useStrongEncryption, 316 final int keyFactoryIterationCount, 317 final boolean writeHeaderToStream) 318 throws GeneralSecurityException, IOException 319 { 320 this(passphrase.toCharArray(), wrappedOutputStream, keyIdentifier, 321 useStrongEncryption, keyFactoryIterationCount, writeHeaderToStream); 322 } 323 324 325 326 /** 327 * Creates a new passphrase-encrypted output stream with the provided 328 * information. 329 * 330 * @param passphrase 331 * The passphrase that will be used to generate the encryption 332 * key. It must not be {@code null}. 333 * @param wrappedOutputStream 334 * The output stream to which the encrypted data (optionally 335 * preceded by a header with details about the encryption) will 336 * be written. It must not be {@code null}. 337 * @param keyIdentifier 338 * An optional identifier that may be used to associate the 339 * encryption details with information in another system. This 340 * is primarily intended for use in conjunction with 341 * UnboundID/Ping Identity products, but may be useful in other 342 * systems. It may be {@code null} if no key identifier is 343 * needed. 344 * @param useStrongEncryption 345 * Indicates whether to attempt to use strong encryption, if it 346 * is available. If this is {@code true} and the JVM supports 347 * the stronger level of encryption, then that encryption will be 348 * used. If this is {@code false}, or if the JVM does not 349 * support the attempted stronger level of encryption, then the 350 * baseline configuration will be used. 351 * @param keyFactoryIterationCount 352 * The iteration count to use when generating the encryption key 353 * from the provided passphrase. 354 * @param writeHeaderToStream 355 * Indicates whether to write the generated 356 * {@link PassphraseEncryptedStreamHeader} to the provided 357 * {@code wrappedOutputStream} before any encrypted data so that 358 * a {@link PassphraseEncryptedInputStream} can read it to obtain 359 * information necessary for decrypting the data. If this is 360 * {@code false}, then the {@link #getEncryptionHeader()} method 361 * must be used to obtain the encryption header so that it can be 362 * stored elsewhere and provided to the 363 * {@code PassphraseEncryptedInputStream} constructor. 364 * 365 * @throws GeneralSecurityException If a problem is encountered while 366 * initializing the encryption. 367 * 368 * @throws IOException If a problem is encountered while writing the 369 * encryption header to the underlying output stream. 370 */ 371 public PassphraseEncryptedOutputStream(@NotNull final char[] passphrase, 372 @NotNull final OutputStream wrappedOutputStream, 373 @Nullable final String keyIdentifier, 374 final boolean useStrongEncryption, 375 final int keyFactoryIterationCount, 376 final boolean writeHeaderToStream) 377 throws GeneralSecurityException, IOException 378 { 379 this(passphrase, wrappedOutputStream, 380 generateProperties(keyIdentifier, useStrongEncryption, 381 keyFactoryIterationCount, writeHeaderToStream)); 382 } 383 384 385 386 /** 387 * Generates an appropriate {@link PassphraseEncryptedOutputStreamProperties} 388 * object from the provided information. 389 * 390 * @param keyIdentifier 391 * An optional identifier that may be used to associate the 392 * encryption details with information in another system. This 393 * is primarily intended for use in conjunction with 394 * UnboundID/Ping Identity products, but may be useful in other 395 * systems. It may be {@code null} if no key identifier is 396 * needed. 397 * @param useStrongEncryption 398 * Indicates whether to attempt to use strong encryption, if it 399 * is available. If this is {@code true} and the JVM supports 400 * the stronger level of encryption, then that encryption will be 401 * used. If this is {@code false}, or if the JVM does not 402 * support the attempted stronger level of encryption, then the 403 * baseline configuration will be used. 404 * @param keyFactoryIterationCount 405 * The iteration count to use when generating the encryption key 406 * from the provided passphrase. 407 * @param writeHeaderToStream 408 * Indicates whether to write the generated 409 * {@link PassphraseEncryptedStreamHeader} to the provided 410 * {@code wrappedOutputStream} before any encrypted data so that 411 * a {@link PassphraseEncryptedInputStream} can read it to obtain 412 * information necessary for decrypting the data. If this is 413 * {@code false}, then the {@link #getEncryptionHeader()} method 414 * must be used to obtain the encryption header so that it can be 415 * stored elsewhere and provided to the 416 * {@code PassphraseEncryptedInputStream} constructor. 417 * 418 * @return The generated properties object. 419 */ 420 @NotNull() 421 private static PassphraseEncryptedOutputStreamProperties generateProperties( 422 @Nullable final String keyIdentifier, 423 final boolean useStrongEncryption, 424 @Nullable final Integer keyFactoryIterationCount, 425 final boolean writeHeaderToStream) 426 { 427 final PassphraseEncryptedOutputStreamProperties properties; 428 if (useStrongEncryption) 429 { 430 properties = new PassphraseEncryptedOutputStreamProperties( 431 PassphraseEncryptionCipherType.getStrongestAvailableCipherType()); 432 } 433 else 434 { 435 properties = new PassphraseEncryptedOutputStreamProperties( 436 PassphraseEncryptionCipherType.AES_128); 437 } 438 439 properties.setKeyIdentifier(keyIdentifier); 440 properties.setWriteHeaderToStream(writeHeaderToStream); 441 442 if (keyFactoryIterationCount != null) 443 { 444 properties.setKeyFactoryIterationCount(keyFactoryIterationCount); 445 } 446 447 return properties; 448 } 449 450 451 452 /** 453 * Creates a new passphrase-encrypted output stream with the provided 454 * information. 455 * 456 * @param passphrase 457 * The passphrase that will be used to generate the encryption 458 * key. It must not be {@code null}. 459 * @param wrappedOutputStream 460 * The output stream to which the encrypted data (optionally 461 * preceded by a header with details about the encryption) will 462 * be written. It must not be {@code null}. 463 * @param properties 464 * The properties to use when encrypting data. It must not be 465 * {@code null}. 466 * 467 * @throws GeneralSecurityException If a problem is encountered while 468 * initializing the encryption. 469 * 470 * @throws IOException If a problem is encountered while writing the 471 * encryption header to the underlying output stream. 472 */ 473 public PassphraseEncryptedOutputStream(@NotNull final String passphrase, 474 @NotNull final OutputStream wrappedOutputStream, 475 @NotNull final PassphraseEncryptedOutputStreamProperties properties) 476 throws GeneralSecurityException, IOException 477 { 478 this(passphrase.toCharArray(), wrappedOutputStream, properties); 479 } 480 481 482 483 /** 484 * Creates a new passphrase-encrypted output stream with the provided 485 * information. 486 * 487 * @param passphrase 488 * The passphrase that will be used to generate the encryption 489 * key. It must not be {@code null}. 490 * @param wrappedOutputStream 491 * The output stream to which the encrypted data (optionally 492 * preceded by a header with details about the encryption) will 493 * be written. It must not be {@code null}. 494 * @param properties 495 * The properties to use when encrypting data. It must not be 496 * {@code null}. 497 * 498 * @throws GeneralSecurityException If a problem is encountered while 499 * initializing the encryption. 500 * 501 * @throws IOException If a problem is encountered while writing the 502 * encryption header to the underlying output stream. 503 */ 504 public PassphraseEncryptedOutputStream(@NotNull final char[] passphrase, 505 @NotNull final OutputStream wrappedOutputStream, 506 @NotNull final PassphraseEncryptedOutputStreamProperties properties) 507 throws GeneralSecurityException, IOException 508 { 509 final SecureRandom random = ThreadLocalSecureRandom.get(); 510 511 final PassphraseEncryptionCipherType cipherType = 512 properties.getCipherType(); 513 final byte[] keyFactorySalt = 514 new byte[cipherType.getKeyFactorySaltLengthBytes()]; 515 random.nextBytes(keyFactorySalt); 516 517 final byte[] cipherInitializationVector = 518 new byte[cipherType.getInitializationVectorLengthBytes()]; 519 random.nextBytes(cipherInitializationVector); 520 521 encryptionHeader = new PassphraseEncryptedStreamHeader(passphrase, 522 cipherType.getKeyFactoryAlgorithm(), 523 properties.getKeyFactoryIterationCount(), keyFactorySalt, 524 cipherType.getKeyLengthBits(), 525 cipherType.getCipherTransformation(), cipherInitializationVector, 526 properties.getKeyIdentifier(), cipherType.getMacAlgorithm()); 527 final Cipher cipher = encryptionHeader.createCipher(Cipher.ENCRYPT_MODE); 528 529 if (properties.writeHeaderToStream()) 530 { 531 encryptionHeader.writeTo(wrappedOutputStream); 532 } 533 534 cipherOutputStream = new CipherOutputStream(wrappedOutputStream, cipher); 535 } 536 537 538 539 /** 540 * Writes an encrypted representation of the provided byte to the underlying 541 * output stream. 542 * 543 * @param b The byte of data to be written. Only the least significant 8 544 * bits of the value will be used, and the most significant 24 bits 545 * will be ignored. 546 * 547 * @throws IOException If a problem is encountered while encrypting the data 548 * or writing to the underlying output stream. 549 */ 550 @Override() 551 public void write(final int b) 552 throws IOException 553 { 554 cipherOutputStream.write(b); 555 } 556 557 558 559 /** 560 * Writes an encrypted representation of the contents of the provided byte 561 * array to the underlying output stream. 562 * 563 * @param b The array containing the data to be written. It must not be 564 * {@code null}. All bytes in the array will be written. 565 * 566 * @throws IOException If a problem is encountered while encrypting the data 567 * or writing to the underlying output stream. 568 */ 569 @Override() 570 public void write(@NotNull final byte[] b) 571 throws IOException 572 { 573 cipherOutputStream.write(b); 574 } 575 576 577 578 /** 579 * Writes an encrypted representation of the specified portion of the provided 580 * byte array to the underlying output stream. 581 * 582 * @param b The array containing the data to be written. It must not 583 * be {@code null}. 584 * @param offset The index in the array of the first byte to be written. 585 * It must be greater than or equal to zero, and less than the 586 * length of the provided array. 587 * @param length The number of bytes to be written. It must be greater than 588 * or equal to zero, and the sum of the {@code offset} and 589 * {@code length} values must be less than or equal to the 590 * length of the provided array. 591 * 592 * @throws IOException If a problem is encountered while encrypting the data 593 * or writing to the underlying output stream. 594 */ 595 @Override() 596 public void write(@NotNull final byte[] b, final int offset, final int length) 597 throws IOException 598 { 599 cipherOutputStream.write(b, offset, length); 600 } 601 602 603 604 /** 605 * Flushes the underlying output stream so that any buffered encrypted output 606 * will be written to the underlying output stream, and also flushes the 607 * underlying output stream. Note that this call may not flush any data that 608 * has yet to be encrypted (for example, because the encryption uses a block 609 * cipher and the associated block is not yet full). 610 * 611 * @throws IOException If a problem is encountered while flushing data to 612 * the underlying output stream. 613 */ 614 @Override() 615 public void flush() 616 throws IOException 617 { 618 cipherOutputStream.flush(); 619 } 620 621 622 623 /** 624 * Closes this output stream, along with the underlying output stream. Any 625 * remaining buffered data will be processed (including generating any 626 * necessary padding) and flushed to the underlying output stream before the 627 * streams are closed. 628 * 629 * @throws IOException If a problem is encountered while closing the stream. 630 */ 631 @Override() 632 public void close() 633 throws IOException 634 { 635 cipherOutputStream.close(); 636 } 637 638 639 640 /** 641 * Retrieves an encryption header with details about the encryption being 642 * used. If this header was not automatically written to the beginning of the 643 * underlying output stream before any encrypted data, then it must be stored 644 * somewhere else so that it can be provided to the 645 * {@link PassphraseEncryptedInputStream} constructor. 646 * 647 * @return An encryption header with details about the encryption being used. 648 */ 649 @NotNull() 650 public PassphraseEncryptedStreamHeader getEncryptionHeader() 651 { 652 return encryptionHeader; 653 } 654}