You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Node.js+ACR122U开发NTAG213密码保护时出现异常半锁定问题求助

Node.js+ACR122U开发NTAG213密码保护时出现异常半锁定问题求助

看起来你遇到的核心问题有两个:一是设置密码后卡片直接进入不可写且无法正常解锁的异常状态,二是移除密码时的认证命令格式错误导致无法通过认证修改配置。我结合NTAG213的 datasheet 和ACR122U的通信规范,帮你梳理问题并给出修复方案:

问题根源拆解

1. 密码认证命令的APDU格式完全错误

你构造的认证APDU存在长度不匹配的问题,这会导致卡片直接拒绝认证请求,自然无法在认证后修改PWD、AUTH0等受保护页面:

  • 你把Lc字段设为0x05,但后续的Data实际长度远大于5字节,APDU格式不合法,卡片不会响应这个无效命令。
  • 另外,ACR122U的Escape命令(D4 42)搭配Transceive子命令(0x01)时,需要正确包裹卡片的PWD_AUTH指令,不能省略或乱填长度。

2. 密码相关页面的写入顺序逻辑错误

你先设置了AUTH0(触发页面保护),再写入PWD和PACK,但AUTH0=0x04会让Page4及之后的页面(包括PWD所在的Page43)变成受保护状态,此时未认证的写操作会被卡片拒绝,相当于刚设置完保护就把密码配置页面锁死了。

修复后的完整代码

1. 修正/nfc/setpassword接口(调整写入顺序+合理验证)

app.post('/nfc/setpassword', async (req, res) => {
  const { password } = req.body;
  if (typeof password !== 'string' || password.length !== 4) {
    return res.status(400).json({ error: 'Password must be a 4-character string' });
  }
  if (!currentReader || !currentCard) {
    return res.status(400).json({ error: 'No card present' });
  }

  try {
    const pwd = Buffer.from(password, 'utf8'); // "1234" → 31 32 33 34
    const pack = Buffer.from([0x84, 0x00, 0x00, 0x00]); // PACK(仅前2字节有效,后2字节为保留位)
    const access = Buffer.from([0x00, 0x00, 0x00, 0x00]); // PROT=0:未认证只读,认证后可读写
    const auth0 = Buffer.from([0x04, 0x00, 0x00, 0x00]); // 从Page4开始保护所有后续页面

    // 核心:先写PWD/PACK(此时AUTH0未设置,页面无保护),再触发权限保护
    await currentReader.write(0x2B, pwd); // PWD (Page43)
    await currentReader.write(0x2C, pack); // PACK (Page44)
    
    // 验证PWD写入(此时还未触发保护,可直接读取)
    const verifyPwd = await currentReader.read(0x2B, 4);
    if (!verifyPwd.equals(pwd)) {
      return res.status(500).json({ error: 'Password write verification failed' });
    }

    // 最后写入权限配置,正式开启保护
    await currentReader.write(0x2A, access); // ACCESS (Page42)
    await currentReader.write(0x29, auth0); // AUTH0 (Page41)

    // 可选:用刚设置的密码做一次认证测试,确认配置生效
    const testAuthCmd = Buffer.concat([
      Buffer.from([0xFF, 0x00, 0x00, 0x00, 0x08]),
      Buffer.from([0xD4, 0x42, 0x01, 0x1B]),
      pwd,
      Buffer.from([0x02])
    ]);
    const testResp = await currentReader.transmit(testAuthCmd, 10);
    if (testResp.length <5 || testResp[0]!==0xD5 || testResp[1]!==0x43 || testResp[2]!==0x01) {
      return res.status(500).json({ error: 'Authentication test failed, configuration may be invalid' });
    }

    return res.status(200).json({ success: true, message: 'Password set successfully' });
  } catch (err) {
    return res.status(500).json({ error: 'Failed to set password', detail: err.message });
  }
});

2. 修正/nfc/removepassword接口(合法APDU+正确响应解析)

app.post('/nfc/removepassword', async (req, res) => {
  const { password } = req.body;
  if (typeof password !== 'string' || password.length !== 4) {
    return res.status(400).json({ error: 'Password must be a 4-character string' });
  }
  if (!currentReader || !currentCard) {
    return res.status(400).json({ error: 'No card present' });
  }

  try {
    const pwdBuf = Buffer.from(password, 'utf8');
    // 构造合法的PWD_AUTH认证APDU
    const authCmd = Buffer.concat([
      Buffer.from([0xFF, 0x00, 0x00, 0x00, 0x08]), // CLA+INS+P1+P2+Lc
      Buffer.from([0xD4, 0x42, 0x01, 0x1B]), // Escape命令+Transceive子命令+卡片PWD_AUTH指令
      pwdBuf,
      Buffer.from([0x02]) // 期望接收2字节PACK响应
    ]);

    const response = await currentReader.transmit(authCmd, 10);
    // 解析ACR122U的响应:格式为D5 43 01 [PACK1] [PACK2] 90 00
    if (response.length < 5 || response[0] !== 0xD5 || response[1] !== 0x43 || response[2] !== 0x01) {
      return res.status(401).json({ error: 'Authentication failed: invalid card response' });
    }
    const receivedPack = response.slice(3, 5);
    const expectedPack = Buffer.from([0x84, 0x00]); // 对应你设置的PACK值
    if (!receivedPack.equals(expectedPack)) {
      return res.status(401).json({ error: 'Invalid password (PACK mismatch)' });
    }

    // 认证成功后,清空所有密码相关配置
    await currentReader.write(0x29, Buffer.from([0xFF, 0x00, 0x00, 0x00])); // AUTH0设为0xFF(取消所有保护)
    await currentReader.write(0x2A, Buffer.from([0x00, 0x00, 0x00, 0x00])); // 重置ACCESS配置
    await currentReader.write(0x2B, Buffer.from([0x00, 0x00, 0x00, 0x00])); // 清空密码
    await currentReader.write(0x2C, Buffer.from([0x00, 0x00, 0x00, 0x00])); // 清空PACK

    return res.status(200).json({ success: true, message: 'Password removed successfully' });
  } catch (err) {
    return res.status(500).json({ error: 'Failed to remove password', detail: err.message });
  }
});

额外注意事项

  1. NTAG213的权限逻辑
    • 你设置的PROT=0完全符合需求:未认证时受保护页面(Page4及之后)只读,认证后可读写;
    • AUTH0=0x04会保护从Page4到最后一页的所有内容,包括用户数据区和密码配置区,这和NFC Tools的行为一致。
  2. 异常恢复:如果卡片已经被锁死,只能通过NFC Tools的“硬重置”功能(擦除整个卡片)来恢复,这是NTAG芯片的硬件保护机制,无法通过软件绕过。

内容来源于stack exchange

火山引擎 最新活动