1. 项目概述:基于Web浏览器的Desfire EV1/EV2/EV3卡操作方案
这个项目实现了一个纯前端网页应用,通过WebSocket与本地服务通信,完成对Mifare Desfire系列智能卡(包括EV1/EV2/EV3版本)的完整操作。作为一个接触式智能卡领域的开发者,我经常需要测试和配置各种Desfire卡,传统方式需要依赖专用软件,而这个方案直接将所有功能搬到了浏览器中,极大提升了开发调试效率。
核心功能包括:
- 卡片基础操作:激活、获取卡片类型、格式化
- 密钥管理:DES/3DES/AES密钥认证与修改
- 应用管理:创建/删除应用、配置应用密钥
- 文件操作:标准文件/备份文件创建、读写数据、事务管理
- 设备控制:发卡器蜂鸣、获取设备序列号
技术亮点:采用纯JavaScript实现,通过WebSocket与本地服务通信(默认端口39189),所有加密操作在发卡器硬件端完成,前端只负责指令发送和结果显示,既保证了安全性又实现了跨平台。
2. 核心功能实现解析
2.1 WebSocket通信架构
通信层采用标准的WebSocket协议,建立连接后通过特定格式的字符串指令与后端交互。关键实现代码如下:
javascript复制var wsUri = "ws://127.0.0.1:39189"; // 必须与RFIDWebServer端口一致
function WebSocketRun(command) {
if (!iswsrun) {
ws = new WebSocket(wsUri);
ws.onopen = function() {
iswsrun = true;
ws.send(command);
};
ws.onmessage = function(evt) {
received_msg = evt.data;
// 结果处理逻辑...
};
ws.onclose = function() {
iswsrun = false;
};
} else {
ws.send(command);
}
}
这种设计实现了:
- 单连接复用:避免频繁建立/断开连接的开销
- 异步响应:通过事件回调处理返回结果
- 超时控制:配套实现超时断开机制(代码中未展示完整)
2.2 密钥管理模块实现
Desfire卡支持三种加密方式,对应不同的密钥长度:
- DES:8字节密钥(16位十六进制)
- 3DES:16字节密钥(32位十六进制)
- AES:16字节密钥(32位十六进制)
密钥认证关键代码:
javascript复制function desfireauthkeyev1() {
let keytype = ComboxEv1AuthMode.selectedIndex;
let keylen = [16, 32, 48][keytype] || 32; // 对应DES/3DES/AES的十六进制长度
let authkeystr = text_authkey.value.trim().replace(/\s+/g, '');
if (!isHex(authkeystr) || authkeystr.length != keylen) {
alert("密钥格式错误");
return;
}
WebSocketRun(`desfireauthkeyev1,${authkeystr},${keyid},${keytype}`);
}
密钥修改则需要同时提供旧密钥和新密钥:
javascript复制function desfirechangekeyev1() {
// ...验证逻辑类似authkeyev1
WebSocketRun(`desfirechangekeyev1,${newkeystr},${keyid},${keytype},${authkeystr}`);
}
安全提示:实际项目中应该避免在前端界面硬编码默认密钥(如示例中的00...00),建议通过加密通道从服务端获取。
3. 应用与文件系统管理
3.1 应用生命周期管理
Desfire卡采用三级存储结构:PICC → 应用 → 文件。每个应用由3字节AID标识(示例中显示为6位十六进制):
javascript复制function desfirecreateapplication() {
let aidstr = text_editaid.value.trim().replace(/\s+/g, '');
if (!isHex(aidstr) || aidstr.length != 6) {
alert("AID必须为6位十六进制");
return;
}
let keysetting = ComboxEditAppKey.selectedIndex * 16
+ ComboxEditConfigKey.selectedIndex * 8
+ ComboxCreateFile.selectedIndex * 4
+ ComboxGetFileID.selectedIndex * 2
+ ComboxMasterKey.selectedIndex;
WebSocketRun(`desfirecreateapplication,${aidstr},${keysetting},${keyids},${Encry}`);
}
应用配置采用位域编码方式:
- 最高位:应用密钥是否可修改
- 次高位:配置密钥是否可修改
- 后续位:文件创建/删除等权限控制
3.2 文件操作实现
支持两种文件类型:
- 标准数据文件:直接读写
- 备份数据文件:支持事务(需显式提交)
文件创建参数包括:
- 文件ID(1字节)
- 通信模式(明文/认证加密/全加密)
- 访问权限(读写密钥设置)
- 文件大小(字节数)
javascript复制function createstfile() {
let comset = ComboxConnMode.selectedIndex < 2 ? ComboxConnMode.selectedIndex : 3;
let accessrights = ComboxRWFileKey.selectedIndex * 16
+ ComboxEditFileConfigKey.selectedIndex
+ (ComboxReadFileKey.selectedIndex * 16
+ ComboxWriteFileKey.selectedIndex) * 256;
if (ComboxFileType.selectedIndex < 1) {
WebSocketRun(`desfirecreatestddatafile,${fileid},${comset},${accessrights},${filebytes}`);
} else {
WebSocketRun(`desfircreatebackupdatafile,${fileid},${comset},${accessrights},${filebytes}`);
}
}
4. 实战经验与问题排查
4.1 常见错误代码处理
项目实现了完善的错误处理机制,部分典型错误及解决方案:
| 错误代码 | 含义 | 解决方案 |
|---|---|---|
| 008 | 未检测到卡片 | 检查卡片放置位置,确认发卡器供电正常 |
| 009 | 多卡冲突 | 移开其他卡片,保持操作区域只有一张卡 |
| 012 | 认证失败 | 检查密钥版本和值,确认卡片未锁定 |
| 013 | 读块失败 | 降低操作速度,确认已通过认证 |
| 023 | 驱动异常 | 重新插拔发卡器,检查服务程序是否运行 |
错误处理函数示例:
javascript复制function DispErrInfo(errcode) {
const errMap = {
"008": "未寻到卡,请将卡放到发卡器的感应区!",
"009": "有多张卡在感应区,寻卡过程中防冲突失败!",
"012": "卡密码认证失败!",
// ...其他错误码
};
return errMap[errcode.slice(-3)] || "未知错误";
}
4.2 性能优化建议
-
批量操作优化:对于需要连续操作多个块的场景,建议:
- 保持WebSocket连接不频繁断开
- 适当增加超时时间(默认示例中未展示参数配置)
-
界面响应优化:
javascript复制function ButtonDisable() { // 禁用所有操作按钮防止重复提交 document.querySelectorAll("button").forEach(btn => { btn.disabled = true; }); } function ButtonEnabled() { // 操作完成后恢复按钮状态 document.querySelectorAll("button").forEach(btn => { btn.disabled = false; }); } -
数据验证强化:
javascript复制function isHex(val) { return /^[\da-fA-F]+$/.test(val) && val.length % 2 === 0; } function isUIntNum(val, max) { let n = parseInt(val); return !isNaN(n) && n >= 0 && (max === undefined || n <= max); }
5. 扩展开发建议
5.1 安全增强方案
-
会话加密:
- 对WebSocket通信内容进行AES加密
- 使用动态生成的会话密钥
-
权限控制:
javascript复制let authToken = localStorage.getItem('authToken'); if (!authToken) { redirectToLogin(); } else { ws = new WebSocket(`${wsUri}?[token](https://taotoken.net?utm_source=general)=${authToken}`); }
5.2 企业级功能扩展
-
批量制卡支持:
- 导入CSV格式的密钥和用户数据
- 自动序列号生成
- 操作日志记录
-
云端同步:
javascript复制function uploadCardData(cardUID, data) { fetch('/api/card-data', { method: 'POST', body: JSON.stringify({uid: cardUID, data: data}), headers: {'Content-Type': 'application/json'} }); } -
跨平台适配:
- 封装为PWA应用支持离线使用
- 开发Electron桌面版本
- 移动端响应式布局优化
这个方案在实际项目中的表现相当可靠,经过我们团队在门禁系统、会员卡管理等场景的验证,可以稳定处理上千次连续操作。对于需要频繁测试卡片功能的开发者和系统集成商,这种基于浏览器的方案比传统桌面软件更加灵活高效。