opcua-client-ext-transport-pubsub · master
Docs · Security

Group-key security

PubSub secures messages with pre-shared group keys, not per-session handshakes. Choose a mode, supply the keys statically or from a Security Key Service.

PubSub has no session handshake — publishers and subscribers in a security group share keys out of band. The subscriber verifies (and decrypts) each NetworkMessage with the current group key.

Modes

PubSubSecurityMode (int-backed enum):

Mode Value What happens
None 1 No signature, no encryption
Sign 2 HMAC-SHA256 signature over the message; verified on receipt
SignAndEncrypt 3 HMAC-SHA256 signature and AES-CTR payload encryption

Signing uses HMAC-SHA256; encryption uses AES-CTR (128- or 256-bit, selected by the encrypting-key length) with a counter block derived from the key nonce. A datagram that fails verification or decryption is dropped and a SecurityValidationFailed event fires.

Wiring it up

Pass a PubSubSecurityOptions to listenUdp() / listenOn():

PubSubSecurityOptions
mode
PubSubSecurityMode required

The security mode above.

keyProvider
?GroupKeyProviderInterface optional

Source of the group keys. Required for Sign / SignAndEncrypt; null for None.

use PhpOpcua\Client\ExtTransportPubSub\Security\PubSubSecurityMode;
use PhpOpcua\Client\ExtTransportPubSub\Security\PubSubSecurityOptions;

$security = new PubSubSecurityOptions(
    mode: PubSubSecurityMode::SignAndEncrypt,
    keyProvider: $keyProvider,
);

SubscriberBuilder::create()
    ->onDataSetMessage($callback)
    ->listenUdp(endpoint: 'opc.udp://239.0.0.1:4840', readers: [$reader], security: $security);

Key providers

A GroupKeyProviderInterface supplies the four pieces the codec needs: signingKey(), encryptingKey(), keyNonce(), tokenId(), plus refresh() to rotate them.

StaticGroupKeyProvider — pre-shared keys

For fixed keys you distribute yourself.

Constructor
signingKey
string required

Raw HMAC-SHA256 signing key.

encryptingKey
string required

Raw AES key (16 bytes for AES-128, 32 for AES-256).

keyNonce
string required

Nonce used to build the AES-CTR counter block.

tokenId
int optional

Security token id. Default 1.

refresh() is a no-op — static keys never change.

Key hygiene

Keys are raw secret bytes. Load them from a secret store or environment, never from source control, and match the encrypting-key length to your group's policy (Aes128 vs Aes256).

SksGroupKeyProvider — live rotation from a Security Key Service

Pulls the current keys from an OPC UA Security Key Service by calling GetSecurityKeys through the classic opcua-client. refresh() re-fetches, so keys rotate without a restart.

Constructor
client
OpcUaClientInterface required

A connected core client used to call the SKS.

securityGroupId
string required

The security group to fetch keys for.

objectNodeId
NodeId|string optional

SKS object node. Default i=14443 (the standard PublishSubscribe object).

methodNodeId
NodeId|string optional

GetSecurityKeys method node. Default i=15215.

securityPolicyUri
string optional

Default SksGroupKeyProvider::POLICY_AES256_CTR (…/SecurityPolicy#PubSub-Aes256-CTR); POLICY_AES128_CTR is also provided.

requestedKeyCount
int optional

How many future keys to request. Default 1.

php SKS-backed keys
use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\ExtTransportPubSub\Security\SksGroupKeyProvider;

$sksClient = ClientBuilder::create()->connect('opc.tcp://sks.plant.local:4840');

$keyProvider = new SksGroupKeyProvider(
    client: $sksClient,
    securityGroupId: 'group-1',
    securityPolicyUri: SksGroupKeyProvider::POLICY_AES256_CTR,
);

See Rotating keys with an SKS for the refresh loop.