You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NXP NTAG213标签写保护密码设置及故障排查求助

NXP NTAG213 NFC标签密码管理问题排查与标准流程

问题背景与故障点

我正在做一个NFC标签项目,需要把公共ID写入标签,核心需求是用密码保护这个ID,防止未授权改写。已经写了辅助类管理密码,但遇到两个问题:

  • 密码重置兼容问题:自己的辅助类能正常设置和移除密码,但设置密码后,就算输入正确密码,用外部NFC工具(比如NFC Tools)也没法重置密码,只能用自己的辅助类移除。
  • 密码检测失效问题:用来检测标签是否设了密码的辅助函数,不管实际有没有密码,始终返回false。

附相关代码:

fun isPasswordProtected(tag: Tag): Boolean {
    val nfcA = NfcA.get(tag)
    try {
        nfcA.connect()
        // Read page 41 (0x29)
        val response = nfcA.transceive(byteArrayOf(0x30, 0x29))
        // Get the AUTH0 byte (first byte of the response)
        val auth0 = response[0]
        // Password protection is enabled if AUTH0 is less than 0xFF
        return auth0 < 0xFF.toByte()
    } catch (e: IOException) {
        LogUtil.log(LogUtil.TAG_NFC, "IOException while reading NFC-A tag -> ${e.message}")
        return false // Or handle the exception as needed
    } finally {
        try {
            nfcA.close()
        } catch (e: IOException) {
            LogUtil.log(LogUtil.TAG_NFC,"IOException while closing NFC-A tag -> ${e.message}")
        }
    }
}

fun writeProtectWithPassword(tag: Tag, password: String, pack: String) {
    val nfcA = NfcA.get(tag)
    try {
        nfcA.connect()
        val passwordBytes = password.toByteArray(Charset.forName("US-ASCII"))
        val packBytes = pack.toByteArray(Charset.forName("US-ASCII"))
        // Write password to page 43 (0x2B)
        nfcA.transceive(byteArrayOf(0xA2.toByte(), 0x2B, passwordBytes[0], passwordBytes[1], passwordBytes[2], passwordBytes[3]))
        // Write PACK to page 44 (0x2C)
        nfcA.transceive(byteArrayOf(0xA2.toByte(), 0x2C, packBytes[0], packBytes[1], 0x00, 0x00))
        // Set AUTH0 to page 9 (0x09) to protect from page 9 onwards
        nfcA.transceive(byteArrayOf(0xA2.toByte(), 0x29, 0x09, 0x00, 0x00, 0x00))
        // (Optional) Set PROT bit in page 42 (0x2A) to 1 for read/write protection
        nfcA.transceive(byteArrayOf(0xA2.toByte(), 0x2A, 0x80.toByte(), 0x00, 0x00, 0x00))
    } catch (e: IOException) {
        LogUtil.log(LogUtil.TAG_NFC,"IOException while writing to NFC-A tag -> ${e.message}")
    } finally {
        try {
            nfcA.close()
        } catch (e: IOException) {
            LogUtil.log(LogUtil.TAG_NFC,"IOException while closing NFC-A tag -> ${e.message}")
        }
    }
}

fun removePasswordProtection(tag: Tag, password: String) {
    val nfcA = NfcA.get(tag)
    try {
        nfcA.connect()
        val passwordBytes = password.toByteArray(Charset.forName("US-ASCII"))
        // Authenticate with the current password
        nfcA.transceive(byteArrayOf(0x1B, passwordBytes[0], passwordBytes[1], passwordBytes[2], passwordBytes[3]))
        val result = nfcA.transceive(byteArrayOf(0x30, 0x29))
        // Set AUTH0 to 0xFF to disable password protection
        nfcA.transceive(byteArrayOf(0xA2.toByte(), 0x29, result[0], result[1], result[2], 0xFF.toByte()))
    } catch (e: IOException) {
        LogUtil.log(LogUtil.TAG_NFC, "IOException while writing to NFC-A tag -> ${e.message}")
    } finally {
        try {
            nfcA.close()
        } catch (e: IOException) {
            LogUtil.log(LogUtil.TAG_NFC,"IOException while closing NFC-A tag -> ${e.message}")
        }
    }
}

问题排查与修复

1. 密码检测函数失效修复

原isPasswordProtected函数的核心问题是字节符号判断错误:Java/Kotlin中byte是有符号类型,0xFF作为有符号byte值为-1,auth0 < 0xFF.toByte()等价于auth0 < -1,而AUTH0的有效值范围是0x000xFF(对应有符号byte的-128-1),导致判断永远不成立,始终返回false。

