一、问题起点:代考的本质是"身份绑定"失效
做在线考试系统的人大多踩过同一个坑:考前你以为自己核验了考生,考中却无法确认屏幕后面坐的还是不是同一个人。传统账号密码体系下,账号是一个可以被共享、被借用的抽象凭证,它和"活人"之间天然存在一道裂缝。这道裂缝正是代考得以发生的土壤。
把问题拆开来看,代考并不神秘,它只是系统性地利用了三类失效:
- 身份锚点失效:报名时核验过一次身份证,但开考时再无手段确认操作者是本人,账号与活人脱节。
- 时间解耦失效:准考证长期有效、入口长期开放,攻击者可以"先借账号摸清题型,再换人进场"。
- 设备解耦失效:账号可在任意电脑、任意浏览器登录,没有任何东西把"账号"钉死到"这台设备、这个考生"。
要堵住这三类失效,核心思路不是把题目加密得多高级,而是给身份核验加一道动态、不可预测、与时间和设备强绑定的因子。这就是双因素认证(2FA)在考试场景的用武之地,而其中成本最低、可审计性最强的一种,就是基于 OATH TOTP 的动态口令。
二、先把 TOTP 的账算清楚:动态口令到底安全在哪
在谈落地之前,先统一对 TOTP 原理的工程认知,否则后面的绑定方案无从设计。
TOTP(Time-based One-Time Password)是 OTP 双因素体系里最主流的一种。它的公式可以一句话概括:
TOTP = Truncate( HMAC(K, 时间计数器) ) mod 10^Digit其中每个要素都有明确的工程含义:
K:客户端与服务端共享的种子密钥,标准编码为 base32 或 hex;- 时间计数器
T = floor((UnixTime - T0) / X),时间步长X通常取 30 秒; HMAC摘要算法可用 SHA1、SHA256、SHA512、SHA224、SHA384,国内合规场景(如教育考试数据涉及个人信息保护)还可使用国密 SM3;Digit为截取位数,常见为 6 位。
下面这段代码用 Python 直观展示了 TOTP 的计算,不依赖任何外部服务,便于理解"动态口令为何每三十秒变一次":
importhmac,hashlib,struct,timedeftotp(secret:bytes,step:int=30,digits:int=6,algo:str="sha1")->str:counter=int(time.time())//step# 30 秒一个窗口msg=struct.pack(">Q",counter)digest=hmac.new(secret,msg,getattr(hashlib,algo)).digest()offset=digest[-1]&0x0Fbinary=struct.unpack(">I",digest[offset:offset+4])[0]&0x7FFFFFFFreturnstr(binary%(10**digits)).zfill(digits)# 种子密钥通常用 base32 编码,扫码注册时由二维码承载secret_b32="JBSWY3DPEHPK3PXP"secret=__import__("base64").b32decode(secret_b32)print(totp(secret,algo="sha1"))# 当前窗口的 6 位口令理解了这段逻辑,就能明白动态口令为什么适合考试场景:
- 口令与时间强绑定。服务端只接受"当前时间窗"内计算的口令,过期即作废,攻击者无法把昨晚偷看到的口令用于今早的考试。
- 口令与种子密钥(设备)强绑定。同一账号换一台没有
K的设备,算不出正确口令,这天然把"身份"钉到了"持有令牌的设备"上。 - 口令不可预测。没有
K就无法推算下一个窗口的口令,抄答案式偷窥失效。
下表汇总了不同摘要算法与编码形态下的典型配置,考试系统在做合规选型时可据此权衡:
| 算法 | 摘要长度(字节) | 推荐种子长度(字节) | base32长度(字符) | 适用场景 |
|---|---|---|---|---|
| SHA1 | 20 | 20 | 32 | 兼容老令牌 |
| SHA256 | 32 | 32 | 52 | 通用安全 |
| SHA512 | 64 | 64 | 103 | 高安全 |
| SHA224 | 28 | 28 | 45 | 平衡型 |
| SHA384 | 48 | 48 | 78 | 平衡型 |
| 国密SM3 | 32 | 32 | 52 | 国内合规 |
关于种子密钥的编码与下发安全
前面表格里反复出现的 base32、hex,是种子密钥K的两种标准编码形态。base32 因为只使用大写字母与数字、不含易混淆字符,最适合印在二维码与手工录入场景;hex 则便于程序间传递与日志留存。无论哪种编码,明文种子一旦泄露,整把口令体系就崩塌,因此下发环节要格外小心:
- 二维码承载的是种子本身,扫码注册意味着"把秘密从服务端转移到考生设备"。这个过程应当走一次性的、带绑定票据的通道:服务端生成临时登记票据,考生扫码后票据作废,种子只在二维码存在的短暂窗口内可见,且每张二维码对应唯一考生。
- 种子不应以明文落库。服务端即便要存种子,也应加密存储,且加密密钥与种子分域管理,避免"数据库一拖库,全量令牌失守"。
- 种子与考生账号的映射要可审计。哪把种子、什么时间、绑定到哪个账号、哪个设备,都应进入审计链,便于事后追溯令牌的来源与去向。
理解这一点后,再看动态口令在考试场景的价值会更清晰:它把"身份"从一串可共享的密码,升级为一把"只在考生设备上存在、每三十秒刷新、不可预测"的密钥派生值。接下来的拆解会看到,这套机制如何被组装进真实考试流程。
三、以安当OTP为例看动态口令在考试场景的技术拆解
抽象的 TOTP 原理要落到考试系统里,还需要一套能"发令牌、验令牌、管用户、接业务"的组件。以安当OTP为例,它基于 OATH TOTP 实现一次性密码,整体由客户端(手机 APP / 硬件令牌 / 微信小程序令牌)与服务端两部分组成,下面的拆解可以对照理解这类产品如何嵌入考试流程。
1. 客户端形态:手机令牌、硬件令牌与微信小程序令牌
- 手机令牌通过扫码注册,把服务端下发的种子密钥写入本地安全存储,之后离线生成三十秒滚动口令,全程无需联网;
- 硬件令牌是预烧录种子的独立设备,适合不允许携带手机的集中考场;
- 微信小程序令牌则把令牌能力轻量化,考生无需安装独立 APP,适合大规模社会考。
三种形态共用同一套 TOTP 算法,服务端只认"种子密钥 + 时间窗",因此一套后端可以同时接纳多种令牌,这正是考试系统需要的能力——不同考场、不同人群用不同形态,后台统一管控。
2. 兼容性与注册体验
手机令牌注册阶段采用标准 TOTP 二维码,因此兼容谷歌验证器、微软验证器、腾讯验证器等通用 APP。这意味着考生即便没有安装厂商 APP,也能用自己已有的验证器完成绑定,降低了大规模推广的门槛。扫码注册的本质是把一段 base32 种子密钥从服务端安全地转移到考生设备,且不经由明文通道传输。
3. 服务端部署与对接
服务端支持本地化部署或 SaaS 形态,满足不同考试主管单位对数据驻留的诉求。在对接方式上,典型路径有两条:
- 通过Radius 协议:把动态口令叠加到既有网络准入/远程接入认证链路上,考生远程接入考场网络时即完成双因素校验;
- 通过API 接口:考试业务系统直接调用校验接口,把口令验证嵌入"进入考试"“切题提交”"关键操作确认"等业务节点。
此外,系统通常支持用户自注册与"一个后台对接多应用",即同一套动态口令体系可以同时守护在线考试、成绩查询、证书下载等多个子系统,避免每个系统各建一套口令体系造成的管理碎片化。
四、时间绑定:三十秒口令如何压缩代考时间窗
代考者最理想的局面,是拿到一个"长期有效、随时可用"的凭证。动态口令的第一个克制点,就是让凭证的寿命被锁死在三十秒里。
设想一个典型的代考剧本:甲考生登录后,把账号密码告诉乙,乙在异地远程操作。若只有静态密码,乙随时可进;叠加 TOTP 双因素后,乙每三十秒就要向甲索取一次新口令——这把"随时可代考"变成了"必须实时协同作弊",操作成本与暴露风险陡增。
时间绑定在考试系统里还有更细的工程讲究:
- 时钟容错窗口:服务端一般允许前后一个时间步长(即 ±30 秒,共 60 秒窗口)的偏差,以容忍考生设备与服务端的轻微时钟漂移。但窗口不宜开得过大,否则等于变相延长了口令有效期。
- 重放防护:同一口令在服务端校验成功后应立即失效,防止代考者截获后"复用一次"。实现上通常用"已用计数器/已用时间窗"做去重。
- 校验失败限流:连续错误应触发锁定或告警,既防暴力穷举,也作为"疑似代考"的异常信号。
下面这段伪代码示意了服务端如何做带时钟容差与重放防护的校验:
defverify_otp(secret:bytes,presented:str,used_windows:set,step:int=30,digits:int=6,drift:int=1)->bool:now=int(time.time())fordinrange(-drift,drift+1):# 容忍时钟漂移counter=(now//step)+d candidate=totp_from_counter(secret,counter,digits)ifcandidate==presented:ifcounterinused_windows:# 重放防护returnFalseused_windows.add(counter)returnTruereturnFalse五、设备绑定:把"账号"钉死到手机令牌或硬件令牌
时间绑定解决了"凭证有效期"问题,但还不足以证明"操作者是考生本人"。真正的身份绑定锚点,是那把只在考生设备上存在的种子密钥。设备绑定的关键在于:令牌一旦注册,就与特定考生、特定设备强关联。
落到不同令牌形态上,绑定逻辑各有侧重:
- 手机令牌:扫码注册时,种子密钥写入考生手机的安全存储区。换手机、卸载重装都需要重新绑定(重新扫码),而重新绑定本身是一次高可信事件,可作为异常审查的触发点。
- 硬件令牌:种子在出厂时烧录,令牌序列号与考生账号一一登记。硬件丢失即挂失该序列号,令牌与账号的映射关系由后台管理。
- 微信小程序令牌:令牌能力运行在考生微信身份下,结合微信实名与考生的报名实名做交叉比对,进一步强化"人-设备-账号"三方绑定。
设备绑定还能直接反制一类常见代考:把账号口令共享给"枪手"。枪手即便拿到账号密码,手上没有考生的令牌,算不出当前窗口口令,进不了考场。下表对比三种令牌在考试防代考中的取舍:
| 令牌形态 | 绑定对象 | 适用考场 | 防共享代考 | 部署成本 |
|---|---|---|---|---|
| 手机令牌 | 考生手机 + 种子密钥 | 社会分散考、远程考 | 强 | 低 |
| 硬件令牌 | 序列号 + 考生账号 | 集中统一考场 | 强 | 中 |
| 微信小程序令牌 | 微信实名 + 考生 | 大规模开放考 | 较强 | 低 |
六、远程身份核验流程设计:动态口令如何嵌入远程接入
很多职业资格考试、企业内训考核是远程进行的,考生通过远程接入方式进入考场网络。这里要特别澄清一个概念误区:远程接入只是网络通达手段,它并不等于身份可信。把"能联网"误当成"是本人",正是远程代考泛滥的根源。
一个稳妥的远程核验流程应当把动态口令放在"进入"和"关键操作"两道关卡:
- 入场关:考生远程接入考场环境时,先做账号密码认证,再做 TOTP 双因素认证。两因子都过,才放行进考场系统。这一步把"账号"与"令牌持有"绑定起来。
- 活体/证件复核关:在考前人脸识别或证件 OCR 复核环节,要求考生再次输入一次动态口令,确认"此刻操作设备"与"入场令牌"一致,防止中途换人。
- 关键操作关:切题、提交、异常申诉等高敏感动作,可要求二次口令确认,把代考者的操作窗口压到最小。
这套流程里,动态口令承担的不是"识别长相"的职责(那是活体与证件系统的活),而是"确认此刻持有令牌的人,就是入场时通过双因素的那个人"。二者互补,构成纵深。
七、防代考的纵深策略:动态口令只是其中一环
必须诚实地说,单靠动态口令无法 100% 杜绝代考——如果枪手物理上拿到了考生的手机和账号,令牌便一同失效。因此落地时要把动态口令放进纵深防御体系:
- 与活体检测互补:入场用动态口令确认"持有令牌",用人脸/活体确认"是本人",两者结论做逻辑与。
- 与行为基线互补:考试行为(打字节奏、切屏次数、答题轨迹)建立基线,异常偏离即使口令正确也触发人工复核。
- 与异常信号互补:同一令牌在极短时间内跨地理位置出现、同一口令被复用、连续校验失败等,都作为"疑似代考"告警喂给监考端。
- 与证件库互补:报名时登记的证件信息,在考中复核时与活体结果比对,形成闭环。
动态口令的价值,是给这条纵深链路提供一道"低成本、高频率、可审计"的硬校验,而不是充当唯一的防线。
八、审计与合规:为什么动态口令可举证
考试主管单位最关心的,往往不是"能不能挡住",而是"挡不住时能不能追责、能不能举证"。动态口令在这方面有天然优势:
- 可留痕:每一次口令校验都是一次带时间戳的服务端事件,记录"谁、何时、哪个窗口、是否通过",天然形成审计日志。
- 可回溯:配合时间计数器,事后可复算考生当时应呈现的口令,验证日志真实性,杜绝日志被事后篡改的质疑。
- 可分级:校验失败、令牌更换、异地出现等事件分级存储,既满足日常监考,也满足监管调证。
- 合规友好:采用国密 SM3 作为摘要算法的动态口令方案,在涉及个人信息保护与关键信息基础设施的考试系统里,更容易满足国产化与合规要求。
一个最小可用的审计事件结构可以设计如下:
{"exam_id":"CERT-2026-0512","user_id":"u_88231","token_type":"mobile_app","time_window":1789000,"result":"pass","src_ip":"10.20.3.14","ts":1789000123}八之一、考试全生命周期的身份绑定节奏
把动态口令简单地"加在登录按钮上"是常见误用。真正有效的做法,是把身份绑定贯穿考试的全生命周期,不同阶段用不同强度的校验,既保证安全又不至于拖慢流程。
- 报名阶段(强绑定):完成实名与证件核验后,要求考生绑定令牌。这一步是"把种子密钥钉到考生"的关键动作,应当配一次人脸或证件复核,确保绑定者是本人而非代报名的中介。
- 考前入场(双因子):远程接入考场时账号密码 + 动态口令,确认"持有令牌的人"与"报名绑定的人"一致。口令来自考生自己的设备,枪手拿不到。
- 考中关键节点(轻量二次确认):切大题、提前交卷、申诉等高风险动作要求再次输入当前窗口口令。由于口令每三十秒刷新,即便同场有串通,也难以把口令实时转给场外协同者。
- 考后查分与证书(持续守护):成绩查询、电子证书下载等也纳入同一套动态口令体系。一来防止考后账号被盗冒领证书,二来让"一个后台对接多应用"真正发挥作用,避免每套子系统各建口令造成管理盲区。
这种"前重后轻、关键节点加重"的节奏,比"全程每一秒都强校验"更贴合考试体验,也更能落地。
八之二、分布式考场的时钟同步与健壮性
在线考试往往跨地域、跨考点,成千上万的考生设备时钟并不一致,这会直接影响 TOTP 的校验成功率。工程上需要两条兜底:
- 服务端以自身时钟为准做窗口比对,并允许 ±1 个步长容差,前文已述。但要注意:容差越大越安全(少误拒),越小越抗代考(短有效期),需要在误拒率与安全性之间取平衡,通常 ±30 秒是公认折中。
- 提供时间校准提示。考生 APP 在生成口令前可探测与服务端的时间差并提示"请将手机时间设为自动同步",把因时钟漂移导致的校验失败前置化解,避免考试进行中才暴露问题。
此外,服务端的冗余与降级策略也影响可用性:当动态口令校验服务短暂不可用时,应有明确的降级预案(如临时放宽、转人工核验),但不能无声降级为"免双因素",否则防代考闸口被悄悄卸掉。
九、小结:身份绑定的三把锁
回到开头的问题——代考的本质是身份绑定失效。动态口令在考试场景里给出的,是三把可落地的锁:
- 时间锁:三十秒滚动口令,让凭证寿命被压缩到代考者无法从容协作;
- 设备锁:种子密钥绑定到手机令牌、硬件令牌或微信小程序令牌,让账号无法被简单共享;
- 审计锁:每一次校验都可留痕、可回溯、可分级,让异常有据可查。
下一节的通用落地建议,会把这三把锁进一步拆成可执行的工程清单。
方案参考
本节给出在线考试与远程身份核验场景落地双因素认证(含动态口令)的通用建议,侧重工程可执行性,不涉及具体产品能力推销。
1. 明确"身份绑定"而非"网络可达"的验收标准
在设计远程考试认证时,验收标准应写成"考生身份与令牌、设备、活体三要素一致",而不是"考生能连进来"。远程接入只是通路,双因素认证才是身份可信的锚点,二者职责要分清。
2. 时间绑定的工程参数
- 时间步长固定为 30 秒、口令 6 位,是与主流 TOTP 客户端兼容的默认值;
- 时钟容差建议 ±1 个步长(共 60 秒),过大等于变相延长有效期;
- 必须实现重放防护(已用窗口去重)与连续失败限流,前者防截获复用,后者防穷举并产出异常信号。
3. 设备绑定的落地方式
- 优先在报名阶段完成令牌绑定,扫码注册把种子密钥安全下发到考生设备;
- 对集中考场可用硬件令牌,对社会分散考可用手机令牌或微信小程序令牌,后台统一管控;
- 令牌重新绑定、挂失、换设备都应视为高可信事件,触发人工复核或二次确认。
4. 防代考的纵深组合
- 动态口令负责"持有令牌"确认,活体/证件负责"是本人"确认,二者做逻辑与;
- 叠加行为基线(打字节奏、切屏频率、答题轨迹)与异常信号(异地、复用、失败)做辅助告警;
- 不要宣称单一因子可解决代考,纵深组合才是正解。
5. 审计与合规
- 每次校验记录 user_id、令牌类型、时间窗口、结果、来源与时间戳;
- 采用国密 SM3 作为摘要算法以满足国内合规与国产化诉求;
- 审计日志分级存储,支持事后复算验证,确保可回溯、可举证。
6. 部署与对接建议
- 对数据驻留敏感的单位优先本地化部署,对弹性扩容需求高的单位考虑 SaaS;
- 远程接入认证链路上可用 Radius 叠加动态口令,业务系统关键节点可用 API 直接校验;
- 尽量复用一套后台对接考试、查分、证书等多个子系统,避免口令体系碎片化带来的管理漏洞。
以上建议可独立评估、按需裁剪,核心始终是:用动态口令把"身份"钉在时间与设备上,再用审计把整个过程锁成可举证的闭环。