Generation of Static Badges

A static Badge is a small key fob with a QR code printed on one side and a badge serial number on the flip side. It provides an alternative to the smartphone based Check-In for less tech-savvy Guests. The QR code for Badges are generated by a Trusted 3rd Party and manufactured in bulk by a print shop. Prior to the first Check-In Guests must personalize their Badge with their Contact Data using the Badge Personalization Frontend.

This chapter describes how the Badges are generated. Subsequent chapters describe how they are personalized and used for check-in.






The following secrets are involved in this process:


Use / Purpose


badge serial number

A random seed to derive the Badge’s intrinsic secrets required to both encrypt and associate Contact Data and perform Check-Ins

Defined by the Badge Generator and printed onto the flip side of the Badge. Entered into the Badge Personalization Frontend to associate Contact Data to a Badge.

data encryption key, data authentication key, tracing secret

Those secrets are required for the encryption of Contact Data and performing Check-Ins at luca locations. They are derived from the before-mentioned badge serial number.

Transiently known to the Badge Generator. Later re-derived by the Badge Personalization Frontend for encrypting/associating Contact Data.

guest keypair

Used to sign the encrypted contact data reference on the badge and (during Badge Personalization) to authenticate the owner of the Badge. This keypair is the Badge-equivalent of the guest keypair held by the Guest App.

The private key is transiently known to the Badge Generator as it is derived from the badge serial number. Later it is re-derived by the Badge Personalization Frontend for authenticating the Contact Data. The public key is stored on the Luca Server.

badge keypair

Used to encrypt the contact data reference while generating a Badge.

The public key is obtained from the Luca Server 2. The private key is known to all Health Departments.

badge attestation keypair

Used to sign freshly registered Badges for later validation by the Operator App/Scanner Frontend.

The private key is kept in the Luca Server for a below-described remote attestation workflow. The scanner can access the associated public key for validation.


The Badge Generator software creates each Badge by randomly choosing a 56-bit serial number. All cryptographic assets and the associated user ID for the generated Badge are then derived from this initial seed (aka. the badge serial number). The newly generated Badge is then registered with the Luca Server via an authenticated API request 1. In response, the Luca Server creates a remote attestation signature for the badge using its badge attestation keypair. Eventually, the relevant information is encoded into a QR code and printed on a plastic key fob along with the initially generated serial number.

The Badge Generator runs the following algorithm to generate a Badge:

# pseudocode

# generate random bytes and use argon2id to derive initial keying material
entropy = random_bytes(7)
seed    = argon2id(entropy, salt="da3ae5ecd280924e",
                   length=16, memorySize=32MiB, iterations=11, parallelism=1)

