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