如何在MySQL中查询哈希后的Discord ID并返回自定义名称
解决方案:带盐哈希可查询的Discord ID存储方案
针对你的需求——既要安全哈希Discord ID防彩虹表,又能快速查询无需全表遍历,结合MySQL 5.7、discord.js v14和Sequelize v6,提供以下三种可行方案,按推荐优先级排序:
方案1:固定盐+迭代哈希(适合小数据集)
你的数据集只有300条,固定盐的安全性风险可控,同时支持直接SELECT WHERE查询。
实现步骤:
- 数据库表结构(Sequelize模型定义):
const { Model, DataTypes } = require('sequelize'); const crypto = require('crypto'); class UserMapping extends Model {} UserMapping.init({ customName: { type: DataTypes.STRING, allowNull: false }, hashedDiscordId: { type: DataTypes.STRING(64), // SHA-256输出长度 allowNull: false, unique: true } }, { sequelize, modelName: 'UserMapping' });
- 哈希生成逻辑(固定盐+迭代哈希,增强抗彩虹表能力):
// 固定盐(存于环境变量,不要硬编码) const FIXED_SALT = process.env.HASH_SALT; const ITERATIONS = 10000; const KEY_LENGTH = 32; function hashDiscordId(discordId) { return crypto.pbkdf2Sync(discordId, FIXED_SALT, ITERATIONS, KEY_LENGTH, 'sha256').toString('hex'); }
- 查询逻辑(discord.js命令示例):
const { SlashCommandBuilder } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('get-custom-name') .setDescription('通过Discord ID查询自定义名称') .addStringOption(option => option.setName('discord-id') .setDescription('目标用户的Discord ID') .setRequired(true)), async execute(interaction) { const discordId = interaction.options.getString('discord-id'); const hashedId = hashDiscordId(discordId); const userMapping = await UserMapping.findOne({ where: { hashedDiscordId: hashedId } }); if (userMapping) { await interaction.reply(`该用户的自定义名称:${userMapping.customName}`); } else { await interaction.reply('未找到对应的自定义名称'); } } };
优缺点:
- 优点:查询速度快(直接索引匹配)、实现简单
- 缺点:若固定盐泄露,攻击者可针对生成彩虹表(但300条数据量极小,实际风险低)
方案2:哈希前缀过滤+随机盐(更高安全性)
保留随机盐的高安全性,同时通过哈希前缀减少需要验证的记录数,避免全表遍历。
实现步骤:
- 数据库表结构:
class UserMapping extends Model {} UserMapping.init({ customName: { type: DataTypes.STRING, allowNull: false }, hashPrefix: { type: DataTypes.STRING(16), // 取SHA-256哈希的前16位 allowNull: false }, salt: { type: DataTypes.STRING(32), allowNull: false }, fullHashedId: { type: DataTypes.STRING(64), allowNull: false } }, { sequelize, modelName: 'UserMapping' }); // 为hashPrefix创建索引,加速查询 UserMapping.addIndex({ fields: ['hashPrefix'] });
- 存储逻辑:
function generateRandomSalt() { return crypto.randomBytes(16).toString('hex'); } async function storeUserMapping(discordId, customName) { const salt = generateRandomSalt(); const fullHash = crypto.pbkdf2Sync(discordId, salt, 10000, 32, 'sha256').toString('hex'); const hashPrefix = fullHash.slice(0, 16); await UserMapping.create({ customName, hashPrefix, salt, fullHashedId: fullHash }); }
- 查询逻辑:
async function getCustomNameByDiscordId(discordId) { // 先计算临时哈希(用任意盐,只取前缀匹配) const tempHash = crypto.pbkdf2Sync(discordId, 'dummy-salt', 1, 32, 'sha256').toString('hex'); const queryPrefix = tempHash.slice(0, 16); // 查询所有前缀匹配的记录 const candidates = await UserMapping.findAll({ where: { hashPrefix: queryPrefix } }); // 逐一验证记录的盐是否匹配 for (const candidate of candidates) { const computedHash = crypto.pbkdf2Sync(discordId, candidate.salt, 10000, 32, 'sha256').toString('hex'); if (computedHash === candidate.fullHashedId) { return candidate.customName; } } return null; }
优缺点:
- 优点:随机盐保证高安全性,前缀过滤大幅减少验证记录数(300条数据下几乎瞬间完成)
- 缺点:比固定盐方案多一步验证,但数据量小无性能问题
方案3:可逆AES加密(直接匹配查询)
放弃哈希改用加密,可直接对输入ID加密后匹配数据库中的密文,实现快速查询。
实现步骤:
- 加密/解密逻辑:
const ENCRYPTION_KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex'); // 32字节密钥(AES-256) const IV_LENGTH = 16; function encryptDiscordId(discordId) { const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipheriv('aes-256-cbc', ENCRYPTION_KEY, iv); let encrypted = cipher.update(discordId); encrypted = Buffer.concat([encrypted, cipher.final()]); return `${iv.toString('hex')}:${encrypted.toString('hex')}`; } function decryptDiscordId(encryptedId) { const [ivHex, encryptedHex] = encryptedId.split(':'); const iv = Buffer.from(ivHex, 'hex'); const encrypted = Buffer.from(encryptedHex, 'hex'); const decipher = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, iv); let decrypted = decipher.update(encrypted); decrypted = Buffer.concat([decrypted, decipher.final()]); return decrypted.toString(); }
- 数据库模型与查询:
class UserMapping extends Model {} UserMapping.init({ customName: { type: DataTypes.STRING, allowNull: false }, encryptedDiscordId: { type: DataTypes.STRING, allowNull: false, unique: true } }, { sequelize, modelName: 'UserMapping' }); // 查询示例 const encryptedInputId = encryptDiscordId(inputDiscordId); const userMapping = await UserMapping.findOne({ where: { encryptedDiscordId: encryptedInputId } });
优缺点:
- 优点:查询速度最快,加密可逆(若需恢复原始ID)
- 缺点:需严格管理加密密钥,密钥泄露会导致所有ID泄露
选择建议:
- 若追求最简单实现且可接受固定盐的微小风险,选方案1
- 若要最高安全性且不介意少量验证开销,选方案2
- 若需要可逆恢复原始ID,选方案3
内容的提问来源于stack exchange,提问作者LeCarbonator
相关产品推荐
相关产品推荐

