PowerShell TOTP 身份验证器

简介

双因素认证(2FA)已经成为如今最常见的安全机制,而基于时间的一次性密码(TOTP: Time-based One-Time Password)则是其中最广泛使用的技术之一。
我们平时使用的 Google Authenticator、Microsoft Authenticator 都是基于同样的算法。TOTP 是一种根据预共享的密钥与当前时间计算一次性密码的算法。它已被互联网工程任务组接纳为RFC 6238标准,成为主动开放认证(OATH)的基石,并被用于众多多重要素验证系统当中。

TOTP是散列消息认证码(HMAC)当中的一个例子。它结合一个私钥与当前时间戳,使用一个密码散列函数来生成一次性密码。由于网络延迟与时钟不同步可能导致密码接收者不得不尝试多次遇到正确的时间来进行身份验证,时间戳通常以30秒为间隔,从而避免反复尝试。

otpauth URL

所有 TOTP 应用(Google Authenticator /Microsoft Authenticator)都使用统一的 otpauth URL 格式。

otpauth URI 格式说明

otpauth:// 是用于 TOTP/HOTP 身份验证器的通用 URI 格式,通常与二维码一起使用,方便在各类身份验证 App 中导入账号。

基本结构

otpauth://TYPE/LABEL?PARAMETERS
  • TYPE

    • totp:基于时间的验证码(常见,典型为 30 秒刷新)

    • hotp:基于计数器的验证码(较少用于扫码导入场景)

  • LABEL(名称部分)

    格式:

    issuer:account-name
    

    示例:

    GitHub:user@gmail.com
    
  • PARAMETERS

    参数写在 ? 后面,以 & 连接。参数名区分大小写(大多数实现小写)。

    必填参数

    • secret
      Base32 编码的密钥(必须),例如 JBSWY3DPEHPK3PXP

    推荐 / 可选参数

    • issuer:发行商(强烈推荐)

    • algorithm:哈希算法,常见值 SHA1(默认)、SHA256、SHA512

    • digits:验证码长度,常见 6(默认)或 8

    • period:TOTP 的时间窗口(秒),默认 30

    • counter:HOTP 专用计数器(仅 hotp 类型需要)

完整示例(TOTP)

otpauth://totp/GitHub:user@gmail.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&algorithm=SHA1&digits=6&period=30

该 URI 格式最初由 Google Authenticator / 社区推广(称为 “Key URI Format”)

字段是否必需说明
type(totp/hotp)必需指定为时间或计数器类型
label(Issuer:Account)必需(或至少 account)显示名称
secret必需Base32 密钥
issuer推荐服务名称,推荐同时存在于 label 和参数
algorithm可选SHA1/SHA256/SHA512(默认 SHA1)
digits可选验证码长度(默认 6)
period可选(TOTP)刷新周期(秒,默认 30)
counter可选(HOTP)初始计数器值

PowerShell 实现 TOTP 的思路

为了实现一个命令行 TOTP 工具,需要解决几个问题:

  • 读取 otpauth URL
  • 解析 issuer、account、secret、period、digits
  • Base32 → Hex → ByteArray
  • 使用 HMAC-SHA1 计算 hash
  • RFC 规定的动态截断(Dynamic Truncation)
  • 输出指定位数字的验证码
  • UI 循环刷新

PowerShell 具体实现

authenticator.ps1

/* by 01022.hk - online tools website : 01022.hk/zh/dnsedu.html */
# ============================
#  配置:从authenticator.txt加载otpauth URL
# ============================
function Get-OtpAuthLinks {
	param (
		[string]$fileName = "authenticator.txt"  # 默认文件名
	)
	$links = @()
	$authenticatorPath = Join-Path (Get-Location) $fileName
	if (Test-Path $authenticatorPath) {
		$fileLinks = Get-Content $authenticatorPath | Where-Object { $_ -notmatch '^\s*(#|//|;)' -and $_.Trim() -ne '' }
		$links += $fileLinks
	}
	# 示例链接
	if ($links.Count -eq 0) {
		$links += "otpauth://totp/Example:user?secret=DMETBKDJAAY3D2K3&issuer=这是一个示例"
	}
	
	return $links
}

