skillmonster/http-message-signature
Composer 安装命令:
composer require skillmonster/http-message-signature
包简介
PHP implementation of RFC 9421 HTTP Message Signatures
README 文档
README
A complete PHP implementation of RFC 9421: HTTP Message Signatures, providing cryptographic signing and verification of HTTP messages.
Features
- ✅ Full RFC 9421 Compliance - Complete implementation of the HTTP Message Signatures standard
- 🔐 Multiple Algorithms - Support for RSA-PSS-SHA512, RSA-PKCS1-v1.5-SHA256, ECDSA-P256-SHA256, HMAC-SHA256, and Ed25519
- 📦 PSR-7 Compatible - Works with any PSR-7 HTTP message implementation
- 🎯 Derived Components - Support for
@method,@target-uri,@authority,@path,@query,@status, and more - 🔄 Multiple Signatures - Add multiple signatures to a single message
- 🔑 Flexible Key Formats - Supports PEM, DER, base64, and raw key formats
- 🛡️ Content Digest - SHA-512 content digest support for body integrity
- 🎲 Nonce Support - Built-in random nonce generation for replay protection
- 🏷️ Tag Parameter - Application-specific tag support
- ⚡ Easy to Use - Simple, intuitive API
- 🧪 Well Tested - Comprehensive test suite
- 📝 Type Safe - Full PHP 8.1+ type declarations
- 🚀 Production Ready - Battle-tested with real-world APIs
Installation
Install via Composer:
composer require skillmonster/http-message-signature
Requirements
- PHP 8.1 or higher
- OpenSSL extension
- JSON extension
- Sodium extension (for Ed25519 support, included in PHP 7.2+)
- PSR-7 HTTP Message implementation (e.g.,
guzzlehttp/psr7)
Quick Start
Basic HMAC Signature
<?php use HttpSignature\HttpSignature; use HttpSignature\Algorithm\HmacSha256; use GuzzleHttp\Psr7\Request; // Create a PSR-7 request $request = new Request( 'POST', 'https://api.example.com/users', [ 'Host' => 'api.example.com', 'Content-Type' => 'application/json' ], '{"hello":"world"}' ); // Initialize the signature handler $httpSignature = new HttpSignature(); $algorithm = new HmacSha256(); $sharedSecret = 'your-secret-key'; // Sign the request $signedRequest = $httpSignature->sign( message: $request, algorithm: $algorithm, privateKey: $sharedSecret, componentNames: ['@method', '@path', '@authority', 'content-type'] ); // Verify the signature $isValid = $httpSignature->verify( message: $signedRequest, algorithm: $algorithm, publicKey: $sharedSecret ); echo $isValid ? "✓ Signature valid" : "✗ Signature invalid";
RSA Signature
<?php use HttpSignature\HttpSignature; use HttpSignature\Algorithm\RsaPssSha512; use GuzzleHttp\Psr7\Request; // Load your RSA keys $privateKey = file_get_contents('/path/to/private-key.pem'); $publicKey = file_get_contents('/path/to/public-key.pem'); $request = new Request('GET', 'https://api.example.com/data', [ 'Host' => 'api.example.com' ]); $httpSignature = new HttpSignature(); $algorithm = new RsaPssSha512(); // Sign with RSA $signedRequest = $httpSignature->sign( message: $request, algorithm: $algorithm, privateKey: $privateKey, componentNames: ['@method', '@authority', '@path'], additionalParams: [ 'keyid' => 'my-rsa-key', 'expires' => time() + 300 // 5 minutes ] ); // Verify $isValid = $httpSignature->verify( message: $signedRequest, algorithm: $algorithm, publicKey: $publicKey );
Supported Algorithms
| Algorithm | Class | RFC 9421 Section | Key Formats | Notes |
|---|---|---|---|---|
| Ed25519 | Ed25519 |
Modern | PEM (PKCS#8), base64, raw | ✅ Recommended - Fast & secure |
| RSA-PSS-SHA512 | RsaPssSha512 |
3.3.1 | PEM, DER, base64 | Requires OpenSSL |
| RSA-PKCS1-v1.5-SHA256 | RsaPkcs1v15Sha256 |
3.3.2 | PEM, DER, base64 | Requires OpenSSL |
| ECDSA-P256-SHA256 | EcdsaP256Sha256 |
3.3.3 | PEM, DER, base64 | Requires OpenSSL |
| HMAC-SHA256 | HmacSha256 |
3.3.4 | Raw string | Built-in, shared secret |
Algorithm Selection Guide
- Ed25519: Best choice for modern APIs. Fast, secure, small signatures (64 bytes)
- RSA-PSS-SHA512: Good for compatibility with existing RSA infrastructure
- ECDSA-P256-SHA256: Smaller keys than RSA, good performance
- HMAC-SHA256: Simple shared secret, good for internal services
Ed25519 Example (Recommended)
Ed25519 is the recommended algorithm for modern APIs due to its speed, security, and small signature size.
Using Ed25519 with PEM Keys
use HttpSignature\Algorithm\Ed25519; // Load PEM format keys (PKCS#8) $privateKey = file_get_contents('private-key.pem'); $publicKey = file_get_contents('public-key.pem'); // Sign with Ed25519 $algorithm = new Ed25519(); $signedRequest = $httpSignature->sign( message: $request, algorithm: $algorithm, privateKey: $privateKey, componentNames: [ '@method', '@target-uri', 'content-type', 'content-digest', 'accept' ], additionalParams: [ 'keyid' => 'my-ed25519-key', 'alg' => 'ed25519', 'tag' => 'payment', 'nonce' => base64_encode(random_bytes(32)), 'created' => time() ] ); // Verify $isValid = $httpSignature->verify($signedRequest, $algorithm, $publicKey);
Generate Keypair Programmatically
use HttpSignature\Algorithm\Ed25519; // Generate new keypair $keypair = Ed25519::generateKeypair(); // Keys are base64-encoded $privateKeyB64 = $keypair['privateKey']; $publicKeyB64 = $keypair['publicKey']; // Decode for use $privateKey = base64_decode($privateKeyB64); $publicKey = base64_decode($publicKeyB64);
Component Identifiers
HTTP Fields
Sign any HTTP header by using its lowercase name:
['content-type', 'date', 'authorization']
Derived Components
Special components derived from the HTTP message:
| Component | Description | Example Value |
|---|---|---|
@method |
HTTP method | GET, POST |
@target-uri |
Full request URI | https://example.com/path?query=value |
@authority |
Host and port | example.com:443 |
@scheme |
URI scheme | https |
@request-target |
Request target | /path?query=value |
@path |
URI path | /path |
@query |
Query string | ?query=value |
@status |
Response status code | 200 |
Advanced Usage
Multiple Signatures
Add multiple signatures to a single message:
$request = new Request('GET', 'https://example.com/data', [ 'Host' => 'example.com' ]); // Add first signature $signedRequest = $httpSignature->sign( message: $request, algorithm: $hmacAlgorithm, privateKey: $secret1, componentNames: ['@method', '@path'], signatureName: 'sig1' ); // Add second signature $signedRequest = $httpSignature->addSignature( message: $signedRequest, algorithm: $rsaAlgorithm, privateKey: $rsaPrivateKey, componentNames: ['@authority', 'date'], signatureName: 'sig2' ); // Verify each signature $valid1 = $httpSignature->verify($signedRequest, $hmacAlgorithm, $secret1, 'sig1'); $valid2 = $httpSignature->verify($signedRequest, $rsaAlgorithm, $rsaPublicKey, 'sig2');
Signature Parameters
Add metadata to your signatures:
$signedRequest = $httpSignature->sign( message: $request, algorithm: $algorithm, privateKey: $privateKey, componentNames: ['@method', '@target-uri', 'content-type'], additionalParams: [ 'keyid' => 'my-key-identifier', // Key identifier 'alg' => 'ed25519', // Algorithm name 'tag' => 'payment', // Application-specific tag 'nonce' => base64_encode(random_bytes(32)), // Random nonce (32 bytes) 'created' => time(), // Creation timestamp 'expires' => time() + 300 // Expiration timestamp (5 min) ] );
Available Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
keyid |
string | Key identifier | "my-key-2024" |
alg |
string | Algorithm name | "ed25519" |
tag |
string | Application tag | "payment", "webhook" |
nonce |
string | Random nonce | Base64-encoded random bytes |
created |
int | Creation timestamp | time() |
expires |
int | Expiration timestamp | time() + 300 |
Query Parameters
Sign specific query parameters:
use HttpSignature\Component\ComponentIdentifier; $components = [ new ComponentIdentifier('@method'), new ComponentIdentifier('@query-param', ['name' => 'user_id']) ];
Content Digest
Include body integrity verification using SHA-512 content digest:
// Generate content digest $body = json_encode(['amount' => 1000, 'currency' => 'LAK']); $hash = hash('sha512', $body, true); $contentDigest = 'sha-512=:' . base64_encode($hash) . ':'; // Create request with digest $request = new Request( 'POST', 'https://api.example.com/payment', [ 'Content-Type' => 'application/json', 'Content-Digest' => $contentDigest, 'Accept' => 'application/json' ], $body ); // Sign including the digest $signedRequest = $httpSignature->sign( message: $request, algorithm: $algorithm, privateKey: $privateKey, componentNames: [ '@method', '@target-uri', 'content-type', 'content-digest', // Include digest in signature 'accept' ] );
Real-World Example: Payment API
Complete example of signing a payment API request with Ed25519:
<?php use HttpSignature\HttpSignature; use HttpSignature\Algorithm\Ed25519; use GuzzleHttp\Psr7\Request; // Configuration $privateKey = file_get_contents('private-key.pem'); $keyId = 'your-key-id'; // Payment data $paymentData = [ 'mid' => 'MERCHANT123', 'amount' => '50000', 'currency' => 'LAK', 'billId' => 'ORDER-' . time(), 'customer' => [ 'name' => 'John Doe', 'email' => 'john@example.com' ] ]; // Create request body and digest $jsonBody = json_encode($paymentData); $contentDigest = 'sha-512=:' . base64_encode(hash('sha512', $jsonBody, true)) . ':'; $nonce = base64_encode(random_bytes(32)); // Create PSR-7 request $request = new Request( 'POST', 'https://api.payment.com/v2/payments/create', [ 'Content-Type' => 'application/json', 'Content-Length' => (string) strlen($jsonBody), 'Content-Digest' => $contentDigest, 'Accept' => 'application/json' ], $jsonBody ); // Sign the request $httpSignature = new HttpSignature(); $algorithm = new Ed25519(); $signedRequest = $httpSignature->sign( message: $request, algorithm: $algorithm, privateKey: $privateKey, componentNames: [ '@method', '@target-uri', 'content-type', 'content-length', 'content-digest', 'accept' ], additionalParams: [ 'keyid' => $keyId, 'alg' => 'ed25519', 'tag' => 'payment', 'nonce' => $nonce, 'created' => time() ] ); // Send with your HTTP client $client = new \GuzzleHttp\Client(); $response = $client->send($signedRequest); echo "Payment created: " . $response->getBody();
Message Format
When a message is signed, two headers are added:
Signature-Input Header
Contains the signature metadata and covered components:
Signature-Input: sig1=("@method" "@path" "content-type");created=1618884473;keyid="test-key"
Signature Header
Contains the actual signature value:
Signature: sig1=:K2qGT5srn2OGbOIDzQ6kYT+ruaycnDAAUpKv+ePFfD6UFh1L1EwBs=:
Security Considerations
What to Sign
Always include components that are critical to your application's security:
For Requests (Recommended):
@method- Prevent method confusion attacks@target-uri- Prevent request redirection (includes full URL)content-type- Prevent content type confusioncontent-length- Verify body lengthcontent-digest- Ensure body integrity (SHA-512 recommended)accept- Specify expected response format
Alternative (Legacy):
@method,@authority,@path- If@target-urinot supported
For Responses:
@status- Verify response statuscontent-type- Verify content typecontent-digest- Verify response body integrity- Critical business headers
Recommended Signature Configuration
// Production-ready configuration $signedRequest = $httpSignature->sign( message: $request, algorithm: new Ed25519(), // Recommended algorithm privateKey: $privateKey, componentNames: [ '@method', '@target-uri', // Full URL protection 'content-type', 'content-length', 'content-digest', // Body integrity 'accept' ], additionalParams: [ 'keyid' => $keyId, 'alg' => 'ed25519', 'tag' => 'payment', // Application context 'nonce' => base64_encode(random_bytes(32)), // Replay protection 'created' => time(), 'expires' => time() + 300 // 5 minutes ] );
Key Management
- Use Ed25519 for new implementations (fast, secure, small keys)
- Use strong, randomly generated keys
- Rotate keys regularly (every 90 days recommended)
- Store private keys securely (HSM, key vault, environment variables)
- Never hardcode keys in source code
- Use different keys for different environments (dev, staging, prod)
- Share only public keys with API providers
Signature Expiration
Always set an expiration time to prevent replay attacks:
'expires' => time() + 300 // 5 minutes for high-security APIs 'expires' => time() + 3600 // 1 hour for less sensitive operations
Replay Protection
Use nonces to prevent replay attacks:
// Generate 32-byte random nonce 'nonce' => base64_encode(random_bytes(32))
Server-side: Track used nonces within the signature validity window.
Content Digest
Always include content digest for POST/PUT requests:
$body = json_encode($data); $hash = hash('sha512', $body, true); $contentDigest = 'sha-512=:' . base64_encode($hash) . ':';
This ensures the request body hasn't been tampered with.
Testing
Run the test suite:
composer test
Run static analysis:
composer phpstan
Check code style:
composer cs-check
Examples
See the examples/ directory for complete working examples:
Basic Examples
basic_usage.php- HMAC signatures and multiple signaturesrsa_signature.php- RSA signatures and tampering detection
Ed25519 Examples
ed25519_signature.php- Ed25519 signature basicsed25519_with_existing_key.php- Using existing Ed25519 keysed25519_http_request.php- Complete HTTP request examplesgenerate_ed25519_keys.php- Generate Ed25519 keypairs
Real-World Integration
uniqpay_laoqr_example.php- Complete UniqPay LaoQR payment API integrationUniqPayClient.php- Production-ready payment API client class
Run examples:
# Basic examples php examples/basic_usage.php php examples/rsa_signature.php # Ed25519 examples php examples/ed25519_signature.php php examples/generate_ed25519_keys.php # Real-world payment API php examples/uniqpay_laoqr_example.php
UniqPay Integration Example
The library includes a complete, production-ready integration with UniqPay's LaoQR payment API:
require_once 'examples/UniqPayClient.php'; $client = new UniqPayClient( privateKeyPath: 'private-key.pem', keyId: 'your-key-id', mid: 'YOUR_MERCHANT_ID', recvId: 'YOUR_RECEIVER_ID', httpClient: new \GuzzleHttp\Client() ); // Create payment $result = $client->createPayment([ 'billId' => 'ORDER-12345', 'terminalId' => '000', 'purpose' => 'Payment for goods', 'amount' => '50000', 'currency' => 'LAK', 'customer' => [ 'id' => 'C001', 'fullname' => 'John Doe', 'email' => 'john@example.com', 'phone' => '+8562012345678' ] ]); if ($result['success']) { $payment = $result['data']['data']; echo "Payment ID: " . $payment['id'] . "\n"; echo "LaoQR Code: " . $payment['laoQr'] . "\n"; }
RFC 9421 Compliance
This library implements the following sections of RFC 9421:
- ✅ Section 2: HTTP Message Components
- 2.1: HTTP Fields
- 2.2: Derived Components
- 2.3: Signature Parameters
- 2.5: Creating the Signature Base
- ✅ Section 3: HTTP Message Signatures
- 3.1: Creating a Signature
- 3.2: Verifying a Signature
- 3.3: Signature Algorithms
- ✅ Section 4: Including a Message Signature in a Message
- 4.1: The Signature-Input HTTP Field
- 4.2: The Signature HTTP Field
- 4.3: Multiple Signatures
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development Setup
git clone https://github.com/skillmonster/http-message-signature.git cd http-message-signature composer install composer test
License
This library is licensed under the MIT License. See the LICENSE file for details.
References
Support
- 📧 Email: vanvixay.litd@gmail.com
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
Made with ❤️ by Vanvixay Thammavong
统计信息
- 总下载量: 3
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-10-13