微信小程序NFC M1卡读写

732 阅读4分钟
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)