Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,44 @@ error.encrypt.encrypt_no_constructor = Get constructor for encryptor failed: %1$
# EncryptUtils.getEncrypt — instantiation failed (encryptType arg)
error.encrypt.encrypt_instantiation_failed = New encryptor instance failed: %1$s

# EncryptionProviderRegistry — provider registration and lookup
error.encrypt.invalid_provider_id = Encryption provider id must not be null or empty
error.encrypt.duplicate_provider = Encryption provider id is already registered: %1$s
error.encrypt.provider_not_found = Encryption provider is not registered: %1$s
error.encrypt.provider_returned_null = Encryption provider returned null: %1$s

# EncryptParameter — use after sensitive state was destroyed
error.encrypt.parameter_destroyed = Encryption parameters have been destroyed
error.encrypt.invalid_page_body_overhead = Invalid page body overhead %1$s for encryption profile %2$s
error.encrypt.page_body_too_short = Encrypted page body size %1$s is smaller than AEAD overhead %2$s
error.encrypt.page_context_invalid_parameter = Page crypto context requires page AEAD parameters
error.encrypt.page_aead_encrypt_unsupported = This encryptor does not implement authenticated page encryption
error.encrypt.page_aead_decrypt_unsupported = This decryptor does not implement authenticated page decryption
error.encrypt.page_context_invalid_file_id = Page crypto context requires a 16-byte file crypto id
error.encrypt.page_context_invalid_chunk_ordinal = Page crypto context requires a nonnegative chunk ordinal
error.encrypt.page_read_requires_chunk_ordinal = Page-AEAD files require readPage(header, type, pageIndex, chunkOrdinal)
error.encrypt.chunk_ordinal_exhausted = File chunk ordinal space is exhausted
error.write.invalid_chunk_ordinal = Encrypted chunks require a file-unique chunk ordinal
error.write.unencrypted_chunk_has_ordinal = Unencrypted chunks must not have a chunk ordinal
error.write.aead_append_requires_file_header = Cannot append encrypted data to a file without an encryption header
error.write.encrypted_chunk_copy_unsupported = Encrypted chunks must be decrypted and re-encrypted before copying
error.encrypt.page_context_invalid_sizes = Invalid page crypto context values, uncompressed size: %1$s, compressed plaintext size: %2$s, page index: %3$s
error.encrypt.page_output_size_mismatch = AEAD page output size mismatch, expected %1$s bytes but got %2$s
error.encrypt.page_plaintext_size_mismatch = AEAD page plaintext size mismatch, expected %1$s bytes but got %2$s
error.chunk.merge_page_aead_unsupported = Page-AEAD encrypted chunks must be decrypted and rewritten before merging

# FileEncryptionHeader — malformed or unsupported file encryption metadata
error.file.encryption_header_invalid_size = Invalid file encryption header size: %1$s
error.file.encryption_header_unsupported_version = Unsupported file encryption header version: %1$s
error.file.encryption_header_malformed = Malformed file encryption header
error.file.encryption_header_trailing_bytes = File encryption header contains trailing bytes
error.file.encryption_header_invalid_component_size = Invalid file encryption header component size: %1$s
error.file.encryption_header_truncated = File encryption header is truncated, expected %1$s bytes but read %2$s
error.file.encryption_header_invalid_field = Invalid file encryption header field: %1$s
error.write.encryption_header_must_precede_data = File encryption header must be written before TsFile data
error.write.encrypted_chunk_context_mismatch = Encrypted chunks must use the destination file encryption context
error.write.encryption_context_immutable = The file encryption context cannot change after its header is written

# === compress ===

# ICompressor.getCompressor / IUnCompressor.getUnCompressor — null type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,44 @@ error.encrypt.encrypt_no_constructor = 获取 encryptor 构造函数失败: %1$s
# EncryptUtils.getEncrypt — instantiation failed (encryptType arg)
error.encrypt.encrypt_instantiation_failed = 实例化 encryptor 失败: %1$s

# EncryptionProviderRegistry — provider registration and lookup
error.encrypt.invalid_provider_id = encryption provider id 不能为空
error.encrypt.duplicate_provider = encryption provider id 已注册: %1$s
error.encrypt.provider_not_found = encryption provider 未注册: %1$s
error.encrypt.provider_returned_null = encryption provider 返回 null: %1$s

