M1 写卡采用NFC MifareClassic技术
M1卡解析
不论是NFC还是读卡模块读,解析流程都是先寻卡,然后验证扇区的密码,取扇区的数据,比如已知要读的数据在2扇区,
那么寻卡后验证时把要验证的扇区号、扇区的密码,和扇区的验证密码类型A/B传过去验证通过后,就可以读取数据了
M1卡数据分布
扇区:一般16个扇区 每个区 有4块,总共64块
* 0-15个扇区:一个扇区对应4个块,所以总共有64个块,序号分别为0-63,第一个扇区对应:0-3块,第二个扇区对应:4-7块...
* 每个扇区的最后一个块用来存放密码或控制位,其余为数据块,一个块占用16个字节,keyA占用6字节,控制位占用4字节,keyB占用6字节
MifareClassic标签的读写流程
* 1,获得NFCAdapter对象
* 2,获得MifareClassic对象
* 3,服务器生成密钥,获取密钥
* 4,读取数据块的数据,连接 Connect(),验证密钥,读取数据,关闭close()
* 5,将数据块写入标签,连接 Connect(),验证密钥,写数据,关闭 close()
一,获取 NFC 实例
const NFCAdapter = wx.getNFCAdapter()
二,获取MifareClassic实例,实例支持MIFARE Classic标签的读写
const MifareClassic = NFCAdapter.getMifareClassic()
三, 开始监听
NFCAdapter.startDiscovery();
四,贴卡,监听回掉
NFCAdapter.onDiscovered((res)=>{
// res.techs Array NFC卡类型,可以判断 CPU卡,或者 M1卡 或者其他
包含 MIFARE Classic 数据M1卡
// res.id 卡ID, 二进制数据,需要转成 16进制或者十进制
// 拿到卡ID,接下来获取服务器获取密钥,密钥由服务器生成
})
五,获取密钥Key,每个扇区都有自己的密钥,卡密钥存储在每个扇区的最后一块
六,链接connect
MifareClassic.connect({
success:(res)=>{
//链接成功
},
fail:(res)=>{
//链接失败
}})
七,验证密钥,无论读写,都需要验证密钥
//sectorIndex 区索引,块索引比如 4区的2块,i结果结果就是 18,64 块的第18块
let i = sectorIndex * 4 + blockIndex;
const params = {
cmd: 0x61, // 验证指令 60,验证A密钥,61 验证B密钥
block: i, // 第几块
cardId: hexStringToArray(snr), // 卡片id onDiscovered获取
key: nfcUtil.buf2hexArr(cardPsd), // 验证当前区KeyB密钥
};
// 发送指令
MifareClassic.transceive({
data: new Uint8Array([params.cmd, params.block,
...params.cardId,...params.key]).buffer,
success: function(res1) {
//验证块成功 },
fail: function(err) {
console.log("验证失败" + i, err, params);
},
});
八,读写NFC 卡数据
// 读指令 0x30 + 块号 可以用于读取某个块的数据
// 写指令 0xA0 + 块号 + 待写入数据 可以用于往某个块写入数据
// 开始读区数据
let arr = [0xA0, i, ...stringToArrayBuffer(writeData)];
let arrayBuffer = new Uint8Array(arr).buffer;
// console.log('开始写文件', arr, arrayBuffer)
MifareClassic.transceive({
data: arrayBuffer,
success: function(res) {
MifareClassic.close()
// console.log("写文件成功" )
},
fail: function(err) {
// console.log("读取失败" + i, err, );
MifareClassic.close()
},
})
读写封装
/**
* 根据索引写数据
* @param {Object} sectorIndex 区 0-15
* @param {Object} blockIndex 块 0-3
* @param {Object} writeData 数据
*/
async writeBlockWithIndex(sectorIndex, blockIndex, writeData) {
return new Promise((resolve, reject) => {
let result = {
success: false,
data: null
}
let that = this;
let i = sectorIndex * 4 + blockIndex;
that.mifareClassic.connect({
success: res => {
let cardPsd = that.allKeys[i]
const params = {
cmd: 0x61, // 验证指令 60,验证A密钥,61 验证B密钥
block: i, // 第几块
cardId: hexStringToArray(this.snr), // 卡片id onDiscovered获取
key: nfcUtil.buf2hexArr(cardPsd), // 验证KeyB密钥
};
//0x03, 0x9b, 0x3d, 0x70, 0x92, 0x4e
// 指令 0x30 + 块号 可以用于读取某个块的数据
// 指令 0xA0 + 块号 + 待写入数据 可以用于往某个块写入数据
that.mifareClassic.transceive({
data: new Uint8Array([params.cmd, params.block, ...params
.cardId,
...params.key
])
.buffer,
success: function(res1) {
// console.log("验证块成功" + i + '', buf2hex(res1.data), res1)
// 开始读区数据
let arr = [0xA0, i, ...stringToArrayBuffer(writeData)];
let arrayBuffer = new Uint8Array(arr).buffer;
// console.log('开始写文件', arr, arrayBuffer)
that.mifareClassic.transceive({
data: arrayBuffer,
success: function(res) {
that.mifareClassic.close()
// console.log("写文件成功",
// res,
// )
result.success = true
result.data = ""
resolve(result.data)
// console.log("写文件成功2", result)
},
fail: function(err) {
// console.log("读取失败" + i, err, );
that.mifareClassic.close()
uni.hideLoading()
uni.showModal({
title:'写卡失败',
showCancel:false
})
},
})
},
fail: function(err) {
console.log("验证失败" + i, err, params);
uni.hideLoading()
uni.showModal({
title:'写卡失败',
showCancel:false
})
that.mifareClassic.close()
},
});
}
})
});
}
/**
* 读取块数据
* @param {Object} sectorIndex 区 0-15
* @param {Object} blockIndex 块 0-3
*/
async readBlockWithIndex(sectorIndex, blockIndex) {
return new Promise((resolve, reject) => {
let result = {
success: false,
data: null
}
let that = this;
let i = sectorIndex * 4 + blockIndex;
that.mifareClassic.connect({
success: res => {
let cardPsd = that.allKeys[i]
const params = {
cmd: 0x61, // 验证指令 60,验证A密钥,61 验证B密钥
block: i, // 第几块
cardId: hexStringToArray(this.snr), // 卡片id onDiscovered获取
key: nfcUtil.buf2hexArr(cardPsd), // 验证KeyB密钥
};
// 0x03, 0x9b, 0x3d, 0x70, 0x92, 0x4e
// 指令 0x30 + 块号 可以用于读取某个块的数据
// 指令 0xA0 + 块号 + 待写入数据 可以用于往某个块写入数据
that.mifareClassic.transceive({
data: new Uint8Array([params.cmd, params.block, ...params
.cardId,
...params.key
])
.buffer,
success: function(res1) {
// console.log("验证块成功" + i + '', buf2hex(res1.data), res1)
// 开始读区数据
let arr = [0x30, i];
let arrayBuffer = new Uint8Array(arr).buffer;
that.mifareClassic.transceive({
data: arrayBuffer,
success: function(res) {
that.mifareClassic.close()
result.success = true
result.data = buf2hex(res.data)
resolve(result.data)
console.log("读取成功解析", result)
},
fail: function(err) {
that.mifareClassic.close()
uni.hideLoading()
uni.showModal({
title:'读卡失败',
showCancel:false
})
},
})
},
fail: function(err) {
console.log("验证失败" + i, err, params);
that.mifareClassic.close()
uni.hideLoading()
uni.showModal({
title:'读卡失败',
showCancel:false
})
},
});
}
})
});
}
调用
// 钱包区 读取3区的0块
let result3_0 = await this.readBlockWithIndex(3, 0)
//冻结区数据 读取4区的0块
let result4_0 = await this.readBlockWithIndex(4, 0)