Node.js中如何混淆顺序生成的用户ID?实现整数双向一一映射
Great question—this is a common need for obfuscating sequential user IDs while maintaining a strict 1:1 bidirectional mapping, which stops bad actors from enumerating all user accounts in your system. Below are the most robust, practical implementations I’d recommend, ordered by how well they balance simplicity, security, and performance:
1. Simplified Feistel Cipher (Best for Most Use Cases)
This is my go-to choice for ID obfuscation. Feistel ciphers are symmetric, reversible, and don’t require heavy cryptography libraries—perfect for mapping sequential integers to seemingly random ones without collisions.
How it works:
Split your integer ID into two equal-sized "halves" (e.g., for a 64-bit ID, split into two 32-bit chunks). Run several rounds (4-8 is usually enough) of:
- Use a secret key to transform the right half with a simple function (e.g., hash, linear math, or bitwise operations).
- XOR the transformed right half with the left half.
- Swap the left and right halves (skip the swap on the final round).
To decrypt, just run the same rounds in reverse order.
Example Python Implementation:
class FeistelIDObfuscator: def __init__(self, key: int, rounds: int = 6): self.key = key self.rounds = rounds self.block_size = 32 # Split 64-bit ID into two 32-bit blocks self.mod = 1 << self.block_size # 2^32 def _round_function(self, val: int) -> int: # Simple but non-trivial round function—customize this for extra security return (val * self.key + 0xDEADBEEF) % self.mod def encode(self, original_id: int) -> int: left = original_id >> self.block_size right = original_id & (self.mod - 1) for _ in range(self.rounds): new_left = right new_right = left ^ self._round_function(right) left, right = new_left, new_right # Final swap back (since we swapped every round except last) return (right << self.block_size) | left def decode(self, obfuscated_id: int) -> int: left = obfuscated_id >> self.block_size right = obfuscated_id & (self.mod - 1) for _ in range(self.rounds): new_right = left new_left = right ^ self._round_function(left) left, right = new_left, new_right return (right << self.block_size) | left
Pros & Cons:
- ✅ Easy to implement without external dependencies
- ✅ Customizable security (more rounds = harder to crack)
- ✅ Works for any integer size (adjust
block_sizeas needed) - ❌ Slightly slower than linear methods (but negligible for ID workloads)
2. Linear Congruential Permutation (LCP)
If you need blazingly fast performance and don’t require military-grade security, LCP is ideal. It uses mathematical properties to create a permutation (1:1 mapping) of integers.
How it works:
Choose three parameters:
m: A prime number larger than your maximum possible user IDa: A primitive root modulom(ensures every input maps to a unique output)b: An integer coprime withm(i.e., gcd(b, m) = 1)
The mapping is:
- Encode:
obfuscated_id = (a * original_id + b) % m - Decode:
original_id = (inv_a * (obfuscated_id - b)) % m
Whereinv_ais the modular inverse ofamodulom(you can precompute this once).
Example Python Implementation:
def modinv(a: int, m: int) -> int: # Compute modular inverse using extended Euclidean algorithm g, x, _ = extended_gcd(a, m) if g != 1: raise ValueError("Modular inverse does not exist") return x % m def extended_gcd(a: int, b: int) -> tuple[int, int, int]: if a == 0: return (b, 0, 1) else: g, y, x = extended_gcd(b % a, a) return (g, x - (b // a) * y, y) # Example parameters (adjust m to be larger than your max user ID) M = 1000003 # Prime number A = 123457 # Primitive root modulo M B = 98765 # Coprime with M INV_A = modinv(A, M) def encode_lcp(original_id: int) -> int: return (A * original_id + B) % M def decode_lcp(obfuscated_id: int) -> int: return (INV_A * (obfuscated_id - B)) % M
Pros & Cons:
- ✅ Extremely fast (single arithmetic operations)
- ✅ Minimal code
- ❌ Less secure if parameters are leaked (easy to reverse-engineer)
- ❌ Requires knowing your maximum possible ID upfront to choose
m
3. AES in ECB Mode (Cryptographically Secure)
If you need unbreakable (for all practical purposes) obfuscation, use AES in ECB mode. Since AES is a block cipher, each unique input block maps to a unique output block—perfect for 1:1 integer mapping.
How it works:
- Convert your integer ID to a fixed-length byte array (e.g., 16 bytes for AES-128).
- Encrypt the byte array with AES-ECB using a secret key.
- Convert the encrypted byte array back to an integer.
Decryption reverses these steps.
Example Python Implementation (using pycryptodome):
from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad class AESIDObfuscator: def __init__(self, key: bytes): # Key must be 16, 24, or 32 bytes for AES-128, AES-192, AES-256 self.cipher = AES.new(key, AES.MODE_ECB) def encode(self, original_id: int) -> int: # Convert integer to 16-byte padded bytes id_bytes = original_id.to_bytes(16, byteorder='big') encrypted_bytes = self.cipher.encrypt(pad(id_bytes, AES.block_size)) return int.from_bytes(encrypted_bytes, byteorder='big') def decode(self, obfuscated_id: int) -> int: encrypted_bytes = obfuscated_id.to_bytes(16, byteorder='big') decrypted_bytes = unpad(self.cipher.decrypt(encrypted_bytes), AES.block_size) return int.from_bytes(decrypted_bytes, byteorder='big')
Pros & Cons:
- ✅ Cryptographically secure (effectively unbreakable with a strong key)
- ✅ Standardized algorithm
- ❌ Requires a cryptography library
- ❌ Slightly slower than the other methods (still fast enough for most apps)
Critical Notes:
- Keep keys secret: No matter which method you choose, leaking the key will let attackers reverse-engineer all IDs.
- Test for collisions: Verify that every original ID maps to a unique obfuscated ID (all methods above guarantee this if implemented correctly).
- Handle overflow: If using 64-bit IDs, make sure your language supports unsigned integers or handles overflow correctly.
内容的提问来源于stack exchange,提问作者nagy.zsolt.hun

