Skip to content

Enhancement/331 move to 256 bit entropy aes - #332

Merged
PropzSaladaz merged 5 commits into
enhancement/security-hardeningfrom
enhancement/331-move-to-256-bit-entropy-aes
Sep 23, 2026
Merged

PropzSaladaz merged 5 commits into
enhancement/security-hardeningfrom
enhancement/331-move-to-256-bit-entropy-aes

Conversation

@PropzSaladaz

@PropzSaladaz PropzSaladaz commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR hardens threshold encryption’s AES-key encapsulation.

The AES key has always been 256 bits long, but the legacy V0 masking logic used the first 32 ASCII characters of a SHA-256 hex digest. That provided only 128 bits of mask entropy. V1 instead uses the 32 raw SHA-256 bytes, providing the full 256 bits of entropy.

To preserve wire compatibility, ciphertexts now carry a version in their existing one-byte header:

Wire Format:
  [ Header | Key_1 | (Key_2) | AES_Payload ]

Header (1 byte):
  Bits [7..2]: Threshold-encryption format version
    0x00: V0 — legacy ASCII-hex masking
    0x01: V1 — raw SHA-256-byte masking (256-bit entropy)
    > 0x01: rejected as unsupported

  Bits [1..0]: Encapsulated-key count
    0x01: one key
    0x02: two keys
    0x00 / 0x03: rejected

This preserves historic V0 header values (0x01 and 0x02) and introduces V1 headers (0x05 and 0x06). Ciphertext size is unchanged.

The high-level encryption API now uses one caller-facing profile:

libBLS::EncryptMetaData metadata;
metadata.encryptionVersion = libBLS::EncryptionVersion::V1;

The profile maps internally to the appropriate TE and deterministic AES-GCM behavior (see #330 ):

  • EncryptionVersion::V0 → TEVersion::V0 and AesGcmVersion::V0
  • EncryptionVersion::V1 → TEVersion::V1 and AesGcmVersion::V1

EncryptionVersion affects the TE wire format and masking for both encrypt() and encryptDeterministic(). The AES-GCM version only affects deterministic IV derivation, so it only changes encryptDeterministic() output.

New encryption defaults to V1. Callers can explicitly select V0 through metadata only when they deliberately need to produce legacy-compatible ciphertexts.

New libBLS versions can parse, validate, and decrypt both V0 and V1 ciphertexts. Parsed ciphertexts retain their version, so share combination and key validation automatically use the correct V0 or V1 mask derivation. Ciphertexts with an outer-header version that disagrees with an embedded key version are rejected.

Compatibility and breaking changes

  • Wire compatibility is one-way:

    • Historic V0 ciphertexts work with the new library.
    • New V1 ciphertexts do not work with an old V0-only library.
    • Consumers must therefore be upgraded before producers begin emitting V1 ciphertexts.
  • Existing high-level encrypt() and encryptDeterministic() call signatures remain unchanged, but their default output is now V1.

  • EncryptMetaData::aesGcmVersion has been replaced by EncryptMetaData::encryptionVersion. This prevents callers from selecting incompatible TE and AES-GCM combinations.

  • Direct AesGcmCipher users must update explicit enum values:

    • previous AesGcmVersion::V1 → new AesGcmVersion::V0
    • previous AesGcmVersion::V2 → new AesGcmVersion::V1
  • Distributed systems using encryptDeterministic() must pin metadata.encryptionVersion in their protocol or epoch configuration. Nodes using different profiles will intentionally produce different ciphertext bytes for the same seed and plaintext.

Test coverage

  • Frozen historic V0 one-key and two-key wire fixtures.
  • V0 fixtures with AES-GCM and TE associated data.
  • Synthetic V0 wrapper round-trip coverage.
  • Explicit V1 round trips for both randomized and deterministic encryption.
  • Deterministic V0/V1 profile-selection coverage.
  • Header parsing, unsupported-version rejection, and key/header-version mismatch rejection.
  • CTest now runs both threshold-encryption test executables.

Fixes #331

@PropzSaladaz
PropzSaladaz requested a review from a team as a code owner September 21, 2026 17:41
@PropzSaladaz PropzSaladaz linked an issue Sep 22, 2026 that may be closed by this pull request
@PropzSaladaz PropzSaladaz self-assigned this Sep 22, 2026
@PropzSaladaz
PropzSaladaz requested a lite review from Copilot September 22, 2026 15:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical and moderate serialization and version-validation issues remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

This PR introduces versioned V0/V1 threshold-encryption formats, enables full-entropy V1 masking by default, preserves V0 compatibility, and updates deterministic AES-GCM profiles.

Changes:

  • Adds version-aware masking, headers, key handling, and encryption profiles.
  • Retains legacy V0 decryption and explicit V0 production.
  • Expands compatibility, round-trip, parsing, and CTest coverage.
File Reviewed change
threshold_encryption/​ThresholdEncryption.cpp Applies version-aware masking during decryption.
threshold_encryption/​threshold_encryption.h Exposes encryption profiles and versioned APIs.
threshold_encryption/​threshold_encryption.cpp Implements profile resolution and mask derivation. Moderate (1 vote): unsupported TEVersion values should be rejected instead of treated as V1.
threshold_encryption/​TEVersion.h Defines threshold-encryption format versions.
threshold_encryption/​EncryptionVersion.h Defines caller-facing encryption profiles.
threshold_encryption/​CMakeLists.txt Registers headers and test executables.
threshold_encryption/​Ciphertext.h Adds versioned ciphertext metadata. Nit (1 vote): clarify that V1 changes mask entropy, not AES-key entropy.
threshold_encryption/​Ciphertext.cpp Implements versioned header serialization and parsing. Critical (2 votes): reject key counts other than 1 or 2 before serialization. Moderate (1 vote): validate versions before comparing embedded keys.
threshold_encryption/​CipheredKey.h Adds key version tracking. Moderate (1 vote): toBytes() omits the version, causing standalone deserialization to default legacy keys to V1.
threshold_encryption/​CipheredKey.cpp Propagates versions during key parsing and creation.
threshold_encryption/​AesGcmCipher.h Updates AES-GCM version declarations and defaults.
threshold_encryption/​AesGcmCipher.cpp Updates deterministic IV derivation handling.
test/​unit_tests_te.cpp Adds masking, version, and header compatibility tests.
test/​test_TE_wrappers.cpp Adds V0/V1 integration and fixture round-trip tests.
test/​legacy_v0_fixtures.h Provides frozen legacy V0 ciphertext fixtures.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread threshold_encryption/Ciphertext.cpp
@PropzSaladaz
PropzSaladaz merged commit 762cc08 into enhancement/security-hardening Sep 23, 2026
18 of 19 checks passed
@PropzSaladaz
PropzSaladaz deleted the enhancement/331-move-to-256-bit-entropy-aes branch September 23, 2026 16:36
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AES256Key uses 128 bit entropy

3 participants