Unicode Normalization with Hmac Signatures
How Unicode Normalization Manifests in Hmac Signatures
Unicode normalization attacks in Hmac Signatures occur when the signing process doesn't account for equivalent character representations. Attackers can exploit this by sending requests with visually identical but computationally different strings, causing signature mismatches or bypassing authentication entirely.
The core issue stems from Unicode's ability to represent the same character through multiple byte sequences. For example, the character 'é' can be encoded as a single code point (U+00E9) or as 'e' plus a combining acute accent (U+0065 U+0301). When Hmac Signatures processes these variants differently during signature generation versus verification, it creates exploitable inconsistencies.
Consider this vulnerable Hmac Signatures implementation:
const crypto = require('crypto');
function generateSignature(secret, message) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(message);
return hmac.digest('hex');
}
// Vulnerable: No normalization
const secret = 'mysecret';
const message1 = 'café'; // U+0063 U+0061 U+0066 U+00E9
const message2 = 'café'; // U+0063 U+0061 U+0066 U+0065 U+0301
console.log(generateSignature(secret, message1));
console.log(generateSignature(secret, message2));
Both strings display identically in most contexts, yet produce different HMAC signatures because the underlying byte sequences differ. An attacker can craft requests using the alternate representation to cause signature verification failures or potentially bypass authentication if the system handles these mismatches inconsistently.
Real-world exploitation often involves:
- Authentication bypass through signature collision attacks
- Request tampering where normalized and unnormalized strings pass verification
- Denial of service when signature mismatches cause legitimate requests to fail
- Cache poisoning in systems that cache based on normalized request strings
The OWASP API Security Top 10 specifically identifies this as a variant of API6: Unrestricted Consumption, where improper input handling leads to authentication bypasses.
Hmac Signatures-Specific Detection
Detecting Unicode normalization vulnerabilities in Hmac Signatures requires systematic testing across multiple Unicode representations. The most effective approach involves comparing signature outputs across different normalization forms.
Manual testing methodology:
const crypto = require('crypto');
const { normalize } = require('unicode-normalization');
function testNormalizationVulnerabilities(secret, message) {
const forms = ['NFC', 'NFD', 'NFKC', 'NFKD'];
const signatures = {};
for (const form of forms) {
const normalized = normalize(form, message);
const hmac = crypto.createHmac('sha256', secret);
hmac.update(normalized);
signatures[form] = hmac.digest('hex');
}
// Check for collisions
const uniqueSignatures = new Set(Object.values(signatures));
if (uniqueSignatures.size < forms.length) {
console.log('VULNERABILITY DETECTED: Signature collisions exist');
console.log(signatures);
}
}
testNormalizationVulnerabilities('mysecret', 'café');
Automated scanning with middleBrick specifically tests for these vulnerabilities by:
- Submitting requests with different Unicode normalization forms
- Comparing signature verification outcomes across variants
- Checking for authentication bypasses when using alternate representations
- Analyzing API responses for inconsistent behavior
middleBrick's black-box scanning approach tests the unauthenticated attack surface by sending requests with various Unicode representations and monitoring for signature verification failures or unexpected authentication outcomes. The scanner's 12 security checks include input validation testing that specifically targets Unicode handling issues.
Key detection indicators include:
- Different signature outputs for visually identical strings
- Authentication success with alternate Unicode representations
- Signature verification failures for legitimate requests
- Inconsistent caching behavior based on Unicode variants
Hmac Signatures-Specific Remediation
Proper remediation requires consistent Unicode normalization throughout the signature lifecycle. The solution involves normalizing all strings before signature generation and verification, ensuring identical byte sequences regardless of input representation.
Correct implementation using Node.js built-in Unicode handling:
const crypto = require('crypto');
// Always normalize to NFC form before signing
function generateSignature(secret, message) {
const normalizedMessage = message.normalize('NFC');
const hmac = crypto.createHmac('sha256', secret);
hmac.update(normalizedMessage);
return hmac.digest('hex');
}
function verifySignature(secret, message, expectedSignature) {
const generated = generateSignature(secret, message);
return crypto.timingSafeEqual(
Buffer.from(generated),
Buffer.from(expectedSignature)
);
}
// Test with different Unicode representations
const secret = 'mysecret';
const messageNFC = 'café'; // U+0063 U+0061 U+0066 U+00E9
const messageNFD = 'café'; // U+0063 U+0061 U+0066 U+0065 U+0301
const sigNFC = generateSignature(secret, messageNFC);
const sigNFD = generateSignature(secret, messageNFD);
console.log('NFC signature:', sigNFC);
console.log('NFD signature:', sigNFD);
console.log('Signatures match:', sigNFC === sigNFD);
console.log('Verification success:', verifySignature(secret, messageNFD, sigNFC));
Best practices for Hmac Signatures implementations:
- Normalize before processing: Apply consistent normalization (typically NFC) to all strings before any cryptographic operations
- Use timing-safe comparisons: Prevent timing attacks during signature verification using crypto.timingSafeEqual
- Validate character encoding: Reject or normalize input that contains invalid or unexpected Unicode sequences
- Test with diverse inputs: Include test cases covering all Unicode normalization forms and edge cases
Additional security considerations:
function secureHmacSigner(secret, message, algorithm = 'sha256') {
// Normalize to NFC (Canonical Decomposition, followed by Canonical Composition)
const normalized = message.normalize('NFC');
// Create HMAC
const hmac = crypto.createHmac(algorithm, secret);
hmac.update(normalized);
// Return both signature and normalized message for verification
return {
signature: hmac.digest('hex'),
normalizedMessage: normalized
};
}
// Input validation for Unicode safety
function validateUnicodeInput(input) {
try {
// Check for invalid sequences
input.normalize();
return true;
} catch (e) {
return false;
}
}
For enterprise deployments, middleBrick's Pro plan provides continuous monitoring that automatically retests APIs on a configurable schedule, alerting you if new Unicode normalization vulnerabilities are introduced in production.