Generic Functions for data exchange - Implementation Guide
26.0.0 - trial-use
Generic Functions for data exchange - Implementation Guide - Local Development build (v26.0.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
Generic Function Pseudonymisation defines how a national Pseudonym Registration Service (PRS) is used to convert an identifier (e.g. a Burgerservicenummer, BSN) into a recipient-specific, single-use pseudonym. The goal is to allow generic functions such as GF Localization to exchange patient-bound information without ever revealing the underlying BSN to the receiving party (e.g. the Nationale Verwijs Index, NVI).
Note: This page is based on version 0.9.0 of the Functional and Technical Design Documents of the Generic Function Localization (Generieke Functie Lokalisatie). The current and previous versions of this document are available at www.generiekefuncties.nl/lokalisatie.
The PRS combines two cryptographic building blocks:
Identifier;The PRS returns the evaluation result encrypted as a JWE for the intended recipient. The client never sees the cleartext pseudonym; the recipient never sees the BSN.
The basic process for obtaining and using a pseudonym is:
Identifier for the patient in JSON Canonicalization Scheme - JCS (RFC8785) e.g. {"landCode":"NL","type":"BSN","value":"999940003"}.Identifier using HKDF, using a fixed info string that binds the result to the intended recipient.blind_factor (kept locally) and a blinded_input (sent to the PRS).blinded_input and returns the result as a JWE (evaluated_output) encrypted with the public key of the intended recipient.evaluated_output) together with the blind_factor to the recipient as part of a downstream transaction (for example, a GF Localization registration or query).blind_factor to obtain the final, stable pseudonym for that recipient.sequenceDiagram
participant client as Pseudonymization<br/>Client (e.g. EHR)
participant prs as Pseudonymization<br/>Service (PRS)
participant recipient as Recipient<br/>(e.g. NVI)
Note over client,recipient: Client-side preparation
client->>client: Build Identifier<br/>{ landCode, type, value }
client->>client: HKDF(SHA-256,<br/>info="{recipient_org}|{recipient_scope}|v1")<br/>→ pseudonym
client->>client: OPRF.blind(pseudonym)<br/>→ (blind_factor, blinded_input)
Note over client,recipient: PRS evaluation
activate prs
client->>prs: POST /oprf/eval<br/>{ encryptedPersonalId,<br/>recipientOrganization, recipientScope }
prs->>prs: Validate input, evaluate OPRF<br/>and encrypt for recipient
prs-->>client: JWE (evaluated_output)<br/>encrypted to recipient public key
deactivate prs
Note over client,recipient: Hand-off to recipient
client->>client: Compose patient identifier:<br/>base64url(JSON{ evaluated_output,<br/>blind_factor })
client->>recipient: Request carrying patient identifier
activate recipient
recipient->>recipient: Decrypt JWE with own private key
recipient->>recipient: De-blind with blind_factor → stable pseudonym
recipient-->>client: Response
deactivate recipient
For more background on how the result is consumed, see GF Localization.
This guide makes the following national choices for pseudonymisation:
info string). The same identifier therefore yields different pseudonyms for different recipients, preventing cross-service correlation. The scope identifies the recipient service only.| Actor | Role | Transactions |
|---|---|---|
| Pseudonymization Client | Derives and blinds the pseudonym, and forwards the result to the recipient | Client for GF-PSD-1 |
| Pseudonym Registration Service (PRS) | Evaluates the blinded input and encrypts the result for the recipient | Server for GF-PSD-1 |
| Recipient | Decrypts and de-blinds the result to obtain its pseudonym | Specified by the generic function that uses the pseudonym |
A Pseudonymization Client is typically embedded in or alongside an EHR, PACS, or another generic-function client (e.g. a Localization Registration Client or Localization Search Client). The client:
Identifier from authoritative source data (e.g. the patient's BSN held in the EHR);info string for the intended recipient and scope;blind_factor for each request (see OPRF blinding), and keep the blind_factor confidential;blinded_input (and the recipient identifiers) to the PRS;blind_factor to the recipient as part of a single downstream transaction;blind_factor beyond the lifetime of the transaction.The PRS is the central national service that evaluates blinded inputs. It uses a key bound to the recipient, and encrypts the result as a JWE with the recipient's public key. The PRS never sees the Identifier or the pseudonym, and does not store its input, its output, or any link between them (see National choices). Its interface is GF-PSD-1.
The Recipient is the organization for which the pseudonym is intended. The recipient scope identifies its service. For example, the Ministry of VWS is the recipient organization for the NVI. It publishes a public key at the PRS, decrypts the JWE with the corresponding private key, and de-blinds the result with the blind_factor to obtain its stable pseudonym. The generic function that uses the pseudonym specifies the recipient's requirements, for example GF Localization.
| ID | Transaction | Client | Server |
|---|---|---|---|
| GF-PSD-1 | Evaluate blinded input | Pseudonymization Client | PRS |
The client sends the blinded_input together with identifiers for the intended recipient to the PRS.
Request.
POST [prs-base-url]/oprf/eval HTTP/1.1
Authorization: [access token]
Content-Type: application/json
{
"encryptedPersonalId": "<base64url-encoded blinded_input>",
"recipientOrganization": "oin:<OIN of the Ministry of VWS>",
"recipientScope": "nationale-verwijsindex"
}
encryptedPersonalId: the blinded_input, base64url-encoded. Despite its name, this value is blinded, not encrypted;recipientOrganization: the OIN of the recipient organization, prefixed with oin:. For the NVI, this is the Ministry of VWS;recipientScope: the recipient service for which the recipient registered its public key.Response. 200 OK with the OPRF evaluation as a JWE encrypted to the recipient's public key:
HTTP/1.1 200 OK
Content-Type: application/json
{
"jwe": "<JWE compact serialization>"
}
The pair of this JWE and the blind_factor together forms the patient identifier that is passed to the recipient. See GF Localization — Register Patient for how this identifier is represented in Patient.identifier, where the JWE is called evaluated_output.
Errors. The PRS returns a JSON body with a detail field that describes the error.
| Status | Cause | Client action |
|---|---|---|
400 Bad Request |
The PRS cannot evaluate the blinded input, or recipientOrganization does not start with oin:. |
Correct the request. Do not retry unchanged. |
401 Unauthorized |
The access token is missing, expired, or invalid. | Obtain a new token and retry. |
403 Forbidden |
The requesting organization is not registered at the PRS, or may not request OPRF pseudonyms. | Do not retry. |
404 Not Found |
The recipient is unknown, or has no public key for recipientScope. |
Check the recipient identifiers. Do not retry unchanged. |
409 Conflict |
The PRS has no active key for the recipient. | Retry later. |
422 Unprocessable Entity |
The request body is invalid, for example a missing field or a value that is not base64url. | Correct the request. |
503 Service Unavailable |
The PRS cannot reach its key store. | Retry later. |
prs:oprf-pseudonym.The Identifier is a small JSON object that uniquely identifies the natural person being pseudonymized. For example, a Dutch citizen identified by its BSN:
{"landCode":"NL","type":"BSN","value":"999940003"}
The Identifier is never sent to the PRS; it is the input to the local HKDF step. This Identifier SHALL be in JSON Canonicalization Scheme - JCS (RFC 8785).
The pseudonym handed to OPRF is derived as:
"{recipient_organization}|{recipient_scope}|v1"The client SHALL blind the 32-byte HKDF output with the Blind function of RFC 9497, OPRF mode (modeOPRF), with ciphersuite ristretto255-SHA512. This yields two values:
blind_factor — a secret kept by the client and later used by the recipient to de-blind the PRS response;blinded_input — the value sent to the PRS for evaluation.Both values are exchanged base64url-encoded.
For each PRS request, the client SHALL generate a new blind_factor with a cryptographically secure random number generator, as the Blind function of RFC 9497 does. The client SHALL NOT reuse a blind_factor for another request. The HKDF output for a patient is always the same. With a reused blind_factor, the blinded_input would be the same too, and the PRS could link all requests about that patient.
Some reference code calls the blind_factor oprf_key. It is not the OPRF key of RFC 9497, which is the PRS's secret key.
The PRS response is a JWE compact serialization encrypted to the recipient's public key. The JWE is opaque to the client and is intended for one-time use in a single downstream transaction.
During a key rotation, the JWE carries the evaluation under the latest key and, in an extraVersions claim, the evaluations under older keys that are still active. The recipient can therefore still match pseudonyms it stored under an older key.
The following snippet (adapted from the reference implementation OPRF.py) shows the client-side HKDF and OPRF steps:
import base64
import rfc8785
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
import pyoprf
def create_blinded_input(personal_identifier, recipient_organization, recipient_scope):
info = f"{recipient_organization}|{recipient_scope}|v1".encode("utf-8")
pid = rfc8785.dumps(personal_identifier)
pseudonym = HKDF(
algorithm=hashes.SHA256(), length=32, salt=None, info=info
).derive(pid)
blind_factor, blinded_input = pyoprf.blind(pseudonym)
return (
base64.urlsafe_b64encode(blind_factor).decode(),
base64.urlsafe_b64encode(blinded_input).decode(),
)
A full reference flow including the call to the PRS is available in the gfmodules-nationale-verwijsindex-registratie-service repository.
A care provider's Localization Registration Client needs to register the existence of patient data at the NVI (see the Radiologist registration use case). Before submitting the Patient resource it:
Identifier from the patient's BSN;recipient_organization = "oin:<OIN of the Ministry of VWS>" and recipient_scope = "nationale-verwijsindex";(blind_factor, blinded_input);POST /oprf/eval on the PRS to obtain the JWE (GF-PSD-1);(evaluated_output, blind_factor) as the NVI patient identifier and places it in Patient.identifier;Patient resource to the NVI.A consulting practitioner's Localization Search Client wants to discover which organisations hold data for a patient. The pseudonym is computed in exactly the same way as for registration, but the resulting NVI patient identifier is used with the identifier search parameter on POST [base]/Patient/_search, together with a search context (see Localization). Because pseudonyms are deterministic for a given recipient and scope, the value will match the pseudonyms used by the registering parties.