参考文档: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);
        }
      })
    })
  }
});
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