# EncryptParameter — use after sensitive state was destroyed
error.encrypt.parameter_destroyed = encryption parameters 已销毁
error.encrypt.invalid_page_body_overhead = encryption profile %2$s 的 page body 额外开销 %1$s 无效
error.encrypt.page_body_too_short = encrypted page body 大小 %1$s 小于 AEAD 额外开销 %2$s
error.encrypt.page_context_invalid_parameter = page crypto context 需要 page AEAD 参数
error.encrypt.page_aead_encrypt_unsupported = 此加密器未实现经过认证的 Page 加密
error.encrypt.page_aead_decrypt_unsupported = 此解密器未实现经过认证的 Page 解密
error.encrypt.page_context_invalid_file_id = page crypto context 需要 16 字节的 file crypto id
error.encrypt.page_context_invalid_chunk_ordinal = page crypto context 需要非负的 chunk ordinal
error.encrypt.page_read_requires_chunk_ordinal = Page-AEAD 文件需要调用 readPage(header, type, pageIndex, chunkOrdinal)
error.encrypt.chunk_ordinal_exhausted = 文件 Chunk 序号已耗尽
error.write.invalid_chunk_ordinal = 加密 Chunk 必须具有文件内唯一的 Chunk 序号
error.write.unencrypted_chunk_has_ordinal = 未加密 Chunk 不得携带 Chunk 序号
error.write.aead_append_requires_file_header = 无加密头的文件不能追加加密数据
error.write.encrypted_chunk_copy_unsupported = 复制加密 Chunk 前必须先解密并重新加密
error.encrypt.page_context_invalid_sizes = page crypto context 值无效,uncompressed size: %1$s,compressed plaintext size: %2$s,page index: %3$s
error.encrypt.page_output_size_mismatch = AEAD page 输出大小不匹配,预期 %1$s 字节,实际 %2$s 字节
error.encrypt.page_plaintext_size_mismatch = AEAD page plaintext 大小不匹配,预期 %1$s 字节,实际 %2$s 字节
error.chunk.merge_page_aead_unsupported = Page-AEAD encrypted chunk 合并前必须先解密并重写

# FileEncryptionHeader — malformed or unsupported file encryption metadata
error.file.encryption_header_invalid_size = file encryption header 大小无效: %1$s
error.file.encryption_header_unsupported_version = 不支持的 file encryption header 版本: %1$s
error.file.encryption_header_malformed = file encryption header 格式错误
error.file.encryption_header_trailing_bytes = file encryption header 包含多余字节
error.file.encryption_header_invalid_component_size = file encryption header 组件大小无效: %1$s
error.file.encryption_header_truncated = file encryption header 已截断,预期 %1$s 字节,实际读取 %2$s 字节
error.file.encryption_header_invalid_field = file encryption header 字段无效: %1$s
error.write.encryption_header_must_precede_data = file encryption header 必须写在 TsFile data 之前
error.write.encrypted_chunk_context_mismatch = encrypted Chunk 必须使用目标文件的加密上下文
error.write.encryption_context_immutable = 文件加密头写入后不能更改文件加密上下文

# === compress ===

# ICompressor.getCompressor / IUnCompressor.getUnCompressor — null type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,116 @@
*/
package org.apache.tsfile.encrypt;

