微信小程序,开箱即用实现NFC读写M1卡及修改密钥

扇区概念

常见的基于 Mifare Classic 技术的 NFC 卡,如 M1 卡 ,有 16 个扇区 。每个扇区由 4 个块(块 0、块 1、块 2、块 3 )组成 。其中,第 0 扇区的块 0 一般用于存放厂商代码等固化信息,不可更改;其他扇区的块 0、块 1、块 2 多为数据块,用于存储用户数据 ;块 3 为控制块,包含密码 A、存取控制信息、密码 B 。
 

全局块

将每个扇区的块放在一起,一共是64块,一个扇区有四块,第一扇区就是0-3,第二扇区就是4-7,以此类推。

全局控制块的计算方法

扇区*4+3

 以第1扇区为例,1*4+3=7,7是控制块,4-5-6是数据块。

读写步骤

获取NFC实例-->发现卡-->认证扇区块-->读写数据

代码示例 

一些细节都在代码里面注释

import Toast from '@vant/weapp/toast/toast';
let that = null
let nfcAdapter = null
Page({

    /**
     * 页面的初始数据
     */
    data: {
        
    },

    /**
     * 生命周期函数--监听页面加载
     */
    onLoad(options) {
        that = this
        this.checkPhoneNfc()
    },

    checkPhoneNfc() {
        let that = this
        //获取NFC实例
        nfcAdapter = wx.getNFCAdapter()
        //开启NFC
        nfcAdapter.startDiscovery({
            success(res) {
                //vantUI 没有安装的可以去掉,或者自己安装一下
                Toast.loading({
                    message: "请刷卡",
                    forbidClick: true,
                    duration: 0
                })
                //监听NFC刷卡
                nfcAdapter.onDiscovered(res => {
                    //一般都会有MIFARE Classic
                    if (res.techs.includes("MIFARE Classic")) {
                        //这里使用getMifareClassic,还有getNfcA/getNfcB,但是流程都是一样的,按照实际情况来就可以了
                        let nfcMAdapter = nfcAdapter.getMifareClassic()
                        //传递实例以及id,id在认证的时候需要用到
                        that.nfcWirteCard(nfcMAdapter, new Uint8Array(res.id))
                    } else {
                        Toast("当前卡片不支持写入格式")
                    }
                });
            },
            fail(err) {
                if (err.errCode == 13001) {
                    Toast("系统NFC开关未开启")
                }
                if (err.errCode == 13000) {
                    Toast("当前设备不支持NFC")
                }
            }
        })
    },

    nfcWirteCard(nfcMAdapter, uid) {
        //连接卡片
        nfcMAdapter.connect()

        //连接上了,会立刻调用
        nfcMAdapter.isConnected({
            success(res) {
                /* 
                    以下代码是通过默认密钥认证,然后修改密钥,再进行读写数据(修改密钥不是必须的,你可以只进行读写,但是认证是必须的)
                    https://developers.weixin.qq.com/miniprogram/dev/api/device/nfc/MifareClassic.transceive.html 
                    transceive() 发送数据
                */
                nfcMAdapter.transceive({
                    /* 
                    参数:
                        1.密钥类型 0x60 是认证密钥A 0x61是认证密钥B 选一个就可以了
                        2.块 0x07 是指认证的全局块 就是扇区1嘛,前面提到过的
                        3.卡id 展开上面传递过来的id
                        4.密钥 0xFF, 0xFF... 是默认密钥,一般空卡都是这个密钥,如果已经加密过的,需要知道修改后的密钥,如果你不知道的话,就没办法读写了
                    */
                    data: new Uint8Array([0x61, 0x07, ...uid, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]).buffer,
                    success(res) {
                        //默认密钥通过,先进行修改密钥
                        that.updateSecret(nfcMAdapter)
                    },
                    fail(err) {
                        //默认密钥不通过,则使用修改后的密钥进行认证
                        that.reAuthSecret(nfcMAdapter,uid)
                    }
                })
            }
        })
    },

    //修改密钥
    updateSecret(nfcMAdapter) {
        //新密钥,看你嵌入式那边咋说了
        const newKey = [0x60, 0x68, 0x73, 0x2A, 0x2C, 0x20]
        const accessBits = [0xFF, 0x07, 0x80, 0x69];//权限,默认就行
        nfcMAdapter.transceive({
            /*
            参数:
                1.指令 0xA0 写入数据 0x30 读取数据
                2.块 0x07 扇区1的控制块 修改密钥必须往控制块里面写
                3.密钥A
                4.权限
                5.密钥B
            */
            data: new Uint8Array([0xA0, 0x07, ...newKey, ...accessBits, ...newKey]).buffer,
            success(res) {
                //修改密钥之后,读写数据
                that.writeData(nfcMAdapter)
            },
            fail(err) {
                Toast.loading({
                    message: "密码写入卡片异常\n请再次刷卡",
                    forbidClick: true,
                    duration: 0
                })
            }
        })
    },

    //修改后的密钥进行认证
    reAuthSecret(nfcMAdapter, uid) {
        //修改后的密钥跟updateSecret方法的新密钥对应
        const newKey = [0x60, 0x68, 0x73, 0x2A, 0x2C, 0x20]
        nfcMAdapter.transceive({
            //认证
            data: new Uint8Array([0x61, 0x07, ...uid, ...newKey]).buffer,
            success(res) {
                //认证成功,读写数据
                that.writeData(nfcMAdapter)
            },
            fail(err) {
                Toast.loading({
                    message: "密码写入卡片异常\n请再次刷卡",
                    forbidClick: true,
                    duration: 0
                })
            }
        })
    },

    //写入数据然后读取
    writeData(nfcMAdapter) {
        let pwd = []
        //遍历你要传递的数据 写入数据必须是16个字节,如果你的数据不够,就在后面加上0xFF或者0x00
        for (let i of "123456789") {
            pwd.push(i.charCodeAt(0))
        }
        //假设数据不够长 "123456789"是九位,那就还差7位
        let str = [0x00,0x00,0x00,0x00,0x00,0x00,0x00]
        pwd.push(...str)
        nfcMAdapter.transceive({
            /*
            写入数据
            参数:
                1.指令 0xA0 写入数据 0x30 读取数据
                2.写入数据的全局块 0x04 因为我们认证的是扇区1的全局控制块,那么可以在全局块4-5-6写入数据
                3.数据
            */
            data: new Uint8Array([0xA0,0x04,...pwd]).buffer,
            success() {
                /*
                读取数据
                参数:
                    1.指令 0xA0 写入数据 0x30 读取数据
                    2.读取数据的全局块 0x04 能写就能读
                */
               nfcMAdapter.transceive({
                   data:new Uint8Array([0x30,0x04]).buffer,
                   success(res){
                       console.log("读取出来的数据",res);
                       //断开连接
                       nfcAdapter.stopDiscovery()
                   }
               })
            },
            fail() {
                Toast.loading({
                    message: "密码写入卡片异常\n请再次刷卡",
                    forbidClick: true,
                    duration: 0
                })
            }
        })
    },

    /**
     * 生命周期函数--监听页面初次渲染完成
     */
    onReady() {

    },

    /**
     * 生命周期函数--监听页面显示
     */
    onShow() {

    },

    /**
     * 生命周期函数--监听页面隐藏
     */
    onHide() {

    },

    /**
     * 生命周期函数--监听页面卸载
     */
    
    onUnload() {
        if(nfcAdapter){
            nfcAdapter.stopDiscovery()
        }
    },

    /**
     * 页面相关事件处理函数--监听用户下拉动作
     */
    onPullDownRefresh() {

    },

    /**
     * 页面上拉触底事件的处理函数
     */
    onReachBottom() {

    },

    /**
     * 用户点击右上角分享
     */
    onShareAppMessage() {

    }
})

可以直接复制代码到JS文件里面尝试一下

验证读写是否成功

打开电脑计算器,里面选择程序员,十进制里面输入49

以此类推,最后得到313233343536373839

拿去转换一下

不知道全局块是0x??

想在扇区13里面写数据,但是不知道该怎么填写。

套用上面的计算公式

13*4+3=55

还是电脑的计算器

结果就是0x37了,0x37是控制块,那么0x34-0x35-0x36就是数据块了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值