DKEK shares and HSM smart cards - SmartCard-HSM perspective

This note describes what a DKEK is, how DKEK shares work for key management on SmartCard-HSM class devices, and how that relates to the Smart Card Shell (scsh) distribution. It is aimed at operators and integrators; it does not replace manufacturer documentation or a formal security analysis. The SmartCard-HSM site is https://www.smartcard-hsm.com/. Smart Card Shell 3 is distributed from https://www.openscdp.org/scsh3/download.html.

Terminology

On SmartCard-HSM devices, DKEK usually denotes Device Key Encryption Key: a 256-bit symmetric key held inside the device that acts as a key-encryption key (KEK) for wrapping sensitive key material when using backup, restore, or migration features.

The same three-letter acronym can appear elsewhere in cryptography with different meanings (for example some standards use KEK hierarchies without this exact name). In this document, DKEK refers to the SmartCard-HSM mechanism unless stated otherwise.

Why a DKEK exists

Many HSMs and smart-card HSMs never release private keys in plaintext. To move a key between devices, or to keep an offline backup, the implementation encrypts the key under a KEK that exists on the card. On SmartCard-HSM, that KEK is the DKEK.

Typical properties:

  • Keys generated on the card can be exported only if a DKEK key domain was configured during initialisation (or equivalent setup), depending on device options and policy.
  • If the device is re-initialised and the DKEK is wiped, encrypted backups that depended on the old DKEK cannot be recovered.
  • The DKEK is not intended to be held as a single secret by one person alone; the design allows splitting the material that builds the DKEK into shares.

What a DKEK share is

A DKEK share is a random 256-bit (32-byte) value. During setup the device is configured to expect a chosen number of shares. Each share is generated inside the device (using the card RNG), then passed to a custodian who stores it, usually as a password-encrypted file or using other formats such as a printable encoding.

The final DKEK is assembled by combining the shares with exclusive-or (XOR): each share is XORed into a running 256-bit value until all shares are imported. Published technical descriptions for SmartCard-HSM state that multiple shares are XORed into a single KEK value inside the key domain.

Important distinction: this is not Shamir secret sharing. With plain XOR composition, you normally need every share to reconstruct the DKEK. Published material also describes optional threshold schemes applied to the password that protects a share file (n-of-m password fragments), which is a separate layer from the XOR combination of random shares.

Roles in key management

  • Key domain: a logical grouping for keys and for the KEK material used to wrap them. Newer devices support multiple key domains; older or simpler profiles may expose a single DKEK domain.
  • Custodians: individuals who receive and protect distinct DKEK shares. Organisational policy decides how many shares to use (common examples in public guides are one to three shares).
  • Key check value (KCV): a short fingerprint used to confirm that the assembled DKEK on a device matches expectations before you rely on backup or import operations.

Off-card handling of shares (files and passwords)

Shares leave the device only in forms that depend on custodian-chosen secrets:

  • Password-based encryption of the share (for example .pbe files in command-line workflows).
  • Optional password threshold splitting, where several people hold fragments of the password needed to decrypt one share.
  • Alternative encodings (for example human-entered formats) aimed at offline storage.

The security of an encrypted share file depends on password strength, resistance to offline guessing, and physical protection of the storage medium.

PKCS#12 import, DKEK shares, and protected card memory (Smart Card Shell Key Manager)

In the Key Manager sources shipped with Smart Card Shell 3 (under keymanager/ in the unpacked distribution), the Key Manager loads PKCS#12 (.p12) files with BouncyCastle (KeyStore("BC", "PKCS12", ...)). The PKCS#12 password only decrypts the container on the host; it is not the DKEK. The shell then has to move private keys into the SmartCard-HSM without sending them in plaintext over the card interface. That step uses the same DKEK-based wrap format as other tooling: the host builds a key blob with DKEK.encodeKey(privateKey, publicKeyFromCertificate), and HSMKeyStore.importRSAKey / importECCKey call unwrapKey on the device so the key material is loaded into protected storage and PKCS#15-style metadata is written. Certificates are stored separately (storeEndEntityCertificate for end-entity certs, storeCACertificate when the P12 entry has no usable private key).

The excerpts below cite concrete paths and line numbers from one unpacked tree; after installation your top-level directory name typically matches the release you downloaded (for example scsh-3.x.y), and line numbers may shift in other releases.

Two implementations exist side by side; both hinge on assembling the same 256-bit DKEK on the host as the card holds when unwrapKey runs.

