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