cXML SharedSecret HMAC — How It Actually Works
Most cXML PunchOut implementations get SharedSecret authentication wrong. Sometimes it's a leading whitespace. Sometimes it's a case mismatch. Sometimes it's a fundamental misunderstanding of what SharedSecret actually is. This guide explains the real mechanics — and the security pitfalls you need to avoid.
What SharedSecret Actually Is
SharedSecret in cXML is, despite the technical-sounding name, just a string that both the buyer and supplier agree on in advance. It's essentially a password — but used differently than typical password authentication.
SharedSecret is not encrypted, not hashed at rest, not exchanged via key agreement. It's just a string that proves both parties know the same secret.
How It Appears in cXML
When a buyer's procurement system sends a PunchOutSetupRequest, the SharedSecret appears in the Sender credentials block:
<Sender>
<Credential domain="NetworkID">
<Identity>buyer_network_id</Identity>
<SharedSecret>Y0urSh@redSecret123</SharedSecret>
</Credential>
<UserAgent>Oracle Fusion iProcurement</UserAgent>
</Sender>
The supplier's endpoint extracts this value and compares it against the stored SharedSecret for that buyer. If they match — authenticated. If not — rejected.
The Two Authentication Methods
Method 1 — Plain Comparison (Simplest)
The supplier simply compares the SharedSecret in the request against the one in their database:
if (received_secret === stored_secret_for_buyer) {
authenticate_and_create_session();
} else {
return_401_unauthorized();
}
This is the most common approach. Simple, straightforward, and good enough for most B2B implementations.
Method 2 — HMAC Signature (More Secure)
In stricter implementations, the SharedSecret is used as the key for HMAC-SHA256 signing. The buyer signs a portion of the request, and the supplier verifies the signature:
// Buyer side (signing)
$signature = hash_hmac('sha256', $payload, $shared_secret);
// Supplier side (verification)
$expected = hash_hmac('sha256', $payload, $shared_secret);
$valid = hash_equals($expected, $received_signature);
This protects against tampering — even if someone intercepts the request, they can't modify it without breaking the signature.
In practice, however, plain SharedSecret comparison is what most procurement systems use. HMAC signing is reserved for high-security implementations or specific buyer requirements.
The Security Model
cXML's security model relies on three layers:
- HTTPS (TLS 1.2+) — encrypts traffic in transit, prevents wire-tapping
- Per-buyer SharedSecret — isolates each buyer's identity
- BuyerCookie — session-level binding, prevents session hijacking
If any one of these layers is broken, the entire authentication chain fails. Suppliers must enforce all three.
Common Mistakes
Mistake 1 — Leading or Trailing Whitespace
The single most common cause of "Authentication Failed" errors:
Stored: "Y0urSh@redSecret123"
Received: " Y0urSh@redSecret123" ← leading space!
The buyer's IT team might have pasted the secret with a stray space. The supplier's database might have stripped or kept whitespace differently. Result: comparison fails.
Fix: Always trim() both sides before comparing. Better yet, configure your input form to strip whitespace automatically.
Mistake 2 — Case Sensitivity
Stored: "MySecret123"
Received: "mysecret123" ← lowercase!
cXML SharedSecret is case-sensitive. MySecret123 and mysecret123 are different secrets.
Fix: Never normalize case. Compare exact strings. If your buyer's system is normalizing — escalate to their IT.
Mistake 3 — Special Character Encoding
XML has reserved characters. If your SharedSecret contains &, <, >, ", or ', they must be XML-encoded:
Secret with ampersand: "Pass&word"
XML-encoded: "Pass&word"
The buyer's system encodes when sending; the supplier's parser decodes when receiving. If either side fails to handle encoding correctly, secrets won't match.
Fix: Avoid special XML characters in SharedSecrets. Stick to alphanumeric + safe symbols: ! @ # $ % * - _ +
Mistake 4 — Storing the Wrong Field
cXML has multiple credential blocks: <From>, <To>, and <Sender>. SharedSecret only appears in <Sender>. Some implementations accidentally check <From> for the secret — which is wrong.
Fix: Always extract SharedSecret from the <Sender><Credential> block specifically.
Mistake 5 — Logging Secrets in Plaintext
Some logging implementations capture the full incoming cXML — including the SharedSecret — in audit logs or error logs.
Risk: If logs are exposed (developer access, backup files, accidental sharing), all your buyer secrets are compromised.
Fix: Always mask SharedSecret values in logs:
// Log this:
"SharedSecret": "Y0u***************123"
// Not this:
"SharedSecret": "Y0urSh@redSecret123"
Mistake 6 — Reusing Secrets Across Buyers
Using the same SharedSecret for multiple buyers breaks isolation. If one buyer's secret leaks, all buyers using the same secret are compromised.
Fix: Generate a unique SharedSecret for each buyer. Treat them as buyer-specific credentials.
How to Generate a Strong SharedSecret
A good SharedSecret should be:
- At least 24 characters — longer is better
- Mix of uppercase, lowercase, digits, symbols — high entropy
- Random — not a dictionary word
- Safe XML characters — no
&,<,>,",'
One-liner to generate in PHP:
echo bin2hex(random_bytes(16));
// Example: a3f8d2b1e4c7f9a0e2d5c8b3f7a1e9d4
Or in JavaScript:
require('crypto').randomBytes(16).toString('hex');
// Example: 7b3a9c2e1d4f5a8b6c0d3e7f9a2b4c1d
Buyer-Side vs Supplier-Side Generation
Industry convention: the supplier generates the SharedSecret and provides it to the buyer.
Rationale:
- Supplier controls their own security policy
- Supplier ensures the secret meets their entropy requirements
- Supplier owns the validation logic
However, some enterprise buyers have their own conventions — Oracle Fusion, for instance, sometimes assigns a Network ID and prefers the buyer's IT to generate the secret. Both approaches work as long as both parties agree.
Rotation and Lifecycle
How often should you rotate SharedSecrets?
| Trigger | Action |
|---|---|
| Suspected compromise | Rotate immediately |
| Departure of buyer's IT staff who had access | Rotate within 7 days |
| Annual security audit | Optional rotation |
| End of buyer contract | Disable buyer entirely |
| Normal operation | No rotation needed |
Unlike user passwords, SharedSecrets don't have a recommended rotation schedule. They're machine credentials — rotate when there's a reason to.
The HMAC Signature Approach (Advanced)
For high-security implementations, some buyers require HMAC-SHA256 signing of cXML payloads. This goes beyond simple SharedSecret comparison.
How It Works
- Buyer constructs the cXML payload
- Buyer computes
HMAC-SHA256(payload, shared_secret) - Signature is added as a header or extrinsic field
- Supplier receives request, computes expected signature
- Supplier compares received vs expected using constant-time comparison
Why Use HMAC Instead of Plain Comparison
- Tamper detection — any modification to the payload breaks the signature
- Replay protection — combined with timestamp + nonce
- Cryptographic guarantee — mathematically sound vs string comparison
When You Need It
- High-value transactions (large enterprise contracts)
- Regulated industries (healthcare, finance, government)
- Buyers with strict security policies (Fortune-500 typically)
- Multi-party integrations with intermediaries
How PunchOutHub Handles This
PunchOutHub implements:
- Plain SharedSecret comparison (standard cXML 1.2 compliance)
- Optional HMAC-SHA256 signature verification (Enterprise tier)
- Automatic whitespace trimming
- Case-sensitive comparison
- Constant-time comparison to prevent timing attacks
- Per-buyer secret isolation
- Audit logging with masked secrets
- Domain-bound RSA-signed license verification (separate layer)
This gives suppliers compliance with both standard and high-security buyer requirements out of the box.
Quick Reference
| Aspect | Best Practice |
|---|---|
| Length | 24+ characters |
| Generation | Cryptographically random |
| Storage (your side) | Encrypted at rest if possible |
| Transmission | HTTPS only, never email |
| Logging | Always masked |
| Per buyer | Unique secret each |
| Comparison | Constant-time, case-sensitive |
| Rotation | On compromise or staff change |
| Backup | Stored in your password manager, not git |
See SharedSecret in action
The PunchOutHub demo accepts test SharedSecret values — experiment with valid and invalid credentials to see authentication in real time.
Open Demo →