@pubnub/mcp
Version:
PubNub Model Context Protocol MCP Server for Cursor and Claude
498 lines (391 loc) • 15.2 kB
Markdown
# How to Use the Crypto Module in PubNub Functions 2.0
The `crypto` module in PubNub Functions provides cryptographic helper functions for common security operations such as hashing, message authentication codes (HMAC), and digital signatures. This is essential for securing data, validating webhooks, and implementing authentication mechanisms in your serverless functions.
## Requiring the Crypto Module
To use the `crypto` module, you first need to require it in your Function:
```javascript
const crypto = require('crypto');
```
**Note:** This is a PubNub-provided crypto module, not the full Node.js `crypto` library. It offers specific cryptographic operations optimized for the Functions environment.
## Available Algorithms
You can list available algorithms via `crypto.ALGORITHM`, which provides constants for different cryptographic operations:
```javascript
const crypto = require('crypto');
// Common algorithms include:
// crypto.ALGORITHM.HMAC_SHA1
// crypto.ALGORITHM.HMAC_SHA256
// crypto.ALGORITHM.HMAC_SHA512
// crypto.ALGORITHM.ED25519
// crypto.ALGORITHM.ECDSA (various curves)
```
## Core Methods
All crypto methods return Promises, so always use `async/await` with `try/catch` for error handling.
### 1. `crypto.hmac(key, message, algorithm)`
Generate a Base64-encoded HMAC (Hash-based Message Authentication Code) signature.
* `key` (String): The secret key for HMAC generation
* `message` (String): The message to sign
* `algorithm` (String): Algorithm constant from `crypto.ALGORITHM`
* **Returns:** Promise that resolves to Base64-encoded HMAC digest
```javascript
export default async (request, response) => {
const crypto = require('crypto');
const vault = require('vault');
try {
// Get webhook secret from vault
const webhookSecret = await vault.get("webhook_secret");
if (!webhookSecret) {
return response.send({ error: "Webhook secret not configured" }, 500);
}
const payload = request.body;
const receivedSignature = request.headers['x-signature'];
// Generate HMAC signature
const expectedSignature = await crypto.hmac(
webhookSecret,
payload,
crypto.ALGORITHM.HMAC_SHA256
);
const expectedHeader = `sha256=${expectedSignature}`;
if (receivedSignature !== expectedHeader) {
console.error("Webhook signature validation failed");
return response.send({ error: "Invalid signature" }, 401);
}
// Process validated webhook
const webhookData = JSON.parse(payload);
console.log("Webhook validated and processed");
return response.send({ message: "Webhook processed successfully" }, 200);
} catch (error) {
console.error('Crypto operation failed:', error);
return response.send({ error: "Internal server error" }, 500);
}
};
```
### 2. Hash Functions
Generate cryptographic hashes of data using various algorithms.
#### `crypto.sha1(message)`
Generate SHA-1 hash (less secure, avoid for new applications).
#### `crypto.sha256(message)`
Generate SHA-256 hash (recommended for most use cases).
#### `crypto.sha512(message)`
Generate SHA-512 hash (for applications requiring longer hashes).
* `message` (String): The data to hash
* **Returns:** Promise that resolves to hex string of the digest
```javascript
export default async (request) => {
const crypto = require('crypto');
const db = require('kvstore');
try {
const userData = request.message.userData;
// Create hash of user data for deduplication
const userDataString = JSON.stringify(userData);
const dataHash = await crypto.sha256(userDataString);
// Check if we've seen this exact data before
const existingHash = await db.get(`user_data_hash:${userData.userId}`);
if (existingHash === dataHash) {
console.log("Duplicate user data detected, skipping processing");
return request.ok();
}
// Store new hash for future deduplication
await db.set(`user_data_hash:${userData.userId}`, dataHash, 1440); // 24 hour TTL
// Process new/changed user data
console.log("Processing new user data");
request.message.isNewData = true;
return request.ok();
} catch (error) {
console.error('Hash operation failed:', error);
return request.abort();
}
};
```
## Practical Examples
### Example 1: API Request Signing
```javascript
export default async (request) => {
const crypto = require('crypto');
const xhr = require('xhr');
const vault = require('vault');
try {
// Get API credentials from vault
const [apiKey, apiSecret] = await Promise.all([
vault.get("api_key"),
vault.get("api_secret")
]);
if (!apiKey || !apiSecret) {
console.error("API credentials missing");
return request.abort();
}
const timestamp = Date.now().toString();
const method = 'POST';
const endpoint = '/api/users';
const requestBody = JSON.stringify(request.message.userData);
// Create signature string
const signatureString = `${method}${endpoint}${timestamp}${requestBody}`;
// Generate HMAC signature
const signature = await crypto.hmac(
apiSecret,
signatureString,
crypto.ALGORITHM.HMAC_SHA256
);
// Make authenticated API request
const response = await xhr.fetch(`https://api.example.com${endpoint}`, {
method: method,
headers: {
'X-API-Key': apiKey,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json'
},
body: requestBody
});
if (response.status === 200) {
console.log("API request successful");
const responseData = JSON.parse(response.body);
request.message.apiResponse = responseData;
} else {
console.error("API request failed:", response.status);
}
return request.ok();
} catch (error) {
console.error('API signing error:', error);
return request.abort();
}
};
```
### Example 2: Secure Token Generation
```javascript
export default async (request, response) => {
const crypto = require('crypto');
const vault = require('vault');
const db = require('kvstore');
try {
const userId = request.body.userId;
if (!userId) {
return response.send({ error: "User ID required" }, 400);
}
// Get token signing secret
const tokenSecret = await vault.get("token_signing_secret");
if (!tokenSecret) {
return response.send({ error: "Token secret not configured" }, 500);
}
const timestamp = Date.now();
const expiry = timestamp + (24 * 60 * 60 * 1000); // 24 hours
// Create token payload
const tokenPayload = JSON.stringify({
userId: userId,
issued: timestamp,
expires: expiry,
scope: 'api_access'
});
// Generate secure token signature
const tokenSignature = await crypto.hmac(
tokenSecret,
tokenPayload,
crypto.ALGORITHM.HMAC_SHA512
);
// Create final token (base64 payload + signature)
const tokenData = {
payload: Buffer.from(tokenPayload).toString('base64'),
signature: tokenSignature
};
const secureToken = Buffer.from(JSON.stringify(tokenData)).toString('base64');
// Store token for validation (with TTL matching expiry)
await db.set(`auth_token:${userId}`, {
tokenHash: await crypto.sha256(secureToken),
expires: expiry,
issued: timestamp
}, 1440); // 24 hours TTL
return response.send({
token: secureToken,
expires: expiry,
type: 'Bearer'
}, 200);
} catch (error) {
console.error('Token generation error:', error);
return response.send({ error: "Token generation failed" }, 500);
}
};
```
### Example 3: Data Integrity Verification
```javascript
export default async (request) => {
const crypto = require('crypto');
const pubnub = require('pubnub');
try {
const messageData = request.message;
// Extract integrity hash if present
const receivedHash = messageData.integrityHash;
delete messageData.integrityHash; // Remove hash from data before verification
if (receivedHash) {
// Verify data integrity
const dataString = JSON.stringify(messageData);
const calculatedHash = await crypto.sha256(dataString);
if (receivedHash !== calculatedHash) {
console.error("Data integrity check failed");
// Publish security alert
await pubnub.fire({
channel: 'security_alerts',
message: {
type: 'integrity_violation',
originalHash: receivedHash,
calculatedHash: calculatedHash,
timestamp: Date.now(),
channel: request.channels[0]
}
});
return request.abort();
}
console.log("Data integrity verified");
}
// Add new integrity hash for downstream processing
const currentDataString = JSON.stringify(messageData);
const newHash = await crypto.sha256(currentDataString);
messageData.integrityHash = newHash;
messageData.verifiedAt = Date.now();
return request.ok();
} catch (error) {
console.error('Integrity verification error:', error);
return request.abort();
}
};
```
### Example 4: Password Hashing and Verification
```javascript
export default async (request, response) => {
const crypto = require('crypto');
const vault = require('vault');
const db = require('kvstore');
try {
const action = request.query.action;
const userId = request.body.userId;
const password = request.body.password;
if (!userId || !password) {
return response.send({ error: "User ID and password required" }, 400);
}
// Get password salt from vault
const passwordSalt = await vault.get("password_salt");
if (!passwordSalt) {
return response.send({ error: "Password salt not configured" }, 500);
}
switch (action) {
case 'hash':
// Hash a new password
const saltedPassword = password + passwordSalt + userId;
const passwordHash = await crypto.sha512(saltedPassword);
// Store hashed password
await db.set(`user_password:${userId}`, {
hash: passwordHash,
created: Date.now(),
algorithm: 'sha512_salted'
}, 525600); // 1 year TTL
return response.send({
message: "Password hashed and stored successfully"
}, 200);
case 'verify':
// Verify a password
const storedPasswordData = await db.get(`user_password:${userId}`);
if (!storedPasswordData) {
return response.send({ error: "User not found" }, 404);
}
const verificationString = password + passwordSalt + userId;
const verificationHash = await crypto.sha512(verificationString);
const isValid = verificationHash === storedPasswordData.hash;
return response.send({
valid: isValid,
...(isValid && { message: "Password verified" })
}, isValid ? 200 : 401);
default:
return response.send({ error: "Invalid action" }, 400);
}
} catch (error) {
console.error('Password operation error:', error);
return response.send({ error: "Password operation failed" }, 500);
}
};
```
### Example 5: Message Fingerprinting for Deduplication
```javascript
export default async (request) => {
const crypto = require('crypto');
const db = require('kvstore');
try {
// Create message fingerprint for deduplication
const messageContent = {
type: request.message.type,
userId: request.message.userId,
content: request.message.content,
// Exclude timestamp and other variable fields
};
const messageString = JSON.stringify(messageContent);
const messageFingerprint = await crypto.sha256(messageString);
// Check if we've seen this message recently (last 5 minutes)
const dedupeKey = `message_fingerprint:${messageFingerprint}`;
const existingMessage = await db.get(dedupeKey);
if (existingMessage) {
console.log("Duplicate message detected, enhancing with count");
// Increment duplicate counter
const duplicateCount = await db.incrCounter(`duplicate_count:${messageFingerprint}`);
request.message.isDuplicate = true;
request.message.duplicateCount = duplicateCount;
request.message.originalTimestamp = existingMessage.timestamp;
} else {
// Store fingerprint for deduplication (5 minute TTL)
await db.set(dedupeKey, {
timestamp: Date.now(),
channel: request.channels[0]
}, 5);
request.message.isDuplicate = false;
request.message.fingerprint = messageFingerprint;
}
return request.ok();
} catch (error) {
console.error('Message fingerprinting error:', error);
return request.ok(); // Don't block message processing
}
};
```
## Security Best Practices
### 1. **Use Strong Algorithms**
```javascript
// Recommended for new applications
const signature = await crypto.hmac(key, message, crypto.ALGORITHM.HMAC_SHA256);
const hash = await crypto.sha256(data);
// Avoid for new applications (use only for legacy compatibility)
const legacyHash = await crypto.sha1(data);
```
### 2. **Secure Key Management**
```javascript
// Always use Vault for secrets
const signingKey = await vault.get("signing_key");
// Never hardcode secrets
const signingKey = "hardcoded_secret"; // ❌ Never do this
```
### 3. **Constant-Time Comparison**
```javascript
// For signature verification, always compare the full signatures
if (receivedSignature === expectedSignature) {
// Valid signature
}
// Avoid early returns that could enable timing attacks
```
### 4. **Salt Your Hashes**
```javascript
// Add salt to prevent rainbow table attacks
const saltedData = data + salt + userId;
const hash = await crypto.sha256(saltedData);
```
### 5. **Handle Errors Securely**
```javascript
try {
const signature = await crypto.hmac(key, message, algorithm);
// Use signature
} catch (error) {
console.error('Crypto operation failed'); // Don't log sensitive details
return request.abort();
}
```
## Important Considerations
* **Algorithm Support:** Check `crypto.ALGORITHM` for available algorithms in your Functions environment
* **Key Management:** Always use the Vault module for storing cryptographic keys and secrets
* **Performance:** Cryptographic operations are computationally intensive; consider their impact on function execution time
* **Error Handling:** Always wrap crypto operations in try/catch blocks
* **Timing Attacks:** Be aware of timing-based security vulnerabilities when comparing hashes or signatures
* **Operation Limits:** Crypto operations may count toward function execution limits
The crypto module provides essential security capabilities for PubNub Functions, enabling you to build secure, authenticated, and tamper-resistant real-time applications.