• Android NFC M1卡读写&芯片卡读写(CPU卡读写)(RFID读写)


    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/sgn5200/article/details/82855478
    Android NFC M1卡读写&芯片卡读写(CPU卡读写)(RFID读写)
    NFC 读写分几种,本文主要讲M1卡扇区读写和芯片卡读写
    权限
    初始化
    1 onCreate( initNFC() )
    2 onResume( )
    3 onPause()
    4 NFC设备刷卡时触发 onNewIntent(Intent)
    1,标签读写
    2,扇区读写
    3 CPU卡读写 重头戏
    NFC 读写分几种,本文主要讲M1卡扇区读写和芯片卡读写
    NFC 标签读写
    NFC 扇区读写
    NFC 文件读写

    权限
    <uses-feature
    android:name="android.hardware.nfc"
    android:required="true"/>

    <uses-permission android:name="android.permission.NFC"/>
    1
    2
    3
    4
    5
    <activity android:name=".ReadTextActivity" android:launchMode="singleTop">
    <intent-filter>
    <action android:name="android.nfc.action.TAG_DISCOVERED"/>
    <action android:name="android.nfc.action.TECH_DISCOVERED" />
    <data android:mimeType="text/plain"/>
    <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
    </activity>
    1
    2
    3
    4
    5
    6
    7
    8
    初始化
    在Activity#onCreate()注册,在Activity#onResume()开启前台调度系统,在Activity#onPause退出前台调度。
    1
    1 onCreate( initNFC() )
    private void initNFC() {
    // 获取nfc适配器,判断设备是否支持NFC功能
    nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    if (nfcAdapter == null) {
    shotToast("当前设备不支持NFC功能");
    } else if (!nfcAdapter.isEnabled()) {
    shotToast("NFC功能未打开,请先开启后重试!");
    }
    pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
    getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
    ndef.addCategory("*/*");
    // 允许扫描的标签类型
    mWriteTagFilters = new IntentFilter[]{ndef};
    mTechLists = new String[][]{
    new String[]{MifareClassic.class.getName()},
    new String[]{NfcA.class.getName()}};// 允许扫描的标签类型
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    2 onResume( )
    @Override
    protected void onResume() {
    super.onResume();
    //开启前台调度系统
    nfcAdapter.enableForegroundDispatch(this, pendingIntent, mWriteTagFilters, mTechLists);
    }
    1
    2
    3
    4
    5
    6
    3 onPause()
    @Override
    protected void onPause() {
    super.onPause();
    nfcAdapter.disableForegroundDispatch(this);
    }
    1
    2
    3
    4
    5
    4 NFC设备刷卡时触发 onNewIntent(Intent)
    给伪代码,详细见下面3点分解
    1
    @Override
    protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    //当该Activity接收到NFC标签时,运行该方法
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()) ||
    NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
    Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    1,标签读写
    Ndef ndef = Ndef.get(tag);//如果ndef为空表示不支持该格式
    //可进行格式 如果格式化失败则不能只能换个方式
    2,M1 扇区读写
    MifareClassic mfc = MifareClassic.get(tag);//CPU卡时 mfc将为空
    3,CPU卡 读写
    NfcCpuUtilsnfc = new NfcCpuUtils(IsoDep.get(tag));
    }
    }

    1,标签读写
    /**
    * 写标签
    * @param ndef
    * @param tag
    * @param ndefMessage
    * @return
    * @throws IOException
    * @throws FormatException
    */
    private boolean writeMsg(Ndef ndef, Tag tag, NdefMessage ndefMessage) throws IOException, FormatException {
    try {
    if (ndef == null) {
    shotToast("格式化数据开始");
    //Ndef格式类
    NdefFormatable format = NdefFormatable.get(tag);
    format.connect();
    format.format(ndefMessage);
    } else {
    shotToast("写入数据开始");
    //数据的写入过程一定要有连接操作
    ndef.connect();
    ndef.writeNdefMessage(ndefMessage);
    }
    return true;
    } catch (IOException e) {
    e.printStackTrace();
    shotToast("IO异常,读写失败");
    } catch (FormatException e) {
    e.printStackTrace();
    shotToast("格式化异常,读写失败");
    } catch (NullPointerException e) {
    shotToast("格NullPointerException异常,读写失败");
    }catch (IllegalStateException e){
    shotToast("Close other technology first!");
    }
    return false;
    }


    /**
    * 读取NFC标签文本数据
    */
    private void readNfcTag(Intent intent) {
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())||
    NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
    Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
    NfcAdapter.EXTRA_NDEF_MESSAGES);
    NdefMessage msgs[] = null;
    int contentSize = 0;
    if (rawMsgs != null) {
    msgs = new NdefMessage[rawMsgs.length];
    for (int i = 0; i < rawMsgs.length; i++) {
    msgs[i] = (NdefMessage) rawMsgs[i];
    contentSize += msgs[i].toByteArray().length;
    }
    }
    try {
    if (msgs != null) {
    print(msgs.length+" 长度");
    NdefRecord record = msgs[0].getRecords()[0];
    String textRecord = parseTextRecord(record);
    mTagText += textRecord + " text " + contentSize + " bytes";
    print(mTagText);
    }
    } catch (Exception e) {
    }
    }
    }

    1
    2,扇区读写
    M1扇区默认是没有密码的,但有部分人闲不住要把密码改了,因此认证过程要加密码,一般认证KeyA就行。普通卡16个扇区64块,第一个扇区等闲不能操作。每个扇区4块,从0数起,第二扇区第一块索引就是8,每个扇区前3块存数据最后一块一般存密码。实例代码读的是2扇区8块。

    /**
    * 扇区写
    * @param tag
    * @param sectorIndex 扇区索引 一般16个扇区 64块
    * @return
    */
    public boolean writeTAG(Tag tag,int sectorIndex) {
    MifareClassic mfc = MifareClassic.get(tag);
    try {
    mfc.connect();
    if (mfc.authenticateSectorWithKeyA(sectorIndex, new byte[]{0x42,0x53,0x4B, (byte) sectorIndex,0x4C,0x53})) { //已知密码认证 r
    // the last block of the sector is used for KeyA and KeyB cannot be overwritted
    int block = mfc.sectorToBlock(sectorIndex);
    mfc.writeBlock(block, "sgn-old000000000".getBytes());
    mfc.close();
    shotToast("旧卡 写入成功");
    return true;
    }else if(mfc.authenticateSectorWithKeyA(sectorIndex, MifareClassic.KEY_NFC_FORUM)){ //新卡 未设密码认证 r
    int block = mfc.sectorToBlock(sectorIndex);
    mfc.writeBlock(block, "SGN-new000000000".getBytes());
    mfc.close();
    shotToast("新卡 写入成功");
    } else{
    shotToast("未认证");
    }
    } catch (IOException e) {
    e.printStackTrace();
    shotToast("扇区连接异常");

    try {
    mfc.close();
    } catch (IOException e1) {
    e1.printStackTrace();
    }
    }
    return false;
    }


    /**
    * 读扇区
    * @return
    */
    private String readTag(Tag tag,MifareClassic mfc,int sectorIndex){
    for (String tech : tag.getTechList()) {
    System.out.println("------------"+tech);
    }
    //读取TAG
    try {
    String metaInfo = "";
    //Enable I/O operations to the tag from this TagTechnology object.
    mfc.connect();
    int type = mfc.getType();//获取TAG的类型
    int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数
    String typeS = "";
    switch (type) {
    case MifareClassic.TYPE_CLASSIC:
    typeS = "TYPE_CLASSIC";
    break;
    case MifareClassic.TYPE_PLUS:
    typeS = "TYPE_PLUS";
    break;
    case MifareClassic.TYPE_PRO:
    typeS = "TYPE_PRO";
    break;
    case MifareClassic.TYPE_UNKNOWN:
    typeS = "TYPE_UNKNOWN";
    break;
    }
    metaInfo += "卡片类型:" + typeS + " 共" + sectorCount + "个扇区 共" + mfc.getBlockCount() + "个块 存储空间: " + mfc.getSize() + "B ";
    int blockIndex;
    if (mfc.authenticateSectorWithKeyA(sectorIndex, new byte[]{0x42,0x53,0x4B, (byte) sectorIndex,0x4C,0x53}) ) {
    blockIndex = mfc.sectorToBlock(sectorIndex);
    byte[] data = mfc.readBlock(blockIndex);
    metaInfo += "旧卡 Block " + blockIndex + " : " + new String(data) + " ";
    }else if( mfc.authenticateSectorWithKeyA(sectorIndex, MifareClassic.KEY_NFC_FORUM)){
    blockIndex = mfc.sectorToBlock(sectorIndex);
    byte[] data = mfc.readBlock(blockIndex);
    metaInfo += "新卡 Block " + blockIndex + " : " + new String(data) + " ";

    }else {
    metaInfo += "Sector " + sectorIndex + ":验证失败 ";
    }
    return metaInfo;
    } catch (Exception e) {
    Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
    e.printStackTrace();
    } finally {
    if (mfc != null) {
    try {
    mfc.close();
    } catch (IOException e) {
    Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG)
    .show();
    }
    }
    }
    return null;
    }

    99
    3 CPU卡读写 重头戏
    先直接上代码,看完代码在说吧,搞这个有点心力疲惫。
    下面是一个写的完全流程,if的嵌套我承认有点low,但有助于流程理解。
    这里要说下外部认证过程:
    devices -----获取4字节随机数---------------------> cpu 卡
    devices <--------随机数+90 00--------------------- cpu 卡
    四个字节随机数+四个字节0 使用密钥进行DES加密,如果是8个随机数DES3加密。
    将命令00 82 00 00 08 以及加密后的随机数取前8位 7f cf 90 a0 5b 9c f1 73发送
    devices ----00 82 00 00 08 7f cf 90 a0 5b 9c f1 73–>cpu 卡
    devices <------------- 90 00---------------------------- cpu 卡

    /**
    * Description : cpu卡写的工具类 命令返回90 00 表示成功
    * CreateAuthor: Cannan
    * CreateTime : 2018/9/22 18:53
    * Project : TestNFC
    */

    public class NfcCpuUtils {

    /**
    * 1. 在“COS命令框”输入“00A40000023F00”,然后点击“发送命令”,进入主目录
    */
    private final byte[] CMD_START = new byte[]{0x00, (byte) 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00}; //6f,15,84,e,31,50,41,59,2e,53,59,53,2e,44,44,46,30,31,a5,3,88,1,1,90,0,
    /**
    * 2. 复合外部认证(秘钥:FFFFFFFFFFFFFFFF,秘钥标识号:00)
    */
    private byte[] CMD_KEY = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
    /**
    * 2.1 获取4位 随机码 {0x00, (byte) 0x84, 0x00, 0x00, 0x04}
    */
    private final byte[] CMD_GET_RANDOM = {0x00, (byte) 0x84, 0x00, 0x00, 0x04};

    private final byte[] CMD_DEL = {(byte) 0x80, 0x0E, 0x00, 0x00, 0x00}; //3.删除主目录下的所有文件:800E000000(注意:这个命令会删除主目录下的所有文件)

    // 4. 建立外部认证秘钥 4.1选择根目录(00A4000000)
    // 4.2建密钥文件 (80 E0 00 00 07 3F 00 B0 01 F0 FF FF
    // 4.3创建外部认证密钥 (80 D4 01 00 0D 39 F0F0 AA 55 FFFFFFFFFFFFFFFF)
    private final byte[] CMD_CREATE_DIR = {0x00, (byte) 0xA4, 0x00, 0x00, 0x02,0x3f,0x00};
    private final byte[] CMD_CREATE_KEY = {(byte) 0x80, (byte) 0xE0, 0x00, 0x00, 0x07, 0x3F, 0x00, (byte) 0xB0, 0x01, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF};
    private final byte[] CMD_CREATE_OUT_KEY = {(byte) 0x80, (byte) 0xD4, (byte) 0x01, (byte) 0x00, (byte) 0x0D, (byte)0x39, (byte) 0xF0, (byte) 0xF0, (byte) 0xAA
    , (byte) 0x55, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
    //5 建立访问自定义文件的密钥文件
    private final byte[] CMD_ACCESS = {(byte) 0x80, (byte) 0xE0, (byte) 0x00, (byte) 0x01, (byte) 0x07, (byte) 0x3F, (byte) 0x01, (byte) 0x8F, (byte) 0x95, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF};
    // 填充密钥123456
    private final byte[] CMD_ACCESS_INTO = {(byte) 0x80, (byte) 0xD4, (byte) 0x01, (byte) 0x01, (byte) 0x08, (byte) 0x3A, (byte) 0xF0, (byte) 0xEF, (byte) 0x44, (byte) 0x55, (byte) 0x12, (byte) 0x34, (byte) 0x56};
    //6. 创建自定义文件,标识为005(80E000050728000FF4F4FF02)
    private final byte[] CMD_ACCESS_FILE = {(byte) 0x80, (byte) 0xE0, (byte) 0x00, (byte) 0x05, (byte) 0x07, (byte) 0x28, (byte) 0x00, (byte) 0x0F, (byte) 0xF4, (byte) 0xF4, (byte) 0xFF, (byte) 0x02};
    //7.写数据到文件标识为0005的文件
    //7.1选中该文件(00A40000020005)
    // 7.2写数据“112233445566”到该文件(00D6000006112233445566)
    private final byte[] CMD_ACCESS_FILE_CHOOICE = {(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x05};
    private final byte[] CMD_ACCESS_FILE_WRITE = {(byte) 0x00, (byte) 0xD6, (byte) 0x00, (byte) 0x00, (byte) 0x06, (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x44, (byte) 0x55, (byte) 0x66};

    // 声明ISO-DEP协议的Tag操作实例
    private final IsoDep tag;

    public NfcCpuUtils(IsoDep tag) throws IOException {
    // 初始化ISO-DEP协议的Tag操作类实例
    this.tag = tag;
    tag.setTimeout(5000);
    tag.connect();
    }

    public byte[] wirte() throws IOException {
    byte[] resp = tag.transceive(CMD_START); //1 进入主目录
    if (checkRs(resp)) {
    print("1 进入主目录成功");
    resp = tag.transceive(CMD_GET_RANDOM); //2 获取随机码
    if (checkRs(resp)) {
    print("2 获取随机码");
    byte[] random = {resp[0], resp[1], resp[2], resp[3], 0x00, 0x00, 0x00, 0x00};//3 随机码4个字节+4个字节0
    byte[] desKey;
    try {
    desKey = encrypt(random, CMD_KEY); //4 生产加密后的随机码
    print("3 生产加密后的随机码");
    printByte(desKey);
    } catch (Exception e) {
    e.printStackTrace();
    desKey = null;
    }
    //00 82 00 00 08 7f cf 90 a0 5b 9c f1 73
    if (desKey != null && desKey.length > 8) {
    byte[] respondKey = {0x00, (byte) 0x82, 0x00, 0x00, 0x08, desKey[0], desKey[1], desKey[2], desKey[3], desKey[4], desKey[5], desKey[6], desKey[7]};
    print("4 生产加密后的随机码命令");
    printByte(respondKey);
    resp = tag.transceive(respondKey); //5 将加密后的随机码发送,注意此处第四字节表示密码标识符00,
    }
    if (checkRs(resp)) {
    print("5 外部认证成功");
    resp = tag.transceive(CMD_DEL);
    if (checkRs(resp)) {
    print("6 删除目录成功");
    resp = tag.transceive(CMD_CREATE_DIR);
    if (checkRs(resp)) {
    print("7 选择目录");
    resp = tag.transceive(CMD_CREATE_KEY);
    if (checkRs(resp)) {
    print("8 建立目录");
    resp = tag.transceive(CMD_CREATE_OUT_KEY);
    if (checkRs(resp)) {
    print("9 创建外部认证密钥成功");
    resp = tag.transceive(CMD_ACCESS);
    if (checkRs(resp)) {
    print("10 建立访问自定义文件的密钥文件成功");
    resp = tag.transceive(CMD_ACCESS_INTO); //11 填充密钥123456
    if (checkRs(resp)) {
    print("11 填充密钥123456成功");
    resp = tag.transceive(CMD_ACCESS_FILE); //12 创建自定义文件,标识为005
    if (checkRs(resp)) {
    print("12 创建自定义文件,标识为005成功");
    resp = tag.transceive(CMD_ACCESS_FILE_CHOOICE); // 13 选中该文件0005
    if (checkRs(resp)) {
    print(" 13 选中该文件0005成功");
    resp = tag.transceive(CMD_ACCESS_FILE_WRITE); //14 写数据“112233445566”到该文件
    if (checkRs(resp)) { //15 应该有关闭连接
    print("14 写数据“112233445566”到该文件成功");
    return "01".getBytes();
    }
    }
    }
    }
    }
    }
    }
    }
    }
    }
    }
    }
    return null;
    }

    private boolean checkRs(byte[] resp) {
    String r = printByte(resp);
    Log.i("---------", "response " + r);
    int status = ((0xff & resp[resp.length - 2]) << 8) | (0xff & resp[resp.length - 1]);
    return status == 0x9000;
    }

    private String printByte(byte[] data) {
    StringBuffer bf = new StringBuffer();

    for (byte b : data) {
    bf.append(Integer.toHexString(b & 0xFF));
    bf.append(",");
    }
    Log.i("TAG", bf.toString());
    return bf.toString();
    }

    private void print(String msg) {
    Log.i("TAG", msg);
    }

    /**
    * Description 根据键值进行加密
    * 随机码4个字节+4个字节0
    *
    * @param data
    * @param key 加密键byte数组
    * @return
    * @throws Exception
    */
    public byte[] encrypt(byte[] data, byte[] key) throws Exception {
    }
    }

    https://blog.csdn.net/sgn5200/article/details/82855478


    注意接收和处理返回的信息,CPU卡常用的APDU指令
    参考文献:很多博客,记不得了,RFID多功能读卡器说明
    https://blog.csdn.net/qq_34075348/article/details/77877306
    FMCOS2.0用户手册 50-70
    如果需要源码:下载地址https://download.csdn.net/download/sgn5200/10688898
    ————————————————
    版权声明:本文为CSDN博主「豆汤包谷饭」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/sgn5200/article/details/82855478

  • 相关阅读:
    小程序自动更新版本
    js深浅拷贝理解
    小程序模仿toast效果
    小程序button默认border
    Java利用POI 读取Excel行列数,坑
    Nginx 极简入门教程
    七、SpringBoot整合持久化层,配置多数据源(SpringBoot系列)
    六、SpringBoot整合aop(SpringBoot系列)
    五、SpringBoot随系统启动任务的方式(SpringBoot系列)
    四、SpringBoot通过CORS解决跨域问题(SpringBoot系列)
  • 原文地址:https://www.cnblogs.com/Alex80/p/11367060.html
Copyright © 2020-2023  润新知