52#if !defined(SIGNET_ENABLE_COMMERCIAL) || !SIGNET_ENABLE_COMMERCIAL
53#error "signet/crypto/pme.hpp requires SIGNET_ENABLE_COMMERCIAL=ON (AGPL-3.0 commercial tier). See LICENSE_COMMERCIAL."
61#include <unordered_map>
77namespace detail::pme {
79static constexpr uint8_t MODULE_FOOTER = 0;
80static constexpr uint8_t MODULE_COLUMN_META = 1;
81static constexpr uint8_t MODULE_DATA_PAGE = 2;
82static constexpr uint8_t MODULE_DICT_PAGE = 3;
83static constexpr uint8_t MODULE_DATA_PAGE_HEADER = 4;
84static constexpr uint8_t MODULE_COLUMN_META_HEADER = 5;
87inline std::string build_aad_legacy(
const std::string& prefix,
89 const std::string& extra =
"") {
91 aad.reserve(prefix.size() + 3 + extra.size());
94 aad.push_back(
static_cast<char>(module_type));
103inline std::string build_aad_spec(
const std::string& prefix,
105 const std::string& extra =
"") {
108 aad.push_back(
static_cast<char>(module_type));
112 uint16_t rg_ord = 0, col_ord = 0, pg_ord = 0;
113 if (!extra.empty()) {
114 auto colon1 = extra.find(
':');
115 if (colon1 != std::string::npos) {
116 auto colon2 = extra.find(
':', colon1 + 1);
117 if (colon2 != std::string::npos) {
118 auto colon3 = extra.find(
':', colon2 + 1);
119 if (colon3 != std::string::npos) {
121 try { col_ord =
static_cast<uint16_t
>(std::stoi(extra.substr(colon1 + 1, colon2 - colon1 - 1))); }
catch (...) {}
122 try { rg_ord =
static_cast<uint16_t
>(std::stoi(extra.substr(colon2 + 1, colon3 - colon2 - 1))); }
catch (...) {}
123 try { pg_ord =
static_cast<uint16_t
>(std::stoi(extra.substr(colon3 + 1))); }
catch (...) {}
126 try { rg_ord =
static_cast<uint16_t
>(std::stoi(extra.substr(colon1 + 1, colon2 - colon1 - 1))); }
catch (...) {}
127 try { pg_ord =
static_cast<uint16_t
>(std::stoi(extra.substr(colon2 + 1))); }
catch (...) {}
133 auto append_le16 = [&](uint16_t v) {
134 aad.push_back(
static_cast<char>(v & 0xFF));
135 aad.push_back(
static_cast<char>((v >> 8) & 0xFF));
138 if (module_type >= MODULE_COLUMN_META) {
140 append_le16(col_ord);
149 const std::string& prefix,
151 const std::string& extra =
"") {
152 if (config.
aad_format == EncryptionConfig::AadFormat::SPEC_BINARY) {
153 return build_aad_spec(prefix, module_type, extra);
155 return build_aad_legacy(prefix, module_type, extra);
163inline std::string pme_key_size_error(
size_t actual_size,
164 const std::string& context) {
166 return "PME: AES-128 (16-byte) keys detected for " + context
167 +
". Signet Forge requires AES-256 (32-byte) keys per "
168 "NIST SP 800-131A. See Gap P-7 for AES-128 interop roadmap.";
170 return "PME: invalid key size (" + std::to_string(actual_size)
171 +
" bytes) for " + context
173 +
" bytes (AES-256).";
204 key_cache_[ck.column_name] = &ck.key;
226 const uint8_t* footer_data,
size_t size)
const {
228 auto license = commercial::require_feature(
"PME encrypt_footer");
229 if (!license)
return license.error();
233 if (config_.
footer_key.size() != cipher->key_size()) {
235 detail::pme::pme_key_size_error(
240 std::string aad = build_aad(config_.
aad_prefix,
241 detail::pme::MODULE_FOOTER);
244 return cipher->encrypt(footer_data, size, aad);
270 const uint8_t* page_data,
size_t size,
271 const std::string& column_name,
272 int32_t row_group_ordinal,
273 int32_t page_ordinal)
const {
275 auto license = commercial::require_feature(
"PME encrypt_column_page");
276 if (!license)
return license.error();
278 const auto& key = get_column_key(column_name);
281 return std::vector<uint8_t>(page_data, page_data + size);
285 if (key.size() != cipher->key_size()) {
287 detail::pme::pme_key_size_error(
288 key.size(),
"column '" + column_name +
"'")};
292 std::string extra = column_name +
":"
293 + std::to_string(row_group_ordinal) +
":"
294 + std::to_string(page_ordinal);
298 std::string aad = build_aad(config_.
aad_prefix,
299 detail::pme::MODULE_DATA_PAGE, extra);
300 return cipher->encrypt(page_data, size, aad);
303 return cipher->encrypt(page_data, size);
321 const uint8_t* metadata,
size_t size,
322 const std::string& column_name)
const {
324 auto license = commercial::require_feature(
"PME encrypt_column_metadata");
325 if (!license)
return license.error();
327 const auto& key = get_column_key(column_name);
330 return std::vector<uint8_t>(metadata, metadata + size);
334 if (key.size() != cipher->key_size()) {
336 detail::pme::pme_key_size_error(
337 key.size(),
"column '" + column_name +
"'")};
340 std::string aad = build_aad(config_.
aad_prefix,
341 detail::pme::MODULE_COLUMN_META, column_name);
342 return cipher->encrypt(metadata, size, aad);
369 const uint8_t* page_data,
size_t size,
370 const std::string& column_name,
371 int32_t row_group_ordinal)
const {
373 auto license = commercial::require_feature(
"PME encrypt_dict_page");
374 if (!license)
return license.error();
376 const auto& key = get_column_key(column_name);
378 return std::vector<uint8_t>(page_data, page_data + size);
382 if (key.size() != cipher->key_size()) {
384 detail::pme::pme_key_size_error(
385 key.size(),
"column '" + column_name +
"'")};
388 std::string extra = column_name +
":"
389 + std::to_string(row_group_ordinal) +
":0";
392 std::string aad = build_aad(config_.
aad_prefix,
393 detail::pme::MODULE_DICT_PAGE, extra);
394 return cipher->encrypt(page_data, size, aad);
396 return cipher->encrypt(page_data, size);
423 const uint8_t* header_data,
size_t size,
424 const std::string& column_name,
425 int32_t row_group_ordinal,
426 int32_t page_ordinal)
const {
428 auto license = commercial::require_feature(
"PME encrypt_data_page_header");
429 if (!license)
return license.error();
431 const auto& key = get_column_key(column_name);
433 return std::vector<uint8_t>(header_data, header_data + size);
438 if (key.size() != cipher->key_size()) {
440 detail::pme::pme_key_size_error(
441 key.size(),
"column '" + column_name +
"'")};
444 std::string extra = column_name +
":"
445 + std::to_string(row_group_ordinal) +
":"
446 + std::to_string(page_ordinal);
447 std::string aad = build_aad(config_.
aad_prefix,
448 detail::pme::MODULE_DATA_PAGE_HEADER, extra);
449 return cipher->encrypt(header_data, size, aad);
464 const uint8_t* header_data,
size_t size,
465 const std::string& column_name)
const {
467 auto license = commercial::require_feature(
"PME encrypt_column_meta_header");
468 if (!license)
return license.error();
470 const auto& key = get_column_key(column_name);
472 return std::vector<uint8_t>(header_data, header_data + size);
476 if (key.size() != cipher->key_size()) {
478 detail::pme::pme_key_size_error(
479 key.size(),
"column '" + column_name +
"'")};
482 std::string aad = build_aad(config_.
aad_prefix,
483 detail::pme::MODULE_COLUMN_META_HEADER,
485 return cipher->encrypt(header_data, size, aad);
511 const uint8_t* footer_data,
size_t size)
const {
513 auto license = commercial::require_feature(
"PME sign_footer");
514 if (!license)
return license.error();
518 detail::pme::pme_key_size_error(
519 config_.
footer_key.size(),
"footer signing")};
523 auto signing_key = derive_footer_signing_key();
526 std::string aad = build_aad(config_.
aad_prefix, detail::pme::MODULE_FOOTER);
527 std::vector<uint8_t> msg;
528 msg.reserve(aad.size() + size);
529 msg.insert(msg.end(), aad.begin(), aad.end());
530 msg.insert(msg.end(), footer_data, footer_data + size);
533 signing_key.data(), signing_key.size(),
534 msg.data(), msg.size());
537 std::vector<uint8_t> out;
538 out.reserve(size + 32);
539 out.insert(out.end(), footer_data, footer_data + size);
540 out.insert(out.end(), hmac.begin(), hmac.end());
560 auto license = commercial::require_feature(
"PME wrap_keys");
561 if (!license)
return license.error();
565 "PME: KMS client not configured for key wrapping"};
568 std::vector<std::pair<std::string, std::vector<uint8_t>>> result;
574 if (!wrapped)
return wrapped.error();
575 result.emplace_back(config_.
footer_key_id, std::move(wrapped.value()));
580 if (!ck.key.empty() && !ck.key_id.empty()) {
581 auto wrapped = config_.
kms_client->wrap_key(ck.key, ck.key_id);
582 if (!wrapped)
return wrapped.error();
583 result.emplace_back(ck.key_id, std::move(wrapped.value()));
592 if (!wrapped)
return wrapped.error();
594 std::move(wrapped.value()));
614 const std::string& column_name)
const {
621 if (ck.column_name == column_name) {
642 return !get_column_key(column_name).empty();
652 std::unordered_map<std::string, const std::vector<uint8_t>*> key_cache_;
657 [[nodiscard]]
const std::vector<uint8_t>& get_column_key(
658 const std::string& column_name)
const {
660 auto it = key_cache_.find(column_name);
661 if (it != key_cache_.end()) {
670 [[nodiscard]] std::array<uint8_t, 32> derive_footer_signing_key()
const {
671 static constexpr uint8_t INFO[] =
"signet-pme-footer-sign-v1";
673 reinterpret_cast<const uint8_t*
>(config_.
aad_prefix.data()),
677 std::array<uint8_t, 32> key{};
678 (void)
hkdf_expand(prk, INFO,
sizeof(INFO) - 1, key.data(), key.size());
689 [[nodiscard]]
static std::vector<uint8_t> generate_iv(
size_t iv_size) {
696 [[nodiscard]] std::string build_aad(
const std::string& prefix,
698 const std::string& extra =
"")
const {
699 return detail::pme::build_aad(config_, prefix, module_type, extra);
707 [[nodiscard]]
static std::vector<uint8_t> prepend_iv(
708 const std::vector<uint8_t>& iv,
709 const std::vector<uint8_t>& ciphertext) {
711 std::vector<uint8_t> out;
712 out.reserve(1 + iv.size() + ciphertext.size());
713 out.push_back(
static_cast<uint8_t
>(iv.size()));
714 out.insert(out.end(), iv.begin(), iv.end());
715 out.insert(out.end(), ciphertext.begin(), ciphertext.end());
722 [[nodiscard]] expected<std::vector<uint8_t>> encrypt_gcm(
723 const std::vector<uint8_t>& key,
725 const std::string& extra,
726 const uint8_t* data,
size_t size)
const {
729 if (module_type > 5) {
731 "PME module_type out of range [0..5] (CWE-20)"};
733 std::string aad = build_aad(config_.
aad_prefix, module_type, extra);
735 return cipher->encrypt(data, size, aad);
744 [[nodiscard]] expected<std::vector<uint8_t>> encrypt_ctr(
745 const std::vector<uint8_t>& key,
746 const uint8_t* data,
size_t size)
const {
750 return cipher->encrypt(data, size);
777 key_cache_[ck.column_name] = &ck.key;
796 const uint8_t* encrypted_footer,
size_t size)
const {
798 auto license = commercial::require_feature(
"PME decrypt_footer");
799 if (!license)
return license.error();
803 if (config_.
footer_key.size() != cipher->key_size()) {
805 detail::pme::pme_key_size_error(
810 std::string aad = build_aad(config_.
aad_prefix,
811 detail::pme::MODULE_FOOTER);
814 return cipher->decrypt(encrypted_footer, size, aad);
831 const uint8_t* encrypted_page,
size_t size,
832 const std::string& column_name,
833 int32_t row_group_ordinal,
834 int32_t page_ordinal)
const {
836 auto license = commercial::require_feature(
"PME decrypt_column_page");
837 if (!license)
return license.error();
839 const auto& key = get_column_key(column_name);
842 return std::vector<uint8_t>(encrypted_page, encrypted_page + size);
845 if (key.size() != cipher->key_size()) {
847 detail::pme::pme_key_size_error(
848 key.size(),
"column '" + column_name +
"'")};
852 std::string extra = column_name +
":"
853 + std::to_string(row_group_ordinal) +
":"
854 + std::to_string(page_ordinal);
857 std::string aad = build_aad(config_.
aad_prefix,
858 detail::pme::MODULE_DATA_PAGE, extra);
859 return cipher->decrypt(encrypted_page, size, aad);
861 return cipher->decrypt(encrypted_page, size);
877 const uint8_t* encrypted_metadata,
size_t size,
878 const std::string& column_name)
const {
880 auto license = commercial::require_feature(
"PME decrypt_column_metadata");
881 if (!license)
return license.error();
883 const auto& key = get_column_key(column_name);
885 return std::vector<uint8_t>(encrypted_metadata,
886 encrypted_metadata + size);
890 if (key.size() != cipher->key_size()) {
892 detail::pme::pme_key_size_error(
893 key.size(),
"column '" + column_name +
"'")};
896 std::string aad = build_aad(config_.
aad_prefix,
897 detail::pme::MODULE_COLUMN_META, column_name);
898 return cipher->decrypt(encrypted_metadata, size, aad);
915 const uint8_t* encrypted_page,
size_t size,
916 const std::string& column_name,
917 int32_t row_group_ordinal)
const {
919 auto license = commercial::require_feature(
"PME decrypt_dict_page");
920 if (!license)
return license.error();
922 const auto& key = get_column_key(column_name);
924 return std::vector<uint8_t>(encrypted_page, encrypted_page + size);
928 if (key.size() != cipher->key_size()) {
930 detail::pme::pme_key_size_error(
931 key.size(),
"column '" + column_name +
"'")};
934 std::string extra = column_name +
":"
935 + std::to_string(row_group_ordinal) +
":0";
938 std::string aad = build_aad(config_.
aad_prefix,
939 detail::pme::MODULE_DICT_PAGE, extra);
940 return cipher->decrypt(encrypted_page, size, aad);
942 return cipher->decrypt(encrypted_page, size);
961 const uint8_t* encrypted_header,
size_t size,
962 const std::string& column_name,
963 int32_t row_group_ordinal,
964 int32_t page_ordinal)
const {
966 auto license = commercial::require_feature(
"PME decrypt_data_page_header");
967 if (!license)
return license.error();
969 const auto& key = get_column_key(column_name);
971 return std::vector<uint8_t>(encrypted_header, encrypted_header + size);
975 if (key.size() != cipher->key_size()) {
977 detail::pme::pme_key_size_error(
978 key.size(),
"column '" + column_name +
"'")};
981 std::string extra = column_name +
":"
982 + std::to_string(row_group_ordinal) +
":"
983 + std::to_string(page_ordinal);
984 std::string aad = build_aad(config_.
aad_prefix,
985 detail::pme::MODULE_DATA_PAGE_HEADER, extra);
986 return cipher->decrypt(encrypted_header, size, aad);
1002 const uint8_t* encrypted_header,
size_t size,
1003 const std::string& column_name)
const {
1005 auto license = commercial::require_feature(
"PME decrypt_column_meta_header");
1006 if (!license)
return license.error();
1008 const auto& key = get_column_key(column_name);
1010 return std::vector<uint8_t>(encrypted_header,
1011 encrypted_header + size);
1015 if (key.size() != cipher->key_size()) {
1017 detail::pme::pme_key_size_error(
1018 key.size(),
"column '" + column_name +
"'")};
1021 std::string aad = build_aad(config_.
aad_prefix,
1022 detail::pme::MODULE_COLUMN_META_HEADER,
1024 return cipher->decrypt(encrypted_header, size, aad);
1045 const std::vector<std::pair<std::string, std::vector<uint8_t>>>& wrapped_keys) {
1047 auto license = commercial::require_feature(
"PME unwrap_keys");
1048 if (!license)
return license.error();
1052 "PME: KMS client not configured for key unwrapping"};
1055 for (
const auto& [key_id, wrapped_dek] : wrapped_keys) {
1056 auto unwrapped = config_.
kms_client->unwrap_key(wrapped_dek, key_id);
1057 if (!unwrapped)
return unwrapped.error();
1061 config_.
footer_key = std::move(unwrapped.value());
1068 if (ck.key_id == key_id) {
1069 ck.key = std::move(unwrapped.value());
1076 "PME: no config slot for KMS key_id '" + key_id +
"'"};
1084 key_cache_[ck.column_name] = &ck.key;
1105 const uint8_t* signed_footer,
size_t size)
const {
1107 auto license = commercial::require_feature(
"PME verify_footer_signature");
1108 if (!license)
return license.error();
1112 "PME: signed footer too short (need at least 32 bytes for HMAC)"};
1117 detail::pme::pme_key_size_error(
1118 config_.
footer_key.size(),
"footer signature verification")};
1121 size_t footer_size = size - 32;
1122 const uint8_t* footer_data = signed_footer;
1123 const uint8_t* expected_hmac = signed_footer + footer_size;
1126 auto signing_key = derive_footer_signing_key();
1129 std::string aad = build_aad(config_.
aad_prefix, detail::pme::MODULE_FOOTER);
1130 std::vector<uint8_t> msg;
1131 msg.reserve(aad.size() + footer_size);
1132 msg.insert(msg.end(), aad.begin(), aad.end());
1133 msg.insert(msg.end(), footer_data, footer_data + footer_size);
1136 signing_key.data(), signing_key.size(),
1137 msg.data(), msg.size());
1141 for (
size_t i = 0; i < 32; ++i) {
1142 diff |= computed_hmac[i] ^ expected_hmac[i];
1147 "PME: footer signature verification failed — data may be tampered"};
1150 return std::vector<uint8_t>(footer_data, footer_data + footer_size);
1160 std::unordered_map<std::string, const std::vector<uint8_t>*> key_cache_;
1163 [[nodiscard]]
const std::vector<uint8_t>& get_column_key(
1164 const std::string& column_name)
const {
1166 auto it = key_cache_.find(column_name);
1167 if (it != key_cache_.end()) {
1176 [[nodiscard]] std::array<uint8_t, 32> derive_footer_signing_key()
const {
1177 static constexpr uint8_t INFO[] =
"signet-pme-footer-sign-v1";
1179 reinterpret_cast<const uint8_t*
>(config_.
aad_prefix.data()),
1183 std::array<uint8_t, 32> key{};
1184 (void)
hkdf_expand(prk, INFO,
sizeof(INFO) - 1, key.data(), key.size());
1193 const uint8_t* ciphertext;
1202 [[nodiscard]]
static expected<IvParsed> parse_iv_header(
1203 const uint8_t* data,
size_t size) {
1207 "PME: encrypted data too short (no IV size byte)"};
1210 uint8_t iv_size = data[0];
1211 if (iv_size == 0 || iv_size > 16) {
1213 "PME: invalid IV size " + std::to_string(iv_size)};
1216 size_t header_len = 1 +
static_cast<size_t>(iv_size);
1217 if (size < header_len) {
1219 "PME: encrypted data too short for IV"};
1232 [[nodiscard]] std::string build_aad(
const std::string& prefix,
1233 uint8_t module_type,
1234 const std::string& extra =
"")
const {
1235 return detail::pme::build_aad(config_, prefix, module_type, extra);
1241 [[nodiscard]] expected<std::vector<uint8_t>> decrypt_gcm(
1242 const std::vector<uint8_t>& key,
1243 uint8_t module_type,
1244 const std::string& extra,
1245 const uint8_t* data,
size_t size)
const {
1248 if (module_type > 5) {
1250 "PME module_type out of range [0..5] (CWE-20)"};
1252 std::string aad = build_aad(config_.
aad_prefix, module_type, extra);
1254 return cipher->decrypt(data, size, aad);
1260 [[nodiscard]] expected<std::vector<uint8_t>> decrypt_ctr(
1261 const std::vector<uint8_t>& key,
1262 const uint8_t* data,
size_t size)
const {
1266 return cipher->decrypt(data, size);
AES-256-CTR stream cipher implementation (NIST SP 800-38A).
AES-256-GCM authenticated encryption (NIST SP 800-38D).
Abstract cipher interface, GCM/CTR adapters, CipherFactory, and platform CSPRNG.
Decrypts Parquet modules using the keys from an EncryptionConfig.
expected< std::vector< uint8_t > > decrypt_column_meta_header(const uint8_t *encrypted_header, size_t size, const std::string &column_name) const
Decrypt a column metadata header (always AES-GCM authenticated).
expected< std::vector< uint8_t > > verify_footer_signature(const uint8_t *signed_footer, size_t size) const
Verify a signed plaintext footer and return the original footer data.
FileDecryptor(const EncryptionConfig &config)
Construct a decryptor from an encryption configuration.
expected< std::vector< uint8_t > > decrypt_dict_page(const uint8_t *encrypted_page, size_t size, const std::string &column_name, int32_t row_group_ordinal) const
Decrypt a dictionary page.
const EncryptionConfig & config() const
Access the underlying EncryptionConfig.
expected< void > unwrap_keys(const std::vector< std::pair< std::string, std::vector< uint8_t > > > &wrapped_keys)
Unwrap DEKs from wrapped blobs using the configured KMS client.
expected< std::vector< uint8_t > > decrypt_data_page_header(const uint8_t *encrypted_header, size_t size, const std::string &column_name, int32_t row_group_ordinal, int32_t page_ordinal) const
Decrypt a data page header (always AES-GCM authenticated).
expected< std::vector< uint8_t > > decrypt_footer(const uint8_t *encrypted_footer, size_t size) const
Decrypt the encrypted FileMetaData (footer).
expected< std::vector< uint8_t > > decrypt_column_page(const uint8_t *encrypted_page, size_t size, const std::string &column_name, int32_t row_group_ordinal, int32_t page_ordinal) const
Decrypt a column data page (AES-GCM or AES-CTR depending on algorithm).
expected< std::vector< uint8_t > > decrypt_column_metadata(const uint8_t *encrypted_metadata, size_t size, const std::string &column_name) const
Decrypt serialized ColumnMetaData (always AES-GCM authenticated).
Encrypts Parquet modules (footer, column metadata, data pages) using the keys and algorithm specified...
EncryptionKeyMetadata column_key_metadata(const std::string &column_name) const
Get key metadata for a column (stored in ColumnChunk.column_crypto_metadata).
expected< std::vector< uint8_t > > encrypt_data_page_header(const uint8_t *header_data, size_t size, const std::string &column_name, int32_t row_group_ordinal, int32_t page_ordinal) const
Encrypt a data page header (always AES-GCM authenticated).
FileEncryptionProperties file_properties() const
Get FileEncryptionProperties for embedding in FileMetaData.
bool is_column_encrypted(const std::string &column_name) const
Check if a column has an encryption key (specific or default).
const EncryptionConfig & config() const
Access the underlying EncryptionConfig.
expected< std::vector< uint8_t > > encrypt_column_page(const uint8_t *page_data, size_t size, const std::string &column_name, int32_t row_group_ordinal, int32_t page_ordinal) const
Encrypt a column data page.
expected< std::vector< uint8_t > > encrypt_dict_page(const uint8_t *page_data, size_t size, const std::string &column_name, int32_t row_group_ordinal) const
Encrypt a dictionary page with the column's encryption key.
expected< std::vector< uint8_t > > encrypt_column_metadata(const uint8_t *metadata, size_t size, const std::string &column_name) const
Encrypt serialized ColumnMetaData with AES-GCM (always authenticated).
expected< std::vector< std::pair< std::string, std::vector< uint8_t > > > > wrap_keys() const
Wrap all DEKs under their KEKs using the configured KMS client.
FileEncryptor(const EncryptionConfig &config)
Construct an encryptor from an encryption configuration.
expected< std::vector< uint8_t > > encrypt_column_meta_header(const uint8_t *header_data, size_t size, const std::string &column_name) const
Encrypt a column metadata header (always AES-GCM authenticated).
expected< std::vector< uint8_t > > sign_footer(const uint8_t *footer_data, size_t size) const
Sign the plaintext footer with HMAC-SHA256 (signed plaintext footer mode).
expected< std::vector< uint8_t > > encrypt_footer(const uint8_t *footer_data, size_t size) const
Encrypt the serialized FileMetaData (footer) with AES-GCM.
A lightweight result type that holds either a success value of type T or an Error.
HKDF key derivation (RFC 5869) using HMAC-SHA256.
std::vector< uint8_t > generate_iv(size_t iv_size)
Generate a random initialization vector of the specified size.
std::array< uint8_t, 32 > hmac_sha256(const uint8_t *key, size_t key_size, const uint8_t *data, size_t data_size)
HMAC-SHA256 (RFC 2104): keyed hash for HKDF.
constexpr size_t PME_AES128_KEY_SIZE
AES-128 key size — detected for interop diagnostics only (Gap P-7).
std::array< uint8_t, 32 > hkdf_extract(const uint8_t *salt, size_t salt_size, const uint8_t *ikm, size_t ikm_size)
HKDF-Extract (RFC 5869 §2.2): Extract a pseudorandom key from input keying material.
constexpr size_t PME_REQUIRED_KEY_SIZE
Required AES-256 key size for all PME operations (NIST SP 800-131A).
bool hkdf_expand(const std::array< uint8_t, 32 > &prk, const uint8_t *info, size_t info_size, uint8_t *output, size_t output_size)
HKDF-Expand (RFC 5869 §2.3): Expand PRK to output keying material.
@ INTERNAL
Key material stored directly in file metadata (testing/dev).
@ AES_GCM_CTR_V1
AES-256-GCM for footer, AES-256-CTR for column data (Parquet default).
@ AES_GCM_V1
AES-256-GCM for both footer and column data.
@ ENCRYPTION_ERROR
An encryption or decryption operation failed (bad key, tampered ciphertext, PME error).
@ INVALID_ARGUMENT
A caller-supplied argument is outside the valid range or violates a precondition.
Lightweight error value carrying an ErrorCode and a human-readable message.
static std::unique_ptr< ICipher > create_footer_cipher(EncryptionAlgorithm, const std::vector< uint8_t > &key)
Create a footer cipher (always authenticated = GCM).
static std::unique_ptr< ICipher > create_metadata_cipher(EncryptionAlgorithm, const std::vector< uint8_t > &key)
Create a metadata cipher (always authenticated = GCM).
static std::unique_ptr< ICipher > create_column_cipher(EncryptionAlgorithm algo, const std::vector< uint8_t > &key)
Create a column data cipher (GCM or CTR based on algorithm).
Top-level configuration structure that drives FileEncryptor / FileDecryptor.
std::vector< uint8_t > default_column_key
Default column key (32 bytes).
std::string default_column_key_id
KMS key identifier for the default column key (EXTERNAL mode).
std::shared_ptr< IKmsClient > kms_client
Optional KMS client for DEK/KEK key wrapping (EXTERNAL key mode).
bool encrypt_footer
If true, the footer is encrypted.
std::vector< uint8_t > footer_key
32-byte AES-256 key for encrypting the Parquet footer (FileMetaData).
KeyMode key_mode
INTERNAL: keys stored in file metadata. EXTERNAL: KMS references only.
std::string aad_prefix
AAD prefix – typically a file identifier or URI.
EncryptionAlgorithm algorithm
Encryption algorithm (GCM everywhere, or GCM-footer + CTR-columns).
std::vector< ColumnKeySpec > column_keys
Per-column key specifications. Columns listed here get their own key.
std::string footer_key_id
KMS key identifier for the footer key (EXTERNAL mode).
Stored in the Parquet FileMetaData.encryption_algorithm field.