# generate key material that should be available via the Badge's serial number only
level_one     = HKDF-HMAC-SHA256(seed, length=64,
data_encryption_key = level_one[0:16]
tracing_seed        = level_one[16:32]
guest_keypair       = level_one[32:64]

# generate additional key material that needs to be available to a scanner that scans the Badge
level_two              = HKDF-HMAC-SHA256(tracing_seed, length=48,
user_id                = toUuid4(level_two[0:16])
badge_verification_key = level_two[16:32]
tracing_secret         = level_two[32:48]

# encrypt a 'blanco' Contact Data Reference using the badge keypair's public key
ephemeral_keys       = newSecp256r1Keypair() # for ECIES with the badge keypair public key
iv                   = ephemeral_keys.public # public key bits truncated to 16 bytes
dh_key               = ECDH(ephemeral_keys.private, badge_keypair.public)
enc_key              = HMAC-SHA256(data=dhKey, key=iv) # truncated to 16 bytes
enc_contact_data_ref = AES-128-CTR(user_id || data_encryption_key, enc_key, iv)

# sign the public Badge data with the guest_keypair
signature = sign(guest_keypair.private, user_id || enc_contact_data_ref)

# register the Badge with the Luca Server and receive an
# attestation of the new Badge using the Badge Attestation Key in response
attestation_signature = httpPOST("/api/v3/users/badge",
                                    "userId":    user_id,
                                    "publicKey": guest_keypair.public,
                                    "data":      enc_contact_data_ref
                                    "signature": signature

QR code and Serial Number Contents

The Badge’s QR code is then constructed by concatenating the following data:

  • badge revision (currently 0x04)

  • device type (0x02 for “Badge”)

  • badge keypair ID (1 byte identifier of the badge keypair used to encrypt the contact data reference)

  • tracing_seed

  • enc_contact_data_ref

  • attestation_signature (in IEEE 1363 format)

  • badge_ephemeral_public_key (ephemeral_keys.public from above)

  • checksum (SHA-256 of all the previous data truncated to 4 bytes)

The created data buffer is then encoded using the Z85 encoding and printed onto a plastic key fob in the form of a QR code. Along with the QR code each Badge features its associated badge serial number that is the random entropy value encoded in base32crockford and split into four digit groups (e.g. LUCA-1337-COOL).

Rationale of Generated and Exposed Secrets

The Badge secret derivation is split into three steps:

  1. Hardening against brute force attacks on the short 56-bit serial number. Ensured by employing the password hash algorithm argon2id. See the security considerations for a discussion on the chosen tuning parameters.

  2. Derivation of ‘secret’ values that only the owner of the Badge should have access to. The derive those values one needs to know the badge serial number with the exception of the tracing_seed which is exposed in plain in the Badge’s QR code. Secret values are required to personalize the Badge or perform contact tracing with it.

  3. Derivation of ‘public’ values that are exposed in the QR code. These values are revealed in the QR code and allow the scanner to perform a Check-In in the name of the Badge’s owner.

All public values revealed on the printed key fob are discussed below.

“badge revision” and “device type”

Those are technical values needed by the scanner to distinguish a Badge from a Guest App (device type) and allow for schema updates (badge revision) of the described Badge process.

badge serial number

The Badge’s owner can use the badge serial number printed on their key fob for two things:

  1. Personalize the Badge by associating their Contact Data with the Badge. For further details see Badge Personalization.

  2. Facilitate a contact tracing by revealing the Badge’s serial number to the Health Department

In both cases, either the Badge Personalization Frontend or the Health Department Frontend will re-derive all necessary secrets from the badge serial number for the desired use case.

badge keypair ID

The badge keypair ID identifies the public key that the Badge Generator used to encrypt the contact data reference during the generation. The badge keypair’s private key is owned by the Health Department and allows it to access the Guest’s contact data if necessary.


Using the public key of the matching badge attestation keypair the Operator App or Scanner Frontend can check the attestation_signature of the scanned Badge and determine whether it is valid or not. See the badge Check-In process for further details.

tracing_seed and Encrypted contact data reference (enc_contact_data_ref)

Using the tracing_seed the scanning Operator App/Scanner Frontend can derive the badge owner’s user ID and tracing secret. With this information, the scanner is able to create a valid Check-In for the Badge’s owner. See the badge Check-In process for further details.

Security Considerations

Choice of argon2id Parameters

The parameters for the argon2id key derivation function are optimized to maximally slow down a brute-force attack, while not prohibitively inconveniencing users of low-end commodity hardware in the Badge Personalization flow. The specific parameters (memorySize: 32MiB, iterations: 11, parallelism: 1) yield an execution time of around 0.5 seconds using a native implementation of the on high-end consumer hardware and around 2 to 5 seconds using a browser-based JavaScript/WebAssembly implementation on a mid-range to low-end mobile device.

A random but fixed salt is used. This is not a problem, as argon2id is not used for password hashing here but for key derivation from random input data (termed entropy above).


For specific tasks the Luca API requires special authentication via a so-called “Bearer token”. Further details about the authentication mechanism of the Luca Server are beyond the scope of this chapter.


The provided public key of the badge keypair is signed by a Health Department using their HDSKP (see also Verification of Health Department Keypair Certificates). This signature is provided by the Luca Server along with said public key.