Shares entered on the host (classic “Import from PKCS#12” in keymanager.js)

The handler importPKCS12 asks how many DKEK shares to apply (often one). For each share, the operator supplies material through the same paths as ordinary DKEK share import (password-protected file, n-of-m password fragments, or PaperKey). Each 32-byte share is XORed into the host DKEK instance. After all shares are combined, the tool opens the PKCS#12 file, extracts the private key and certificate, forms the wrapped blob, and calls importRSAKey or importECCKey followed by certificate storage.

Share assembly and wrapping:

KeyManager.prototype.importPKCS12 = function(node) {
    var str = Dialog.prompt(KeyManager.DKEK_NO_OF_SHARES, "1");
    if (str == null) {
        return;
    }

    var shares = parseInt(str);

    var dkek = new DKEK(this.crypto);
    for (var cnt = 0; cnt < shares; cnt++) {
        var dkekshare = this.inputDKEKShare();
        dkek.importDKEKShare(dkekshare);
        dkekshare.clear();
    }
            if (key != null) {
                print("Importing key and certificate...");

                var pubkey = cert.getPublicKey();
                var blob = dkek.encodeKey(key, pubkey);

                if (pubkey.getComponent(Key.MODULUS)) {
                    hkey = this.ks.importRSAKey(alias, blob, pubkey.getSize());
                    var signalgo = Crypto.RSA_PSS_SHA256;
                } else {
                    hkey = this.ks.importECCKey(alias, blob, pubkey.getSize());
                    var signalgo = Crypto.ECDSA_SHA256;
                }

                this.ks.storeEndEntityCertificate(alias, cert);

                // Test import
                var msg = new ByteString("Hello World", ASCII);

                var signature = hkey.sign(signalgo, msg);

                assert(this.crypto.verify(pubkey, signalgo, msg, signature), "Signature verification of imported key failed");
                print("Import completed");
            } else {
                print("Importing certificate...");

                this.ks.storeCACertificate(alias, cert);

            }
            this.createOutline();
        } while (aliases.length > 1 && Dialog.prompt("Import more keys ?"));

    } while (Dialog.prompt("Import more PKCS#12 files ?"));
    dkek.clear();

For this path to succeed, the SmartCard-HSM must already contain the same DKEK that those shares define. In practice that means the device was initialised for that key domain and all required shares were imported onto the card earlier; the Key Manager session only recombines the shares on the PC so it can encrypt the import blob consistently with the firmware’s unwrap. If the host DKEK and the card DKEK differ, unwrap fails or the import is rejected.

Transient key domain for PKCS#12 (220-importp12-plugin.js)

The plug-in “Import from PKCS#12” uses a different pattern when an empty key domain slot is available: it creates a fresh DKEK key domain that expects a single share, generates one random 32-byte share on the host, imports that share into the card, XORs it in the host DKEK object, performs the same encodeKey / importRSAKey or importECCKey / certificate store sequence, then clears the host state and calls deleteKEK on that key domain identifier.

Key-domain creation, share import, and teardown:

    var kdid = -1;
    do {
        kdid++;
        var kd = sc.queryKeyDomainStatus(kdid);
        if ((kd.sw == 0x6A86) || (kd.sw == 0x6D00)) {
            Dialog.prompt("No empty key domain found.");
            return
        }
    } while (kd.sw != 0x6A88);

    // Create DKEK domain with random DKEK
    sc.createDKEKKeyDomain(kdid, 1);
    var share = crypto.generateRandom(32);
    sc.importKeyShare(kdid, share);

    // Create DKEK encoder and import share
    var dkek = new DKEK(crypto);
    dkek.importDKEKShare(share);
    } while (Dialog.prompt("Import more PKCS#12 files ?"));
    dkek.clear();
    sc.deleteKEK(kdid);

Here the share is created for a one-off import: the card and host agree on a temporary DKEK solely to wrap the PKCS#12 private key for unwrapKey. The plug-in then deletes the key-encryption key in that domain (deleteKEK); the imported asymmetric keys remain in the device’s normal key store, while the short-lived DKEK used for the transfer is not left in place for backup or migration unless you also use a longer-lived key-domain configuration elsewhere.

How many shares appear in practice

  • One share is the minimal case: a single random or custodian-held 32-byte value XORed into the DKEK.
  • Multiple shares mean the loop runs several times on the host (importDKEKShare for each); the card must have received the same number of shares during its own setup so the XOR result matches.

Operational distinction worth preserving

  • PKCS#12 password: unwraps the file on the workstation only; choose it independently of DKEK share passwords.
  • DKEK shares: define the KEK the card uses to accept wrapped private-key blobs; organisational controls (files, PaperKey, n-of-m on a share password) apply here, not to the PKCS#12 file’s own password.