public class EncryptParameter {
import org.apache.tsfile.exception.encrypt.EncryptException;
import org.apache.tsfile.i18n.Messages;

import javax.security.auth.Destroyable;

import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;

/**
* Parameters required to encrypt or decrypt one TsFile.
*
* <p>The two-argument constructor preserves the legacy encryption API. New file-level encryption
* schemes should use {@link #pageAeadBuilder()} and identify an {@link IEncryptProvider} and one of
* its immutable encryption profiles by stable ids. Runtime implementation class names and algorithm
* implementation details are deliberately not part of the persisted parameters.
*/
public class EncryptParameter implements AutoCloseable, Destroyable {

public static final int FILE_CRYPTO_ID_LENGTH = 16;

private final String type;
private final byte[] key;
private final boolean pageAead;
private final String providerId;
private final String profileId;
private final String keyId;
private final String keyVersion;
private final byte[] wrappedDataKey;
private final byte[] fileCryptoId;
private final AtomicLong nextChunkOrdinal;

private transient volatile IEncrypt fileEncrypt;
private transient volatile boolean destroyed;

public EncryptParameter(String type, byte[] key) {
this.type = type;
this.key = key;
this.pageAead = false;
this.providerId = null;
this.profileId = null;
this.keyId = null;
this.keyVersion = null;
this.wrappedDataKey = null;
this.fileCryptoId = null;
this.nextChunkOrdinal = new AtomicLong();
}

private EncryptParameter(Builder builder) {
this.type = null;
this.key = copy(builder.key);
this.pageAead = true;
this.providerId = builder.providerId;
this.profileId = builder.profileId;
this.keyId = builder.keyId;
this.keyVersion = builder.keyVersion;
this.wrappedDataKey = copy(builder.wrappedDataKey);
this.fileCryptoId = copy(builder.fileCryptoId);
this.nextChunkOrdinal =
builder.nextChunkOrdinal == null ? new AtomicLong() : builder.nextChunkOrdinal;
}

public static Builder pageAeadBuilder() {
return new Builder();
}

/** Returns an independent parameter object with no initialized runtime provider state. */
public EncryptParameter copy() {
if (destroyed) {
throw new EncryptException(Messages.get("error.encrypt.parameter_destroyed"));
}
if (!isTdePageAead()) {
return new EncryptParameter(type, key);
}
return pageAeadBuilder()
.key(key)
.providerId(providerId)
.profileId(profileId)
.keyId(keyId)
.keyVersion(keyVersion)
.wrappedDataKey(wrappedDataKey)
.fileCryptoId(fileCryptoId)
.nextChunkOrdinal(nextChunkOrdinal)
.build();
}

/** Allocates a file-unique identity before the first page of a chunk is encrypted. */
public long nextChunkOrdinal() {
if (!pageAead || destroyed) {
throw new EncryptException(Messages.get("error.encrypt.page_context_invalid_parameter"));
}
long ordinal =
nextChunkOrdinal.getAndUpdate(value -> value == Long.MAX_VALUE ? value : value + 1);
if (ordinal == Long.MAX_VALUE) {
throw new EncryptException(Messages.get("error.encrypt.chunk_ordinal_exhausted"));
}
return ordinal;
}

/** Continues allocation after the highest retained chunk during append or crash recovery. */
public void resumeAfterChunkOrdinal(long ordinal) {
if (!pageAead || destroyed) {
throw new EncryptException(Messages.get("error.encrypt.page_context_invalid_parameter"));
}
if (ordinal == Long.MAX_VALUE) {
throw new EncryptException(Messages.get("error.encrypt.chunk_ordinal_exhausted"));
}
if (ordinal >= 0) {
nextChunkOrdinal.accumulateAndGet(ordinal + 1, Math::max);
}
}

public byte[] getKey() {
Expand All @@ -35,4 +137,169 @@ public byte[] getKey() {
public String getType() {
return type;
}

public String getProviderId() {
return providerId;
}

public String getProfileId() {
return profileId;
}

public String getKeyId() {
return keyId;
}

public String getKeyVersion() {
return keyVersion;
}

public byte[] getWrappedDataKey() {
return copy(wrappedDataKey);
}

public byte[] getFileCryptoId() {
return copy(fileCryptoId);
}

public int getPageBodyOverhead() {
int overhead = getOrCreateFileEncrypt().getPageBodyOverhead();
if (overhead <= 0) {
throw new EncryptException(
Messages.format("error.encrypt.invalid_page_body_overhead", overhead, profileId));
}
return overhead;
}

public boolean isTdePageAead() {
return pageAead;
}

/** Copies of one file share the ordinal allocator; independently created files never do. */
public boolean sharesPageAeadFileContext(EncryptParameter other) {
return other != null
&& pageAead
&& other.pageAead
&& !destroyed
&& !other.destroyed
&& nextChunkOrdinal == other.nextChunkOrdinal
&& Objects.equals(providerId, other.providerId)
&& Objects.equals(profileId, other.profileId)
&& Objects.equals(keyId, other.keyId)
&& Objects.equals(keyVersion, other.keyVersion)
&& Arrays.equals(fileCryptoId, other.fileCryptoId)
&& Arrays.equals(wrappedDataKey, other.wrappedDataKey)
&& Arrays.equals(key, other.key);
}

IEncrypt getOrCreateFileEncrypt() {
if (destroyed) {
throw new EncryptException(Messages.get("error.encrypt.parameter_destroyed"));
}
IEncrypt current = fileEncrypt;
if (current == null) {
synchronized (this) {
if (destroyed) {
throw new EncryptException(Messages.get("error.encrypt.parameter_destroyed"));
}
current = fileEncrypt;
if (current == null) {
current = EncryptionProviderRegistry.create(this);
fileEncrypt = current;
}
}
}
return current;
}

@Override
public boolean isDestroyed() {
return destroyed;
}

@Override
public void destroy() {
IEncrypt current;
synchronized (this) {
if (destroyed) {
return;
}
destroyed = true;
current = fileEncrypt;
fileEncrypt = null;
if (key != null && isTdePageAead()) {
Arrays.fill(key, (byte) 0);
}
}
if (current != null) {
current.close();
}
}

@Override
public void close() {
destroy();
}

private static byte[] copy(byte[] value) {
return value == null ? null : Arrays.copyOf(value, value.length);
}

public static final class Builder {

private byte[] key;
private String providerId;
private String profileId;
private String keyId;
private String keyVersion;
private byte[] wrappedDataKey;
private byte[] fileCryptoId;
private AtomicLong nextChunkOrdinal;

private Builder() {}

public Builder key(byte[] key) {
this.key = copy(key);
return this;
}

public Builder providerId(String providerId) {
this.providerId = providerId;
return this;
}

public Builder profileId(String profileId) {
this.profileId = profileId;
return this;
}

public Builder keyId(String keyId) {
this.keyId = keyId;
return this;
}

public Builder keyVersion(String keyVersion) {
this.keyVersion = keyVersion;
return this;
}

public Builder wrappedDataKey(byte[] wrappedDataKey) {
this.wrappedDataKey = copy(wrappedDataKey);
return this;
}

public Builder fileCryptoId(byte[] fileCryptoId) {
this.fileCryptoId = copy(fileCryptoId);
return this;
}

private Builder nextChunkOrdinal(AtomicLong nextChunkOrdinal) {
this.nextChunkOrdinal = nextChunkOrdinal;
return this;
}

public EncryptParameter build() {
return new EncryptParameter(this);
}
}
}
Loading
Loading