news 2026/6/10 2:13:28

电子签名:笔迹核验全方案拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
电子签名:笔迹核验全方案拆解

目录

一、核验核心目标与维度

1. 核心目标

2. 核验维度

二、核验技术方案

1. 前置准备

2. 数据库扩展设计

(1)用户签名特征表(user_sign_feature)

3. 核心核验流程(四层校验)

流程总览

(1)第一层:设备源校验(快速拦截非法设备)

(2)第二层:数据合法性校验(拦截伪造数据)

(3)第三层:摘要防篡改校验(核心防篡改)

(4)第四层:笔迹特征比对(防代签)

4. 核验入口整合(核心 Service)

5. 核验接口(Controller)

三、汉王 ESP560 专属适配要点

1. 特征提取适配

2. 离线核验支持

3. 阈值配置化

四、测试与异常处理

1. 核验测试用例

2. 异常处理

五、可视化与审计

1. 核验结果展示

2. 核验日志审计

六、核心扩展

1. 批量核验

2. 司法级核验


笔迹核验是考核签名项目的核心风控环节,针对汉王 ESP560 的 2048 级压感特性,需实现「数据合法性校验→摘要防篡改→笔迹特征比对→设备源校验」四层核验逻辑,确保签名为本人真实操作、数据未被篡改。以下是完整的笔迹核验实施方案:

一、核验核心目标与维度

1. 核心目标

  • 防篡改:校验签名原始数据(坐标 / 压力 / 时间戳)是否被修改;
  • 防代签:对比签名笔迹特征(压感变化、书写轨迹)与历史数据一致性;
  • 防伪造:校验数据是否来自合法的汉王 ESP560 设备。

2. 核验维度

核验维度核验内容核心依据
数据合法性压感值范围(0-2048)、坐标范围(0-800/0-480)、时间戳连续性ESP560 硬件参数(2048 级压感、800×480 分辨率)
摘要防篡改对比当前签名数据 SM3 摘要与存储的原始摘要哈希不可逆特性,数据篡改则摘要不一致
笔迹特征比对书写速度、压感变化趋势、笔画拐点、书写轨迹相似度汉王 ESP560 SDK 笔迹特征提取接口
设备源校验校验签名设备 ID 是否在白名单内、设备驱动版本是否合规设备注册台账、汉王官方驱动版本

二、核验技术方案

1. 前置准备

  • 汉王 SDK 集成:引入汉王 ESP560 的笔迹特征比对 SDK(hanvon-feature-sdk.jar),支持提取笔迹特征值、计算相似度;
  • 历史特征库:在 MySQL 中建立user_sign_feature表,存储员工历史签名的特征值(加密存储);
  • 设备白名单:在 Redis 中维护 ESP560 设备 ID 白名单(hanvon:esp560:whitelist),仅允许合法设备签名。

2. 数据库扩展设计

(1)用户签名特征表(user_sign_feature)

java

运行

