YubiKey Authentication
Yubico OTP validation with Radiator - cloud and offline paths
- What is YubiKey?
- Cloud Validation (yubikey HTTP backend)
- Offline Validation (yubikey action)
- Choosing a Validation Backend
- Cloud Validation Configuration
- Backend Configuration
- Choosing a timeout value
- AAA Policy
- Using a Self-Hosted Validation Server
- Offline Validation Configuration
- Backend Schema
- Backend Configuration
- AAA Policy
- Password + OTP (Two-Factor)
- Related Documentation
What is YubiKey?
A YubiKey is a hardware security token manufactured by Yubico. When the user presses the button, the token generates a one-time password (OTP) and types it as keyboard input. Each OTP is unique and expires after a single use, which prevents replay attacks even if the OTP is intercepted in transit.
The Yubico OTP format is a 44-character modhex-encoded string. Modhex is Yubico's custom encoding for hexadecimal using just letters. The first 12 characters are the public UID — a stable identifier for the token. The remaining 32 characters are AES-128 encrypted payload that contains:
- A 6-byte private UID (secret identity check)
- A 16-bit usage counter (power-on count)
- A 24-bit timestamp
- An 8-bit session counter (button-press count within the current session)
- A CRC16 checksum
Radiator supports two validation paths explained below.
Cloud Validation (yubikey HTTP backend)
The yubikey HTTP backend forwards the OTP to an external Yubico KSM-compatible validation server (such as api.yubico.com). Radiator constructs a signed HMAC-SHA1 request, sends it over HTTPS, and verifies the HMAC-signed response.
Flow:
- User sends RADIUS Access-Request with the OTP as the PAP password.
- Radiator sends a signed GET request to the validation server.
- The server verifies the OTP, checks its replay database, and returns a signed response.
- Radiator verifies the response HMAC and maps
OKto accept.
This path requires no local key storage and offloads replay tracking to the Yubico cloud infrastructure.
Offline Validation (yubikey action)
The yubikey pipeline action decrypts and validates the OTP locally inside Radiator without any network call. This requires the AES-128 key for the token to be stored in a Radiator-accessible backend (database, file, etc.).
Flow:
- User sends RADIUS Access-Request with the OTP as the PAP password.
- A backend query loads
yubikey.secret(and optionally public UID and replay counters) into the authentication context. - The
yubikeyaction decrypts the OTP, verifies CRC16, checks the private UID, and compares usage and session counters against stored values. - On success, updated counters are persisted to the backend.
This path gives the fastest response time and works without internet access.
Choosing a Validation Backend
For production deployments, use the Yubico cloud service (api.yubico.com) or a self-hosted YubiKey validation server (YK-VAL + YK-KSM) as the validation backend. These provide token lifecycle management — provisioning, revocation, and key rotation — that Radiator does not include.
Radiator's offline yubikey action performs OTP decryption and replay counter checks but does not provide tools for provisioning tokens, revoking compromised keys, or managing token expiration. It is suitable for small deployments and testing where keys are managed manually.
| Deployment | Recommended backend | Notes |
|---|---|---|
| Production | Yubico cloud or self-hosted YK-VAL/YK-KSM | Full lifecycle management |
| Small / lab | Offline yubikey action with local database | Manual key management |
Cloud Validation Configuration
Backend Configuration
backends {
yubikey "YUBIKEY_CLOUD" {
url "https://api.yubico.com/wsapi/2.0/verify";
username "12345"; # Yubico client ID
secret "base64encodedsecret="; # Yubico API key (base64)
timeout 5s;
# connections 64; # optional, default 1024
}
}
| Field | Required | Description |
|---|---|---|
url | Yes | Base URL of the validation server |
username | Yes | Client ID issued by Yubico (or your own KSM) |
secret | Yes | Base64-encoded API key for HMAC request signing |
timeout | Yes | Request timeout (e.g. 5s, 500ms) |
connections | No | Max concurrent HTTP connections (default: 1024) |
tls | No | Custom TLS configuration block |
Choosing a timeout value
Cloud validation goes over the public internet and is subject to variable latency. If the Yubico server does not respond within the configured timeout, Radiator treats the request as a backend error and the pipeline stops. Wrap the backend call in a try action to handle timeout errors explicitly:
try {
backend {
name "YUBIKEY_CLOUD";
}
} catch {
reject "YubiKey cloud validation failed: %{aaa.caught_error}";
}
Recommended starting values:
- Yubico cloud (
api.yubico.com):5s— allows for typical internet round-trips and brief Yubico API delays. - Self-hosted KSM on the same LAN:
1s–2s— lower latency expected; a tighter timeout detects hung servers faster.
AAA Policy
No backend query is needed before calling the HTTP backend. The OTP is extracted directly from the PAP response.
aaa {
policy "DEFAULT" {
handler "AUTHENTICATION" {
conditions all {
radius.request.code == radius.ACCESS_REQUEST;
}
@execute {
backend {
name "YUBIKEY_CLOUD";
}
}
}
}
}
Using a Self-Hosted Validation Server
Replace the Yubico cloud URL with the address of a self-hosted KSM-compatible server (such as YK-VAL or a compatible implementation):
backends {
yubikey "YUBIKEY_INTERNAL" {
url "https://ykval.internal.example.com/wsapi/2.0/verify";
username "1";
secret "base64encodedkey=";
timeout 3s;
tls {
ca "INTERNAL_CA";
}
}
}
Offline Validation Configuration
Note: Offline validation is intended for testing and small deployments. See Choosing a Validation Backend for production recommendations.
Backend Schema
Store one row per token. The aes_key, usage_counter, and session_counter columns are required — the counter columns provide replay protection. Replay protection works the same way as for TOTP/HOTP: counters are loaded from the database at request time, checked against the OTP, and written back on success. There is no separate in-memory replay cache.
CREATE TABLE yubikeys (
username TEXT NOT NULL,
public_uid TEXT, -- 12-character modhex, e.g. "vvccccriigjn"
aes_key BLOB NOT NULL, -- 16-byte binary AES-128 key (store as BLOB, not hex string)
usage_counter INTEGER NOT NULL DEFAULT 0,
session_counter INTEGER NOT NULL DEFAULT 0
);
Important:
aes_keymust be stored as a raw 16-byteBLOB, not as a hex text string. Use a SQL hex literal when inserting:X'9fafa61d1ccafd37a33d7a3703356cd5'.
Backend Configuration
backends {
sqlite "USERS" {
filename "users.db";
query "FIND_USER" {
statement "SELECT aes_key, public_uid, usage_counter, session_counter FROM yubikeys WHERE username = ?";
bindings {
aaa.identity;
}
mapping {
yubikey.secret = aes_key;
yubikey.public = public_uid;
yubikey.counter = usage_counter;
yubikey.session = session_counter;
}
}
statement "UPDATE_COUNTERS" {
statement "UPDATE yubikeys SET usage_counter = ?, session_counter = ? WHERE username = ?";
bindings {
yubikey.counter;
yubikey.session;
aaa.identity;
}
}
}
}
AAA Policy
aaa {
policy "DEFAULT" {
handler "AUTHENTICATION" {
conditions all {
radius.request.code == radius.ACCESS_REQUEST;
}
@execute {
backend {
name "USERS";
query "FIND_USER";
}
yubikey;
backend {
name "USERS";
query "UPDATE_COUNTERS";
}
}
}
}
}
Password + OTP (Two-Factor)
YubiKey OTP is typically used as the second factor. Users enter their static password and OTP concatenated in the PAP password field, such as mypassword<otp>. The range parameter on the pap and yubikey actions splits the field:
@execute {
backend {
name "USERS";
query "FIND_USER";
}
# Validate static password (everything except the last 44 characters)
pap {
range -44 0 exclusive;
}
# Validate YubiKey OTP (last 44 characters)
yubikey {
range -44 0;
}
backend {
name "USERS";
query "UPDATE_COUNTERS";
}
}
The FIND_USER query must also populate user.password for pap to work:
SELECT password, aes_key, public_uid, usage_counter, session_counter
FROM yubikeys JOIN users USING (username)
WHERE username = ?
Related Documentation
yubikeyaction - Offline validation action referenceyubikeycontext variables - Variables read and written during offline validation- Duo, YubiKey, and RSA-AM backends - HTTP backend reference
papaction - Combine withyubikeyfor password + OTP
- What is YubiKey?
- Cloud Validation (yubikey HTTP backend)
- Offline Validation (yubikey action)
- Choosing a Validation Backend
- Cloud Validation Configuration
- Backend Configuration
- Choosing a timeout value
- AAA Policy
- Using a Self-Hosted Validation Server
- Offline Validation Configuration
- Backend Schema
- Backend Configuration
- AAA Policy
- Password + OTP (Two-Factor)
- Related Documentation
About Radiator software development security
Architecture Overview
Backend Load Balancing
Basic Installation
Comparison Operators
Configuration Editor
Configuration Import and Export
Data Types
Duration Units
Execution Context
Execution Pipelines
Filters
Getting a Radiator License
Health check /live and /ready
High Availability and Load Balancing
High availability identifiers
HTTP Basic Authentication
Introduction
Linux systemd support
Local AAA Backends
Log storage and formatting
Management API privilege levels
Namespaces
Password Hashing
Pipeline Directives
Probabilistic Sampling
Prometheus scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Template Rendering CLI
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables
About Radiator software development security
Architecture Overview
Backend Load Balancing
Basic Installation
Comparison Operators
Configuration Editor
Configuration Import and Export
Data Types
Duration Units
Execution Context
Execution Pipelines
Filters
Getting a Radiator License
Health check /live and /ready
High Availability and Load Balancing
High availability identifiers
HTTP Basic Authentication
Introduction
Linux systemd support
Local AAA Backends
Log storage and formatting
Management API privilege levels
Namespaces
Password Hashing
Pipeline Directives
Probabilistic Sampling
Prometheus scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Template Rendering CLI
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables