Azerothcore使用的SRP6无密码认证NodeJS实现
前言:最近正在折腾一个在国内已经消失的游戏。读书的时候玩得多,后面参加工作后不怎么玩了。但有几个老朋友,偶尔还是想进去看看艾泽拉斯。在Github上发现了一个很牛的开源项目——Azerothcore,是魔兽世界WLK 3.3.5服务端。搭建好了后,可以游玩。但还需要给朋友提供注册服务。Github上有一个PHP的注册项目。而我主要使用语言是JavaScript。因此想用JS实现注册页面。由于魔兽服务端和客户端之间使用的是
SRP6
的校验方式,用户数据库中并不存在用户密码。存的是注册时生成的salt
和verifier
。
本文的主要内容是关于注册时用户名和密码相关的SRP6
计算、校验。不涉及到前端的表单页面、web服务端数据库操作逻辑。不包含完整的注册流程业务场景。
了解SRP6
动手之前了解下SRP6
:
SRP6
(Secure Remote Password Protocol 6)是一种用于安全远程密码验证的协议。它通过 Diffie-Hellman 密钥交换和哈希函数,提供了一种安全且有效的方法来保护密码传输过程中的安全性。
在 SRP6
中,为了增强安全性,使用了一些额外的技术手段,比如使用了一个随机的“salt”
值和一个“verifier”
值来防止预计算字典攻击。因此,SRP6
不仅能够保护密码的安全传输,同时还能够防止密码被暴力破解。
SRP6
的工作原理是在客户端和服务器之间建立一个共享密钥。在建立共享密钥的过程中,客户端和服务器会相互发送一些消息,并进行计算,最终建立起一个共享密钥。这个共享密钥只有客户端和服务器知道,第三方无法获得。
实现加密
先看下auth
数据库的account
表数据结构,示例:
一个用户的校验数据包含下面几个字段(确实不存在密码):
username
salt
verfier
已知用户名username
,是由用户输入。下面开始计算salt
和verfier
。
1. 随机salt
:
/**
* 获取注册数据
* @param username string 用户名
* @param password string 密码
* @returns [Buffer, Buffer] 注册数据
*/
function getRegistrationData(username:string, password:string): [Buffer, Buffer] {
const salt: Buffer = crypto.randomBytes(32);
const verifier: Buffer = calculateSRP6Verifier(username, password, salt);
return [salt, verifier];
}
2. 计算verifier
:
calculateSRP6Verifier函数以username
、password
和salt
为参数输入,返回verifier
。
首先计算h1
,它是用username
和password
的大写拼接的SHA1哈希。然后计算h2
,它是salt
和h1
的拼接的SHA1哈希。最后,它通过将生成器_g
和h2
与模数_N
的模幂运算,并将结果转换为长度为32的buffer缓冲,即得到verifier
。
import crypto from "crypto";
import { toBigIntLE, toBufferLE } from "bigint-buffer";
import sha1 from "sha1";
import { modPow } from 'bigint-crypto-utils';
// 常量
const _g: bigint = BigInt(7);
const _N: bigint = BigInt('0x894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7');
/**
* 计算SRP6验证器
* @param username string 用户名
* @param password string 密码
* @param salt Buffer 盐
* @returns Buffer 验证器
*/
function calculateSRP6Verifier(username: string, password: string, salt: Buffer): Buffer {
const h1: Buffer = Buffer.from(sha1((username + ':' + password).toUpperCase()), 'hex') ;
const h2: bigint = toBigIntLE(Buffer.from(sha1(Buffer.concat([salt, h1])), 'hex'))
let verifier: Buffer = toBufferLE(modPow(_g, h2, _N), 32)
verifier = Buffer.concat([verifier, Buffer.alloc(32 - verifier.length, '\0')]);
return verifier;
}
依赖说明:
crypto
:Node.js内置的加密模块,用于生成随机字节和哈希函数。bigint-buffer
:用于在Buffer和BigInt之间进行转换的库。sha1
:用于计算SHA1哈希的库。bigint-crypto-utils
:用于在BigInt上执行加密操作的库。
校验函数
当需要修改密码的时候或其他需要检验用户的场景。需要和服务器进行校验。 那么需要实现一个校验函数。
verifySRP6函数以username
、password
、salt
、verifier
为参数输入,并返回一个布尔值,表示否已通过身份验证。
salt
、verifier
是注册时存进数据库的,按用户名查询可得。用户密码输入后根据注册时生成的salt
是可以再次计算出verifier
,然后这2个verifier
进行对比。完成校验。
/**
* 验证SRP6
* @param user string 用户名
* @param pass string 密码
* @param salt Buffer 盐
* @param verifier Buffer 验证器
* @returns boolean 是否验证成功
*/
function verifySRP6(user: string, pass: string, salt: Buffer, verifier: Buffer): boolean {
const paddedVerifier: Buffer = calculateSRP6Verifier(user, pass, salt)
return verifier.equals(paddedVerifier);
}
总结:
本文介绍了一种安全的远程密码验证协议SRP6
,SRP6
协议通过 Diffie-Hellman 密钥交换和哈希函数提供了一种安全且有效的方法来保护密码传输过程中的安全性。然后通过JavaScript的方式实现了SRP6
加密函数。
转载自:https://juejin.cn/post/7225454746949435449