@Entity @Table(name = "user_sign_feature") public class UserSignFeature { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long userId; // 员工ID private String featureValue; // 笔迹特征值(SM4加密) private String deviceId; // 签名设备ID private LocalDateTime createTime; // 特征录入时间 // getter/setter }

3. 核心核验流程(四层校验)

流程总览

plaintext

前端提交签名数据 → 第一层:设备源校验 → 第二层:数据合法性校验 → 第三层:摘要防篡改校验 → 第四层:笔迹特征比对 → 返回核验结果
(1)第一层:设备源校验(快速拦截非法设备)

java

运行

/** * 设备源校验:校验设备ID是否在白名单、驱动版本是否合规 * @param deviceId ESP560设备ID * @param driverVersion 设备驱动版本 * @return 校验结果 */ public boolean checkDeviceValid(String deviceId, String driverVersion) { // 1. 从Redis获取设备白名单 Set<String> whiteList = redisTemplate.opsForSet().members("hanvon:esp560:whitelist"); if (whiteList == null || !whiteList.contains(deviceId)) { log.error("设备ID{}不在白名单内", deviceId); return false; } // 2. 校验驱动版本(汉王ESP560最低驱动版本:V2.1.0) if (VersionUtil.compare(driverVersion, "V2.1.0") < 0) { log.error("设备{}驱动版本过低,当前{},要求≥V2.1.0", deviceId, driverVersion); return false; } return true; }
(2)第二层:数据合法性校验(拦截伪造数据)

java

运行

/** * 数据合法性校验:校验压感、坐标、时间戳 * @param signRawData 签名原始数据(JSON字符串) * @return 校验结果 */ public boolean checkDataValid(String signRawData) { try { JSONObject signObj = JSONUtil.parseObj(signRawData); JSONArray points = signObj.getJSONArray("points"); if (points.isEmpty()) { log.error("签名数据为空"); return false; } // 记录上一个点的时间戳,校验连续性 long lastTime = 0; for (int i = 0; i < points.size(); i++) { JSONObject point = points.getJSONObject(i); // 1. 压感值校验(0-2048) int pressure = point.getInt("pressure"); if (pressure < 0 || pressure > 2048) { log.error("压感值{}超出ESP560范围(0-2048)", pressure); return false; } // 2. 坐标校验(800×480) int x = point.getInt("x"); int y = point.getInt("y"); if (x < 0 || x > 800 || y < 0 || y > 480) { log.error("坐标({},{})超出ESP560范围(800×480)", x, y); return false; } // 3. 时间戳校验(递增、无跳变) long time = point.getLong("time"); if (i > 0 && time < lastTime) { log.error("时间戳不连续,上一个{},当前{}", lastTime, time); return false; } // 4. 时间间隔校验(书写间隔≤1000ms,防止伪造) if (i > 0 && (time - lastTime) > 1000) { log.error("书写间隔过长({}ms),疑似伪造数据", time - lastTime); return false; } lastTime = time; } return true; } catch (Exception e) { log.error("数据合法性校验失败", e); return false; } }
(3)第三层:摘要防篡改校验(核心防篡改)

java

运行

/** * 摘要防篡改校验:对比SM3摘要 * @param examNo 考核单号 * @param signRawData 待核验签名数据 * @return 校验结果 */ public boolean checkHashValid(String examNo, String signRawData) { // 1. 获取存储的原始摘要 String storedHash = redisTemplate.opsForValue().get("sign:hash:" + examNo); if (storedHash == null) { ExamSignRecord record = signRecordRepository.findByExamNo(examNo) .orElseThrow(() -> new RuntimeException("签名记录不存在")); storedHash = record.getSignHash(); // 缓存摘要,提升后续核验效率 redisTemplate.opsForValue().set("sign:hash:" + examNo, storedHash, 7, TimeUnit.DAYS); } // 2. 计算当前数据的SM3摘要 String currentHash = smCryptoUtil.sm3Digest(signRawData.getBytes(StandardCharsets.UTF_8)); // 3. 对比摘要 boolean valid = storedHash.equals(currentHash); if (!valid) { log.error("摘要不一致,存储{},当前{}", storedHash, currentHash); } return valid; }
(4)第四层:笔迹特征比对(防代签)

java

运行

/** * 笔迹特征比对:提取特征值,对比与历史签名的相似度 * @param userId 员工ID * @param signRawData 待核验签名数据 * @return 校验结果(相似度≥80%则通过) */ public boolean checkFeatureMatch(Long userId, String signRawData) { // 1. 获取用户历史签名特征值 List<UserSignFeature> historyFeatures = userSignFeatureRepository.findByUserId(userId); if (historyFeatures.isEmpty()) { log.warn("用户{}无历史签名特征,跳过特征比对", userId); return true; // 首次签名跳过,仅记录特征 } // 2. 提取当前签名的特征值(调用汉王SDK) HanvonFeatureExtractor extractor = new HanvonFeatureExtractor(); String currentFeature = extractor.extract(signRawData); // 提取特征值 if (StrUtil.isBlank(currentFeature)) { log.error("提取当前签名特征失败"); return false; } // 3. 对比与历史特征的相似度(调用汉王SDK) HanvonFeatureComparator comparator = new HanvonFeatureComparator(); boolean match = false; for (UserSignFeature history : historyFeatures) { // 解密历史特征值 String historyFeature = new String(smCryptoUtil.sm4Decrypt(history.getFeatureValue()), StandardCharsets.UTF_8); // 计算相似度(0-100) int similarity = comparator.compare(currentFeature, historyFeature); log.info("用户{}笔迹相似度:{}%", userId, similarity); if (similarity >= 80) { // 阈值可配置 match = true; break; } } // 4. 首次核验通过后,更新最新特征(滚动更新) if (match) { UserSignFeature newFeature = new UserSignFeature(); newFeature.setUserId(userId); newFeature.setFeatureValue(smCryptoUtil.sm4Encrypt(currentFeature.getBytes(StandardCharsets.UTF_8))); newFeature.setDeviceId(extractDeviceId(signRawData)); newFeature.setCreateTime(LocalDateTime.now()); userSignFeatureRepository.save(newFeature); } return match; }

4. 核验入口整合(核心 Service)

java

运行

@Service public class SignVerifyService { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private SmCryptoUtil smCryptoUtil; @Autowired private ExamSignRecordRepository signRecordRepository; @Autowired private UserSignFeatureRepository userSignFeatureRepository; /** * 全流程笔迹核验 * @param examNo 考核单号 * @param userId 员工ID * @param signRawData 待核验签名数据 * @param deviceId 设备ID * @param driverVersion 驱动版本 * @return 核验结果+失败原因 */ public VerifyResult verifySign(String examNo, Long userId, String signRawData, String deviceId, String driverVersion) { VerifyResult result = new VerifyResult(); // 第一层:设备源校验 if (!checkDeviceValid(deviceId, driverVersion)) { result.setSuccess(false); result.setReason("非法设备/驱动版本过低"); return result; } // 第二层:数据合法性校验 if (!checkDataValid(signRawData)) { result.setSuccess(false); result.setReason("签名数据非法(压感/坐标/时间戳异常)"); return result; } // 第三层:摘要防篡改校验 if (!checkHashValid(examNo, signRawData)) { result.setSuccess(false); result.setReason("签名数据已篡改(摘要不一致)"); return result; } // 第四层:笔迹特征比对 if (!checkFeatureMatch(userId, signRawData)) { result.setSuccess(false); result.setReason("笔迹特征不匹配(疑似代签)"); return result; } // 所有校验通过 result.setSuccess(true); result.setReason("核验通过"); // 更新验签状态 signRecordRepository.updateVerifyStatus(examNo, "核验通过"); return result; } // 内部校验方法(前文已定义:checkDeviceValid/checkDataValid/checkHashValid/checkFeatureMatch) } // 核验结果封装 @Data public class VerifyResult { private boolean success; private String reason; private int similarity; // 笔迹相似度(可选) }

5. 核验接口(Controller)

java

运行

@RestController @RequestMapping("/api/exam/sign/verify") public class SignVerifyController { @Autowired private SignVerifyService signVerifyService; @PostMapping("/hanvon") public Result<VerifyResult> verifyHanvonSign( @RequestParam String examNo, @RequestParam Long userId, @RequestBody String signRawData, @RequestParam String deviceId, @RequestParam String driverVersion) { VerifyResult result = signVerifyService.verifySign(examNo, userId, signRawData, deviceId, driverVersion); return Result.success(result); } }

三、汉王 ESP560 专属适配要点

1. 特征提取适配

  • 汉王 ESP560 的 2048 级压感是核心特征,提取特征时需启用「压感权重优先」模式:

    java

    运行

    extractor.setConfig("pressureWeight", "0.7"); // 压感权重70%,坐标权重30%
  • 适配 ESP560 的 800×480 分辨率,特征提取时需归一化坐标(避免不同设备分辨率影响)。

2. 离线核验支持

  • 若 ESP560 设备离线,可先缓存核验请求,联网后自动触发核验;
  • 离线状态下仅执行前三层校验(设备 / 数据 / 摘要),第四层特征比对待联网后完成。

3. 阈值配置化

  • 将笔迹相似度阈值(80%)、时间间隔阈值(1000ms)配置在application.yml,支持动态调整:

    yaml

    hanvon: sign: similarity-threshold: 80 time-interval-threshold: 1000

四、测试与异常处理

1. 核验测试用例

测试场景测试步骤预期结果
合法签名核验本人在 ESP560 签名,提交核验四层校验通过,返回 “核验通过”
篡改压感数据修改签名数据中的压感值(如改为 3000),提交核验第二层校验失败,原因 “压感值超出范围”
篡改坐标数据修改坐标为 900(超出 800),提交核验第二层校验失败,原因 “坐标超出范围”
代签核验他人模仿签名,提交核验第四层校验失败,原因 “笔迹特征不匹配”
非法设备签名使用非白名单 ESP560 设备签名,提交核验第一层校验失败,原因 “非法设备”

2. 异常处理

  • 特征提取失败:记录日志,降级为仅前三层校验,人工复核;
  • 历史特征为空:首次签名跳过特征比对,仅记录特征值,二次签名开始校验;
  • 设备白名单更新:提供后台接口,支持动态添加 / 删除设备 ID,无需重启服务。

五、可视化与审计

1. 核验结果展示

  • 前端展示核验结果(通过 / 失败)、失败原因、笔迹相似度;
  • 支持查看签名轨迹对比图(当前签名 vs 历史签名)。

2. 核验日志审计

  • 记录每一次核验的「考核单号、用户 ID、设备 ID、核验结果、失败原因、时间」;
  • 支持按时间 / 用户 / 设备筛选核验日志,导出审计报表。

六、核心扩展

1. 批量核验

  • 支持按考核批次批量核验签名,生成核验报告(通过率、失败原因分布);
  • 批量核验采用异步任务(@Async),避免阻塞主线程。

2. 司法级核验

  • 对接汉王司法级笔迹鉴定接口,核验结果可生成司法鉴定报告;
  • 签名数据同步至区块链存证平台,核验结果上链,不可篡改。

该方案基于汉王 ESP560 的硬件特性,实现了从 “设备源头” 到 “笔迹特征” 的全维度核验,既满足考核场景的风控需求,又符合电子签名法的合规要求。可直接集成到 SpringBoot 考核签名项目中,如需调整核验维度(如增加人脸核验联动),可按需扩展。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 21:57:10

终极指南:快速掌握Oscar视觉语言模型的5个核心技巧

终极指南&#xff1a;快速掌握Oscar视觉语言模型的5个核心技巧 【免费下载链接】Oscar Oscar and VinVL 项目地址: https://gitcode.com/gh_mirrors/os/Oscar Oscar是一个强大的开源视觉语言模型&#xff08;VLM&#xff09;&#xff0c;由微软研究院开发&#xff0c;专…

作者头像 李华
网站建设 2026/6/4 22:50:37

为什么你的Open-AutoGLM总在初始化阶段崩溃?:基于日志的逆向诊断法

第一章&#xff1a;Open-AutoGLM 模型启动报错排查修复在部署 Open-AutoGLM 模型过程中&#xff0c;常见的启动报错包括依赖缺失、环境变量未配置以及端口冲突等问题。正确识别错误日志中的关键信息是解决问题的第一步。检查运行环境与依赖项 确保 Python 环境版本符合要求&…

作者头像 李华
网站建设 2026/6/9 21:25:46

Open-AutoGLM推理性能为何上不去?深入剖析3大瓶颈与对应加速策略

第一章&#xff1a;Open-AutoGLM 推理速度优化路径在部署 Open-AutoGLM 模型时&#xff0c;推理速度直接影响用户体验与系统吞吐能力。为提升其性能表现&#xff0c;需从模型结构、运行时环境和硬件适配三个维度进行系统性优化。模型剪枝与量化 通过结构化剪枝移除冗余神经元&a…

作者头像 李华
网站建设 2026/6/9 17:28:58

FaceFusion与Basecamp团队协作工具联动机制设计

FaceFusion与Basecamp团队协作工具联动机制设计 在影视后期、虚拟内容创作日益依赖AI技术的今天&#xff0c;一个现实问题摆在面前&#xff1a;最强大的AI模型往往掌握在工程师手中&#xff0c;而真正需要它们的——导演、制片人、视觉设计师——却因操作门槛过高而难以直接使用…

作者头像 李华
网站建设 2026/6/9 17:28:53

C++程序员AI大模型转型指南:4-6个月学习路线图,超详细攻略,系统程序员智能时代新起点!

前言&#xff1a;C程序员的大模型时代优势 作为一名C开发者&#xff0c;你已经拥有了大多数AI工程师梦寐以求的核心能力&#xff1a;内存管理、多线程编程、性能优化和系统级思维。这些技能在大模型推理优化、底层框架开发和高性能计算中具有不可替代的价值。 本指南将为你量…

作者头像 李华