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 The passphrase that will be used to generate 192 * the encryption key. It must not be 193 * {@code null}. 194 * @param wrappedOutputStream The output stream to which the encrypted data 195 * (optionally preceded by a header with 196 * details about the encryption) will be written. 197 * 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 The passphrase that will be used to generate 222 * the encryption key. It must not be 223 * {@code null}. 224 * @param wrappedOutputStream The output stream to which the encrypted data 225 * (optionally preceded by a header with 226 * details about the encryption) will be written. 227 * 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 The passphrase that will be used to generate 249 * the encryption key. It must not be 250 * {@code null}. 251 * @param wrappedOutputStream The output stream to which the encrypted data 252 * (optionally preceded by a header with 253 * details about the encryption) will be written. 254 * It must not be {@code null}. 255 * @param keyIdentifier An optional identifier that may be used to 256 * associate the encryption details with 257 * information in another system. This is 258 * primarily intended for use in conjunction with 259 * UnboundID/Ping Identity products, but may be 260 * useful in other systems. It may be 261 * {@code null} if no key identifier is needed. 262 * @param useStrongEncryption Indicates whether to attempt to use strong 263 * encryption, if it is available. If this is 264 * {@code true} and the JVM supports the stronger 265 * level of encryption, then that encryption will 266 * be used. If this is {@code false}, or if the 267 * JVM does not support the attempted stronger 268 * level of encryption, then the baseline 269 * configuration will be used. 270 * @param writeHeaderToStream Indicates whether to write the generated 271 * {@link PassphraseEncryptedStreamHeader} to the 272 * provided {@code wrappedOutputStream} before 273 * any encrypted data so that a 274 * {@link PassphraseEncryptedInputStream} can 275 * read it to obtain information necessary for 276 * decrypting the data. If this is 277 * {@code false}, then the 278 * {@link #getEncryptionHeader()} method must be 279 * used to obtain the encryption header so that 280 * it can be stored elsewhere and provided to the 281 * {@code PassphraseEncryptedInputStream} 282 * constructor. 283 * 284 * @throws GeneralSecurityException If a problem is encountered while 285 * initializing the encryption. 286 * 287 * @throws IOException If a problem is encountered while writing the 288 * encryption header to the underlying output stream. 289 */ 290 public PassphraseEncryptedOutputStream(final String passphrase, 291 final OutputStream wrappedOutputStream, 292 final String keyIdentifier, 293 final boolean useStrongEncryption, 294 final boolean writeHeaderToStream) 295 throws GeneralSecurityException, IOException 296 { 297 this(passphrase.toCharArray(), wrappedOutputStream, keyIdentifier, 298 useStrongEncryption, writeHeaderToStream); 299 } 300 301 302 303 /** 304 * Creates a new passphrase-encrypted output stream with the provided 305 * information. 306 * 307 * @param passphrase The passphrase that will be used to generate 308 * the encryption key. It must not be 309 * {@code null}. 310 * @param wrappedOutputStream The output stream to which the encrypted data 311 * (optionally preceded by a header with 312 * details about the encryption) will be written. 313 * It must not be {@code null}. 314 * @param keyIdentifier An optional identifier that may be used to 315 * associate the encryption details with 316 * information in another system. This is 317 * primarily intended for use in conjunction with 318 * UnboundID/Ping Identity products, but may be 319 * useful in other systems. It may be 320 * {@code null} if no key identifier is needed. 321 * @param useStrongEncryption Indicates whether to attempt to use strong 322 * encryption, if it is available. If this is 323 * {@code true} and the JVM supports the stronger 324 * level of encryption, then that encryption will 325 * be used. If this is {@code false}, or if the 326 * JVM does not support the attempted stronger 327 * level of encryption, then the baseline 328 * configuration will be used. 329 * @param writeHeaderToStream Indicates whether to write the generated 330 * {@link PassphraseEncryptedStreamHeader} to the 331 * provided {@code wrappedOutputStream} before 332 * any encrypted data so that a 333 * {@link PassphraseEncryptedInputStream} can 334 * read it to obtain information necessary for 335 * decrypting the data. If this is 336 * {@code false}, then the 337 * {@link #getEncryptionHeader()} method must be 338 * used to obtain the encryption header so that 339 * it can be stored elsewhere and provided to the 340 * {@code PassphraseEncryptedInputStream} 341 * constructor. 342 * 343 * @throws GeneralSecurityException If a problem is encountered while 344 * initializing the encryption. 345 * 346 * @throws IOException If a problem is encountered while writing the 347 * encryption header to the underlying output stream. 348 */ 349 public PassphraseEncryptedOutputStream(final char[] passphrase, 350 final OutputStream wrappedOutputStream, 351 final String keyIdentifier, 352 final boolean useStrongEncryption, 353 final boolean writeHeaderToStream) 354 throws GeneralSecurityException, IOException 355 { 356 final SecureRandom random = new SecureRandom(); 357 358 final byte[] keyFactorySalt = new byte[KEY_FACTORY_SALT_LENGTH_BYTES]; 359 random.nextBytes(keyFactorySalt); 360 361 final byte[] cipherInitializationVector = 362 new byte[CIPHER_INITIALIZATION_VECTOR_LENGTH_BYTES]; 363 random.nextBytes(cipherInitializationVector); 364 365 final int keyFactoryIterationCount; 366 final String macAlgorithm; 367 PassphraseEncryptedStreamHeader header = null; 368 CipherOutputStream cipherStream = null; 369 if (useStrongEncryption) 370 { 371 keyFactoryIterationCount = STRONG_KEY_FACTORY_ITERATION_COUNT; 372 macAlgorithm = STRONG_MAC_ALGORITHM; 373 374 final Boolean supportsStrongEncryption = SUPPORTS_STRONG_ENCRYPTION.get(); 375 if ((supportsStrongEncryption == null) || 376 Boolean.TRUE.equals(supportsStrongEncryption)) 377 { 378 try 379 { 380 header = new PassphraseEncryptedStreamHeader(passphrase, 381 STRONG_KEY_FACTORY_ALGORITHM, keyFactoryIterationCount, 382 keyFactorySalt, STRONG_KEY_FACTORY_KEY_LENGTH_BITS, 383 CIPHER_TRANSFORMATION, cipherInitializationVector, 384 keyIdentifier, macAlgorithm); 385 386 final Cipher cipher = header.createCipher(Cipher.ENCRYPT_MODE); 387 if (writeHeaderToStream) 388 { 389 header.writeTo(wrappedOutputStream); 390 } 391 392 cipherStream = new CipherOutputStream(wrappedOutputStream, cipher); 393 SUPPORTS_STRONG_ENCRYPTION.compareAndSet(null, Boolean.TRUE); 394 } 395 catch (final Exception e) 396 { 397 Debug.debugException(e); 398 SUPPORTS_STRONG_ENCRYPTION.set(Boolean.FALSE); 399 } 400 } 401 } 402 else 403 { 404 keyFactoryIterationCount = BASELINE_KEY_FACTORY_ITERATION_COUNT; 405 macAlgorithm = BASELINE_MAC_ALGORITHM; 406 } 407 408 if (cipherStream == null) 409 { 410 header = new PassphraseEncryptedStreamHeader(passphrase, 411 BASELINE_KEY_FACTORY_ALGORITHM, keyFactoryIterationCount, 412 keyFactorySalt, BASELINE_KEY_FACTORY_KEY_LENGTH_BITS, 413 CIPHER_TRANSFORMATION, cipherInitializationVector, keyIdentifier, 414 macAlgorithm); 415 416 final Cipher cipher = header.createCipher(Cipher.ENCRYPT_MODE); 417 if (writeHeaderToStream) 418 { 419 header.writeTo(wrappedOutputStream); 420 } 421 422 cipherStream = new CipherOutputStream(wrappedOutputStream, cipher); 423 } 424 425 encryptionHeader = header; 426 cipherOutputStream = cipherStream; 427 } 428 429 430 431 /** 432 * Writes an encrypted representation of the provided byte to the underlying 433 * output stream. 434 * 435 * @param b The byte of data to be written. Only the least significant 8 436 * bits of the value will be used, and the most significant 24 bits 437 * will be ignored. 438 * 439 * @throws IOException If a problem is encountered while encrypting the data 440 * or writing to the underlying output stream. 441 */ 442 @Override() 443 public void write(final int b) 444 throws IOException 445 { 446 cipherOutputStream.write(b); 447 } 448 449 450 451 /** 452 * Writes an encrypted representation of the contents of the provided byte 453 * array to the underlying output stream. 454 * 455 * @param b The array containing the data to be written. It must not be 456 * {@code null}. All bytes in the array will be written. 457 * 458 * @throws IOException If a problem is encountered while encrypting the data 459 * or writing to the underlying output stream. 460 */ 461 @Override() 462 public void write(final byte[] b) 463 throws IOException 464 { 465 cipherOutputStream.write(b); 466 } 467 468 469 470 /** 471 * Writes an encrypted representation of the specified portion of the provided 472 * byte array to the underlying output stream. 473 * 474 * @param b The array containing the data to be written. It must not 475 * be {@code null}. 476 * @param offset The index in the array of the first byte to be written. 477 * It must be greater than or equal to zero, and less than the 478 * length of the provided array. 479 * @param length The number of bytes to be written. It must be greater than 480 * or equal to zero, and the sum of the {@code offset} and 481 * {@code length} values must be less than or equal to the 482 * length of the provided array. 483 * 484 * @throws IOException If a problem is encountered while encrypting the data 485 * or writing to the underlying output stream. 486 */ 487 @Override() 488 public void write(final byte[] b, final int offset, final int length) 489 throws IOException 490 { 491 cipherOutputStream.write(b, offset, length); 492 } 493 494 495 496 /** 497 * Flushes the underlying output stream so that any buffered encrypted output 498 * will be written to the underlying output stream, and also flushes the 499 * underlying output stream. Note that this call may not flush any data that 500 * has yet to be encrypted (for example, because the encryption uses a block 501 * cipher and the associated block is not yet full). 502 * 503 * @throws IOException If a problem is encountered while flushing data to 504 * the underlying output stream. 505 */ 506 @Override() 507 public void flush() 508 throws IOException 509 { 510 cipherOutputStream.flush(); 511 } 512 513 514 515 /** 516 * Closes this output stream, along with the underlying output stream. Any 517 * remaining buffered data will be processed (including generating any 518 * necessary padding) and flushed to the underlying output stream before the 519 * streams are closed. 520 * 521 * @throws IOException If a problem is encountered while closing the stream. 522 */ 523 @Override() 524 public void close() 525 throws IOException 526 { 527 cipherOutputStream.close(); 528 } 529 530 531 532 /** 533 * Retrieves an encryption header with details about the encryption being 534 * used. If this header was not automatically written to the beginning of the 535 * underlying output stream before any encrypted data, then it must be stored 536 * somewhere else so that it can be provided to the 537 * {@link PassphraseEncryptedInputStream} constructor. 538 * 539 * @return An encryption header with details about the encryption being used. 540 */ 541 public PassphraseEncryptedStreamHeader getEncryptionHeader() 542 { 543 return encryptionHeader; 544 } 545}