Relationship to the Smart Card Shell installer

Smart Card Shell 3 is distributed as an IzPack-based, self-contained JAR (alongside zip archives) from OpenSCDP / CardContact. The downloadable artefact name follows the release, for example scsh-3.x.y-installer.jar. Current installers and archives are published at https://www.openscdp.org/scsh3/download.html. The JAR packs a large core payload (resources/packs/pack-Core) that contains JavaScript modules for card tooling.

Among those modules is a DKEK helper used with SmartCard-HSM:

  • Module path in the bundle: scsh/sc-hsm/DKEK (see require("scsh/sc-hsm/DKEK") inside the SmartCard-HSM support code).
  • It implements host-side operations that correspond to command-line tools for manipulating DKEK shares outside the card.

What the bundled DKEK helper does (implementation-level)

The following points summarise behaviour visible in the embedded DKEK.js source inside pack-Core. They describe the host library, not necessarily every firmware detail inside the chip.

  1. Internal state
    The helper keeps a 32-byte value initialised to zero. Importing a share XORs it into that value, matching the XOR assembly model.

  2. Key check value
    The KCV is derived as the first eight bytes of SHA-256 over the 32-byte DKEK value. This lets operators compare devices without revealing the full key.

  3. Wrapping keys under the DKEK
    From the 32-byte DKEK, two AES keys are derived with SHA-256 and distinct domain separation constants (labels 00000001 and 00000002 in hex) for encryption and integrity (CMAC-style use appears in the wrapping code paths). This matches the documented pattern that the DKEK seeds separate encryption and integrity keys for protected blobs.

  4. Encrypting a share for storage

    • A random eight-byte salt is generated.
    • A key and IV are produced from the salt and password using a stretching routine based on repeated MD5 processing (the code attempts a single-call MD5 with a very large iteration count, with a slower fallback loop).
    • The plaintext share is padded and encrypted with AES-CBC.
    • The on-disk form begins with the ASCII prefix Salted__ followed by the salt and ciphertext, similar in spirit to common OpenSSL password-based encryption envelopes.
  5. Card protocol surface in the same module family
    The SmartCard-HSM host wrapper issues secure-message APDUs with instruction 0x52 and function variants such as creating a DKEK key domain (P1 = 0x01) and importing a share (P1 = 0x00). Wrap and unwrap operations use other implementation-specific INS codes in the same source. Exact allowable parameters depend on firmware and initialisation state.

Practical guidance (non-exhaustive)

  • Decide how many shares you need based on organisational control, not on convenience alone. More shares increase operational friction; fewer shares increase concentration of risk.
  • Treat encrypted share files as highly sensitive; offline guessing attacks are a real concern if files leak.
  • Record KCVs and key-domain identifiers as part of your key-management records so restores can be validated.
  • Distinguish DKEK key domains from XKEK-style domains on devices that support both: XKEK domains use different agreement mechanisms (documented authenticated ECDH-based setups), whereas DKEK domains are built from imported shares as described here.

References and further reading

Limitations of this note

Firmware evolves; APDU layouts, maximum numbers of shares, and domain features depend on device generation and version. Always verify behaviour against the documentation for your exact device and software stack. This document does not analyse side-channel resistance, PKCS#11 policy, or organisational compliance requirements.

Random window resizing on MSI MS-158K laptops: a Rocky Linux fix

1. Problem

On MSI gaming laptops built on the MS-158K motherboard and running Rocky Linux 9 or 10, all open windows shrink briefly at random intervals and then snap back to their original size. There is no obvious trigger and the timing is irregular. The board carries two AMD GPUs: a Radeon Vega (Cezanne) integrated GPU at 0000:07:00.0 which drives the built-in screen, and a discrete Radeon RX 5500M (Navi 14) at 0000:03:00.0 which has no connected display output. The only trace left in the kernel log is:

[19809.613998] [drm] kiq ring mec 2 pipe 1 q 0
[19809.616987] amdgpu 0000:03:00.0: [drm] Cannot find any crtc or sizes
[19809.617003] amdgpu 0000:03:00.0: amdgpu: ring gfx_0.0.0 uses VM inv eng 0 on hub 0
... (full ring reinitialization output)
[19809.620157] amdgpu 0000:03:00.0: [drm] Cannot find any crtc or sizes

2. Diagnosis

Step 1: Identify which GPU owns the display

The MS-158K board carries two AMD GPUs: a Radeon RX 5500M discrete GPU and a Radeon Vega integrated GPU. Check which DRM card has a connected output:

