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四个配置页,标准流程如下:
- 建立连接:通过NFC-A协议与标签建立通信。
- 设置密码(page43):写入4字节ASCII密码,不足补0,超过截断。
- 设置PACK(page44):写入2字节认证响应码,剩余2字节填0,PACK是外部工具验证认证结果的关键。
- 配置AUTH0(page41):设置受保护的起始页面,例如0x09表示page9及之后的页面需要认证才能读写;设为0xFF则关闭密码保护。
- 配置PROT位(page42,可选):设为0x80时,读取受保护页面也需要认证;设为0x00时,仅写操作需要认证。
- 验证配置:读取page41~page44确认参数正确,再通过认证后操作受保护页面验证权限。
注意:所有配置页的修改都需要先完成密码认证(未设置密码时除外),配置必须严格遵循NXP规范,否则外部工具无法识别。
内容的提问来源于stack exchange,提问作者Aswin .A.S
相关产品推荐
相关产品推荐