# ============================
#  函数:解析 otpauth URL
# ============================
function Parse-OtpAuthUrl {
	param([string]$url)

	# 先解码 URL,防止 URL 编码的问题
	$urlDecoded = [System.Uri]::UnescapeDataString($url).Trim()

	# 移除 "otpauth://totp/" 部分
	$clean = $urlDecoded -replace "^otpauth://totp/", ""

	# 按 "?" 分割 URL,分为账户部分和查询参数部分
	$parts = $clean -split "\?"
	$namePart = $parts[0]
	$queryPart = $parts[1]

	# 解析账户部分
	$account = $namePart
	$issuerFromName = ""
	if ($namePart -match "(.+?):(.+)") {
		$issuerFromName = $matches[1]
		$account = $matches[2]
	}

	# 解析查询参数部分
	$params = @{}
	foreach ($q in $queryPart -split "&") {
		$kv = $q -split "="
		$params[$kv[0]] = $kv[1]
	}

	# 获取 period 和 digits,若无则使用默认值
	$period = if ($params["period"]) { [int]$params["period"] } else { 30 }
	$digits = if ($params["digits"]) { [int]$params["digits"] } else { 6 }
	$issuer = if ($params["issuer"]) { $params["issuer"] } else { $issuerFromName }

	# 返回包含解析信息的对象
	return [PSCustomObject]@{
		Issuer    = $issuer
		Account   = $account
		Secret    = $params["secret"]
		Period    = $period
		Digits    = $digits
	}
}

function ConvertFrom-Base32 {
	param([string]$Base32String)
	
	$Base32String = $Base32String.ToUpper() -replace '[=]', '' -replace '[^A-Z2-7]', ''
	$base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
	$bytes = [System.Collections.Generic.List[byte]]::new()
	
	$buffer = 0
	$bitsLeft = 0
	
	foreach ($char in $Base32String.ToCharArray()) {
		$value = $base32Chars.IndexOf($char)
		if ($value -eq -1) { continue }
		
		$buffer = ($buffer -shl 5) -bor $value
		$bitsLeft += 5
		
		if ($bitsLeft -ge 8) {
			$bytes.Add([byte](($buffer -shr ($bitsLeft - 8)) -band 0xFF))
			$bitsLeft -= 8
		}
	}
	
	return $bytes.ToArray()
}

function Get-Otp($SECRET, $LENGTH, $WINDOW){
	$enc = [System.Text.Encoding]::UTF8
	$hmac = New-Object -TypeName System.Security.Cryptography.HMACSHA1
	$hmac.key = Convert-HexToByteArray(Convert-Base32ToHex(($SECRET.ToUpper())))
	$timeBytes = Get-TimeByteArray $WINDOW
	$randHash = $hmac.ComputeHash($timeBytes)
	
	$offset = $randhash[($randHash.Length-1)] -band 0xf
	$fullOTP = ($randhash[$offset] -band 0x7f) * [math]::pow(2, 24)
	$fullOTP += ($randHash[$offset + 1] -band 0xff) * [math]::pow(2, 16)
	$fullOTP += ($randHash[$offset + 2] -band 0xff) * [math]::pow(2, 8)
	$fullOTP += ($randHash[$offset + 3] -band 0xff)

	$modNumber = [math]::pow(10, $LENGTH)
	$otp = $fullOTP % $modNumber
	$otp = $otp.ToString("0" * $LENGTH)
	return $otp
}

function Get-TimeByteArray($WINDOW) {
	$span = [int]((Get-Date).ToUniversalTime() - [datetime]'1970-01-01').TotalSeconds
	$unixTime = [Convert]::ToInt64([Math]::Floor($span/$WINDOW))
	$byteArray = [BitConverter]::GetBytes($unixTime)
	[array]::Reverse($byteArray)
	return $byteArray
}

function Convert-HexToByteArray($hexString) {
	$byteArray = $hexString -replace '^0x', '' -split "(?<=\G\w{2})(?=\w{2})" | %{ [Convert]::ToByte( $_, 16 ) }
	return $byteArray
}

