Executive summary
Este módulo describe cómo generar identidades derivadas seguras a partir del contenedor de identidad (definido en Module 1). Una identidad derivada es una credencial criptográfica y verificable (ej.: credencial móvil, token software, certificación temporal) que:
Está vinculada de forma inmutable al contenedor base (mediante hashes y firmas).
Es derivada con mecanismos criptográficos fuertes (KDF basados en SHAKE / SHA3) para evitar exposición de claves maestras.
Puede exportarse de forma segura (cifrada y firmada) hacia un dispositivo destino (smartphone, HSM del proveedor) con trazabilidad y control de acceso.
Se adoptan principios y requisitos derivados de los estándares internacionales: uso de ECDSA P-256 y SHA-256 para firmas, SHA3/SHAKE para hashing y KDF, encapsulado CMS-like para objetos firmados y cifrados, y controles de auditoría y privacidad.
Requisitos técnicos (resumen)
Algoritmos aprobados: ECDSA P-256 para firmas; AES-GCM 256 para cifrado simétrico; SHA3-256 / SHAKE256 para hashing y KDF; HMAC-SHA256 para derivación de OTP/PII limited functions cuando aplique.
Niveles de garantía: mapear la derivación a IAL/AAL/FAL (NIST SP 800-63) — registrar el nivel en la credencial derivada.
Formato de salida: JSON canonizado + firma (CMS/PKCS#7 style) y opcionalmente empaquetado en un sobre cifrado (AEAD).
Custodia de claves: claves maestras en HSM o módulos PKCS#11; derivaciones en memoria o en secure element.
Interoperabilidad biométrica: conservar formatos ISO/IEC 19794 cuando se incluya evidencia biométrica.
Modelo lógico — elementos principales
Una identidad derivada (objeto derived_identity) contiene:
{
"derived_id": "",
"source_container_ref": {
"container_uuid": "",
"container_hash": ""
},
"purpose": "mobile-id" | "federation" | "temporary-token",
"assurance": { "IAL": 2, "AAL": 2, "FAL": 1 },
"derived_keys": {
"signing_pub": "",
"enc_pub": ""
},
"validity": {
"not_before": "...",
"not_after": "..."
},
"policies": { "export_restrictions": "...", "usage": "authn,sign" },
"audit": [ ... ],
"integrity": {
"hash": "",
"signature": { "alg":"ECDSA-P256-SHA256", "value":"" }
}
}
source_container_ref incluye el hash del contenedor base para ligadura inmutable.
derived_keys contiene las claves públicas derivadas o certificadas. Las claves privadas derivadas deben permanecer en el destino (smartphone secure enclave o HSM) o en un módulo seguro del servidor.
Flujo de derivación (alto nivel)
Autorización: validar que existe permiso administrativo para generar la derivada (policy + consent).
Selección de propósito y parámetros: elegir tipo de identidad derivada (mobile, federation, ephemeral), duración y nivel de garantía.
Derivación de material criptográfico: usar KDF (SHAKE256) sobre el container_hash + contexto (purpose, device_id, salt/nonce) para generar claves de sesión y claves derivadas.
Generación de par de claves derivadas: preferiblemente en destino (HSM/secure element) o generadas localmente y luego inyectadas cifradas.
Certificación / Emisión: emitir un certificado X.509 (o token firmado) vinculado al contenedor; la CA registra la relación.
Empaquetado seguro: generar un paquete exportable (JSON canonizado) + firmar (ECDSA P-256) + cifrar la carga simétrica con AES-GCM usando una clave derivada que el destino podrá recuperar.
Entrega y auditoría: entrega mediante canal seguro; almacenar registro de emisión con hash y firma en repositorio de evidencia.
Diseño criptográfico (detalles)
Seed de derivación: seed = SHA3-256(container_canonical_bytes) — este seed es la referencia inmutable.
Contextual KDF: usar SHAKE256(seed || context || nonce) para generar material de longitud variable. context debe incluir purpose, dest_id, assurance, y nonce con alta entropía.
Claves derivadas: generar K_sign (32 bytes) y K_enc (32 bytes) desde el XOF; usar K_sign para generar una clave ECDSA P-256 mediante map-to-curve (o usar KDF as seed to deterministic key gen). Si se requiere compatibilidad FIPS, preferir generar par de claves en HSM.
Cifrado de transporte: AES-GCM-256 con IV único por paquete (12 bytes), asociado AAD con metadata canonizada (derived_id, source container hash, purpose).
Firma final: firmar el paquete con ECDSA P-256 usando la clave emisora (CA o IDIssuer) para garantizar rastreabilidad.
Encapsulado: empaquetar en formato JSON + integrity.signature; para integración con infraestructuras existentes, ofrecer opción de generar CMS/PKCS7 signedData/envelopedData.
Consideraciones sobre biometría y privacidad
No exponer plantillas biométricas crudas: si se requiere prueba biométrica en la credencial derivada, almacenar solo una referencia/verifier token o usar técnicas de template protection (cancellable biometrics, secure sketch, fuzzy commitment) y HMAC.
Derivación de credenciales basadas en biométricos: usar HMAC-SHA256 sobre template_hash + nonce para derivar material, sin exponer la plantilla.
Retención mínima: registrar en la credencial derivada sólo lo mínimo necesario (assurance level, flags de biometric_present) y no la plantilla ni la imagen.
Código de ejemplo 1 — derive_identity.py
Objetivo: derivar claves y crear la estructura de derived_identity a partir del container_hash y parámetros de contexto. Usa cryptography (instalar con pip install cryptography). Cada línea tiene comentario explicativo.
derive_identity.py
Author: Antonio José Socorro Marín © 2025
Ejemplo: derivar claves y estructura de identidad derivada usando SHAKE256 y ECDSA P-256
import os
import json
import base64
from datetime import datetime, timedelta
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.hashes import SHAKE256
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hmac
--------------------
Helper: canonical JSON bytes
--------------------
def canonical_json_bytes(obj):
# Serializa JSON en orden de claves consistente para hashing.
return json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode('utf-8')
--------------------
KDF XOF: SHAKE256 to generate variable-length bytes
--------------------
def shake256_kdf(seed_bytes, context_bytes, output_len=64):
# seed_bytes: entrada principal (p.ej. hash del contenedor)
# context_bytes: contexto (purpose, dest_id, nonce)
shake = hashes.Hash(SHAKE256(256))
# SHAKE256 via cryptography Hash + update accepts XOF by finalization? cryptography's SHAKE as Hash requires using finalization different
# We'll use SHAKE256 directly via the Hash API: supply combined input and then use .finalize_for_digest isn't available.
# Instead implement with low-level: use SHAKE256 via hashes.Hash; cryptography supports SHAKE by passing SHAKE256(bit_length) and .finalize() returns XOF of requested size.
shake_ctx = SHAKE256(256) # 256-bit security for XOF width
# cryptography doesn't let creating Hash(SHAKE256) and then request n bytes with finalize(n) in older versions.
# We'll emulate using the high-level API: use hashes.Hash(SHAKE256(256)), then .finalize() with output length via derive.
h = hashes.Hash(SHAKE256(256))
h.update(seed_bytes)
h.update(b'|')
h.update(context_bytes)
# finalize with requested length
xof = h.finalize()
# Note: h.finalize() yields 32 bytes for SHAKE256 with default? For portability, we instead use standard library hashlib.shake_256
import hashlib
shake2 = hashlib.shake_256()
shake2.update(seed_bytes)
shake2.update(b'|')
shake2.update(context_bytes)
return shake2.digest(output_len)
--------------------
Deterministic ECDSA key generation from seed (NOT FIPS if done outside HSM) - demo only
--------------------
def generate_ecdsa_p256_from_seed(seed_bytes):
# WARNING: For production, prefer key generation inside HSM. This is a demo deterministic method.
# We'll derive a 32-byte scalar and map to private key.
# Use SHA256 over seed to reduce to 32 bytes
digest = hashes.Hash(hashes.SHA256())
digest.update(seed_bytes)
sk_bytes = digest.finalize() # 32 bytes
# Convert to int and reduce modulo curve order
int_val = int.from_bytes(sk_bytes, 'big')
# Use curve order for secp256r1
curve = ec.SECP256R1()
order = curve.curve.order # not directly accessible; fall back to known order
# secp256r1 order:
order = int("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16)
priv_int = (int_val % (order - 1)) + 1
# Build private key from int (cryptography doesn't provide private_key_from_int directly)
# Use ec.derive_private_key
private_key = ec.derive_private_key(priv_int, ec.SECP256R1())
return private_key
--------------------
Main derivation function
--------------------
def derive_identity(container_hash_hex, purpose, dest_id, validity_days=90):
# container_hash_hex: hex string (SHA3-256 of container)
# purpose: string describing derived credential purpose
# dest_id: identifier of destination (device id or recipient id)
seed = bytes.fromhex(container_hash_hex) # seed bytes
# Nonce for uniqueness (in production use secure random and store in audit)
nonce = os.urandom(16)
# Build context for KDF: purpose || dest_id || nonce || ISO timestamp
context = purpose.encode('utf-8') + b'|' + dest_id.encode('utf-8') + b'|' + nonce
# Derive 64 bytes of key material (K_sign||K_enc)
km = shake256_kdf(seed, context, output_len=64)
k_sign = km[:32] # 32 bytes
k_enc = km[32:64] # 32 bytes
# Generate ECDSA keypair from k_sign seed (demo)
priv_key = generate_ecdsa_p256_from_seed(k_sign)
pub_key = priv_key.public_key()
# Serialize public key to PEM
pub_pem = pub_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8')
# Build derived identity structure
derived_id = {
"derived_id": base64.urlsafe_b64encode(os.urandom(12)).decode('ascii').rstrip('='),
"source_container_ref": {"container_hash": container_hash_hex},
"purpose": purpose,
"assurance": {"IAL": 2, "AAL": 2, "FAL": 1},
"derived_keys": {"signing_pub": pub_pem},
"validity": {
"not_before": datetime.utcnow().isoformat() + "Z",
"not_after": (datetime.utcnow() + timedelta(days=validity_days)).isoformat() + "Z"
},
"policies": {"export_restrictions": "no_share", "usage": "authn,sign"},
"audit": [],
}
# Compute integrity hash (SHA3-256) over canonical JSON
import hashlib
canonical = canonical_json_bytes(derived_id)
h = hashlib.sha3_256()
h.update(canonical)
integrity_hash = h.hexdigest()
derived_id["integrity"] = {"hash": integrity_hash, "hash_alg": "SHA3-256"}
# For demo, sign the canonical derived_id with priv_key (ECDSA SHA256)
signature = priv_key.sign(canonical, ec.ECDSA(hashes.SHA256()))
signature_b64 = base64.b64encode(signature).decode('ascii')
derived_id["integrity"]["signed"] = {"alg": "ECDSA-P256-SHA256", "signature_b64": signature_b64, "signer": "derived-seed-demo"}
# Return derived identity and encryption key material (k_enc) for packaging
return derived_id, k_enc, nonce
--------------------
Demo run
--------------------
if name == "main":
# Example: container hash (hex); in production, obtain SHA3-256 of canonical container
example_container_hash = "d2f3a4b5c6d7e8f90123456789abcdef0123456789abcdef0123456789abcdef"
derived, k_enc, nonce = derive_identity(example_container_hash, purpose="mobile-id", dest_id="device-01")
print(json.dumps(derived, indent=2, ensure_ascii=False))
# k_enc should be delivered securely to destination (wrapped), not printed in prod.
Notas técnicas sobre el script:
El método generate_ecdsa_p256_from_seed es demostrativo: para producción no se debe generar claves privadas fuera del HSM. En entornos reales, la clave privada derivada debería generarse en el destino (secure element) o ser importada mediante un mecanismo seguro (wrapped key).
Se utiliza hashlib.shake_256 para KDF (SHAKE256 XOF) — apropiado para generar material de longitud variable.
El derived_id incluye integrity.hash con SHA3-256 y una firma demo con la clave derivada; en entorno real la firma sería la del IDIssuer o CA.
Código de ejemplo 2 — export_derived_bundle.py
Objetivo: empaquetar la identidad derivada en JSON canonizado, cifrarlo con AES-GCM usando k_enc, firmar el paquete con la CA emisora (ECDSA P-256), y crear un sobre exportable. Comentarios línea por línea.
export_derived_bundle.py
Author: Antonio José Socorro Marín © 2025
Empaqueta, cifra y firma la identidad derivada para exportación segura.
import os
import json
import base64
from datetime import datetime
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
Helper canonical JSON bytes (copiado)
def canonical_json_bytes(obj):
return json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode('utf-8')
Simula carga de clave privada de CA (en PEM) - en producción usar HSM
def load_ca_private_key(pem_path="ca_private.pem", password=None):
with open(pem_path, "rb") as f:
data = f.read()
return serialization.load_pem_private_key(data, password=password)
AES-GCM encrypt function: returns ciphertext and associated metadata
def aes_gcm_encrypt(key, plaintext_bytes, aad_bytes=None):
# key: 32 bytes (AES-256)
# generate 12-byte nonce (IV)
iv = os.urandom(12)
aesgcm = AESGCM(key)
# Encrypt: returns ciphertext || tag
ct = aesgcm.encrypt(iv, plaintext_bytes, aad_bytes)
return iv, ct
Sign with ECDSA P-256 (CA)
def sign_with_ecdsa_p256(private_key, data_bytes):
signature = private_key.sign(data_bytes, ec.ECDSA(hashes.SHA256()))
return signature
Main pack function
def pack_and_protect(derived_identity_obj, k_enc_bytes, ca_priv_pem_path="ca_private.pem"):
# Step 1: canonicalize derived identity JSON
canonical = canonical_json_bytes(derived_identity_obj)
# Step 2: create AAD (associated data) with minimal metadata to bind IV and purpose - here using derived_id and source ref
aad = json.dumps({
"derived_id": derived_identity_obj.get("derived_id"),
"source_container_hash": derived_identity_obj.get("source_container_ref", {}).get("container_hash"),
"purpose": derived_identity_obj.get("purpose"),
"timestamp": datetime.utcnow().isoformat() + "Z"
}, sort_keys=True, separators=(',', ':')).encode('utf-8')
# Step 3: Encrypt canonical payload with AES-GCM using k_enc_bytes
iv, ciphertext = aes_gcm_encrypt(k_enc_bytes, canonical, aad)
# Step 4: Load CA private key (demo). Production: use HSM to sign the envelope, not file-based key
ca_priv = load_ca_private_key(ca_priv_pem_path)
# Step 5: Build envelope object (metadata + encrypted payload)
envelope = {
"envelope_version": "1.0",
"metadata": {
"aad_b64": base64.b64encode(aad).decode('ascii'),
"iv_b64": base64.b64encode(iv).decode('ascii'),
"ciphertext_b64": base64.b64encode(ciphertext).decode('ascii'),
"cipher": "AES-256-GCM"
}
}
# Step 6: Canonicalize envelope metadata for signing
envelope_canonical = canonical_json_bytes(envelope)
# Step 7: Sign envelope with CA ECDSA-P256
signature = sign_with_ecdsa_p256(ca_priv, envelope_canonical)
signature_b64 = base64.b64encode(signature).decode('ascii')
# Step 8: Attach signature and return final package
final_package = {
"envelope": envelope,
"signature": {
"alg": "ECDSA-P256-SHA256",
"value_b64": signature_b64,
"signer": "CN=IDIssuer"
}
}
return final_package
--------------------
Demo usage
--------------------
if name == "main":
# Example: load derived identity from a file or previous step; here we mock minimal structure
derived_identity = {
"derived_id": "abc123def",
"source_container_ref": {"container_hash": "d2f3..."},
"purpose": "mobile-id",
"derived_keys": {"signing_pub": "-----BEGIN PUBLIC KEY-----\n..."},
"validity": {"not_before":"2025-09-28T00:00:00Z","not_after":"2025-12-27T00:00:00Z"}
}
# For demo, k_enc is random 32 bytes (in real flow, returned from derive_identity)
k_enc = os.urandom(32)
# For demo, ensure ca_private.pem exists (create a demo key if not)
if not os.path.exists("ca_private.pem"):
# create ephemeral CA key (demo only)
demo_key = ec.generate_private_key(ec.SECP256R1())
pem = demo_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
with open("ca_private.pem", "wb") as f:
f.write(pem)
package = pack_and_protect(derived_identity, k_enc, ca_priv_pem_path="ca_private.pem")
print(json.dumps(package, indent=2, ensure_ascii=False))
Notas sobre el empaquetado:
k_enc (la clave simétrica) debe llegar al destino de forma segura; en la práctica se usa key wrapping con la clave pública del destino (RSA OAEP o ECDH-ES + AES-KW) o la clave se deriva en destino a partir del mismo seed y contexto si el destino posee el seed.
En entornos FIPS, la firma y wrapping deben realizarse en HSM o equivalentes.
Verificación en el destino (esquema)
En el dispositivo destino (p.ej., secure element del smartphone o HSM del proveedor) se realiza:
Importación del paquete: verificar firma del envelope con la clave pública del IDIssuer.
Desenvolver la clave de sesión: recuperar k_enc mediante key wrapping (si se usó) o derivar localmente con el mismo seed/context y nonce.
Descifrar AES-GCM: validar AAD (metadata) y descifrar la payload.
Validar integridad del derived_identity: comprobar integrity.hash y la firma incluida en derived_identity.
Instalación local: si todo pasa, almacenar claves privadas en secure element y registrar la instalación en auditoría local con huella e identidad del dispositivo.
Gestión del ciclo de vida y revocación
Revocación: emitir CRL/OCSP para certificados emitidos a identidades derivadas. Registrar en CA la referencia al container_hash para revocar por origen si es necesario.
Suspensión temporal: marcar derived_identity como suspended en la base de emisiones sin revocar inmediatamente.
Reemisión: permitir re-derivación con nuevo nonce y auditoría.
Escalamiento de garantías: para aumentar AAL/IAL se requiere re-proofing y re-enrollment.
Auditoría, trazabilidad y evidencia
Guardar en repositorio sellado (WORM or signed log store) el container_hash, derived_id, envelope hash y la firma de emisión con timestamp.
Mantener metadata: operador, autorización, purpose, dest_id, nonce, expiry.
Recomendado usar mecanismos de sello temporal (RFC 3161 or blockchain anchoring) para pruebas a largo plazo.
Riesgos y mitigaciones (resumen)
Riesgo: exfiltración de claves maestras → Mitigación: HSM, separación de roles, no derivación fuera de hardware en producción.
Riesgo: Man-in-the-Middle en entrega de bundle → Mitigación: envelope firmado por CA, uso de key wrapping con clave pública del destino y canal TLS con mTLS.
Riesgo: Replay de paquetes exportados → Mitigación: incluir nonce, not_before, not_after, y auditoría única por derived_id.
Riesgo: correlación de identidad y privacidad → Mitigación: minimizar datos en derivada; usar pseudónimos y políticas de retención; aplicar técnicas de privacy-preserving (selective disclosure, tokens con claims minimalistas).
Compliance & mapping a estándares
Algoritmos y longitudes: ECDSA P-256, SHA-256 y SHA3-256 (FIPS 180/202/SP800-78 guidance).
Assurance levels: mapear a IAL/AAL/FAL de NIST SP 800-63 para clasificación de la derivada.
Biometrics formats: preservar ISO/IEC 19794 template formats y almacenar solo referencias o protected templates (SP 800-76).
PIV/Derived credentials: seguir principios de FIPS 201-3 para correlación entre credenciales PIV y derivadas.
Implementación práctica: checklist operativo
HSM: instalar y configurar HSM con PKCS#11 y políticas de acceso.
IDMS/CMS: definir endpoints, roles y políticas de autorización para derivación.
Consentimiento: almacenar autorización legible del titular (consent artifacts).
Testbed: crear entorno Docker con emulación de destino (secure element), CA y repositorio de evidencias.
Integración: validar con escenarios reales (mobile provisioning, federated SSO, offline enrolment).
Auditoría: habilitar export firmado y retención en repositorios con control de acceso.
Conclusión y siguientes pasos
El Módulo 2 define una arquitectura práctica, segura y alineada con estándares para generar identidades derivadas a partir del contenedor original. Los artefactos generados (derived_identity bundles) son verificables, trazables y exportables con garantías criptográficas.
Top comments (0)