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