AID-H-005.002: Homomorphic Encryption for AI
Pillars: data, model, infra
Lifecycle: building, operation
Scope boundary
Owns privacy-preserving supported computation over ciphertext, including scheme, precision, multiplicative depth, key management, operator coverage, and explicit hybrid HE-plus-attested-enclave transitions.
AID-H-009.005: AID-H-005.002 protects data by evaluating supported operations over ciphertext and may use an enclave only for unsupported operations; AID-H-009.005 protects plaintext computation inside a remotely attested confidential runtime. Homomorphic encryption and trusted-execution confidentiality have different plaintext exposure, operator, and trust assumptions.
Warning
Extremely High on Inference Latency & Computational Cost
Performing computations on encrypted data is orders of magnitude slower than on plaintext.
Inference Latency: Can be 100x to 10,000x higher than a non-encrypted model. A prediction that takes milliseconds on plaintext could take many seconds or even minutes.
Training: Training a model with HE is often computationally prohibitive for all but the simplest models.
Implementation guidance
- AID-H-005.002-G001: Implement and tune a homomorphic encryption configuration for the target AI workload, including scheme choice, depth budgeting, and parameter sizing.
- AID-H-005.002-G002: Use partially homomorphic encryption for specific operations (addition, multiplication).
- AID-H-005.002-G003: Implement hybrid approaches combining HE with secure enclaves for complex operations.
AID-H-005.002-G001
Before you begin
General-purpose deep models with unsupported depth/precision remain unapproved, not silently protected.
Production implementation
Compile the exact circuit, obtain parameters from the maintained library's security estimator or reviewed parameter set rather than hand-built literals, generate keys in the approved HSM/threshold service, distribute evaluation keys without secret keys, validate ciphertext context/level/size before computation, enforce depth and noise/precision bounds at every stage, return ciphertext only, and reject context mismatch, exhausted depth or accuracy/SLO breach.
Independent verification
An independent cryptography runner rebuilds the context from the signed profile, validates provider security estimates, executes known-answer, malformed-ciphertext, wrong-context/key, depth-boundary, precision/overflow and load fixtures, compares encrypted/decrypted results with the plaintext reference within policy tolerance, and reconciles every eligible operation.
OpenFHE CKKS executable fixture
# File: he/ckks_linear_fixture.py
# Dependency: openfhe Python wrapper, version pinned in the build lock
from openfhe import (
CCParamsCKKSRNS, GenCryptoContext, KEYSWITCH, LEVELEDSHE, PKE,
)
def encrypted_affine(
values: list[float],
weights: list[float],
bias: list[float],
*,
multiplicative_depth: int,
scaling_modulus_bits: int,
):
if not values or len(values) != len(weights) or len(values) != len(bias):
raise ValueError("vector populations differ")
if multiplicative_depth < 1 or scaling_modulus_bits < 1:
raise ValueError("invalid signed CKKS parameters")
parameters = CCParamsCKKSRNS()
parameters.SetMultiplicativeDepth(multiplicative_depth)
parameters.SetScalingModSize(scaling_modulus_bits)
parameters.SetBatchSize(len(values))
context = GenCryptoContext(parameters)
context.Enable(PKE)
context.Enable(KEYSWITCH)
context.Enable(LEVELEDSHE)
keys = context.KeyGen()
context.EvalMultKeyGen(keys.secretKey)
encrypted = context.Encrypt(
keys.publicKey, context.MakeCKKSPackedPlaintext(values)
)
weighted = context.EvalMult(
encrypted, context.MakeCKKSPackedPlaintext(weights)
)
result = context.EvalAdd(
weighted, context.MakeCKKSPackedPlaintext(bias)
)
# Fixture-only readback. Production keeps the secret key in a separate
# threshold or attested decryptor and returns only the authorized result.
plaintext = context.Decrypt(keys.secretKey, result)
plaintext.SetLength(len(values))
return plaintext.GetRealPackedValue()Derive depth, scale, ring dimension, security level, batch size, and acceptable approximation error from the reviewed circuit and signed cryptographic profile. Reject a circuit whose measured depth or precision exceeds that profile; never copy these fixture parameters into production without that analysis.
AID-H-005.002-G002
Before you begin
Owned by cryptography and aggregation-service teams for bounded additive or multiplicative workloads that need only a reviewed partially homomorphic operation, such as privacy-preserving sums, and do not require an FHE circuit.
Production implementation
Generate keys under threshold/separated custody, publish only the scoped public key, authenticate every participant ciphertext and enforce domain/range proofs or trusted-client bounds, reject duplicate/wrong-key/malformed inputs, aggregate only the complete authorized cohort, release only the policy-approved aggregate to the threshold decryptor, enforce minimum cohort and purpose, and never decrypt or log individual contributions.
Independent verification
An independent runner verifies the key ceremony and threshold, executes known-answer, negative/overflow, malformed, duplicate, missing-participant, wrong-key and below-minimum-cohort fixtures, compares the decrypted aggregate with an authorized plaintext reference, and reconciles every eligible participant/input.
Executable additive-homomorphic path
# File: he/paillier_sum.py
# Dependency: phe
import os
from phe import paillier
def encrypt_vector(public_key, values: list[float]):
if not values:
raise ValueError("empty vector")
return [public_key.encrypt(float(value)) for value in values]
def aggregate(ciphertext_vectors: list[list]):
if not ciphertext_vectors:
raise ValueError("empty ciphertext cohort")
width = len(ciphertext_vectors[0])
if width == 0 or any(len(vector) != width for vector in ciphertext_vectors):
raise ValueError("ciphertext vector shapes differ")
return [
sum((vector[index] for vector in ciphertext_vectors[1:]),
ciphertext_vectors[0][index])
for index in range(width)
]
# Integration fixture; production provisions the private key only to the
# separated aggregate-decryption service.
policy_key_bits = int(os.environ["HE_PAILLIER_KEY_BITS"])
if policy_key_bits < 1:
raise ValueError("signed HE key-size policy is invalid")
public_key, private_key = paillier.generate_paillier_keypair(
n_length=policy_key_bits
)
encrypted = [encrypt_vector(public_key, [1.0, 2.0]),
encrypt_vector(public_key, [3.0, 4.0])]
decrypted_sum = [private_key.decrypt(value) for value in aggregate(encrypted)]
assert decrypted_sum == [4.0, 6.0]Authenticate every ciphertext and bind it to one cohort, vector schema, key ID, and round. The aggregator must not receive the private key; the separated decryptor verifies the complete cohort and releases only the policy-authorized aggregate.
AID-H-005.002-G003
Bind deployment values to one signed runtime policy
Use this hybrid path only for a reviewed circuit whose non-linear stage must enter a measured confidential-computing boundary. The separately administered runtime policy supplies the digest-pinned image, namespace, Pod identity, runtime class, scratch-memory quantity, command timeout, and digest of the Rego attestation rule. A protected bootstrap profile supplies only the finite timeout needed to verify that signed policy.
#!/usr/bin/env bash
set -euo pipefail
umask 077
test -n "$AIDEFEND_SIGNATURE_VERIFY_TIMEOUT_SECONDS"
[[ "$AIDEFEND_SIGNATURE_VERIFY_TIMEOUT_SECONDS" =~ ^[1-9][0-9]*$ ]]
SNAPSHOT_DIR=$(mktemp -d)
chmod 0700 "$SNAPSHOT_DIR"
trap 'rm -rf -- "$SNAPSHOT_DIR"' EXIT
install -m 0400 -- policy/he-stage-runtime.json "$SNAPSHOT_DIR/runtime-policy.json"
install -m 0400 -- policy/he-stage-runtime.sig "$SNAPSHOT_DIR/runtime-policy.sig"
install -m 0400 -- /opt/aidefend/trust/he-stage-runtime.pub "$SNAPSHOT_DIR/runtime-policy.pub"
install -m 0400 -- evidence/he-stage-attestation.json "$SNAPSHOT_DIR/attestation.json"
install -m 0400 -- evidence/he-stage-attestation.sig "$SNAPSHOT_DIR/attestation.sig"
install -m 0400 -- /opt/aidefend/trust/attestation-authority.pub "$SNAPSHOT_DIR/attestation.pub"
install -m 0400 -- policy/he-stage.rego "$SNAPSHOT_DIR/he-stage.rego"
install -m 0400 -- /opt/aidefend/trust/he-evaluator-image.pub "$SNAPSHOT_DIR/image.pub"
timeout --signal=KILL "$AIDEFEND_SIGNATURE_VERIFY_TIMEOUT_SECONDS" cosign verify-blob \
--key "$SNAPSHOT_DIR/runtime-policy.pub" --bundle "$SNAPSHOT_DIR/runtime-policy.sig" \
"$SNAPSHOT_DIR/runtime-policy.json"
python - "$SNAPSHOT_DIR/runtime-policy.json" "$SNAPSHOT_DIR/attestation.json" \
"$SNAPSHOT_DIR/he-stage.rego" "$SNAPSHOT_DIR/pod.json" <<'PY'
import hashlib, json, os, re, sys
from pathlib import Path
FIELDS={"schema_version","policy_version","namespace","pod_name","runtime_class_name","image_ref","scratch_size_limit","command_timeout_seconds","rego_sha256"}
DNS=re.compile(r"^[a-z0-9](?:[-a-z0-9.]*[a-z0-9])?$")
IMAGE=re.compile(r"^[^\s]+@sha256:[0-9a-f]{64}$")
QUANTITY=re.compile(r"^[1-9][0-9]*(?:Ki|Mi|Gi|Ti|Pi|Ei|K|M|G|T|P|E)$")
SHA256=re.compile(r"^[0-9a-f]{64}$")
def strict(path,label):
raw=Path(path).read_bytes()
def unique(pairs):
result={}
for key,value in pairs:
if key in result: raise ValueError(f"duplicate {label} key: {key}")
result[key]=value
return result
def reject(value): raise ValueError(f"non-finite {label} value: {value}")
value=json.loads(raw.decode("utf-8",errors="strict"),object_pairs_hook=unique,parse_constant=reject)
if not isinstance(value,dict): raise ValueError(label+" must be an object")
return value,raw
policy,policy_raw=strict(sys.argv[1],"runtime policy")
attestation,_=strict(sys.argv[2],"attestation")
if set(policy)!=FIELDS or policy["schema_version"]!="aidefend.hybrid-he-stage-policy.v1": raise ValueError("runtime policy schema differs")
for field in FIELDS-{"command_timeout_seconds"}:
if not isinstance(policy[field],str) or not policy[field]: raise ValueError(field+" must be non-empty")
if not all(DNS.fullmatch(policy[field]) for field in ("namespace","pod_name","runtime_class_name")): raise ValueError("Kubernetes identity invalid")
if not IMAGE.fullmatch(policy["image_ref"]): raise ValueError("image_ref must be digest pinned")
if not QUANTITY.fullmatch(policy["scratch_size_limit"]): raise ValueError("invalid scratch_size_limit")
timeout_seconds=policy["command_timeout_seconds"]
if isinstance(timeout_seconds,bool) or not isinstance(timeout_seconds,int) or timeout_seconds<=0: raise ValueError("command timeout must be positive")
if not SHA256.fullmatch(policy["rego_sha256"]): raise ValueError("invalid Rego digest")
if hashlib.sha256(Path(sys.argv[3]).read_bytes()).hexdigest()!=policy["rego_sha256"]: raise PermissionError("Rego differs from signed policy")
if not attestation: raise ValueError("attestation object is empty")
pod={"apiVersion":"v1","kind":"Pod","metadata":{"name":policy["pod_name"],"namespace":policy["namespace"],"labels":{"app":policy["pod_name"]}},"spec":{"runtimeClassName":policy["runtime_class_name"],"automountServiceAccountToken":False,"restartPolicy":"Never","containers":[{"name":"evaluator","image":policy["image_ref"],"args":["--require-attestation","--ciphertext-input-only"],"securityContext":{"allowPrivilegeEscalation":False,"readOnlyRootFilesystem":True,"runAsNonRoot":True,"capabilities":{"drop":["ALL"]}},"volumeMounts":[{"name":"scratch","mountPath":"/scratch"}]}],"volumes":[{"name":"scratch","emptyDir":{"medium":"Memory","sizeLimit":policy["scratch_size_limit"]}}]}}
payload=(json.dumps(pod,sort_keys=True,separators=(",",":"))+"\n").encode()
fd=os.open(sys.argv[4],os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o400)
with os.fdopen(fd,"wb") as handle: handle.write(payload); handle.flush(); os.fsync(handle.fileno())
print(json.dumps({"policy_sha256":hashlib.sha256(policy_raw).hexdigest(),"manifest_sha256":hashlib.sha256(payload).hexdigest()},sort_keys=True))
PY
COMMAND_TIMEOUT_SECONDS=$(jq -er '.command_timeout_seconds' "$SNAPSHOT_DIR/runtime-policy.json")
IMAGE_REF=$(jq -er '.image_ref' "$SNAPSHOT_DIR/runtime-policy.json")
NAMESPACE=$(jq -er '.namespace' "$SNAPSHOT_DIR/runtime-policy.json")
POD_NAME=$(jq -er '.pod_name' "$SNAPSHOT_DIR/runtime-policy.json")
timeout --signal=KILL "$COMMAND_TIMEOUT_SECONDS" cosign verify --key "$SNAPSHOT_DIR/image.pub" "$IMAGE_REF"
timeout --signal=KILL "$COMMAND_TIMEOUT_SECONDS" cosign verify-blob \
--key "$SNAPSHOT_DIR/attestation.pub" --bundle "$SNAPSHOT_DIR/attestation.sig" \
"$SNAPSHOT_DIR/attestation.json"
OPA_RESULT=$(timeout --signal=KILL "$COMMAND_TIMEOUT_SECONDS" opa eval --fail --format raw \
--data "$SNAPSHOT_DIR/he-stage.rego" --input "$SNAPSHOT_DIR/attestation.json" \
'data.aidefend.he_stage.allow == true')
test "$OPA_RESULT" = true
timeout --signal=KILL "$COMMAND_TIMEOUT_SECONDS" kubectl apply --server-side --dry-run=server -f "$SNAPSHOT_DIR/pod.json"
timeout --signal=KILL "$COMMAND_TIMEOUT_SECONDS" kubectl apply --server-side --field-manager=aidefend-hybrid-stage -f "$SNAPSHOT_DIR/pod.json"
timeout --signal=KILL "$COMMAND_TIMEOUT_SECONDS" kubectl wait --namespace "$NAMESPACE" \
--for=condition=Ready --timeout="$COMMAND_TIMEOUT_SECONDS"s "pod/$POD_NAME"
timeout --signal=KILL "$COMMAND_TIMEOUT_SECONDS" kubectl get pod --namespace "$NAMESPACE" \
"$POD_NAME" -o json > "$SNAPSHOT_DIR/pod-readback.json"
python - "$SNAPSHOT_DIR/pod.json" "$SNAPSHOT_DIR/pod-readback.json" <<'PY'
import json,sys
from pathlib import Path
expected=json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
observed=json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
if not observed.get("metadata",{}).get("uid"): raise PermissionError("pod UID readback missing")
for field in ("runtimeClassName","automountServiceAccountToken"):
if observed.get("spec",{}).get(field)!=expected["spec"][field]: raise PermissionError("pod readback mismatch: "+field)
want=expected["spec"]["containers"][0]; got=observed.get("spec",{}).get("containers",[{}])[0]
for field in ("name","image","args","securityContext","volumeMounts"):
if got.get(field)!=want[field]: raise PermissionError("container readback mismatch: "+field)
if observed.get("spec",{}).get("volumes")!=expected["spec"]["volumes"]: raise PermissionError("scratch volume readback mismatch")
PY
for fixture in wrong-measurement wrong-image stale wrong-nonce wrong-audience; do
set +e
timeout --signal=KILL "$COMMAND_TIMEOUT_SECONDS" opa eval --fail --format raw \
--data "$SNAPSHOT_DIR/he-stage.rego" --input "evidence/he-stage-fixtures/$fixture.json" \
'data.aidefend.he_stage.allow == true' >/dev/null
rc=$?
set -e
test "$rc" -eq 1
doneEvidence and failure handling
Retain the exact private policy, signature, trust-key, attestation, Rego, manifest, and Pod-readback snapshots plus image-verification output and negative-fixture results. Deny deployment or key release on signature failure, malformed or duplicate JSON, a mutable image reference, policy/Rego mismatch, attestation denial or verifier error, Kubernetes dry-run/apply/readback mismatch, timeout, or any negative fixture that is accepted. Host processes never receive plaintext or scoped decryption material.
Open-source tools
- Microsoft SEAL
- HElib
- OpenFHE
- TenSEAL
- Lattigo
Source-available / open-weight tools
- Concrete ML (commercial patent license required) (BSD-3-Clause-Clear; source-available)
Commercial / hosted tools
- Duality Platform
- Enveil ZeroReveal Machine Learning
- IBM HElayers SDK
- CRYPTOLAB HEaaN Private AI
Threat mappings
MITRE ATLAS
- AML.T0025 Exfiltration via Cyber Means (encrypted data remains protected even if exfiltrated)
MAESTRO
- Data Exfiltration (L2)
- Data Leakage (Cross-Layer) (HE prevents data leakage across computational layers)
OWASP LLM Top 10 2026
- LLM02:2026 Sensitive Information Disclosure
OWASP ML Top 10 2023
- N/A
OWASP Top 10 for Agentic Applications 2026
- N/A
NIST Adversarial Machine Learning 2025
- N/A
Cisco Integrated AI Security and Safety Framework
- AITech-8.2 Data Exfiltration / Exposure (Protects computation inputs while encrypted; authorized decrypted outputs need separate controls)
- AITech-8.3 Information Disclosure (Protects computation inputs while encrypted; authorized decrypted outputs need separate controls)
Google Secure AI Framework 2.0 - Risks
- N/A
Databricks AI Security Framework 3.0
- Raw Data 1.4: Ineffective storage and encryption (HE provides encryption during computation)
- Datasets 3.2: Ineffective storage and encryption