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>
| Component | Description |
|---|---|
| Issuer JWT | The main credential signed by the issuer, containing hashes of disclosures in the _sd array |
| Disclosures | Base64url-encoded JSON arrays containing [salt, claim_name, claim_value] |
| Key Binding JWT | Optional JWT proving possession of a holder key (contains aud, nonce, iat, sd_hash) |
How Selective Disclosure Works
When an issuer creates an SD-JWT:
- Each selectively disclosable claim is replaced with a hash digest in the JWT's
_sdarray - The original claim values are encoded as separate disclosures
- The issuer signs the JWT containing only the hashes
- The complete SD-JWT (JWT + all disclosures) is given to the holder
When a holder presents to a verifier:
- The holder selects which claims to reveal
- Only the selected disclosures are included in the presentation
- A Key Binding JWT proves the holder controls the credential (optional)
- The verifier receives the issuer JWT plus only the revealed disclosures
Core Service
The IDK provides SdJwtService for all SD-JWT operations:
- Android/Kotlin
- iOS/Swift
// 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
// Get the SD-JWT service from the session
let sdJwtService = session.component.sdJwtService
// Issue SD-JWT
let issueResult = try await sdJwtService.issueSdJwt(args: issueArgs)
// Create presentation with selective disclosure
let presentResult = try await sdJwtService.presentSdJwt(args: presentArgs)
// Verify SD-JWT or presentation
let verifyResult = try await sdJwtService.verifySdJwt(args: verifyArgs)
Payload Builder Extensions
The IDK extends JwsPayloadBuilder with selective disclosure functions:
- Android/Kotlin
- iOS/Swift
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)
}
import SphereonCrypto
import SphereonSdJwt
let payload = JwsPayloadBuilder()
// Standard claims (always visible)
.iss("https://issuer.example.com")
.iat(Int64(Date().timeIntervalSince1970))
.exp(Int64(Date().timeIntervalSince1970) + 86400)
// Selectively disclosable claims
.claimSd(name: "email", value: "user@example.com")
.claimSd(name: "given_name", value: "John")
.claimSd(name: "family_name", value: "Doe")
.claimSd(name: "age", value: 25)
.claimSd(name: "verified", value: true)
.build()
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:
| Claim | Reason |
|---|---|
iss | Required for trust establishment and signature verification |
aud | Required to determine if JWT is intended for the verifier |
exp | Required for token lifetime validation |
nbf | Required for token lifetime validation |
cnf | Required for holder binding validation |
The IDK intentionally does not provide *Sd() variants for these claims to discourage unsafe usage.
SD-JWT vs Other Formats
| Feature | SD-JWT | mDoc | Standard JWT |
|---|---|---|---|
| Selective Disclosure | Yes | Yes | No |
| Format | JWT + Disclosures | CBOR | JWT |
| Key Binding | Optional | Required | No |
| Revocation | Via status list | Via MSO validity | Expiration only |
| Standards | RFC 9901 | ISO 18013-5 | RFC 7519 |
Next Steps
- Issuance - Create SD-JWT credentials with selective disclosure
- Presentation - Present credentials and verify presentations