function Convert-Base32ToHex($base32) {
	$base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
	$bits = "";
	$hex = "";

	for ($i = 0; $i -lt $base32.Length; $i++) {
		$val = $base32chars.IndexOf($base32.Chars($i));
		$binary = [Convert]::ToString($val, 2)
		$staticLen = 5
		$padder = '0'
		$bits += Add-LeftPad $binary.ToString()  $staticLen  $padder
	}

	for ($i = 0; $i+4 -le $bits.Length; $i+=4) {
		$chunk = $bits.Substring($i, 4)
		$intChunk = [Convert]::ToInt32($chunk, 2)
		$hexChunk = Convert-IntToHex($intChunk)
		$hex = $hex + $hexChunk
	}
	return $hex;
}

function Convert-IntToHex([int]$num) {
	return ('{0:x}' -f $num)
}

function Add-LeftPad($str, $len, $pad) {
	if(($len + 1) -ge $str.Length) {
		while (($len - 1) -ge $str.Length) {
			$str = ($pad + $str)
		}
	}
	return $str;
}

# ============================
#  主循环
# ============================
$accounts = Get-OtpAuthLinks | ForEach-Object { Parse-OtpAuthUrl $_ }
$lastCycle = -1
while ($true) {
	$now = [int]((Get-Date).ToUniversalTime() - [datetime]'1970-01-01').TotalSeconds
	$period = $accounts[0].Period
	$cycle = [Convert]::ToInt64([Math]::Floor($now/$period))
	$remain = $period - ($now % $period)

	if ($cycle -ne $lastCycle) {
		$lastCycle = $cycle
		Clear-Host
		Write-Host "========== 身份验证器 =========="
		foreach ($acc in $accounts) {
			$code = Get-Otp -SECRET $acc.Secret -LENGTH $acc.Digits -WINDOW $acc.Period
			Write-Host ("网站: " + $acc.Issuer)
			Write-Host ("账号: " + $acc.Account)
			Write-Host "验证码: " -NoNewline
			Write-Host $code -ForegroundColor Green
			Write-Host "----------------------------------------"
		}
	}

	[Console]::SetCursorPosition(0, [Console]::CursorTop)
	Write-Host ("剩余时间: $remain 秒") -NoNewLine
	Start-Sleep -Milliseconds 950
}

authenticator.txt

# 身份验证器otpauth URL配置文件
# 
# 每行一个otpauth URL
# 示例:otpauth://totp/GitHub:user1?secret=DMETBKDJAAY3D2K3&issuer=GitHub

使用方法

  • 在脚本目录创建一个文件:authenticator.txt

    • 每行写一个 otpauth 链接,例如:
      otpauth://totp/GitHub:user@gmail.com?secret=ABC123&issuer=GitHub
  • 将脚本保存为:authenticator.ps1,编码 UTF8 BOM

  • 运行程序:authenticator.ps1

也可在Github进行下载

运行效果

图片

MATLAB代码实现了一个基于多种智能优化算法优化RBF神经网络的回归预测模型,其核心是通过智能优化算法自动寻找最优的RBF扩展参数(spread),以提升预测精度。 1.主要功能 多算法优化RBF网络:使用多种智能优化算法优化RBF神经网络的核心参数spread。 回归预测:对输入特征进行回归预测,适用于连续值输出问题。 性能对比:对比不同优化算法在训练集和测试集上的预测性能,绘制适应度曲线、预测对比图、误差指标柱状图等。 2.算法步骤 数据准备:导入数据,随机打乱,划分训练集和测试集(默认7:3)。 数据归一化:使用mapminmax将输入和输出归一化到[0,1]区间。 标准RBF建模:使用固定spread=100建立基准RBF模型。 智能优化循环: 调用优化算法(从指定文件夹中读取算法文件)优化spread参数。 使用优化后的spread重新训练RBF网络。 评估预测结果,保存性能指标。 结果可视化: 绘制适应度曲线、训练集/测试集预测对比图。 绘制误差指标(MAE、RMSE、MAPE、MBE)柱状图。 十种智能优化算法分别是: GWO:灰狼算法 HBA:蜜獾算法 IAO:改进天鹰优化算法,改进①:Tent混沌映射种群初始化,改进②:自适应权重 MFO:飞蛾扑火算法 MPA:海洋捕食者算法 NGO:北方苍鹰算法 OOA:鱼鹰优化算法 RTH:红尾鹰算法 WOA:鲸鱼算法 ZOA:斑马算法
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值