Security
Getting Started
Introduction ConnectionCore
Browsing Reading-writing Method-call Subscriptions History-readReference
Types Error-handling Security Architecture Extension-object-codecs Testing Events Trust-storeSecurity
Security Policies
Each policy defines the algorithms used for encryption and signing:
RSA Policies
| Policy | Asymmetric Sign | Asymmetric Encrypt | Symmetric Sign | Symmetric Encrypt | Min Key |
|---|---|---|---|---|---|
| None | -- | -- | -- | -- | -- |
| Basic128Rsa15 | RSA-SHA1 | RSA-PKCS1-v1_5 | HMAC-SHA1 | AES-128-CBC | 1024 bit |
| Basic256 | RSA-SHA1 | RSA-OAEP | HMAC-SHA1 | AES-256-CBC | 1024 bit |
| Basic256Sha256 | RSA-SHA256 | RSA-OAEP | HMAC-SHA256 | AES-256-CBC | 2048 bit |
| Aes128Sha256RsaOaep | RSA-SHA256 | RSA-OAEP | HMAC-SHA256 | AES-128-CBC | 2048 bit |
| Aes256Sha256RsaPss | RSA-PSS-SHA256 | RSA-OAEP-SHA256 | HMAC-SHA256 | AES-256-CBC | 2048 bit |
ECC Policies
| Policy | Asymmetric Sign | Key Agreement | Symmetric Sign | Symmetric Encrypt | Curve |
|---|---|---|---|---|---|
| EccNistP256 | ECDSA-SHA256 | ECDH P-256 | HMAC-SHA256 | AES-128-CBC | prime256v1 |
| EccNistP384 | ECDSA-SHA384 | ECDH P-384 | HMAC-SHA384 | AES-256-CBC | secp384r1 |
| EccBrainpoolP256r1 | ECDSA-SHA256 | ECDH BP-256 | HMAC-SHA256 | AES-128-CBC | brainpoolP256r1 |
| EccBrainpoolP384r1 | ECDSA-SHA384 | ECDH BP-384 | HMAC-SHA384 | AES-256-CBC | brainpoolP384r1 |
ECC policies use ECDH key agreement instead of RSA encryption. The OpenSecureChannel message is sign-only (no asymmetric encryption). Symmetric keys are derived via HKDF instead of P_SHA.
The Brainpool curves are the European alternative to NIST curves. They provide equivalent security levels but with curve parameters generated in a verifiable way ("nothing-up-my-sleeve"). Required by BSI TR-03116 and other European regulations. The protocol is identical to NIST — only the underlying curve differs.
Tip: For new deployments, use
Basic256Sha256,Aes256Sha256RsaPss, or any ECC policy for modern security. Choose NIST curves for maximum interoperability or Brainpool curves for European regulatory compliance. The older policies (Basic128Rsa15,Basic256) exist for legacy server compatibility.
ECC support disclaimer: The ECC security policies (ECC_nistP256, ECC_nistP384, ECC_brainpoolP256r1, ECC_brainpoolP384r1) are implemented following the OPC UA 1.05.3 specification, but should be considered experimental. The implementation is aligned with 1.05.4 regarding
ReceiverCertificateThumbprintand HKDF salt encoding. Two ECC-specific changes from 1.05.4 (per-message IV derivation and LegacySequenceNumbers) are not yet implemented. See the ECC 1.05.4 Compliance section in the roadmap for a detailed technical analysis of each point, its impact, and the planned fix.As of today, no commercial OPC UA server vendor — not Siemens, not Beckhoff, not Kepware, not any other — has released firmware or hardware with ECC OPC UA endpoints. This is not a limitation of this library: it is the current reality of the OPC UA ecosystem. The specification defines ECC support, but the industrial market has not yet adopted it in production devices.
The ECC implementation in this library has been developed and tested exclusively against UA-.NETStandard, the OPC Foundation's reference implementation, used as the counterpart for integration testing. It has not been validated against any physical industrial device or commercial server product.
If you are evaluating ECC for a production deployment, be aware that you will likely need a UA-.NETStandard-based server (or a custom server built on it) as the only available counterpart. The RSA policies are the proven, battle-tested choice for real-world industrial deployments today.
Certificate Setup
RSA Certificates
# 1. Create a CA
openssl genpkey -algorithm RSA -out ca.key -pkeyopt rsa_keygen_bits:2048
openssl req -x509 -new -key ca.key -days 365 -out ca.pem \
-subj "/CN=Test CA"
# 2. Create a client certificate signed by the CA
openssl genpkey -algorithm RSA -out client.key -pkeyopt rsa_keygen_bits:2048
openssl req -new -key client.key -out client.csr \
-subj "/CN=OPC UA Client" \
-addext "subjectAltName=URI:urn:opcua-client:client"
openssl x509 -req -in client.csr -CA ca.pem -CAkey ca.key \
-CAcreateserial -days 365 -out client.pem \
-copy_extensions copyECC Certificates
# P-256 client certificate
openssl ecparam -name prime256v1 -genkey -noout -out client-ecc.key
openssl req -new -key client-ecc.key -out client-ecc.csr \
-subj "/CN=OPC UA ECC Client" \
-addext "subjectAltName=URI:urn:opcua-client:client"
openssl x509 -req -in client-ecc.csr -CA ca.pem -CAkey ca.key \
-CAcreateserial -days 365 -out client-ecc.pem \
-copy_extensions copyNote: The
subjectAltNameURI is required by OPC UA. It must match the application URI your server expects.
Client Configuration
RSA
use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\Security\SecurityPolicy;
use PhpOpcua\Client\Security\SecurityMode;
$client = ClientBuilder::create()
->setSecurityPolicy(SecurityPolicy::Basic256Sha256)
->setSecurityMode(SecurityMode::SignAndEncrypt)
->setClientCertificate(
'/path/to/client.pem', // PEM or DER, auto-detected
'/path/to/client.key',
'/path/to/ca.pem' // optional: appended to the certificate chain
)
->connect('opc.tcp://server:4840');ECC
$client = ClientBuilder::create()
->setSecurityPolicy(SecurityPolicy::EccNistP256)
->setSecurityMode(SecurityMode::SignAndEncrypt)
->setClientCertificate(
'/path/to/client-ecc.pem',
'/path/to/client-ecc.key',
)
->connect('opc.tcp://server:4848');If you skip setClientCertificate(), the library auto-generates a self-signed certificate in memory:
- RSA policies: RSA 2048-bit certificate
- ECC policies: ECC certificate matching the security policy curve (P-256, P-384, brainpoolP256r1, or brainpoolP384r1)
Warning: Auto-generated certificates are ephemeral. Every new
Clientinstance gets a different certificate. For production, always provide your own.
ECC with Username/Password
$client = ClientBuilder::create()
->setSecurityPolicy(SecurityPolicy::EccNistP256)
->setSecurityMode(SecurityMode::SignAndEncrypt)
->setUserCredentials('admin', 'admin123')
->connect('opc.tcp://server:4848');For ECC policies, the password is encrypted using the EccEncryptedSecret protocol (ECDH key agreement + AES encryption). The client automatically:
- Requests an ephemeral ECDH key from the server via
AdditionalHeaderin CreateSession - Generates its own ephemeral ECDH keypair
- Derives an AES encryption key from the ECDH shared secret
- Encrypts the password and signs the blob with ECDSA
CertificateManager API
Utilities for loading and inspecting X.509 certificates:
use PhpOpcua\Client\Security\CertificateManager;
$cm = new CertificateManager();
// Load certificates -- PEM and DER both work
$derBytes = $cm->loadCertificatePem('/path/to/cert.pem');
$derBytes = $cm->loadCertificateDer('/path/to/cert.der');
// Load a private key
$privateKey = $cm->loadPrivateKeyPem('/path/to/key.pem');
// Inspect
$thumbprint = $cm->getThumbprint($derBytes); // SHA1 hash (binary)
$keyLength = $cm->getPublicKeyLength($derBytes); // bytes (256 = 2048-bit key)
$publicKey = $cm->getPublicKeyFromCert($derBytes); // OpenSSLAsymmetricKey
$appUri = $cm->getApplicationUri($derBytes); // from SAN extension
$keyType = $cm->getKeyType($derBytes); // OPENSSL_KEYTYPE_RSA or OPENSSL_KEYTYPE_EC
// Generate self-signed certificates
$rsa = $cm->generateSelfSignedCertificate('urn:my-app'); // RSA 2048
$ecc = $cm->generateSelfSignedCertificate('urn:my-app', 'prime256v1'); // ECC P-256
$bp = $cm->generateSelfSignedCertificate('urn:my-app', 'brainpoolP256r1'); // ECC Brainpool P-256
// Returns: ['certDer' => string, 'privateKey' => OpenSSLAsymmetricKey]MessageSecurity API
Low-level cryptographic operations. You rarely need these directly -- the SecureChannel handles them -- but they are available:
use PhpOpcua\Client\Security\MessageSecurity;
$ms = new MessageSecurity();
// Asymmetric (RSA or ECDSA -- auto-detected from key type)
$signature = $ms->asymmetricSign($data, $privateKey, $policy);
$valid = $ms->asymmetricVerify($data, $signature, $derCert, $policy);
$encrypted = $ms->asymmetricEncrypt($data, $derCert, $policy); // RSA only
$decrypted = $ms->asymmetricDecrypt($data, $privateKey, $policy); // RSA only
// Symmetric (AES + HMAC)
$signature = $ms->symmetricSign($data, $signingKey, $policy);
$valid = $ms->symmetricVerify($data, $signature, $signingKey, $policy);
$encrypted = $ms->symmetricEncrypt($data, $encKey, $iv, $policy);
$decrypted = $ms->symmetricDecrypt($data, $encKey, $iv, $policy);
// Key derivation
$keys = $ms->deriveKeys($secret, $seed, $policy); // P_SHA1/P_SHA256 (RSA)
$keys = $ms->deriveKeysHkdf($ikm, $salt, $info, $policy); // HKDF (ECC)
// Returns: ['signingKey' => ..., 'encryptingKey' => ..., 'iv' => ...]
// ECC-specific operations
$shared = $ms->computeEcdhSharedSecret($privateKey, $publicKey); // ECDH
$pair = $ms->generateEphemeralKeyPair('prime256v1'); // Ephemeral EC keypair
$pubKey = $ms->loadEcPublicKeyFromBytes($uncompressedPoint, 'prime256v1'); // Load from X+Y
$raw = $ms->ecdsaDerToRaw($derSignature, 32); // DER -> raw R||S
$der = $ms->ecdsaRawToDer($rawSignature, 32); // raw R||S -> DERConnection Flow
RSA
Client Server
| |
|--- HEL ---------------------->| TCP handshake
|<-- ACK -----------------------|
| |
|--- OPN (asymmetric) --------->| Encrypted with server's RSA public key
|<-- OPN response --------------| Contains server nonce
| |
| [derive symmetric keys |
| via P_SHA from nonces] |
| |
|--- MSG (symmetric) ---------->| AES encrypted, HMAC signed
|<-- MSG (symmetric) ----------|ECC
Client Server
| |
|--- HEL ---------------------->| TCP handshake
|<-- ACK -----------------------|
| |
|--- OPN (sign-only) ---------->| ECDSA signed, NOT encrypted
|<-- OPN response (signed) ----| Contains server ephemeral key
| |
| [ECDH key agreement |
| + HKDF key derivation] |
| |
|--- MSG (symmetric) ---------->| AES encrypted, HMAC signed
|<-- MSG (symmetric) ----------|Phase 1 -- Discovery. The client connects without security, calls GetEndpoints, and retrieves the server's certificate. For ECC endpoints, the server certificate uses an ECC key.
Phase 2 -- Asymmetric (OpenSecureChannel).
- RSA: The client sends an OPN request encrypted with the server's RSA public key.
- ECC: The OPN request is sign-only (ECDSA). No asymmetric encryption. The client nonce contains the ephemeral ECDH public key (X+Y coordinates, 64 bytes for P-256, 96 for P-384).
Phase 3 -- Key derivation.
- RSA: Symmetric keys derived via P_SHA256(serverNonce, clientNonce).
- ECC: Symmetric keys derived via HKDF from the ECDH shared secret. The salt includes the label ("opcua-client"/"opcua-server") and both nonces. The salt key length in the uint16 prefix depends on the security mode.
Phase 4 -- Symmetric (Messages). All MSG and CLO messages use the derived symmetric keys. Messages are signed with HMAC and encrypted with AES-CBC. This phase is identical for RSA and ECC policies.
The SecureChannel class manages this entire lifecycle: asymmetric key exchange, symmetric key derivation, message signing/encryption/padding, sequence number tracking, and token/channel ID management.
Events:
SecureChannelOpenedis dispatched after the secure channel is established (with channelId, securityPolicy, and securityMode).SecureChannelClosedis dispatched before the channel is closed. See Events.