blob: 3d0c13879a174d08eb382c765066e658ca62ed8e [file] [view]
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Encryption
Encryption is applied after Page compression and reversed before decompression. Headers,
Statistics, schemas, and indexes remain visible. TsFile v4 supports two encryption mechanisms,
both under version byte `0x04`:
| Mode | How the reader recognizes it | Chunk and Page layout |
| --- | --- | --- |
| Plaintext | No file encryption header; legacy level `0` or absent | Base Chunk Header; unencrypted Page body |
| Legacy encryption | No file encryption header; legacy level `1` or `2` | Base Chunk Header; algorithm-specific encrypted body |
| Page AEAD | `0x06` file encryption header at offset `7` | Extended Chunk Header; authenticated Page body defined by a profile |
Check for the file encryption header before parsing Chunks. In an AEAD file, the legacy footer
properties still say unencrypted; the header takes precedence.
The write and read paths are:
```text
encoded page payload -> compression -> encryption -> stored_data
stored_data -> authenticated decryption when applicable -> decompression -> value decoding
```
## File Encryption Header
An AEAD file puts one `FileEncryptionHeader` immediately after the seven-byte magic/version
prefix, before any Chunk Group or operation-index record:
```text
FileEncryptionHeader :=
byte marker = 0x06
UVarInt payload_size
byte header_version = 0x01
VarString provider_id
VarString profile_id
VarString key_id
VarString key_version
UVarInt file_crypto_id_size = 16
byte file_crypto_id[16]
UVarInt wrapped_data_key_size
byte wrapped_data_key[wrapped_data_key_size]
```
`payload_size` counts from `header_version` through the wrapped key, excluding the marker and the
length field itself. Its allowed range is `1..1048576`, and parsing must end exactly at that
boundary. The four string identifiers must be non-null and non-blank. The file crypto ID is
exactly 16 bytes, and the wrapped data key is non-empty. These binary lengths use `UVarInt`, not
the nullable `VarBinary` used for footer properties.
The **provider** identifies the encryption implementation or service. Its **profile** names a
fixed byte-format contract: Page framing, nonce and tag representation, size mapping, and Chunk
proof size. A provider can offer several profiles. The file stores these public identifiers and
the wrapped data key, rather than an implementation class name or the plaintext key. The reader needs
the matching provider/profile and access to the external key material.
Header version `0x01` is the supported layout. Unknown versions, truncated fields, trailing bytes,
a second encryption header, or a header anywhere except offset `7` are errors.
## AEAD Chunk Header
The file encryption header also changes how every Chunk Header is parsed. The Chunk marker
stays the same, but three fields follow `encoding_type`:
```text
int64 chunk_ordinal
int32 chunk_authentication_size
byte chunk_authentication[chunk_authentication_size]
```
`chunk_ordinal` is a non-negative identifier allocated uniquely across the file, including Time
and Value Chunks. The proof length must match the profile and lie in `1..1024`. These fields are
part of the Header, so none of them count towards `data_size`.
The proof binds the ordinal to the Chunk's absolute position. Verify it before using that
identity. Moving a Chunk changes the authenticated position and therefore requires a new proof.
Its associated data is the following big-endian sequence:
```text
int32 domain_size = 20
byte domain[20] = UTF8("TsFile/ChunkPosition")
int32 associated_data_version = 1
byte file_crypto_id[16]
int64 absolute_chunk_marker_offset
int64 chunk_ordinal
```
## AEAD Page Body and Context
For a non-empty Page, `compressed_size` records the entire encrypted body, including framing,
nonces, tags, and padding. It is larger than the compressed plaintext length, even with
`UNCOMPRESSED`. A profile may use fixed overhead or a size-dependent layout, so use its size
mapping to recover the candidate plaintext length.
Page authentication also binds the body to its file, Chunk, and Page position. The associated
data is the exact big-endian sequence below. `SizedBytes` means an `int32` byte length followed
by those bytes; text is UTF-8.
```text
int32 associated_data_version = 1
SizedBytes provider_id
SizedBytes profile_id
SizedBytes key_id
SizedBytes key_version
SizedBytes file_crypto_id
int64 chunk_ordinal
int32 page_index
int32 uncompressed_size
int32 compressed_plaintext_size
```
`page_index` starts at zero for each physical Chunk and counts empty aligned Value Pages as well.
An all-null Value Page is still the single `UVarInt(0)` described in [Nulls](../Nulls.md); it has
no ciphertext or Page authentication tag.
The candidate plaintext length becomes trusted only after authentication succeeds. Decryption
must return that length, and decompression must then return `uncompressed_size` bytes. An invalid
tag, wrong key, unsupported profile, or length mismatch is an error; do not retry as plaintext.
## Legacy File Properties
When there is no file encryption header, read the following `TsFileMetadata` properties:
| Property | Meaning |
| --- | --- |
| `encryptLevel` | `0` for no encryption; `1` for a directly stored data key; `2` for a data key encrypted by an external master key |
| `encryptType` | Legacy encryption/decryption algorithm identifier |
| `encryptKey` | Text representation of the data key or key-wrapping result |
These property values contain text. At level `1`, the data key is stored directly. At level `2`,
an external master-key decryptor unwraps it before Page decryption. Missing `encryptLevel` means
`0`; levels `1` and `2` require `encryptType` and a non-empty `encryptKey`. Unknown levels and
unavailable algorithms are errors.
Legacy encryption uses the algorithm's encrypt/decrypt operations without the AEAD Chunk suffix
or Page context. `compressed_size` is still the actual stored output length; it equals compressed
plaintext length only for a length-preserving algorithm.
AEAD writers set these legacy properties to level `0`, type
`org.apache.tsfile.encrypt.UNENCRYPTED`, and an empty key. The AEAD header supplies the real
mode and wrapped key, so those values must not be interpreted as a plaintext fallback.
## Security Scope
Page AEAD authenticates non-empty Page bodies and their context, while Chunk proofs bind ordinals
to file positions. It does not authenticate every visible field or provide a whole-file digest.
Legacy encryption does not necessarily provide authentication. See [Checksumming](Checksumming.md)
for whole-file integrity and [Error Recovery](Error-Recovery.md) for retaining encryption context
when rebuilding a footer.