dotnwat opened a new issue, #51669:
URL: https://github.com/apache/arrow/issues/51669

   ### Describe the bug, including details regarding any error messages, 
version, and platform.
   
   ### Describe the bug, including details regarding any error messages, 
version, and platform.
   
   A Parquet file written with modular encryption in plaintext-footer mode and 
the `AES_GCM_CTR_V1` algorithm cannot be read back, by Arrow or by any other 
reader. The writer encrypts the pages with AES-CTR, as it should, but records 
`AES_GCM_V1` in `FileMetaData.encryption_algorithm`. A reader takes the 
algorithm from that field, tries to open the CTR pages as GCM modules, and 
fails authentication.
   
   Nothing is reported at write time, so the file looks fine until someone 
tries to read an encrypted column.
   
   #### Reproduction
   
   ```python
   import pyarrow as pa, pyarrow.parquet as pq, pyarrow.parquet.encryption as pe
   
   key = b"0123456789012345"
   table = pa.table({"a": [1, 2, 3]})
   for algo in ("AES_GCM_V1", "AES_GCM_CTR_V1"):
       props = pe.create_encryption_properties(
           key, plaintext_footer=True, encryption_algorithm=algo)
       pq.write_table(table, "t.parquet", encryption_properties=props)
       try:
           pq.read_table(
               "t.parquet",
               decryption_properties=pe.create_decryption_properties(key))
           print(pa.__version__, algo, "ok")
       except OSError as e:
           print(pa.__version__, algo, "FAILED:", e)
   ```
   
   Output:
   
   ```
   25.0.1 AES_GCM_V1 ok
   25.0.1 AES_GCM_CTR_V1 FAILED: Failed decryption finalization
   ```
   
   The same two algorithms with an encrypted footer (`plaintext_footer=False`) 
both round-trip, as does `AES_GCM_V1` with a plaintext footer. Only the 
combination of a plaintext footer and `AES_GCM_CTR_V1` fails.
   
   #### Cause
   
   `FileMetaDataBuilder::FileMetaDataBuilderImpl::Finish` in 
`cpp/src/parquet/metadata.cc` builds the footer's algorithm for 
plaintext-footer mode like this (current `main`):
   
   ```cpp
   // if plaintext footer, set footer signing algorithm
   auto file_encryption_properties = properties_->file_encryption_properties();
   if (file_encryption_properties && 
!file_encryption_properties->encrypted_footer()) {
     EncryptionAlgorithm signing_algorithm;
     EncryptionAlgorithm algo = file_encryption_properties->algorithm();
     signing_algorithm.aad.aad_file_unique = algo.aad.aad_file_unique;
     signing_algorithm.aad.supply_aad_prefix = algo.aad.supply_aad_prefix;
     if (!algo.aad.supply_aad_prefix) {
       signing_algorithm.aad.aad_prefix = algo.aad.aad_prefix;
     }
     signing_algorithm.algorithm = ParquetCipher::AES_GCM_V1;
   
     metadata_->__set_encryption_algorithm(ToThrift(signing_algorithm));
   ```
   
   The hard-coded `AES_GCM_V1` treats `FileMetaData.encryption_algorithm` as 
the algorithm of the footer signature, which is indeed always GCM. But the 
field is the file's encryption algorithm. `parquet.thrift` describes it as:
   
   ```
   /**
    * Encryption algorithm. This field is set only in encrypted files
    * with plaintext footer. Files with encrypted footer store algorithm id
    * in FileCryptoMetaData structure.
    */
   8: optional EncryptionAlgorithm encryption_algorithm
   ```
   
   and the reader uses it that way: 
`SerializedFile::ParseMetaDataOfEncryptedFileWithPlaintextFooter` in 
`cpp/src/parquet/file_reader.cc` passes 
`file_metadata_->encryption_algorithm().algorithm` to the 
`InternalFileDecryptor`, which then builds the page decryptors from it.
   
   parquet-java writes the file's actual algorithm in this field 
(`ParquetFileWriter.serializeFooter`: 
`parquetMetadata.setEncryption_algorithm(fileEncryptor.getEncryptionAlgorithm())`).
   
   #### Evidence that the pages are fine and only the field is wrong
   
   Taking a file produced by the reproduction above, changing the one byte that 
selects the `EncryptionAlgorithm` union member in the footer from `AES_GCM_V1` 
to `AES_GCM_CTR_V1`, and recomputing the footer signature for the changed 
bytes, gives a file that pyarrow 25.0.1 reads correctly, with footer 
verification enabled and the data equal to what was written. So the reader 
needs no change, and the pages in affected files are valid CTR modules.
   
   #### Suggested fix
   
   Write the file's algorithm instead of the constant:
   
   ```cpp
   signing_algorithm.algorithm = algo.algorithm;
   ```
   
   The signature itself does not depend on this value: both the writer and 
`FileMetaData::VerifySignature` use the GCM cipher for the footer regardless of 
the file's algorithm (`metadata = true`).
   
   The existing encryption configurations in 
`cpp/src/parquet/encryption/write_configurations_test.cc` use `AES_GCM_CTR_V1` 
only with an encrypted footer and a plaintext footer only with `AES_GCM_V1`, 
which is why the round-trip tests do not catch this. A configuration combining 
the two would cover it.
   
   Files already written with this combination stay unreadable after the fix, 
since their footers name the wrong algorithm.
   
   #### Version and platform
   
   - pyarrow 25.0.1 (PyPI wheel, manylinux_2_28 x86_64), Python 3.14.4, Linux 
x86_64 (Fedora 44).
   - The hard-coded line is present on `main` as of commit fcac3dfd8e 
(2026-09-30).
   
   ### Component(s)
   
   C++, Parquet
   
   
   ### Component(s)
   
   C++


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to