修复后的函数:

fun isPasswordProtected(tag: Tag): Boolean {
    val nfcA = NfcA.get(tag)
    try {
        nfcA.connect()
        // 读取page 41 (0x29)
        val response = nfcA.transceive(byteArrayOf(0x30, 0x29))
        if (response.size < 4) return false // 确保读取到完整页面数据
        // 将AUTH0转为无符号int判断
        val auth0 = response[0].toInt() and 0xFF
        // AUTH0不等于0xFF时,密码保护生效
        return auth0 != 0xFF
    } catch (e: IOException) {
        LogUtil.log(LogUtil.TAG_NFC, "读取NFC-A标签时发生IO异常 -> ${e.message}")
        return false
    } finally {
        try {
            nfcA.close()
        } catch (e: IOException) {
            LogUtil.log(LogUtil.TAG_NFC,"关闭NFC-A标签时发生IO异常 -> ${e.message}")
        }
    }
}

2. 外部工具无法重置密码的修复

原removePasswordProtection函数存在两个问题:

  • 修改AUTH0时错误覆盖了page41的第4个字节,而非仅修改第一个字节(AUTH0位)
  • 未校验认证结果,可能在认证失败的情况下执行无效操作

修复后的函数:

fun removePasswordProtection(tag: Tag, password: String) {
    val nfcA = NfcA.get(tag)
    try {
        nfcA.connect()
        val passwordBytes = password.toByteArray(Charsets.US_ASCII)
        if (passwordBytes.size != 4) {
            LogUtil.log(LogUtil.TAG_NFC, "密码必须为4字节ASCII字符")
            return
        }
        // 执行密码认证,成功后会返回2字节PACK值
        val authResponse = nfcA.transceive(byteArrayOf(0x1B, passwordBytes[0], passwordBytes[1], passwordBytes[2], passwordBytes[3]))
        if (authResponse.size != 2) {
            LogUtil.log(LogUtil.TAG_NFC, "密码认证失败")
            return
        }
        // 读取当前page41配置,仅修改AUTH0为0xFF
        val page41 = nfcA.transceive(byteArrayOf(0x30, 0x29))
        if (page41.size == 4) {
            nfcA.transceive(byteArrayOf(0xA2.toByte(), 0x29, 0xFF.toByte(), page41[1], page41[2], page41[3]))
        }
        // 可选:关闭PROT位(如果之前开启了读保护)
        val page42 = nfcA.transceive(byteArrayOf(0x30, 0x2A))
        if (page42.size == 4) {
            val newPage42 = (page42[0].toInt() and 0x7F).toByte() // 清除PROT位(bit7)
            nfcA.transceive(byteArrayOf(0xA2.toByte(), 0x2A, newPage42, page42[1], page42[2], page42[3]))
        }
    } catch (e: IOException) {
        LogUtil.log(LogUtil.TAG_NFC, "写入NFC-A标签时发生IO异常 -> ${e.message}")
    } finally {
        try {
            nfcA.close()
        } catch (e: IOException) {
            LogUtil.log(LogUtil.TAG_NFC,"关闭NFC-A标签时发生IO异常 -> ${e.message}")
        }
    }
}

另外,原writeProtectWithPassword中如果开启了PROT位(0x80),会导致读取受保护页面也需要认证,部分外部工具可能只支持写保护场景,建议如果仅需防止改写,将PROT位设为0x00。

NXP NTAG213设置密码保护的标准流程

NTAG213的密码保护配置涉及page41~page44四个配置页,标准流程如下:

  1. 建立连接:通过NFC-A协议与标签建立通信。
  2. 设置密码(page43):写入4字节ASCII密码,不足补0,超过截断。
  3. 设置PACK(page44):写入2字节认证响应码,剩余2字节填0,PACK是外部工具验证认证结果的关键。
  4. 配置AUTH0(page41):设置受保护的起始页面,例如0x09表示page9及之后的页面需要认证才能读写;设为0xFF则关闭密码保护。
  5. 配置PROT位(page42,可选):设为0x80时,读取受保护页面也需要认证;设为0x00时,仅写操作需要认证。
  6. 验证配置:读取page41~page44确认参数正确,再通过认证后操作受保护页面验证权限。

注意:所有配置页的修改都需要先完成密码认证(未设置密码时除外),配置必须严格遵循NXP规范,否则外部工具无法识别。

内容的提问来源于stack exchange,提问作者Aswin .A.S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 11:22:04