Skip to content

Device Bootstrapping and Configuration

Device bootstrapping in KNX IoT establishes secure communication between a Management Client (MaC) and KNX IoT devices through device individualization and configuration download. All communication uses OSCORE-secured CoAP for authentication and data protection.

Device bootstrapping consists of two independent phases: authenticating the device and installing security credentials (Part 1), and downloading device configuration data and security tokens (Part 2). These phases can run independently or in combination to complete the full initialization of a KNX IoT device into a KNX installation.

Overview

Phase Description Endpoints
Configure Individual Address (Part 1) Authenticate with the device (PASE: SPAKE2+ handshake, tool key install, PASE access token delete), assign the individual address, and disable programming mode. /.well-known/knx/spake, /auth/at, /.well-known/knx/ia, /dev/pm
Configuration Download (Part 2) Factory-reset (preserving IA), transition through the load state machine, and download tables, parameters, and security tokens. /.well-known/knx, /a/lsm, /fp/r,/fp/p, /fp/g, /p/*, /auth/at

The MaC (Management Client) is the commissioning tool that orchestrates both phases. Both phases use OSCORE-secured CoAP. The shared secret derives from the PASE handshake in Part 1, then the commissioning tool replaces it with a long-lived tool key for Part 2.

PASE (Password Authenticated Session Establishment) is the complete session setup protocol. It contains SPAKE2+ (RFC 9383) as its cryptographic core for mutual authentication and key agreement, then continues with tool key installation and temporary key cleanup:

Protocol Scope
SPAKE2+ 3-step cryptographic key exchange on /.well-known/knx/spake that derives K_shared
PASE SPAKE2+ handshake + install tool key + delete PASE access token on /auth/at

Bootstrapping w/ TLS and LDevID Enrollment

Devices that support X.509 certificates can also use TLS as an extension to the password-based SPAKE2+ flow. This document describes only the SPAKE2+-based PASE variant. See Device Bootstrapping w/ TLS for the full TLS onboarding and LDevID enrollment workflow.

Configure Individual Address (Part 1)

Part 1 authenticates the device using PASE (Password Authenticated Session Establishment) and installs a persistent tool key for secure communication. It assigns the device a unique Individual Address (IA) and disables programming mode.

Before Part 1 begins, the installer may activate programming mode on the device, typically by pressing a physical programming button. The device then advertises its programming mode state (for example, via mDNS), signaling the MaC that the device is ready for configuration. Battery-powered or sleepy devices, for example, wake up or switch to a faster wake-up cycle for a limited time after an installer presses the programming button.

MaC Client                              KNX IoT Device
     |                                        |
     |--- [NON] GET /.well-known/core ------->| Discovery (multicast, ?ia or ?sn)
     |<-- [NON] 2.05 Content -----------------|
     |                                        |
     |--- [CON] GET /a/lsm ------------------>| Connection and auth check
     |<-- [ACK] 4.01 Unauthorized ------------|
     |                                        |
     |          ┌-------- PASE --------┐      |
     |          │                      │      |
     |          │  ┌-- SPAKE2+ --┐     │      |
     |--- [CON] POST /.well-known/knx/spake ->| Step 1: Parameter Request
     |<-- [CON] 2.04 Changed -----------------| Step 1: Parameter Response
     |--- [CON] POST /.well-known/knx/spake ->| Step 2: Credential Request (shareP)
     |<-- [CON] 2.04 Changed -----------------| Step 2: Credential Response (shareV, confirmV)
     |--- [CON] POST /.well-known/knx/spake ->| Step 3: Verification (confirmP)
     |<-- [CON] 2.04 Changed -----------------| Step 3: Verification Response
     |          │  └-------------┘     │      |
     |          │                      │      |
     |--- [CON] POST /auth/at --------------->| Install access token w/ tool key (OSCORE)
     |<-- [ACK] 2.01 Created -----------------|
     |          │                      │      |
     |--- [CON] DELETE /auth/at/<kid> ------->| Delete PASE access token w/ temporary key (OSCORE)
     |<-- [ACK] 2.02 Deleted -----------------|
     |          │                      │      |
     |          └----------------------┘      |
     |                                        |
     |--- [CON] POST /.well-known/knx/ia ---->| Write Individual Address + IID
     |<-- [ACK] 2.04 Changed -----------------|
     |                                        |
     |--- [CON] PUT /dev/pm ----------------->| Disable programming mode
     |<-- [ACK] 2.04 Changed -----------------|
     |                                        |

Device Individualization Steps

1. Device Discovery

The MaC sends a multicast [NON] GET /.well-known/core to locate the device. The two queries are alternatives, the MaC uses whichever matches its available information:

  1. Query by IA (?ia=<ia>&iid=<iid>): checks whether a device with this individual address already exists in the installation. Useful during re-commissioning. Devices also use this query at runtime to resolve IPv6 addresses, for example, for Direct Unicast Message Exchange.
  2. Query by SN (?sn=<serial>): locates an unconfigured device by serial number (from barcode/QR). The device responds with its current IA (for example, 15.15.255 if unconfigured).

2. Connection and Authentication Check

A [CON] GET /a/lsm establishes the UDP/IPv6 transport path and tests the current authentication state. An unconfigured device responds with 4.01 Unauthorized because no OSCORE context exists yet, PASE establishes it in the next step. During re-commissioning, a 2.05 response confirms that the existing credentials are still valid.

3. PASE (Password Authenticated Session Establishment)

PASE establishes a temporary OSCORE session between the MaC and the device. It consists of three sub-steps: the SPAKE2+ cryptographic handshake, tool key installation, and temporary key cleanup.

3a. SPAKE2+ Handshake

The 3-step SPAKE2+ key exchange on /.well-known/knx/spake derives K_shared. See SPAKE2+ Protocol Detail below for the full cryptographic specification.

SPAKE2+ Visualizer

The SPAKE2+ Visualizer provides a step-by-step guide to understand and verify the SPAKE2+ handshake.

3b. Install Tool Key

After the SPAKE2+ handshake establishes a temporary OSCORE context, the client installs a persistent tool key via POST /auth/at.

Access Token Format (CBOR):

[{
    0:  kid,             // key identifier (hex string of key_identifier bytes)
    8:  {                // cnf (confirmation)
        4: {             // OSCORE_Input_Material
            0: sender_id,    // bytes (key_identifier from security CSV)
            2: master_secret // 16 bytes
        }
    },
    9:  scope,           // ["if.c", "if.p", "if.d", "if.sec", "if.swu", "if.g.s"]
    38: 2,               // profile = OSCORE
}]

Access Token Format and Scope

See Device Access Control for the full access token CBOR structure, credential types (OSCORE and X.509), and scope definitions.

3c. Delete PASE Access Token

The commissioning tool removes the temporary PASE access token with the PASE key: DELETE /auth/at/<kid> (for example, /auth/at/+64STfs).

After this step, the PASE session is complete. The device now uses only the persistent tool key for all following OSCORE communication.

4. Write Individual Address

POST /.well-known/knx/ia with CBOR payload:

{12: ia, 26: iid}  // ia = individual address (int), iid = installation ID (int)

5. Disable Programming Mode

PUT /dev/pm with CBOR payload {1: false}.

This clears the programming mode flag that the installer activated before Part 1. The device stops advertising its readiness for configuration (for example, via mDNS).

Configuration Download (Part 2)

Part 2 downloads the actual device configuration, function blocks, and security tokens after the MaC has established a secure tool key connection. This phase includes a factory reset to clear any previous configuration, then uses the Load State Machine to orchestrate the sequential download of tables, parameters, and cryptographic material.

MaC Client                              KNX IoT Device
     |                                        |
     |    (Part 1 completed—tool key active)  |
     |                                        |
     |--- GET /dev/sna, /dev/da, /dev/mid --->| Read device info
     |<-- 2.05 Content -----------------------|
     |                                        |
     |--- POST /.well-known/knx ------------->| Reset(FactoryResetWithoutIA)
     |<-- 2.04 Changed (Success, 5 s) --------|
     |                                        |
     |    ~~~ wait for device reboot ~~~      |
     |                                        |
     |--- POST /a/lsm ----------------------->| Unload
     |<-- 2.04 Changed (Unloaded) ------------|
     |                                        |
     |--- POST /a/lsm ----------------------->| StartLoading
     |<-- 2.04 Changed (Loading) -------------|
     |                                        |
     |--- POST /fp/g, /fp/r, /fp/p, /auth/at >| Download Function Point Tables and access tokens
     |<-- 2.01 Created -----------------------|  (repeated per entry)
     |                                        |
     |--- PUT /p/* (parameters) ------------->| Configure parameters
     |<-- 2.04 Changed -----------------------|  (repeated per param)
     |                                        |
     |--- PUT ApplicationProgramId ---------->| Lock application identity
     |<-- 2.04 Changed -----------------------|
     |                                        |
     |--- POST /a/lsm ---------------------->| LoadComplete
     |<-- 2.04 Changed (Loaded) --------------|
     |                                        |
     |--- GET /a/lsm ------------------------>| Verify loaded
     |<-- 2.05 Content (Loaded) --------------|
     |                                        |
     |--- GET Fingerprint ------------------->| Read config fingerprint
     |<-- 2.05 Content (config hash) ---------|
     |                                        |

Configuration Download Steps

After Part 1 establishes a secure tool key connection, the MaC downloads device configuration through several steps. These steps include reading device properties, resetting configuration, managing the load state machine, and installing tables and parameters.

1. Read Device Information

After the Configure Individual Address phase, the client reads device properties to confirm identity:

Property Endpoint Example Value
Subnet Address GET /dev/sna 2.1
Device Address GET /dev/da 4
Manufacturer ID GET /dev/mid 0x00fd
Hardware Type GET /dev/hwt 0x0001000200030004

2. Factory Reset (Without IA)

POST /.well-known/knx with CBOR payload:

{2: "reset", 1: 2}  // erase_code = 2 → FactoryResetWithoutIA

The device responds with 2.04 Changed and a reset delay (for example, 5 s). The individual address remains intact. All configuration data erases during reboot. The device responds with IA=15.15.255 after reboot, then resumes with its assigned IA once the OSCORE context is re-established.

3. Load State Machine

The load state machine governs configuration download:

Unloaded ──→ StartLoading ──→ Loading ──→ LoadComplete ──→ Loaded
Transition Method Payload Response
→ Unloaded POST /a/lsm Unload Unloaded
→ Loading POST /a/lsm StartLoading Loading
→ Loaded POST /a/lsm LoadComplete Loaded

4. Download Tables

While in Loading state, the client posts table entries:

Group Object and Function Point Tables

The section Group Communication describes how Group Objects map data points to group addresses. It also explains how Function Point Tables link them for device-to-device communication. For the CBOR key definitions and examples of each table, see Device Function Point Tables.

Group Object Table (POST /fp/g)

Maps application data points to group addresses. Each entry contains a resource path, group address, and resource type (for example, rt for read/transmit, t for transmit only).

Recipient Table (POST /fp/r)

Defines the group communication target the device sends to (who receives messages). Entries can configure unicast (point-to-point) or multicast delivery, specifying the destination address and the group addresses covered.

Publisher Table (POST /fp/p)

Defines the group communication sources the device receives from (who publishes messages). As with the Recipient Table, entries can be unicast or multicast.

Access Token Table (POST /auth/at)

The MaC configures one access token per security context: one for each multicast group, one for each unicast peer, and a separate tool key for MaC access. See Device Access Control for the full access token structure and credential types.

5. Configure Parameters

Application-specific parameters use PUT /p/*:

The device stores each parameter as a PUT to the corresponding /p/<path> resource with a CBOR-encoded value.

6. Application Program Identity

PUT ApplicationProgramId locks the application identity (manufacturer + program version).

7. Complete Loading and Verify

  1. POST /a/lsm = LoadComplete → device transitions to Loaded.
  2. GET /a/lsm → confirms Loaded state.
  3. GET Fingerprint → returns a configuration hash.

SPAKE2+ Protocol Detail

SPAKE2+ (RFC 9383) is a Password Authenticated Key Exchange (PAKE) that enables two parties who share a password to establish a mutual authenticated shared secret. KNX IoT uses SPAKE2+ as the cryptographic core of PASE (Password Authenticated Session Establishment). SPAKE2+ handles only the key agreement—PASE wraps it with tool key installation and temporary key cleanup (see step 3).

Cryptographic Parameters

Parameter Value
Curve NIST P-256 (secp256r1)
Hash SHA-256
KDF HKDF-SHA256
Password KDF PBKDF2-HMAC-SHA256
PBKDF2 output 80 bytes → w0s \|\| w1s (40 + 40)
Cofactor (h) 1 (identity—no cofactor multiplication needed)
MAC HMAC-SHA-256
Shared key length 16 bytes (AES-CCM-16-64-128 master secret)

Curve Constants

Field prime (p):
  0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF

Curve parameters:
  a = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC
  b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B

Generator (G):
  Gx = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296
  Gy = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5

Order (N):
  0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551

M and N Constants (RFC 9383)

These are nothing-up-the-sleeve points used for blinding:

M = 04 886E2F97ACE46E55BA9DD7242579F2993B64E16EF3DCAB95AFD497333D8FA12F
       5FF355163E43CE224E0B0E65FF02AC8E5C7BE09419C785E0CA547D55A12E2D20

N = 04 D8BBD6C639C62937B04D997F38C3770719C629D7014D49A24B4F98BAA1292B49
       07D60AA6BFADE45008A636337F5168C64D9BD36034808CD564490B1E656EDBE7

KNX IoT Protocol Constants

Constant Value Description
TT_CONTEXT "knxpase" (7 bytes) Transcript context string
HKDF_INFO_CONFIRM "ConfirmationKeys" HKDF info for confirmation keys (64 bytes output)
HKDF_INFO_SHARED "SharedKey" HKDF info for shared key (16 bytes output)
HKDF_SALT empty (0 bytes) Salt for both HKDF derivations
ID_PROVER empty (0 bytes) Prover identity (KNX IoT spec)
ID_VERIFIER empty (0 bytes) Verifier identity (KNX IoT spec)

Complete Protocol Sequence

Phase 0: Offline Registration

The device stores configuration information during manufacturing, which remains unchanged during protocol execution.

Device (Verifier) stores during manufacturing:
  w0 = w0s mod N
  L = w1 * G (compressed point)
  salt, iterations (PBKDF2 params)
  Password transferred to client (QR code/barcode)

Phase 1: Parameter Exchange

The MaC and device exchange random nonces and PBKDF2 parameters to set up key derivation.

MaC                                         KNX IoT Device
     |                                            |
     |--- POST /.well-known/knx/spake ----------->| Step 1
     |    CBOR: {0: kid, 15: rnd_client}          | (send kid and random)
     |                                            |
     |<-- 2.04 Changed ---------------------------| Step 1
     |    CBOR: {15: rnd_server,                  | (recv server random
     |            12: {5: salt, 16: iterations}}  | and PBKDF2 params)
     |                                            |

Phase 2: Key Derivation (Both Sides, Offline)

Both parties independently compute the same shared keys using the exchanged parameters.

Client side:

inputHash = len(pw)||pw || len(idProver) || len(idVerifier)
w0s||w1s = PBKDF2-HMAC-SHA256(inputHash, salt, iterations, 80)
w0 = w0s mod N
w1 = w1s mod N
Generate random x: x ∈ [1, N-1]
Compute: ShareP = x·G + w0·M (65 bytes, uncompressed)

Device side:

Already has w0 and L from manufacturing
Generate random y: y ∈ [1, N-1]
Compute: ShareV = y·G + w0·N (65 bytes, uncompressed)

Phase 3: Credential Exchange

The MaC and device exchange blinded shares and verify mutual knowledge of the password through MAC values.

Message exchange:

MaC                                         KNX IoT Device
     |                                            |
     |--- POST /.well-known/knx/spake ----------->| Step 2
     |    CBOR: {10: shareP}                      | (send ShareP)
     |                                            |
     |<-- 2.04 Changed ---------------------------| Step 2
     |    CBOR: {11: shareV, 13: confirmV}        | (recv ShareV+confirmV)
     |                                            |
     |--- POST /.well-known/knx/spake ----------->| Step 3
     |    CBOR: {14: confirmP}                    | (send confirmP)
     |                                            |
     |<-- 2.04 Changed ---------------------------| Step 3
     |    (verification complete)                 | (auth confirmed)
     |                                            |

Device computation (after receiving ShareP):

Z = y · (ShareP − w0·M)
V = y · L
K_main = SHA-256(TT)
K_confirmP||K_confirmV = HKDF(K_main, "ConfirmationKeys", 64)
K_shared = HKDF(K_main, "SharedKey", 16)
confirmV = HMAC-SHA-256(K_confirmV, ShareP)

Client computation (after receiving ShareV):

Z = x · (ShareV − w0·N)
V = w1 · (ShareV − w0·N)
K_main = SHA-256(TT)
K_confirmP||K_confirmV = HKDF(K_main, "ConfirmationKeys", 64)
K_shared = HKDF(K_main, "SharedKey", 16)
Verify: HMAC(K_confirmV, ShareP) == received confirmV
confirmP = HMAC-SHA-256(K_confirmP, ShareV)

Device verification (after receiving confirmP):

Verify: HMAC(K_confirmP, ShareV) == received confirmP

Phase 4: Temporary OSCORE Context Established

Both parties now share the same K_shared. This concludes the SPAKE2+ handshake. The resulting temporary OSCORE context enables the remaining PASE steps (tool key installation and PASE access token deletion).

Both MaC and KNX IoT Device now share K_shared (16 bytes)

Temporary OSCORE access token established with:
  kid = provided key identifier
  master_secret = K_shared (16 bytes)
  sender_id = kid as UTF-8 bytes
  recipient_id = empty (zero length)
  context_id = omit entirely (do NOT set to empty)
  scope = ["if.sec"] (security interface only)
  algorithm = AES-CCM-16-64-128

OSCORE Context ID

Setting context_id to an empty byte string changes HKDF key derivation (CBOR 0x40 vs 0xf6) and adds a spurious Key ID flag to the OSCORE option.

PBKDF2 Input Construction

Non-Standard PBKDF2 Input Format

The PBKDF2 input is not the raw password. KNX IoT prepends length-prefixed identity fields to the password before passing it to PBKDF2. This differs from standard PBKDF2 usage and is critical for correct implementation.

input = len(pw) || pw || len(idProver) || len(idVerifier)
  • All lengths are 8-byte little-endian unsigned integers.
  • pw = UTF-8 encoding of the password string
  • idProver = empty (length = 0)
  • idVerifier = empty (length = 0)

Pseudocode:

pbkdf2_input = LE64(len(pw)) || pw || LE64(0) || LE64(0)
output       = PBKDF2-HMAC-SHA256(pbkdf2_input, salt, iterations, dkLen=80)
w0s          = output[0..39]     // first 40 bytes
w1s          = output[40..79]    // last 40 bytes

Scalar Derivation

w0 = int(w0s, big-endian) mod N
w1 = int(w1s, big-endian) mod N
L  = w1 · G    (stored on device, compressed point, 33 bytes)

Share Computation

Client:  ShareP = x·G + w0·M     (uncompressed, 65 bytes, prefix 0x04)
Server:  ShareV = y·G + w0·N     (uncompressed, 65 bytes, prefix 0x04)

Where x and y are random scalars in [1, N-1].

Shared Secret Computation

Server side:

Z = y · (ShareP − w0·M)
V = y · L

Client side:

Z = x · (ShareV − w0·N)
V = w1 · (ShareV − w0·N)

Both sides compute the same Z and V values. This core SPAKE2+ property ensures that only someone who knows the password can remove the correct underlying point from the blinded share.

Transcript Hash (TT → K_main)

The transcript hash binds all protocol values into a single SHA-256 digest:

TT = len(Context)||Context
  || len(idProver)||idProver
  || len(idVerifier)||idVerifier
  || len(M)||M
  || len(N)||N
  || len(ShareP)||ShareP
  || len(ShareV)||ShareV
  || len(Z)||Z
  || len(V)||V
  || len(w0)||w0

K_main = SHA-256(TT)

Each entry uses an 8-byte little-endian unsigned integer as a prefix. The || operator denotes concatenation.

  • Context = "knxpase" (7 bytes)
  • idProver, idVerifier = empty (0 bytes each)
  • M, N = uncompressed point bytes (65 bytes each)
  • ShareP, ShareV, Z, V = uncompressed point bytes (65 bytes each)
  • w0 = minimal big-endian byte encoding (no leading zero padding)

Key Derivation from K_main

K_confirmP || K_confirmV = HKDF-SHA256(
    salt = "",
    ikm  = K_main,
    info = "ConfirmationKeys",
    L    = 64 bytes
)
K_confirmP = first 32 bytes
K_confirmV = last 32 bytes

K_shared = HKDF-SHA256(
    salt = "",
    ikm  = K_main,
    info = "SharedKey",
    L    = 16 bytes
)

Confirmation Values

confirmV = HMAC-SHA-256(K_confirmV, ShareP)   → server sends to client
confirmP = HMAC-SHA-256(K_confirmP, ShareV)   → client sends to server

The client verifies confirmV before sending confirmP. This ensures the server knows the password before the client reveals its own confirmation.

PASE CBOR Key Mapping

CBOR Key Field Direction
0 kid (key identifier) Request (step 1)
5 salt (within pbkdf2) Response (step 1)
10 shareP Request (step 2)
11 shareV Response (step 2)
12 pbkdf2 params {5: salt, 16: iterations} Response (step 1)
13 confirmV Response (step 2)
14 confirmP Request (step 3)
15 rnd (32-byte nonce) Both (step 1)
16 iterations (within pbkdf2) Response (step 1)

Test Vectors

These vectors can verify any SPAKE2+ implementation.

PBKDF2 / Scalar Derivation

password:   "LJFYEARVK56STM70"
iterations: 1000
salt:       "aabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccdd"

w0s: "937aa8dbd5d1db6b26a2304c68537e68a5b96570d0ae10c414af5eb333cecd22ae40338ac336fac6"
w1s: "88390042af5c552316569419c985c42218a189d494b38ff9e2796f8b4b7b0c5488b1b80dcc601d9d"
w0:  "fc740bb5ff06fa22cc60e9add391cde0b38baddbc599ecead03366bd53d102a0"
w1:  "c5b2e93b91f06ebd3c55c54c2162b824cbe117de2ea9a852636004672eb97fa8"
L:   "0254fac7ad879c01d15034131d12c01c66cd802274c9301f5b6df75c5d72a5a65f"

Share Computation Vectors

x: "18d51a5e1b8cad2d95a4c3f9e7b3a0f2c6d8e4f1a3b5c7d9e0f2a4b6c8d0e2f4"
y: "2a3b4c5d6e7f8091a2b3c4d5e6f7081920314253647586970a1b2c3d4e5f6071"

ShareP: "04c797ee127bddec5d030c2cbfee1b9e1fca371e841aa8f56c8083048f6e6ae45b
         31b8d8063f6295cad1342c202c755b937b75d69c041e64557317b7e2bcbe4fe2"
ShareV: "04322b44a7b3bcf1d424948c273c11f5cfc882e3cfac1a333602710d7c42feaf5b
         a8a32ec10d5fe3541e3a346122f675e02714f99331a2a092137d3cf68de02d9c"

K_main Derivation

These vectors use independent inputs (different w0, ShareP, ShareV) from the preceding sections and verify the transcript hash and key derivation in isolation.

Z:      "045a7523840b1eaefb99e25a21e9145c5cc7702029a373d7984b6cca8bfbc0f53c
         da0295af096bf1cf8bb6f6ed1b32c650ddf796f6ff4a889c50b3b20f2327aa76"
V:      "048754934d34b2b2e944ef9868b7b588412ee6c14e3dfa61510ccbe4efa19fa18a
         491c6d6537018c1ef07178b7eca2b7157528e12b23f9eccf0d39b7926399b6b0"
ShareP: "047abd3bafbb9a613c8c6bd03c5f1861d2189366b447a5a80ce9ce74bfa0d25ddd
         a63e55d830574796009de2c3caac284af372a214e2fa99ef26d5323852257d82"
ShareV: "044304e7f309e840a23ddebe74bc169618291a83fa7100da87c78fe91dc984f156
         94867a5baa620e568f95dfd30b652fa48272ba1855a2c71d932a3e42e382cd99"
w0:     "20a4c3dee4ad09025425114b70b794784957be31d8e32204d53d9ef447aec0fa"

K_main: "da4a6618f5c52b7e9b92de0fdd63f1473b23454ad4c72849ba93f61bcd6db5a0"

Full Protocol Result

shared_secret: "a4432d14f99d9ccef8cf7965e9cbb920"  # 16 bytes = OSCORE master secret