SD-JWT — cómo funciona la divulgación selectiva
Este artículo profundiza la mecánica interna de SD-JWT (Selective Disclosure JWT) para entender por qué un verificador puede confirmar atributos sin ver los datos completos. Es la pieza central del modelo de privacy en SD-JWT VC.
El problema que SD-JWT resuelve
Un JWT clásico firma toda la payload junta. Si querés ocultar parte:
- No podés. Ocultarla rompe la firma.
- Tenés que emitir múltiples JWTs (uno por subset de atributos).
- O usar técnicas más complejas como ZKPs.
SD-JWT propone una solución diferente: en vez de firmar los datos directamente, firmar los hashes de cada atributo. Cuando hay que presentar, se revela solo lo necesario + los hashes correspondientes.
La estructura interna
Un SD-JWT consiste en:
<JWT>~<Disclosure 1>~<Disclosure 2>~<Disclosure 3>~...
Donde:
<JWT>es un JWT firmado normalmente.<Disclosure N>son strings que pueden o no incluirse cuando se presenta.
Anatomía del JWT
El payload del JWT incluye los hashes:
{
"iss": "did:web:salta.gob.ar",
"sub": "did:key:z6Mk...",
"iat": 1714521600,
"_sd": [
"K7H_5xV2bL...",
"P8N_2bL3kM...",
"Q3J_9wM7nP..."
],
"_sd_alg": "sha-256",
"vct": "ConstanciaDomicilio"
}
_sdes un array de hashes (uno por cada atributo selectively disclosable)._sd_algindica qué algoritmo de hash se usó.- Los atributos que NO están bajo
_sdson visibles always (ej:iss,iat).
Anatomía de un Disclosure
Cada disclosure es una cadena base64url-encoded que contiene un JSON con tres elementos:
[
"8I-vSb04...salt",
"domicilio",
"Av. Belgrano 1234, Salta"
]
- El primer elemento es un salt (aleatorio, único por disclosure).
- El segundo es el nombre del atributo.
- El tercero es el valor del atributo.
Encoded a base64url, esto es un string como WyI4SS12U2IwNCIsImRvbWljaWxpbyIsIkF2LiBCZWxncmFubyAxMjM0Il0=.
La conexión entre JWT y Disclosure
Cada hash en _sd del JWT corresponde a un Disclosure. La conexión es matemática:
hash = sha-256(base64url(JSON.stringify([salt, name, value])))
El emisor:
- Genera salt aleatorio por cada atributo.
- Calcula el hash de la concatenación.
- Incluye el hash en
_sdy firma todo. - Manda al holder el JWT + los Disclosures.
El holder presenta selectivamente
Cuando el holder quiere presentar solo "nombre" y "domicilio":
- 1Toma el JWT completo (no se modifica).
- 2Toma solo los Disclosures de los atributos que quiere revelar.
- 3Manda al verifier:
<JWT>~<Disclosure_nombre>~<Disclosure_domicilio>~ - 4Los Disclosures de otros atributos (fecha_nacimiento, etc.) NO se incluyen.
El verifier verifica
El verifier recibe <JWT>~<Disclosure_nombre>~<Disclosure_domicilio>~. Sus pasos:
- 1Validar firma del JWT. Confirma que viene del emisor genuino.
- 2Para cada Disclosure recibido:
- Decode base64url para obtener
[salt, name, value]. - Calcular el hash sha-256 de
base64url(JSON.stringify([salt, name, value])). - Verificar que ese hash está en el array
_sddel JWT.
- Decode base64url para obtener
- 3Si todos los Disclosures matchean hashes: los atributos revelados son auténticos.
- 4Para los atributos no revelados: el verifier ve los hashes pero no puede inferir el valor (los hashes son irreversibles).
Por qué los salts importan
Sin salt, el ataque sería:
- Si el verifier sabe que un atributo está bajo
_sdy conoce el valor probable ("Juan", "María", etc.), puede calcular el hash de cada candidato y comparar. - Salt previene este ataque porque el atacante necesitaría adivinar el salt + el valor.
Cada salt debe ser:
- Aleatorio (no derivable).
- Único por disclosure (no compartir entre atributos).
- Suficientemente largo (típicamente 128 bits / 16 bytes).
Selective disclosure de elementos en arrays
SD-JWT también soporta divulgación selectiva de elementos individuales dentro de un array:
{
"address": {
"street": "Av. Belgrano 1234",
"city": "Salta",
"country": "Argentina"
}
}
Con SD-JWT, cada campo puede ser selectively disclosable independientemente. El holder puede revelar country sin revelar street o city.
Key binding — la pieza adicional
Más allá de selective disclosure, SD-JWT VC incluye key binding para evitar que un atacante que intercepta el JWT lo presente como suyo.
El JWT incluye en cnf la clave pública del holder. Al presentar, el holder firma un JWT adicional (key binding JWT, KB-JWT) sobre el nonce + audience. El verifier verifica esa firma con la cnf clave del SD-JWT original.
<SD-JWT>~<Disclosure1>~<Disclosure2>~<KB-JWT>
Sin KB-JWT, alguien que intercepta el SD-JWT puede presentarlo a otro verifier. Con KB-JWT, cada presentación requiere firma fresca del holder.
Implementación
Librerías que soportan SD-JWT VC:
| Lenguaje | Librería |
|---|---|
| JavaScript/TypeScript | @sd-jwt/core, @sd-jwt/vc |
| Python | sd-jwt-python |
| Go | sd-jwt-go |
| Java | sd-jwt-java |
| Rust | sd-jwt-rs |
Implementar SD-JWT desde cero es complejo. Usar librerías validadas.
Referencias
Relacionados
- SD-JWT VC — el formato preferido de Sovra — formato VC sobre SD-JWT
- Selective disclosure — la revelación selectiva — el concepto
- Hashes, salts y nonces — terminología criptográfica — terminología criptográfica

