NFC-MifareClassic1K卡-M1卡详解
参考文档:https://developers.weixin.qq.com/community/develop/article/doc/0008ea3fa80b881383cf54bcd51813。
参考文档:https://developers.weixin.qq.com/community/develop/article/doc/0008ea3fa80b881383cf54bcd51813
https://www.mwahiot.com/Service/mifare_classic_s50_technical_details.html
M1 基础知识
我们把M1的内存区比作一个硬盘,它的16个扇区就相当于把硬盘分区分了A、B、C…等16个磁盘,每个磁盘上又创了四个文件(块0,块1,块2,块3),每个块都是用16个字节的16进制存放数据。
小程序: 小程序没有扇区的概念,因为NFC块都是连续的, 所以一般卡16个扇区, 每个扇区4个块, 对于小程序来说就是 0~63 块共计64块。比如第0扇区的第0块,就是0x00。
块0~块2:存放用户自定义的数据
块3:存放了当前扇区的密钥A(占用6字节)、密钥B(占用6字节)、和各个块的访问控制权限(占用4字节)。
UID:nfc 卡的唯一识别码。UID一般用于识别卡片本身,相当于人的身份证号。
密钥:包含密钥A和密钥B,可以用于控制对应扇区读写访问,允许进行权限管理。它们分别用来认证对扇区的访问。在使用前,必须先通过认证才能进行读、写、增值和减值等操作。不同应用和不同的用户可以根据实际需要选择是使用密钥A还是密钥B,或者两者兼用。通常情况下密钥A用于读取操作,密钥B用于写入操作,但是这可以通过修改访问控制位来改变。在设计上,密钥A往往有更多地被设计为不容易更改,而密钥B可能更加灵活,但这完全取决于应用程序的具体需求和配置。大多数新的MIFARE Classic卡出厂时,密钥A和密钥B都设置为默认值,通常是六个字节的全FF或全00 (例如FF FF FF FF FF FF或00 00 00 00 00 00)。注意: 在操作密钥时,务必要谨慎。密钥A和密钥B被不正确设置可能会导致您永久失去对扇区的访问权限。
认证
带有密钥验证的卡,必读。每次在读或者写时都要先验证在读写, 不然就会失败。认证是M1进行读取和写入之前必须处理的操作,如果你要操作的块读写只需要密钥A的认证,那就只要做一次密钥A的认证就可以进行读写了,这完全取决于上面说的访问控制权限。需要知道卡密码,基本初始密码都是FFFFFFFFFFFF或者000000000000
操作指令
验证指令
[0x60]+要操作的块[0x01]+卡的ID[0xde, 0x63, 0x35, 0x9c,]+秘钥[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
验证指令:0x60 是验证指令。
要操作的块:写入或读取不同的块 要验证不同的块, 比如读取 0x01 块 要操作的块就要写[0x01];当要要操作 16扇区第3块时, 小程序的要操作的块就要写[0x3f](十进制63)。
卡的ID:可以任意写,只要满足是 16进制的 4字节数据即可,卡认证的时候主要是用密钥进行认证的。
密钥:问卡商要,新卡默认密码通常为 FF FF FF FF FF FF或00 00 00 00 00 00
读取指令
[0x30,0x01] : 读取第0个扇区第二块
验证通过后可以进行读取。
读取指令:0x30
要操作的块:写入或读取不同的块 要验证不同的块, 比如读取 0x01 块 要操作的块就要写[0x01];当要要操作 16扇区第3块时, 小程序的要操作的块就要写[0x3f](十进制63)。
写入指令
[0xa0,0x09,0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
验证通过后可以进行写入。在第9块写入 6个字节的数据。
写入指令:0xa0
要操作的块:写入或读取不同的块 要验证不同的块, 比如读取 0x01 块 要操作的块就要写[0x01];当要要操作 16扇区第3块时, 小程序的要操作的块就要写[0x3f](十进制63)。
写入数据:我的这个卡写入时必须要组合成 18个字节才能写入, 少于或多都会写入失败。[指令(0xa0),块号(0x09),…16个字节].length = 18, 内容不够的填充FF或者填充其他的内容, 数组必须凑够18个元素。
修改密钥
验证通过后可以进行修改,和写入指令一样,不过需要注意,操作时务必要谨慎。密钥A和密钥B被不正确设置可能会导致您永久失去对扇区的访问权限。
示例代码
<view>{{title}}</view>
<view>
<button size="default" type="primary" onTap="startDiscovery">startDiscovery</button>
</view>
let adapter;
Page({
data: {
title: '请点击 startDiscovery 按钮',
},
onLoad(query) {
var that = this;
adapter = my.getNFCAdapter();
console.log("获取NFC实例", adapter);
adapter.onDiscovered(res => {
console.log("读取到卡片了", res);
if (res.techs.includes(adapter.tech.mifareClassic)) {
console.log('发现' + adapter.tech.mifareClassic + '卡');
let mifareClassic = adapter.getMifareClassic();
mifareClassic.connect({
success: res => {
console.log("设备已连接", res)
console.log("开始拼接验密指令。。。");
const arr = [
0x60, // 验密指令,0x60 代表验证密钥 A,0x61 代表验证密钥 B
0x00, // 要操作的块
0xD5, 0x13, 0xCC, 0xE3, // UID 可以任意写,只要满足是 16进制的 4字节数据即可,卡认证的时候主要是用密钥进行认证的。
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 密钥
];
var arrayBuffer = new Uint8Array(arr).buffer;
console.log("解密指令为:", arrayBuffer);
mifareClassic.transceive({
data: arrayBuffer,
success: function (res) {
console.log(that.buf2hex(res.data));
console.log('发送数据并解密成功, 接收数据如下:', res);
// 接下来可对当前扇区进行读写操作
// ...
// 读取
const readArr = [0x30, 0x00];
that.readChunkHandle(mifareClassic, readArr).then(res => {
console.log(that.buf2hex(res.data));
mifareClassic.close({
success() {
console.log('连接关闭');
adapter.stopDiscovery({
success() {
console.log('停止检测');
}
});
}
});
}).catch(err => {
console.log(err);
})
// 写入
},
fail: function (err) {
console.log('发送数据失败', err);
my.showToast({
title: 'nfc失败!',
icon: 'error'
})
}
})
}
})
} else {
console.log('没发现卡');
my.showToast({
title: '卡片类型有误!',
icon: 'error'
})
}
})
},
startDiscovery() {
let that = this;
adapter.startDiscovery({
success: function (res) {
console.log(res);
that.setData({
title: '请将设备放入识别区NFC'
})
},
fail: function (res) {
that.setData({
title: '刷新重试'
})
console.log(res);
if (res.errCode == 13000) {
//当前设备不支持NFC
} else if (res.errCode == 13001) {
//系统NFC开关未打开
} else {
//未知错误
}
}
});
},
buf2hex(arrayBuffer) {
return Array.prototype.map.call(new Uint8Array(arrayBuffer), x => ('00' + x.toString(16)).slice(-2)).join('');
},
readChunkHandle(mifareClassic, readArr) {
return new Promise((resolve, reject) => {
const readArrayBuffer = new Uint8Array(readArr).buffer;
mifareClassic.transceive({
data: readArrayBuffer,
success: function (res) {
console.log('读取区块成功');
resolve(res);
},
fail: function (err) {
console.log('读取区块失败', err);
reject(err);
}
})
})
},
writeChunkHandle(mifareClassic, writeArr) {
return new Promise((resolve, reject) => {
const writeArrBuffer = new Uint8Array(writeArr).buffer;
mifareClassic.transceive({
data: writeArrBuffer,
success: function (res) {
console.log('写入区块成功');
resolve(res);
},
fail: function (err) {
console.log('写入区块失败', err);
reject(err);
}
})
})
}
});
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)