for card in /sys/class/drm/card*/; do
  echo "$card: $(cat ${card}*/status 2>/dev/null | sort -u)"
done

The output should look like this:

/sys/class/drm/card0/: disconnected   ← Radeon RX 5500M, PCI 0000:03:00.0, no display
/sys/class/drm/card1/: connected      ← Radeon Vega, PCI 0000:07:00.0, eDP-1 built-in screen

card0 is the Radeon RX 5500M with no connected output. card1 is the Radeon Vega driving the laptop's built-in screen.

Step 2: Correlate the log pattern

Expanding the dmesg context around each occurrence of the log message reveals a consistent pattern appearing immediately before every window disruption:

sudo dmesg | grep -B 20 "kiq ring mec"

Every occurrence follows this sequence:

[  55.515940] pci_bus 0000:03: Allocating resources        ← dGPU waking from suspend
[  55.515971] amdgpu 0000:03:00.0: amdgpu: PSP is resuming...
[  55.753346] amdgpu 0000:03:00.0: amdgpu: SMU is resumed successfully!
[  55.754555] [drm] kiq ring mec 2 pipe 1 q 0              ← full GPU reinit
[  55.757627] amdgpu 0000:03:00.0: [drm] Cannot find any crtc or sizes
...

The line pci_bus 0000:03: Allocating resources appears immediately before every resume cycle, confirming that the kernel PCI bus is repeatedly reallocating resources as the dGPU wakes from the D3 (powered-off) power state.

Step 3: Confirm the power management state

cat /sys/bus/pci/devices/0000:03:00.0/power/control
cat /sys/bus/pci/devices/0000:03:00.0/power/runtime_status

This confirms the dGPU is under auto runtime power management, causing it to cycle between suspended and active states repeatedly.

3. Root cause

The Radeon RX 5500M at PCI address 0000:03:00.0 is repeatedly suspended and resumed by the kernel's PCI runtime power management. Each time it resumes, the kernel fires a DRM hotplug event. The Wayland compositor (GNOME Shell) reacts to that hotplug event by briefly reconfiguring the display state — which causes all windows to shrink momentarily and then restore once the compositor determines that no display change has actually taken place.

The Cannot find any crtc or sizes message is a consequence, not the cause: the Radeon RX 5500M has no connected display outputs on the MS-158K, so every time the driver queries for CRTCs after resuming, it finds none.

4. Solution

Stop the Radeon RX 5500M from entering runtime suspend by setting its power control to on.

Step 1: Test immediately (no reboot required)

echo "on" > /sys/bus/pci/devices/0000:03:00.0/power/control

If the window shrinking stops after running this command, the diagnosis is confirmed.

Step 2: Make the fix permanent via udev

A udev rule ensures the setting is applied automatically on every boot. Using the vendor ID (0x1002, AMD) and device ID (0x7340, Navi 14) is more reliable than matching on the PCI address alone, as the address can vary between kernels and firmware revisions:

cat > /etc/udev/rules.d/99-dgpu-pm.rules << 'EOF'
SUBSYSTEM=="pci", ATTR{vendor}=="0x1002", ATTR{device}=="0x7340", ATTR{power/control}="on"
EOF

Apply the rule without rebooting:

udevadm control --reload-rules
udevadm trigger

Step 3: Verify

cat /sys/bus/pci/devices/0000:03:00.0/power/control

Expected output:

on

Watch dmesg to confirm the resume cycles have stopped:

sudo dmesg -w | grep -E "Allocating resources|PSP is resuming|kiq ring"

No further output should appear once the fix is in place.

5. Notes

  • This issue has been observed on MSI gaming laptops based on the MS-158K motherboard running Rocky Linux 9 and 10. The board pairs a Radeon Vega (Cezanne) integrated GPU with a Radeon RX 5500M (Navi 14) discrete GPU. The discrete GPU has no connected display output and is left under automatic PCI power management by default, which triggers the problem.
  • The udev rule matches on the AMD vendor ID (0x1002) and the Navi 14 device ID (0x7340). This is more dependable than matching on the PCI address 0000:03:00.0, which could in principle shift between firmware updates. To find the correct IDs on another machine, run lspci -nn | grep -i vga and read the [vendor:device] values from the output.
  • Setting power control to on stops the Radeon RX 5500M from sleeping, which raises idle power consumption slightly and may reduce battery life a little. If this is a concern and the GPU is never used for rendering, blacklisting the amdgpu module for that device is an alternative — though this requires some care to avoid affecting the Radeon Vega.
  • If tlp or power-profiles-daemon is installed, it may override the udev rule after boot. Check with systemctl status tlp power-profiles-daemon. Should overriding occur, a systemd service with After=tlp.service (or the relevant daemon) can re-apply the setting, following the same approach described in the Bluetooth dongle fix post.
  • The symptom — all windows shrinking briefly then snapping back — is easy to mistake for a window manager fault, a compositor bug, or a monitoring tool accidentally resizing windows. The key diagnostic clue is the pci_bus: Allocating resources line in dmesg appearing immediately before each window disruption.

