@ukeyfe/react-native-nfc-litecard
v1.0.11
Published
NFC read/write for MIFARE Ultralight AES (LiteCard mnemonic storage)
Readme
@ukeyfe/react-native-nfc-litecard
English | 中文
React Native NFC 读写库,适用于 MIFARE Ultralight AES(MF0AES(H)20),专为 LiteCard 助记词存储设计。
设计原则:库只返回状态码(
code)和数据(data),不提供面向用户的提示文案。调用方应根据NfcStatusCode自行映射本地化字符串。
功能概览
| 功能 | 方法 | 说明 |
|------|------|------|
| 检测卡片 | checkCard() | 检测卡片是否为空或已有数据 |
| 读取助记词 | readMnemonic() | 读取 BIP-39 助记词(需密码) |
| 读取昵称 | readUserNickname() | 读取卡片上的用户昵称 |
| 读取重试次数 | readMnemonicRetryCount() | 读取 PIN 重试计数器 |
| 重置重试次数 | resetRetryCountTo10() | 将 PIN 重试计数器重置为默认值(10) |
| 初始化卡片 | initializeCard() | 用出厂密码认证,写入助记词 + 设置新密码 |
| 更新卡片 | updateCard() | 更新助记词和密码(需旧密码) |
| 修改密码 | updatePassword() | 仅修改密码(需旧密码) |
| 写入昵称 | writeUserNickname() | 写入用户昵称 |
| 重置卡片 | resetCard() | 清除助记词数据,设置新密码,保持保护开启 |
| 卡片版本 | getCardVersion() | 读取卡片产品版本信息(无需认证) |
| 真伪校验 | readOriginality() | 读取 ECC 原厂签名,验证 NXP 正品 |
安装
npm install @ukeyfe/react-native-nfc-litecard对等依赖
本库需要以下对等依赖:
npm install react-native react-native-nfc-manager快速开始
import {
NfcStatusCode,
checkCard,
readMnemonic,
initializeCard,
updateCard,
updatePassword,
writeUserNickname,
readUserNickname,
resetCard,
readMnemonicRetryCount,
resetRetryCountTo10,
getCardVersion,
readOriginality,
} from '@ukeyfe/react-native-nfc-litecard';API 文档
checkCard(password?, onCardIdentified?)
检测卡片状态(空卡 / 有数据)。
无密码(快速探测):
const result = await checkCard();
if (result.code === NfcStatusCode.CHECK_EMPTY) {
// 空卡 – 可以初始化
} else if (result.code === NfcStatusCode.CHECK_HAS_DATA) {
// 有数据(或已读保护,无法确定)
}有密码(认证后深度检测,适用于读保护卡):
const result = await checkCard('password');
if (result.code === NfcStatusCode.CHECK_EMPTY) {
// 空卡 – 可以写入
} else if (result.code === NfcStatusCode.CHECK_HAS_DATA) {
// 已有合法助记词备份,类型:result.data?.type
} else if (result.code === NfcStatusCode.AUTH_WRONG_PASSWORD) {
// 密码错误
}参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| password | string | 否 | 卡片保护密码。不传时直接读取(适合未加密卡片);传入时先进行 AES 认证,再读取全量数据并 CRC16 校验(适合读保护卡,结果更准确)。 |
| onCardIdentified | () => void | 否 | NFC 会话建立后回调,可用于 UI 提示如"已检测到卡片"。 |
返回状态码:
| 状态码 | 含义 |
|--------|------|
| NfcStatusCode.CHECK_EMPTY (10104) | 空卡 – 无助记词数据 |
| NfcStatusCode.CHECK_HAS_DATA (10105) | 卡片有数据。传入密码时,data.type 包含助记词类型(如 "12 words (128-bit)")。 |
| NfcStatusCode.AUTH_WRONG_PASSWORD (40002) | 密码错误(仅在传入密码时可能出现) |
| NfcStatusCode.NFC_CONNECT_FAILED (40001) | NFC 连接失败 – 未贴卡或设备不支持 |
readMnemonic(password, onCardIdentified?)
读取助记词(需密码认证)。认证前自动递减重试计数器,认证成功后重置为 10。
const result = await readMnemonic('your-password');
if (result.success) {
console.log('助记词:', result.data?.mnemonic);
console.log('类型:', result.data?.type); // "12 words (128-bit)"
console.log('昵称:', result.data?.nickname);
console.log('重试次数:', result.data?.retryCount);
}参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| password | string | 是 | 卡片保护密码,用于 AES-128 认证 |
| onCardIdentified | () => void | 否 | 认证成功、开始读取前回调,可用于 UI 提示如"正在读取"。 |
返回 data 字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| mnemonic | string | BIP-39 助记词(如 "abandon abandon ... about") |
| type | string | 助记词类型(如 "12 words (128-bit)"、"24 words (256-bit)") |
| entropyHex | string | 熵的十六进制字符串 |
| rawBytes | string | 卡上原始数据的十六进制字符串(类型 + 熵) |
| nickname | string | 用户昵称(如卡上已设置) |
| retryCount | number | 认证成功后的重试次数(正常为 10) |
initializeCard(mnemonic, newPassword, defaultPassword, onCardIdentified?)
初始化卡片:用出厂默认密码认证,写入助记词,设置新密码。卡片必须已启用 AES 保护(出厂默认)。
const result = await initializeCard(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
'your-password',
'000000' // 出厂默认密码
);
if (result.code === NfcStatusCode.INIT_SUCCESS) {
console.log('初始化成功');
}参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| mnemonic | string | 是 | BIP-39 助记词,支持 12/15/18/21/24 个词。库会转为熵 + CRC16 写入卡片。 |
| newPassword | string | 是 | 新保护密码,用于派生 AES-128 密钥写入卡片。 |
| defaultPassword | string | 是 | 卡片当前(出厂默认)密码,用于认证。 |
| onCardIdentified | () => void | 否 | 认证成功、开始写入前回调,可用于 UI 提示如"正在写入"。 |
updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?, options?)
更新卡片:使用旧密码认证后,写入新助记词和新密码。认证前自动递减重试计数器。
const result = await updateCard('old-password', 'new-password', 'new mnemonic words ...');
if (result.code === NfcStatusCode.WRITE_SUCCESS) {
console.log('更新成功');
}预检查已有备份:
const result = await updateCard('old-password', 'new-password', 'mnemonic ...', undefined, {
precheckExistingMnemonic: true,
});
if (result.code === NfcStatusCode.PRECHECK_HAS_BACKUP) {
// 卡片已有合法助记词备份,跳过写入
console.log('已有备份,类型:', result.data?.type);
} else if (result.code === NfcStatusCode.WRITE_SUCCESS) {
console.log('写入成功');
}参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| oldPassword | string | 是 | 当前卡片密码,用于 AES 认证 |
| newPassword | string | 是 | 新密码,写入后卡片使用此密码保护 |
| newMnemonic | string | 是 | 新 BIP-39 助记词(12/15/18/21/24 个词) |
| onCardIdentified | () => void | 否 | 认证成功后回调,可用于 UI 提示如"正在写入"。 |
| options.precheckExistingMnemonic | boolean | 否 | 为 true 时,认证后先读取卡片数据并校验 CRC16,若已有合法助记词则返回 PRECHECK_HAS_BACKUP,不执行写入。 |
updatePassword(oldPassword, newPassword, onCardIdentified?)
仅修改密码,卡片上的助记词数据不变。认证前自动递减重试计数器。
const result = await updatePassword('old-password', 'new-password');
if (result.code === NfcStatusCode.UPDATE_PASSWORD_SUCCESS) {
console.log('密码修改成功');
}参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| oldPassword | string | 是 | 当前卡片密码,用于 AES 认证 |
| newPassword | string | 是 | 新密码,修改后卡片使用此密码 |
| onCardIdentified | () => void | 否 | 认证成功后回调 |
writeUserNickname(password, nickname, onCardIdentified?)
写入用户昵称。昵称以 UTF-8 编码,最大 12 字节(超出部分截断)。
const result = await writeUserNickname('your-password', 'MyCard');
if (result.code === NfcStatusCode.WRITE_NICKNAME_SUCCESS) {
console.log('昵称写入成功');
}参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| password | string | 是 | 卡片保护密码,用于 AES 认证 |
| nickname | string | 是 | 用户昵称;UTF-8 最大 12 字节(约 4 个中文字符或 12 个英文字母) |
| onCardIdentified | () => void | 否 | 认证成功后回调 |
readUserNickname(password?, onCardIdentified?)
读取卡片上的用户昵称。
const result = await readUserNickname('your-password');
if (result.success) {
console.log('昵称:', result.data?.nickname);
}参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| password | string | 否 | 卡片密码。读保护启用时(PROT=1)必填,否则可选。 |
| onCardIdentified | () => void | 否 | 认证成功后回调 |
resetCard(password, newPassword, onCardIdentified?)
重置卡片:清除助记词数据,设置新密码,关闭读写保护。昵称保留不变。
const result = await resetCard('old-password', 'new-password');
if (result.code === NfcStatusCode.RESET_SUCCESS) {
console.log('重置成功');
}⚠️ 此操作不可逆,卡片上的助记词数据将被永久擦除。
参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| password | string \| undefined | 否 | 当前卡片密码。卡片已保护时必填,否则传 undefined。认证前自动递减重试计数器。 |
| newPassword | string | 是 | 重置后要设置的密码。 |
| onCardIdentified | () => void | 否 | 认证成功后回调 |
readMnemonicRetryCount(onCardIdentified?)
读取卡片上的 PIN 重试计数器。无需密码认证(计数器页面在保护区域之外)。
const result = await readMnemonicRetryCount();
if (result.success) {
console.log('剩余重试次数:', result.data?.retryCount);
}参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| onCardIdentified | () => void | 否 | NFC 会话建立后回调 |
resetRetryCountTo10(onCardIdentified?)
将 PIN 重试计数器重置为默认值(10)。无需密码认证。
const result = await resetRetryCountTo10();参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| onCardIdentified | () => void | 否 | NFC 会话建立后回调 |
getCardVersion(onCardIdentified?)
读取卡片产品版本信息。无需密码认证。
const result = await getCardVersion();
if (result.success) {
console.log('厂商:', result.data?.version?.vendorId); // 0x04 = NXP
console.log('类型:', result.data?.version?.productType); // 0x03 = Ultralight
console.log('版本:', result.data?.version?.majorVersion); // 0x04 = AES
}readOriginality(onCardIdentified?)
读取 ECC 原厂签名,验证卡片是否为 NXP 正品。无需密码认证。
const result = await readOriginality();
if (result.success) {
console.log('签名:', result.data?.signature); // 96 字符 hex 字符串
}NFC 锁管理
用于应用层 NFC 会话生命周期管理:
import {
isNfcOperationLocked,
releaseNfcOperationLock,
markNfcOperationCancelledByCleanup,
consumeNfcOperationCancelledByCleanup,
} from '@ukeyfe/react-native-nfc-litecard';| 方法 | 说明 |
|------|------|
| isNfcOperationLocked() | 检查 NFC 操作锁是否被持有 |
| releaseNfcOperationLock() | 强制释放锁(用于页面卸载时) |
| markNfcOperationCancelledByCleanup() | 标记当前操作被清理中断 |
| consumeNfcOperationCancelledByCleanup() | 消费清理标志(返回是否被设置过) |
NfcResult 结构
所有 API 返回统一的 NfcResult:
interface NfcResult {
code: number; // 状态码 – 与 NfcStatusCode 比较
success: boolean; // 操作是否成功
data?: { // 可选数据,仅部分操作返回
mnemonic?: string;
type?: string;
entropyHex?: string;
rawBytes?: string;
nickname?: string;
retryCount?: number;
aesKeyHex?: string;
crc16?: number;
};
}错误处理示例
import { NfcStatusCode, readMnemonic } from '@ukeyfe/react-native-nfc-litecard';
const result = await readMnemonic('password');
switch (result.code) {
case NfcStatusCode.READ_SUCCESS:
console.log('读取成功:', result.data?.mnemonic);
break;
case NfcStatusCode.AUTH_WRONG_PASSWORD:
alert('密码错误');
break;
case NfcStatusCode.NFC_CONNECT_FAILED:
alert('NFC 连接失败,请重新贴卡');
break;
case NfcStatusCode.NFC_USER_CANCELED:
// iOS 用户取消 – 静默处理
break;
case NfcStatusCode.READ_TIMEOUT:
alert('读取超时 – 请移开卡片后重新贴卡');
break;
case NfcStatusCode.RETRY_COUNT_EXHAUSTED:
alert('重试次数已耗尽 – 卡片已锁定');
break;
default:
alert('操作失败');
}状态码
成功码
| 常量 | 值 | 说明 |
|------|------|------|
| READ_SUCCESS | 10102 | 助记词读取成功 |
| READ_NICKNAME_SUCCESS | 10103 | 昵称读取成功 |
| CHECK_EMPTY | 10104 | 空卡 |
| CHECK_HAS_DATA | 10105 | 卡片有数据 |
| READ_RETRY_COUNT_SUCCESS | 10106 | 重试次数读取成功 |
| GET_VERSION_SUCCESS | 10107 | 卡片版本读取成功 |
| READ_SIG_SUCCESS | 10108 | 原厂签名读取成功 |
| INIT_SUCCESS | 10201 | 初始化成功 |
| WRITE_SUCCESS | 10203 | 写入/更新成功 |
| UPDATE_PASSWORD_SUCCESS | 10204 | 密码修改成功 |
| WRITE_NICKNAME_SUCCESS | 10205 | 昵称写入成功 |
| RESET_SUCCESS | 10206 | 卡片重置成功 |
| PRECHECK_HAS_BACKUP | 10207 | 卡片已有合法备份,跳过写入 |
错误码
| 常量 | 值 | 说明 |
|------|------|------|
| NFC_CONNECT_FAILED | 40001 | NFC 连接失败 |
| AUTH_WRONG_PASSWORD | 40002 | 密码错误 |
| AUTH_INVALID_RESPONSE | 40003 | 认证响应无效 |
| AUTH_VERIFY_FAILED | 40004 | 认证校验失败 |
| READ_FAILED | 40005 | 读取失败 |
| WRITE_FAILED | 40006 | 写入失败 |
| INVALID_MNEMONIC | 40007 | 助记词无效 |
| UNSUPPORTED_MNEMONIC_LENGTH | 40008 | 不支持的助记词长度 |
| INVALID_CARD_DATA | 40009 | 卡片数据无效 |
| UNKNOWN_ERROR | 40010 | 未知错误 |
| NFC_USER_CANCELED | 40011 | 用户取消 NFC 扫描(iOS) |
| READ_TIMEOUT | 40012 | 读取超时 |
| NFC_LOCK_TIMEOUT | 40013 | NFC 锁超时 |
| CRC16_CHECK_FAILED | 40014 | CRC16 校验失败 |
| RETRY_COUNT_EXHAUSTED | 40015 | PIN 重试次数耗尽,卡片已锁定 |
存储格式
卡片使用熵压缩方式存储 BIP-39 助记词:
[类型 1B] [熵 16-32B] [CRC16 2B]| 类型 | 助记词长度 | 熵大小 | |------|-----------|--------| | 0x01 | 12 个词 | 16 字节(128 位) | | 0x02 | 15 个词 | 20 字节(160 位) | | 0x03 | 18 个词 | 24 字节(192 位) | | 0x04 | 21 个词 | 28 字节(224 位) | | 0x05 | 24 个词 | 32 字节(256 位) |
安全性
- AES-128 硬件级双向认证(3-pass)
- SHA-256 密钥派生:密码 → SHA-256 → 取前 16 字节作为 AES 密钥
- CRC16-Modbus 校验和:数据完整性校验
- CMAC 安全消息:认证后所有命令附带 AES-CMAC(NIST 800-38B)防篡改
- PIN 重试计数器:密码错误时自动递减,认证成功后重置为 10
- 安全随机数:认证使用
crypto.getRandomValues()(需要 Hermes ≥ 0.72 或react-native-get-random-valuespolyfill)
项目结构
src/
├── index.ts # 公共 API 导出
├── constants.ts # 共享常量(页地址、NFC 指令、助记词类型)
├── types.ts # 统一 NfcStatusCode、NfcResult 接口、错误映射
├── crypto.ts # AES 加解密、密钥派生、安全随机数
├── utils.ts # CRC16、十六进制转换、数组工具
├── nfc-core.ts # NFC 锁、transceive、认证、重试计数器
├── reader.ts # 读取 API
└── writer.ts # 写入 API平台支持
| 平台 | 技术 | 要求 | |------|------|------| | iOS | MifareIOS | iPhone 7 及以上 | | Android | NfcA | 支持 NFC 的设备 |
许可证
MIT
