MFA,究竟是什么验证码?
MFA 验证码是一种动态安全密码,通常用于登录账号时的第二道安全关卡。它不像普通密码那样固定不变,而是每 30 秒自动刷新一次的 6 位数字,即使别人知道了你的账号密码,没有这个动态码也登不进去 。你通常需要在手机上安装专门的验证器 App(如 Google Authenticator、Microsoft Authenticator 或云厂商自带 App)来查看这个码 。
以上是度娘的解释,简单普及一下这是什么,用在哪里。下面,我们就一起来,为自己的网站加一个mfa验证码吧!相信做完了这些,你就对他没啥陌生感了~
首先,我们先得弄明白,整个流程是什么样的。简单来说,我们需要在网站后台,根据自己的密码或者用户id,作为唯一值,base32 生成一串独有的秘钥。有了,这个秘钥,就可以拼接成一个字符串:
otpauth://totp/【系统名】:【用户名】?secret=【Base32密钥】&issuer=【系统名】
比如,我的这串秘钥是 JBSWY3DPEHPK3PXP,账号zhangsan,那我的绑定码字符串就可以是 otpauth://totp/博客园:zhangsan?secret=JBSWY3DPEHPK3PXP&issuer=博客园。 把这串字符转为二维码,就可以用 Google Authenticator、Microsoft Authenticator 扫码绑定了。这样,就有了一条记录。点开进去,就是一个6位数的数字,还有一个30s的倒计时刷新。这样,后台登录时候,就可以使用用户提交的6位数字,来校验是否一致,登录了。这里给一些,计算这个码的代码吧。这个才是最核心的地方。
//PHP代码
/** * 生成 TOTP MFA 验证码 * @param string $secret Base32 编码的密钥 * @param int $timeStep 时间步长,默认 30 秒 * @param int $digits 验证码位数,默认 6 * @return string */ function totpGenerate(string $secret, int $timeStep = 30, int $digits = 6): string { // Base32 解码 $secret = base32Decode($secret); // 时间计数器 $timestamp = time(); $counter = intdiv($timestamp, $timeStep); // 计数器转 8 字节大端 $counterBytes = pack('N*', 0) . pack('N*', $counter); // HMAC-SHA1 $hmac = hash_hmac('sha1', $counterBytes, $secret, true); // 动态截取 $offset = ord(substr($hmac, -1)) & 0x0F; $binary = unpack('N', substr($hmac, $offset, 4))[1]; $binary &= 0x7FFFFFFF; // 取指定位数 $otp = $binary % (10 ** $digits); return str_pad((string)$otp, $digits, '0', STR_PAD_LEFT); }
import base64
import hashlib
import hmac
import struct
import time
#python
def totp(secret, digits=6, period=30):
key = base64.b32decode(secret, casefold=True)
t = int(time.time() // period)
t_bytes = struct.pack(">Q", t)
h = hmac.new(key, t_bytes, hashlib.sha1).digest()
offset = h[-1] & 0x0F
code = (struct.unpack(">I", h[offset:offset+4])[0] & 0x7FFFFFFF) % (10**digits)
return str(code).zfill(digits)
# 使用
print(totp("JBSWY3DPEHPK3PXP"))
//js
function base32Decode(secret) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; let bits = ''; for (let c of secret.toUpperCase()) { bits += chars.indexOf(c).toString(2).padStart(5, '0'); } let bytes = []; for (let i = 0; i + 8 <= bits.length; i += 8) { bytes.push(parseInt(bits.slice(i, i + 8), 2)); } return Buffer.from(bytes); } function totp(secret, digits = 6, period = 30) { const key = base32Decode(secret); const time = Math.floor(Date.now() / 1000 / period); const timeBuf = Buffer.alloc(8); timeBuf.writeBigUInt64BE(BigInt(time), 0); const hmac = require('crypto').createHmac('sha1', key).update(timeBuf).digest(); const offset = hmac[hmac.length - 1] & 0x0F; const code = (hmac.readUInt32BE(offset) & 0x7FFFFFFF) % (10 ** digits); return code.toString().padStart(digits, '0'); } // 使用 console.log(totp("JBSWY3DPEHPK3PXP"));
//java import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class TOTP { public static String base32Decode(String secret) { return new String(Base64.getDecoder().decode(secret)); } public static String totp(String secret, int digits, int period) throws Exception { byte[] key = Base64.getDecoder().decode(secret); long time = System.currentTimeMillis() / 1000 / period; byte[] data = new byte[8]; for (int i = 8; i-- > 0; time >>>= 8) { data[i] = (byte) time; } Mac mac = Mac.getInstance("HmacSHA1"); mac.init(new SecretKeySpec(key, "HmacSHA1")); byte[] hash = mac.doFinal(data); int offset = hash[hash.length - 1] & 0x0F; int code = ((hash[offset] & 0x7F) << 24) | ((hash[offset + 1] & 0xFF) << 16) | ((hash[offset + 2] & 0xFF) << 8) | (hash[offset + 3] & 0xFF); code = code % (int) Math.pow(10, digits); return String.format("%0" + digits + "d", code); } public static void main(String[] args) throws Exception { System.out.println(totp("JBSWY3DPEHPK3PXP", 6, 30)); } }
最后,来一句总结。其实,本质就是,客户端,用你的秘钥,给你生成当前30s内的6位数码。你传回服务器,服务器也会生成一份,对比一致,就没问题。优点就是,不需要任何费用,还安全。缺点么,大家想吧!
滴水成冰,世间不存在毫无意义的付出,时间终会给你答案。

浙公网安备 33010602011771号