A practical guide to fixing Bluetooth mouse freezes caused by USB power management on Linux

1. Problem

A Bluetooth mouse connected via a USB dongle was freezing for 1-2 seconds intermittently. The cause was Linux USB power management suspending the Bluetooth dongle to save power.

2. Solution

Disable USB autosuspend for the Bluetooth dongle via a udev rule.

Step 1: Identify the Bluetooth Dongle

lsusb | grep -i bluetooth

In our case:

Bus 002 Device 006: ID 0bda:a728 Realtek Semiconductor Corp. Bluetooth Radio

Step 2: Verify udev Attribute Names

udevadm info -a /sys/bus/usb/devices/2-13/ | grep -E "idVendor|idProduct"

This confirmed the device uses ATTR (not ATTRS) for its own attributes.

Step 3: Create the udev Rule

cat > /etc/udev/rules.d/99-bluetooth-dongle.rules << EOF
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0bda", ATTR{idProduct}=="a728", TEST=="power/control", ATTR{power/control}="on"
EOF

Step 4: Apply the Rule

udevadm control --reload-rules
udevadm trigger

Step 5: Verify

for dev in /sys/bus/usb/devices/*/; do
    name=$(cat "$dev/product" 2>/dev/null || echo "unknown")
    control=$(cat "$dev/power/control" 2>/dev/null)
    echo "$name  control=$control"
done | grep -i bluetooth

Expected output:

Bluetooth Radio  control=on

3. Troubleshooting: udev Rule Overridden by Power Manager

If the mouse continues to freeze after applying the udev rule, a power management service may be resetting the USB power control back to auto. Check for running power management daemons:

systemctl status tlp
systemctl status power-profiles-daemon
systemctl status tuned

In our case power-profiles-daemon was active and overriding the udev rule. The fix is to create a systemd service that enforces the setting after the daemon starts:

cat > /etc/systemd/system/usb-bluetooth-power.service << 'EOF'
[Unit]
Description=Disable USB autosuspend for Bluetooth dongle
After=power-profiles-daemon.service

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo on > /sys/bus/usb/devices/2-13/power/control'
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now usb-bluetooth-power.service

Verify the service is active and the setting is correct:

systemctl status usb-bluetooth-power.service
cat /sys/bus/usb/devices/2-13/power/control

Expected output: service active (exited) and control=on.

4. Further Fix: Enforcing the Setting with a systemd Timer

If the mouse continues to freeze, power-profiles-daemon is periodically resetting the power control back to auto after boot. A oneshot service only runs once and cannot counter this. The solution is a systemd timer that re-enforces the setting every 30 seconds:

cat > /etc/systemd/system/usb-bluetooth-power.service << 'EOF'
[Unit]
Description=Disable USB autosuspend for Bluetooth dongle

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo on > /sys/bus/usb/devices/2-13/power/control'
EOF

cat > /etc/systemd/system/usb-bluetooth-power.timer << 'EOF'
[Unit]
Description=Enforce USB autosuspend off for Bluetooth dongle

[Timer]
OnBootSec=10
OnUnitActiveSec=30s

[Install]
WantedBy=timers.target
EOF

systemctl daemon-reload
systemctl enable --now usb-bluetooth-power.timer

Verify the timer is running:

systemctl status usb-bluetooth-power.timer

5. Notes

  • Always verify the correct udev attribute style with udevadm info -a first — using ATTRS instead of ATTR is a common mistake that prevents the rule matching.
  • Power management daemons such as power-profiles-daemon and tlp can override udev rules — use the systemd service workaround if this occurs.
  • The device path /sys/bus/usb/devices/2-13/ may differ on your system — verify it with the vendor/product ID loop shown in the verification step above.
  • The same approach applies to any USB device suffering from autosuspend issues.
Creative Commons - Attribution 2.5 Generic. Powered by Blogger.

DKEK shares and HSM smart cards - SmartCard-HSM perspective

Terminology Why a DKEK exists What a DKEK share is Roles in key management Off-card handling of shares (files and password...

Search This Blog

Translate