Skip to main content
Version: v0.13

SD-JWT Overview

SD-JWT (Selective Disclosure for JWTs) enables holders to present only specific claims from a credential, enhancing privacy while maintaining cryptographic verifiability. The IDK provides comprehensive support for creating, presenting, and verifying SD-JWT credentials per RFC 9901.

What is SD-JWT?

Traditional JWTs reveal all claims to every verifier. SD-JWT solves this by allowing the issuer to create a credential where individual claims can be selectively disclosed. When presenting the credential, the holder chooses which claims to reveal, and the verifier can only see those specific claims.

The format extends standard JWT with disclosure tokens that are cryptographically bound to the main credential. This binding ensures that disclosed claims cannot be forged or modified while keeping undisclosed claims invisible to the verifier.

SD-JWT Structure

An SD-JWT credential consists of three parts:

<issuer-jwt>~<disclosure1>~<disclosure2>~...~<disclosureN>~<key-binding-jwt>
ComponentDescription
Issuer JWTThe main credential signed by the issuer, containing hashes of disclosures in the _sd array
DisclosuresBase64url-encoded JSON arrays containing [salt, claim_name, claim_value]
Key Binding JWTOptional JWT proving possession of a holder key (contains aud, nonce, iat, sd_hash)

How Selective Disclosure Works

When an issuer creates an SD-JWT:

  1. Each selectively disclosable claim is replaced with a hash digest in the JWT's _sd array
  2. The original claim values are encoded as separate disclosures
  3. The issuer signs the JWT containing only the hashes
  4. The complete SD-JWT (JWT + all disclosures) is given to the holder

When a holder presents to a verifier:

  1. The holder selects which claims to reveal
  2. Only the selected disclosures are included in the presentation
  3. A Key Binding JWT proves the holder controls the credential (optional)
  4. The verifier receives the issuer JWT plus only the revealed disclosures
SD-JWT Selective Disclosure

Core Service

The IDK provides SdJwtService for all SD-JWT operations:

// Get the SD-JWT service from the session
val sdJwtService = session.component.sdJwtService

// Issue SD-JWT
val issueResult = sdJwtService.issueSdJwt(issueArgs)

// Create presentation with selective disclosure
val presentResult = sdJwtService.presentSdJwt(presentArgs)

// Verify SD-JWT or presentation
val verifyResult = sdJwtService.verifySdJwt(verifyArgs)

// Access individual commands for advanced usage
val commands = sdJwtService.commands
commands.issueSdJwt
commands.presentSdJwt
commands.verifySdJwt

Payload Builder Extensions

The IDK extends JwsPayloadBuilder with selective disclosure functions:

import com.sphereon.crypto.jose.jws.jwsPayload
import com.sphereon.sdjwt.*

val payload = jwsPayload {
// Standard claims (always visible)
iss("https://issuer.example.com")
iat(System.currentTimeMillis() / 1000)
exp(System.currentTimeMillis() / 1000 + 86400)

// Selectively disclosable claims
claimSd("email", "user@example.com")
claimSd("given_name", "John")
claimSd("family_name", "Doe")
claimSd("age", 25)
claimSd("verified", true)

// Nested object as selectively disclosable
objClaimSd("address") {
custom("street", "123 Main St")
custom("city", "Springfield")
custom("country", "US")
}

// Add decoy digests for privacy
minimumDigests(5)
}

Data Types

SdJwt

The parsed SD-JWT structure:

data class SdJwt<JwtType>(
val jwt: JwtType, // Signed JWT
val header: JsonObject, // JWT header
val payload: SdJwtPayload, // Payload with disclosure info
val disclosures: List<Disclosure>, // All disclosures
val keyBindingJwt: KeyBindingJwt? // Optional KB-JWT
) {
val algorithm: String? // From header
val keyId: String? // From header
val type: String? // From header
}

SdJwtPayload

Provides both undisclosed and resolved views of the payload:

data class SdJwtPayload(
val undisclosedPayload: JsonObject, // Contains _sd array with digests
val fullPayload: JsonObject, // All disclosed claims resolved
val digestedDisclosures: Map<String, Disclosure> // Digest to disclosure mapping
)

Disclosure

Individual disclosure structure:

data class Disclosure(
val salt: String, // Random salt
val key: String, // Claim name
val value: JsonElement, // Claim value
val encoded: String, // Base64url-encoded [salt, key, value]
val digest: String? // Hash of encoded disclosure
)

KeyBindingJwt

Key Binding JWT for holder authentication:

data class KeyBindingJwt(
val jwt: String,
val header: JsonObject,
val payload: JsonObject
) {
val audience: String? // Verifier identifier
val nonce: String? // Fresh nonce
val issuedAt: Long? // Timestamp
val sdHash: String? // Hash of presentation
}

Security Considerations

Per RFC 9901, certain claims should NOT be made selectively disclosable:

ClaimReason
issRequired for trust establishment and signature verification
audRequired to determine if JWT is intended for the verifier
expRequired for token lifetime validation
nbfRequired for token lifetime validation
cnfRequired for holder binding validation

The IDK intentionally does not provide *Sd() variants for these claims to discourage unsafe usage.

SD-JWT vs Other Formats

FeatureSD-JWTmDocStandard JWT
Selective DisclosureYesYesNo
FormatJWT + DisclosuresCBORJWT
Key BindingOptionalRequiredNo
RevocationVia status listVia MSO validityExpiration only
StandardsRFC 9901ISO 18013-5RFC 7519

Next Steps

  • Issuance - Create SD-JWT credentials with selective disclosure
  • Presentation - Present credentials and verify presentations