Python转TypeScript:itsdangerous Flask Cookie编码签名差异问题
Q&A Breakdown
Are only the signature parts different?
Yes, only the timestamp and signature sections differ between the two outputs. The payload (first long segment) is identical in both tokens. The root cause is how each implementation encodes the timestamp:
- Python encodes the timestamp as a 4-byte big-endian integer, then URL-safe base64 encodes the binary data (resulting in a short 6-character segment like
ZSRbJg). - The
itsdangerous.jslibrary encodes the timestamp as a string representation of the Unix epoch (e.g.,"1696881446"), then base64 encodes that string (resulting in a longer segment likeMTY5Njg4MTQ0Ng).
Since the signature is computed over the combined payload + timestamp, this difference in timestamp encoding leads to a mismatched signature.
Fix for itsdangerous.js
The itsdangerous.js library does not natively replicate Python's timestamp encoding. To fix this, you would need to override the library's timestamp handling to pack the integer into 4-byte big-endian bytes before base64 encoding. However, this requires modifying the library's source or extending its classes, which is fragile.
A more reliable approach is to implement the logic manually in TypeScript without relying on the ported library.
Manual TypeScript Implementation (No Port Library)
This code replicates Python's exact cookie generation logic, including timestamp encoding, key derivation, and signature calculation:
import * as crypto from 'crypto'; async function getFlaskSessionToken(data: Record<string, any>): Promise<string> { const secretKey = process.env.FLASK_SECRET_KEY as string; const salt = 'cookie-session'; // 1. Serialize payload (use TaggedJSON logic below if needed for non-JSON types) const serializedPayload = JSON.stringify(data); const payloadB64 = urlSafeBase64Encode(Buffer.from(serializedPayload, 'utf8')); // 2. Generate and encode timestamp as 4-byte big-endian integer const timestamp = Math.floor(Date.now() / 1000); const timestampBuffer = Buffer.alloc(4); timestampBuffer.writeUInt32BE(timestamp, 0); const timestampB64 = urlSafeBase64Encode(timestampBuffer); // 3. Combine payload and timestamp for signing const message = `${payloadB64}.${timestampB64}`; // 4. Derive signing key using HMAC-SHA1 with salt const signingKey = crypto.createHmac('sha1', secretKey).update(salt).digest(); // 5. Compute signature const signatureBuffer = crypto.createHmac('sha1', signingKey).update(message).digest(); const signatureB64 = urlSafeBase64Encode(signatureBuffer); // 6. Combine all parts into final token return `${payloadB64}.${timestampB64}.${signatureB64}`; } // Helper: URL-safe base64 encoding (matches Python's URLSafeSerializer) function urlSafeBase64Encode(buffer: Buffer): string { return buffer.toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); }
Notes on TaggedJSONSerializer
If your payload includes non-JSON native Python types (like tuples), you need to replicate Flask's TaggedJSONSerializer logic. For example, tuples are serialized as {" t": [...]}. Add this custom serialization function if needed:
function taggedJSONSerialize(data: any): string { function replacer(key: string, value: any): any { if (Array.isArray(value) && Object.prototype.toString.call(value) === '[object Array]') { // Check if it's a tuple (Python-specific) if (value.__proto__ !== Array.prototype) { return {" t": value}; } } return value; } return JSON.stringify(data, replacer); }
Replace JSON.stringify(data) with taggedJSONSerialize(data) in the main function.
内容的提问来源于stack exchange,提问作者chrismead

