1. 项目概述为什么要在OpenWrt上折腾SSH双因素认证如果你和我一样把家里的路由器刷成了OpenWrt那它大概率已经成了你网络的核心枢纽。除了路由你可能还用它跑了Docker、挂载了硬盘做轻量NAS或者部署了一些自用的服务。这时SSH就是通往这个“数字堡垒”的管理员通道。默认的密码登录在如今撞库、暴力破解横行的网络环境下就像给大门只上了一把普通的挂锁安全感实在有限。双因素认证2FA就是给这把锁再加一道指纹或者动态密码锁。Google Authenticator实现的基于时间的一次性密码TOTP是其中非常经典和可靠的一种方式。它不依赖短信避免了SIM卡劫持风险也不需要额外的硬件令牌只需你手机上的一个App。这个项目的核心就是在OpenWrt的SSH服务上集成这套机制让登录时除了密码还必须输入手机上Google AuthenticatorApp生成的、每30秒变化一次的6位数字码。但这里有个很实际的痛点万一你手机丢了、没电了或者App数据没了岂不是把自己锁在门外了所以一个成熟的方案必须包含“逃生通道”。这就是标题里“附密码 fallback 端口”的由来。它的思路很巧妙我们配置两个SSH服务实例监听不同的端口。比如主端口22强制要求密码TOTP双因素认证而备用端口2222或其他任意端口则回退到传统的密码认证。平时我们只用端口22享受双因素的安全真到了紧急情况我们可以通过端口2222仅用密码登录进去修复配置。这既提升了安全性又避免了“作茧自缚”的风险。接下来我会带你一步步实现这个配置。整个过程涉及OpenWrt的包管理、SSH服务配置、PAM可插拔认证模块集成以及防火墙规则调整。我会把每个步骤的原理、可能遇到的坑以及我的实操心得都讲清楚让你不仅能配通更能理解背后的逻辑。2. 核心组件与原理浅析在动手之前花几分钟了解一下核心组件是如何协同工作的能帮你更好地排查问题。整个认证流程涉及三个关键部分OpenSSH-Server、PAM和Google Authenticator的PAM模块。2.1 OpenSSH, PAM与Google Authenticator的关系OpenSSH是提供SSH服务的软件。当有连接尝试时它负责处理加密、会话建立并将认证请求转发给系统的认证机制。PAM是一个强大的、模块化的认证框架。你可以把它想象成一个认证“流水线”或“插件系统”。SSH服务并不直接处理密码或密钥而是把“用户XXX想登录提供了凭证YYY”这个任务交给PAM。PAM则根据配置文件/etc/pam.d/sshd按顺序调用一系列模块来验证这个请求。每个模块负责一项具体的认证工作比如检查密码pam_unix.so、限制尝试次数pam_tally2.so或者就是我们今天要用的验证TOTP码pam_google_authenticator.so。Google Authenticator PAM模块就是这个流水线上的一个特殊工位。它的工作流程是用户首次运行时模块会生成一个密钥Secret Key和一组备用代码Scratch Codes并显示给用户。用户需要把这个密钥录入到手机App如Google Authenticator、Authy、Microsoft Authenticator等中。认证时用户除了输入密码还需要输入手机App根据当前时间和共享密钥计算出的6位动态码。PAM模块收到动态码后使用本地存储的密钥和当前时间进行相同的计算比对结果。一致则通过该模块的认证。2.2 TOTP协议与备用端口方案设计TOTP原理简述它基于HMAC-SHA1算法。你和服务器这里指OpenWrt共享一个密钥。认证时服务器和你的手机App都取当前的Unix时间戳通常以30秒为一个时间窗口用这个密钥和当前时间窗口值通过算法计算出一个哈希值再从中截取出6位数字。由于服务器和手机的时间大致同步且算法相同所以算出的结果在同一个30秒窗口内是一致的。这保证了动态码的一次性和时效性。备用端口Fallback Port方案设计这是本方案的精髓也是安全性与可用性的平衡点。我们通过运行两个dropbearOpenWrt默认的轻量级SSH服务器实例来实现。主实例端口22使用完整的PAM认证栈其中包含了pam_google_authenticator.so模块。这意味着登录时必须同时满足密码正确且TOTP码正确。备用实例端口2222使用一个简化版的PAM配置移除了pam_google_authenticator.so模块只进行传统的密码认证。这个端口不应该对公网开放仅在内部网络或紧急情况下使用。这样设计的好处是日常所有外部访问都经过坚固的双因素认证。而备用通道的存在确保了管理员在极端情况下如手机丢失且未备份密钥不会永久失去访问权限避免了整个系统“变砖”的风险。你需要做的就是通过防火墙规则严格限制对备用端口的访问来源。3. 环境准备与软件包安装首先通过SSH登录到你的OpenWrt路由器。确保你有root权限并且系统软件源已正确配置。不同版本的OpenWrt软件源地址可能不同如果安装失败可能需要检查/etc/opkg/distfeeds.conf文件。3.1 安装必需软件包我们需要安装三个核心包google-authenticator-libpam 提供PAM模块pam_google_authenticator.so。libqrencode 用于在终端生成二维码方便将密钥录入手机。nano或vim 一个你熟悉的文本编辑器。OpenWrt默认的vi可能比较简陋。opkg update opkg install google-authenticator-libpam libqrencode nano注意opkg update是更新软件包列表非常重要。如果遇到“Package google-authenticator-libpam not found”的错误可能是因为你的OpenWrt版本较旧或软件源架构不匹配。可以尝试在OpenWrt官方软件包仓库网站查找对应版本的正确包名。安装完成后可以验证PAM模块是否存在ls /lib/security/pam_google_authenticator.so如果能看到这个文件说明模块安装成功。3.2 为用户初始化Google Authenticator这个步骤需要为每一个需要通过SSH双因素登录的系统用户单独执行。通常我们只为root用户配置因为管理OpenWrt主要使用root。切换到root用户如果当前不是su -运行初始化命令google-authenticator接下来会有一系列交互式提问这是最关键的一步Do you want authentication tokens to be time-based (y/n) y 输入y选择基于时间的令牌TOTP这是标准模式。屏幕上会显示一个大大的二维码如果终端支持以及一行secret key密钥和Verification code当前验证码。同时还会给出5个emergency scratch codes紧急备用码。Do you want me to update your “/root/.google_authenticator” file? (y/n) y 输入y将配置保存到用户家目录下的隐藏文件中。Do you want to disallow multiple uses of the same authentication token? (y/n) y 输入y禁止重复使用同一个令牌提升安全性。By default, tokens are good for 30 seconds... Do you want to do so? (y/n) n 输入n不延长窗口期。保持30秒的默认值在时间稍有偏差时系统会自动尝试前后一个窗口共90秒通常足够。Do you want to enable rate-limiting? (y/n) y 输入y启用速率限制。这能防止暴力破解动态码最多每30秒允许3次尝试。实操心得立即扫码/备份 在程序退出前务必用你的认证App如Google Authenticator扫描屏幕上的二维码或者手动输入那串secret key。同时把5个紧急备用码抄下来保存在安全的地方比如密码管理器。这是你丢失手机后的救命稻草。密钥文件 所有配置密钥、备用码、设置都保存在/root/.google_authenticator文件中。备份这个文件就相当于备份了你的双因素认证。你可以把它加密后存到别处。测试 初始化完成后先不要急着修改SSH配置。打开手机App看到生成的6位数字码在跳动。在终端里运行google-authenticator -d -r 3 -w 3可以再次显示二维码和密钥信息方便你核对。4. 配置PAM与双SSH实例这是整个配置的核心部分我们需要修改PAM配置来启用Google Authenticator并配置两个Dropbear实例。4.1 配置PAM启用Google AuthenticatorOpenWrt中SSH的PAM配置文件通常是/etc/pam.d/sshd。但为了不影响其他可能依赖PAM的服务更稳妥的做法是为SSH创建一个独立的配置或者直接修改它。我们先备份原文件cp /etc/pam.d/sshd /etc/pam.d/sshd.backup然后编辑/etc/pam.d/sshd文件。你需要理解PAM配置的语法每一行定义一个模块及其控制标志、参数。控制标志如required表示本模块必须成功但失败不会立即返回会继续执行后续模块sufficient表示本模块成功即足以通过认证。我们需要在密码认证模块之后添加Google Authenticator模块。找到包含pam_unix.so负责密码验证的那一行通常长这样auth required pam_unix.so在它下面添加一行auth required pam_google_authenticator.so修改后的相关部分看起来像这样# Standard Un*x authentication. auth required pam_unix.so # Google Authenticator for 2FA auth required pam_google_authenticator.so重要提示pam_google_authenticator.so模块必须放在pam_unix.so模块之后。因为PAM是按顺序执行的我们需要先验证密码是否正确再验证动态码。如果顺序反了会导致即使用户不存在也会要求输入动态码这可能泄露用户存在信息。4.2 配置主SSH实例端口22启用2FAOpenWrt的SSH服务器是dropbear它的配置文件是/etc/config/dropbear。这个文件使用UCI统一配置接口语法。首先查看当前配置uci show dropbear通常会显示一个名为dropbear.dropbear[0]的配置段里面包含了Port、PasswordAuth等选项。我们需要确保主实例监听22端口并启用密码认证因为2FA包含了密码环节uci set dropbear.dropbear[0].PasswordAuthon uci set dropbear.dropbear[0].RootPasswordAuthon uci set dropbear.dropbear[0].Port22 # 确保接口监听所有地址或者根据需求调整 uci set dropbear.dropbear[0].InterfacelanInterface选项可以限制SSH监听在哪个网络接口上例如只在内网lan接口监听这样外网就无法直接连接SSH安全性更高。这取决于你的网络拓扑。4.3 创建备用SSH实例Fallback端口UCI允许配置多个dropbear实例。我们将创建一个新的实例专门用于备用端口。添加一个新的dropbear配置段uci add dropbear dropbear uci rename dropbear.dropbear[-1]fallback第一条命令添加一个新段dropbear[-1]指代刚添加的最后一个段。第二条命令给它起个名字叫fallback方便识别。配置这个备用实例uci set dropbear.fallback.enable1 uci set dropbear.fallback.PasswordAuthon uci set dropbear.fallback.RootPasswordAuthon uci set dropbear.fallback.Port2222 # 可以换成任何非22的端口 uci set dropbear.fallback.Interfacelan # 同样建议只监听内网 # 关键指定一个不使用Google Authenticator的PAM配置文件 uci set dropbear.fallback.PamAuthenticationon这里的关键是PamAuthenticationon它告诉dropbear使用PAM。但默认它还是会去找/etc/pam.d/sshd。为了让这个实例跳过2FA我们有两种方案方案A推荐创建独立的PAM配置文件复制原SSH PAM配置并移除Google Authenticator行cp /etc/pam.d/sshd /etc/pam.d/sshd_fallback sed -i /pam_google_authenticator.so/d /etc/pam.d/sshd_fallback修改dropbear.fallback的配置指定使用这个新的PAM文件。但标准的UCIdropbear配置可能没有直接指定PAM服务名的选项。这时我们需要更底层的方法。方案B通过启动参数指定更可靠Dropbear的启动参数可以在UCI中通过GatewayPorts或ExtraArgs取决于版本传递。更通用的方法是直接修改启动脚本。但OpenWrt 21.02之后我们可以利用/etc/config/dropbear中的option BannerFile或自定义选项并通过/etc/rc.local或自定义initscript来启动第二个实例。不过这变得复杂了。一个更简洁的实践方案我们不为备用实例配置PAM而是利用Dropbear的-s参数禁用PAM使其仅使用系统密码文件/etc/shadow进行认证。这样它就会完全忽略PAM配置自然也绕过了Google Authenticator。uci set dropbear.fallback.PamAuthenticationoff # 或者保持默认的off这样备用实例就变成了一个纯密码认证的SSH服务。这正是我们需要的“逃生通道”。提交所有UCI更改uci commit dropbear重启dropbear服务以应用主实例配置并启动备用实例service dropbear restart service dropbear enable # 检查备用实例是否启动可能需要手动启用 /etc/init.d/dropbear start_fallback # 如果存在这样的脚本或者 service dropbear start # 通常会启动所有enable的实例你可以用netstat -tlnp | grep :22和netstat -tlnp | grep :2222来检查两个端口是否都在监听。5. 防火墙与安全加固配置配置好了服务下一步就是通过防火墙严格控制访问这是安全架构中不可或缺的一环。5.1 配置OpenWrt防火墙firewallOpenWrt使用firewall4nftables作为防火墙后端配置通过/etc/config/firewall进行。为主SSH端口22放行通常为了从外网管理我们会在WAN区域打开22端口。但既然我们已经有了更安全的2FA可以继续保留。如果你希望更安全可以关闭WAN对22端口的访问只通过VPN连接内网后再使用SSH。这里假设你需要在WAN上开放。uci add firewall rule uci set firewall.rule[-1].nameAllow-WAN-SSH-2FA uci set firewall.rule[-1].srcwan uci set firewall.rule[-1].prototcp uci set firewall.rule[-1].dest_port22 uci set firewall.rule[-1].targetACCEPT严格限制备用端口2222的访问这是安全的关键。绝对不要在WAN区域开放2222端口。我们应该只允许来自受信任IP地址比如你的家庭内网网段或者你固定公网IP的连接。uci add firewall rule uci set firewall.rule[-1].nameAllow-LAN-Fallback-SSH uci set firewall.rule[-1].srclan # 仅限lan区域即内网 # 或者使用src_ip指定一个更精确的IP段如 192.168.1.0/24 # uci set firewall.rule[-1].src_ip192.168.1.0/24 uci set firewall.rule[-1].prototcp uci set firewall.rule[-1].dest_port2222 uci set firewall.rule[-1].targetACCEPT如果你有固定的公网IP并且想在紧急情况下从外部访问备用端口可以添加一条非常具体的规则uci add firewall rule uci set firewall.rule[-1].nameAllow-Trusted-IP-Fallback-SSH uci set firewall.rule[-1].srcwan uci set firewall.rule[-1].src_ip你的.公网.IP.地址 uci set firewall.rule[-1].prototcp uci set firewall.rule[-1].dest_port2222 uci set firewall.rule[-1].targetACCEPT警告谨慎使用此条规则。暴露备用端口会增加风险面。确保你的root密码强度极高并考虑定期更换。提交并应用防火墙配置uci commit firewall service firewall restart5.2 其他安全建议禁用Root的密码登录可选但推荐对于主端口22既然我们已经有了2FA可以保留root密码登录。但更激进的安全策略是在主端口上禁用root密码登录强制使用SSH密钥2FA。这需要先生成SSH密钥对并在/etc/ssh/sshd_configDropbear是/etc/dropbear/authorized_keys中配置公钥然后将dropbear.dropbear[0].RootPasswordAuth设为off。这样攻击者即使猜到密码没有密钥也无法登录。备用端口2222则保持root密码登录作为最后的保障。使用非标准端口将主SSH端口从22改为一个高位端口如50222可以显著减少来自互联网的自动化扫描和攻击噪音。只需修改dropbear.dropbear[0].Port的值并相应调整防火墙规则即可。备用端口也可以用一个不常见的高位端口。Fail2ban在OpenWrt上安装配置fail2ban可以自动封锁多次登录失败的IP地址有效抵御暴力破解。这对于暴露在公网上的端口尤其有用。6. 完整功能测试与验证配置完成后必须进行全面的测试确保双因素认证生效且备用通道工作正常。6.1 测试主SSH端口2FA认证从另一台电脑比如你的笔记本电脑尝试通过主端口登录。ssh root你的OpenWrt路由IP -p 22你会看到和以前不一样的提示Password:输入root密码后不会直接登录而是会看到Verification code:这时打开你手机上的Google Authenticator App找到对应OpenWrt的条目输入当前显示的6位数字码。测试场景与预期结果密码正确动态码正确 成功登录。✅密码正确动态码错误或超时 登录被拒绝提示“Permission denied”。❌密码错误 登录被拒绝不会进入动态码输入环节。❌实操心得时间同步是关键 TOTP依赖于时间。确保你的OpenWrt路由器时间准确。可以安装ntpclient或chrony来同步时间opkg install chrony service chronyd start service chronyd enable。手机时间也请确保是自动同步的。“窗口”问题 如果你输入动态码时总是提示错误但确认密码和动态码都正确可能是时间不同步超出了允许的窗口。google-authenticator初始化时如果回答了“n” to “Do you want to do so?”它会使用默认的“窗口漂移”为1即允许前后一个30秒周期。你可以尝试在输入动态码时稍等几秒或者快速连续输入两次当前周期和下一个周期的码。6.2 测试备用SSH端口密码认证同样从测试电脑上尝试通过备用端口登录。ssh root你的OpenWrt路由IP -p 2222这次你应该只会被要求输入密码Password:输入正确的root密码后应该能直接登录不会要求输入动态码。重要这个测试最好从你预设的允许访问备用端口的IP地址进行例如你的内网IP。如果你设置了只允许特定IP访问从其他IP尝试连接应该会被防火墙拒绝。6.3 模拟“逃生”场景这是最重要的测试。假设你手机无法使用没电、丢失你需要通过备用端口登录来重新配置或禁用2FA。使用备用端口2222和密码成功登录。登录后你可以选择为root用户重新初始化2FA 运行google-authenticator会生成新的密钥和二维码。你需要用新手机扫描。注意这会覆盖旧的.google_authenticator文件旧的令牌立即失效。临时禁用某个用户的2FA 直接编辑对应用户的PAM配置。最快速的方法是注释掉/etc/pam.d/sshd中pam_google_authenticator.so那一行在行首加#然后重启dropbear服务。但这会全局禁用2FA。更精细的控制 PAM支持非常复杂的规则。你可以创建另一个PAM配置文件通过pam_succeed_if.so模块判断来源IP或用户来决定是否要求2FA。但这属于高级用法。7. 故障排查与日常维护指南即使按照步骤操作也可能会遇到问题。这里列出一些常见问题及解决方法。7.1 常见问题速查表问题现象可能原因排查步骤与解决方案SSH连接主端口输入密码后直接“Permission denied”没要求动态码。1. PAM配置中模块顺序错误。2.pam_google_authenticator.so模块未正确安装或路径不对。3. 对应用户的~/.google_authenticator文件不存在或权限错误。1. 检查/etc/pam.d/sshd确保pam_google_authenticator.so在pam_unix.so之后。2. 运行ls /lib/security/pam_google_authenticator.so确认文件存在。3. 检查/root/.google_authenticator文件是否存在ls -la /root/权限应为600-rw-------。输入正确的密码和动态码仍提示“Permission denied”。1. 系统时间不同步。2. 手机App中添加的条目密钥错误。3. PAM模块的“窗口”设置太窄。1. 在OpenWrt上运行date在手机上查看时间对比是否相差超过1分钟。安装时间同步服务。2. 在OpenWrt上运行google-authenticator -d -r 3 -w 3显示密钥与手机App中录入的比对。或者用备用码登录后重新初始化。3. 初始化时如果选择了不延长窗口默认是允许前后1个周期的。尝试在动态码刚刷新时立即输入。备用端口无法连接。1. 防火墙未放行该端口或限制了来源IP。2. 第二个dropbear实例未成功启动。3. 端口冲突。1. 运行netstat -tlnp | grep :2222查看端口是否监听。运行logread | grep dropbear查看服务日志。2. 检查UCI配置uci show dropbear确认fallback段enable1。3. 运行service dropbear restart并再次检查。确认2222端口未被其他程序占用。运行google-authenticator命令提示“not found”。软件包未成功安装。运行opkg list-installed | grep google-authenticator确认。确保运行了opkg update并检查软件源配置。登录时提示“PAM unable to dlopen(...)”或“PAM symbol not found”。PAM模块依赖的库缺失或架构不匹配。安装完整的PAM支持包opkg install libpam。确保安装的google-authenticator-libpam版本与系统架构如aarch64_cortex-a53, mipsel_24kc等匹配。7.2 日常维护与备份建议备份密钥文件 定期或在每次重置后将/root/.google_authenticator文件加密备份到其他安全位置。这个文件包含了所有秘密。保管好紧急备用码 将初始化时生成的5个紧急备用码妥善保存。它们是最后的保障。考虑使用兼容的认证器 除了Google Authenticator像Authy、Microsoft Authenticator、Bitwarden、1Password等都支持扫描TOTP二维码添加账户。Authy的优势是多设备同步且备份在云端需权衡安全性。路由器重置/升级后的恢复 如果你需要重置OpenWrt或升级固件在操作前确保你有备用端口密码登录的能力。备份/root/.google_authenticator文件和/etc/pam.d/sshd以及/etc/config/dropbear、/etc/config/firewall配置文件。升级后重新安装软件包恢复配置文件并放回密钥文件注意权限为600。审计日志 定期检查logread或/var/log/auth.log如果启用关注失败的登录尝试尤其是针对备用端口的尝试以便及时发现异常。配置完成后你的OpenWrt路由器的SSH管理入口安全性就得到了质的提升。双因素认证有效抵御了密码泄露或暴力破解的风险而精心设计的备用端口方案又确保了管理的可靠性不会因为认证设备的故障而将自己锁在门外。这套组合拳对于将OpenWrt作为家庭网络核心或边缘服务器的用户来说是一个非常值得投入的安全实践。