• Android 关于IC卡的读写和加密


    关于IC卡的基本介绍

    先对相关的基础知识进行一下讲解。

    RFID: 叫射频识别技术,分为接触式(需要插卡)与非接触式(只需刷卡),NFC就是从这个技术发展而来的,包含多个频段,915MHz,125KHz,13.56MHz,2.4GHz等。
    ID卡: 主要工作在125KHz,只有一个身份识别码,判断方式就是卡身有一串卡号,使用时需要联网进行操作。
    IC卡: 主要工作在13.56MHz,里面有存储空间,可以进行读写,脱机工作(公交卡,门禁卡等)。
    NFC: 近场通讯技术,只能工作在13.56MHz,所以能读取全部工作在这个频段的卡,是属于RFID技术的,但是又有新的功能,可以理解为RFID的子类。

    日常较多接触的是13.56频段的IC卡,由于不同的厂家生产的不同的芯片,数据格式与通信协议是不同的,所以需要对应,Android在这方面是有完整的底层支持的,你看到的NfcA、NfcB、NfcF、NfcV、IsoDep、Ndef这些就是对应不同的数据格式或者通讯协议的。举个例子,nxp公司的MIFARE Classic数据格式就是NfcA,MIFARE DESFire数据格式是IsoDep,二代身份证用的就是NfcB,Sony生产的Felica用的就是NfcF,德州仪器的VicinityCard卡用的是NfcV。

    MIFARE Classic S50内部结构

    公司使用的是MIFARE Classic S50的IC卡,也叫M1卡,是市面上较常见的类型。

    存储大小1k,分为16个扇区,每个扇区分四个块,每块可以储存十六个字节的数据。第0扇区的第0块为厂家信息,无法修改,IC卡的卡号便是读取的这里。扇区最后一块,也就是第三块是密码块。

    扇区密码块数据结构详解

    每个扇区的最后一块为密码块,每个扇区的密码独立,所以十六个扇区可以有十六个不同的密码,读取扇区数据前需要核对对应密码,校验成功才能读取。密码块可以分为三部分来理解。
    密码块十六个字节,前六个字节是密码A,中间四个字节是控制位,后六个字节是密码B。一般新买的卡片,密码A和密码B都是ff ff ff ff ff ff(16进制),所以新卡可以直接通过这个密码读取到卡号。新卡控制位默认是ff 07 80 69,密码块总结就是:6个字节的密码A + 4个字节密钥控制位 + 6个字节的密码B。
    关于控制位的算法与逻辑较复杂,有兴趣的可以看这个:控制位解读IC卡详解

    我这里可以初略总结一下:
    1.默认方式
    控制位为“FF 07 80 69”,这种方式下密钥A或密钥B都可以读写数据区,密钥A可写密钥区,优点是密钥控制字无需重新计算,读写方便,缺点是安全性能差,密钥A容易泄露。

    2.密钥B写方式
    控制位为“7F 07 88 69”,这种方式下密钥A或密钥B都可以读写数据区,而对于密钥区只能由密钥B来写。优点是密钥B权限最高,只要知道密钥B,无论密钥A写成什么都可以改写,由最高管理员掌握密钥B,可下发多种密钥A的一般管理员,一般不会废卡的。缺点是密钥B很重要,一旦忘记,卡就不能再改写密钥了。

    3.A读B写方式
    控制位为“08 77 8F 69”,这种方式下由密钥A读密钥B来写,可以说是上面一种方式的变体,对于密钥B有更强的保护。

    4.只读不写方式
    控制位为“FF 00 F0 69”,这种方式下密钥A或密钥B都可以读数据区,但都不能写数据区(数值可减少,不能增加),密钥A可以改写密钥区。这种方式对于数据是极大的保护,尤其是定额卡,里面的钱只能减少而不能增加。

    需求分析

    公司使用S50的IC卡,需要实现读取卡号,写入与修改数据,对写入的数据加密。保密等级要求不高,所以使用默认的存取控制就行。

    代码实现

    下面展示Android代码的实现。对了,记得在AndroidManifest里增加权限:

    <uses-permission android:name="android.permission.NFC" />
    
    • 1

    读取数据

    /**
     * @author wy
     * 读取工具类
     */
    public class NfcReadHelper {
        private Tag tag;
        private NFCCallback callback;
        private static NfcReadHelper helper;
        /**
         * 默认初始密码
         */
        private byte[] bytes = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
    
        public NfcReadHelper(Intent intent) {
            this.tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        }
    
        /**
         * 单例初始化
         *
         * @param intent
         * @return
         */
        public static NfcReadHelper getInstence(Intent intent) {
            if (helper == null) {
                helper = new NfcReadHelper(intent);
            }
            return helper;
        }
    
        /**
         * 设置NFC卡的密码
         *
         * @param str
         * @return
         */
        public NfcReadHelper setPassword(String str) {
            if (null != str && (str.length() <= 6)) {
                for (int i = 0; i < str.length(); i++) {
                    bytes[i] = (byte) str.charAt(i);
                }
            }
            return helper;
        }
    
        /**
         * 读取NFC卡的全部信息
         *
         * @param callback
         */
        public void getAllData(final NFCCallback callback) {
            ThreadPoolManager.getInstance().execute(() -> {
                Map<String, List<String>> map = new HashMap<>();
                MifareClassic mfc = MifareClassic.get(tag);
                if (null != mfc) {
                    try {
                        //链接NFC
                        mfc.connect();
                        //获取扇区数量
                        int count = mfc.getSectorCount();
                        //用于判断时候有内容读取出来
                        boolean flag = false;
                        for (int i = 0; i < count; i++) {
                            List<String> list = new ArrayList<>();
                            //验证扇区密码,否则会报错(链接失败错误)
                            boolean isOpen = mfc.authenticateSectorWithKeyA(i, bytes);
                            if (isOpen) {
                                //获取扇区里面块的数量
                                int bCount = mfc.getBlockCountInSector(i);
                                //获取扇区第一个块对应芯片存储器的位置
                                int bIndex = mfc.sectorToBlock(i);
                                //String data1 = "";
                                for (int j = 0; j < bCount; j++) {
                                    //读取数据
                                    byte[] data = mfc.readBlock(bIndex);
                                    bIndex++;
                                    list.add(byteToString(data));
                                }
                                flag = true;
                            }
                            map.put(i + "", list);
                        }
                        if (flag) {
                            callback.callBack(map);
                        } else {
                            callback.error();
                        }
                    } catch (Exception e) {
                        callback.error();
                        e.printStackTrace();
                    } finally {
                        try {
                            mfc.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        }
    
        /**
         * 读取NFC卡的特定扇区信息
         *
         * @param a        扇区
         * @param b        块
         * @param callback
         */
        public void getData(final int a, final int b, final NFCCallback callback) {
            ThreadPoolManager.getInstance().execute(() -> {
                Map<String, List<String>> map = new HashMap<>();
                MifareClassic mfc = MifareClassic.get(tag);
                if (null != mfc) {
                    try {
                        mfc.connect();
                        int count = mfc.getSectorCount();
                        if (a < 0 || a > count - 1) {
                            callback.error();
                            return;
                        }
                        int bCount = mfc.getBlockCountInSector(a);
                        if (b < 0 || b > bCount - 1) {
                            callback.error();
                            return;
                        }
                        boolean isOpen = mfc.authenticateSectorWithKeyA(a, bytes);
                        if (isOpen) {
                            int bIndex = mfc.sectorToBlock(a);
                            byte[] data = mfc.readBlock(bIndex + b);
                            callback.callBack(byteToString(data));
                        } else {
                            callback.error();
                        }
                    } catch (Exception e) {
                        callback.error();
                        e.printStackTrace();
                    } finally {
                        try {
                            mfc.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                } else {
                    callback.error();
                }
            });
        }
    
        /**
         * 返回监听类
         */
        public interface NFCCallback {
            /**
             * 返回读取nfc卡的全部信息
             *
             * @param data 前面代表扇区 四个块的数据用#号隔开
             */
            default void callBack(Map<String, List<String>> data){
            };
    
            void callBack(String data);
    
            void error();
        }
    
        /**
         * 将byte数组转化为字符串(带字母的)
         *
         * @param src
         * @return
         */
        public static String byteToString(byte[] src) {
            StringBuilder stringBuilder = new StringBuilder();
            if (src == null || src.length <= 0) {
                return null;
            }
            char[] buffer = new char[2];
            for (int i = 0; i < src.length; i++) {
                buffer[0] = Character.forDigit((src[i] >>> 4) & 0x0F, 16);
                buffer[1] = Character.forDigit(src[i] & 0x0F, 16);
                System.out.println(buffer);
                stringBuilder.append(buffer);
            }
            return stringBuilder.toString();
        }
    
        /**
         * 将byte数组转化为字符串(纯数字,十位数)
         *
         * @param src
         * @return
         */
        public static String byteToString_num(byte[] src) {
            byte[] bytes2 = new byte[src.length];
            for (int i = 0; i < src.length; i++) {
                bytes2[i] = src[src.length - 1 - i];
            }
            String carstr = new BigInteger(AppUtils.bytesToHexString(bytes2, bytes2.length), 16).toString();
            StringBuffer carsb = new StringBuffer(carstr);
            if (!TextUtils.isEmpty(carstr)) {
                int te = 10 - carstr.length();
                for (int i = 0; i < te; i++) {
                    carsb.insert(0, "0");
                }
            }
            return carsb.toString();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209

    写入数据

    /**
     * @author wy
     * 写入工具类
     */
    public class NFCWriteHelper {
    
        private Tag tag;
        private NFCCallback callback;
        private static NFCWriteHelper helper;
        /**
         * 默认初始密码
         */
        private byte[] bytes = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
        private static int PASSWORD_LENTH = 6;
    
        public NFCWriteHelper(Intent intent) {
            this.tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        }
    
        /**
         * 单例初始化
         *
         * @param intent
         * @return
         */
        public static NFCWriteHelper getInstence(Intent intent) {
            if (helper == null) {
                helper = new NFCWriteHelper(intent);
            }
            return helper;
        }
    
        /**
         * 设置NFC卡的读取密码
         *
         * @return
         */
        public NFCWriteHelper setReadPassword(byte[] bytes) {
            this.bytes = bytes;
            return helper;
        }
    
        /**
         * 设置NFC卡的读取密码
         *
         * @param str
         * @return
         */
        public NFCWriteHelper setReadPassword(String str) {
            if (null != str && (str.length() <= PASSWORD_LENTH)) {
                for (int i = 0; i < str.length(); i++) {
                    bytes[i] = (byte) str.charAt(i);
                }
            }
            return helper;
        }
    
        /**
         * 写卡
         *
         * @param str      书写内容,16个字节
         * @param a        书写的扇区 (从0开始数)
         * @param b        书写的块(从0开始数)
         * @param callback 返回监听
         */
        public void writeData(String str, int a, int b, NFCCallback callback) {
            MifareClassic mfc = MifareClassic.get(tag);
            byte[] data = new byte[16];
            if (null != mfc) {
                try {
                    //连接NFC
                    mfc.connect();
                    //获取扇区数量
                    int count = mfc.getSectorCount();
                    //如果传进来的扇区大了或者小了直接退出方法
                    if (a > count - 1 || a < 0) {
                        callback.isSusses(false);
                        return;
                    }
                    //获取写的扇区的块的数量
                    int bCount = mfc.getBlockCountInSector(a);
                    //如果输入的块大了或者小了也是直接退出
                    if (b > bCount - 1 || b < 0) {
                        callback.isSusses(false);
                        return;
                    }
                    //将字符转换为字节数组
                    for (int i = 0; i < 16; i++) {
                        if (i < str.length()) {
                            data[i] = (byte) str.charAt(i);
                        } else {
                            data[i] = (byte) 'f';
                        }
                    }
                    //验证扇区密码
                    boolean isOpen = mfc.authenticateSectorWithKeyA(a, bytes);
                    if (isOpen) {
                        int bIndex = mfc.sectorToBlock(a);
                        //写卡
                        mfc.writeBlock(bIndex + b, data);
                        callback.isSusses(true);
                    } else {
                        callback.isSusses(false);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    callback.isSusses(false);
                } finally {
                    try {
                        mfc.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    
        /**
         * 修改密码
         *
         * @param password 书写密码,16个字节
         * @param a        书写的扇区
         * @param callback 返回监听
         */
        public void changePasword(String password, int a, final NFCCallback callback) {
            MifareClassic mfc = MifareClassic.get(tag);
            byte[] data = new byte[16];
            if (null != mfc) {
                try {
                    mfc.connect();
                    if (password.length() != PASSWORD_LENTH) {
                        callback.isSusses(false);
                        return;
                    }
                    int count = mfc.getSectorCount();
                    if (a > count - 1 || a < 0) {
                        callback.isSusses(false);
                        return;
                    }
                    //将密码转换为keyA
                    for (int i = 0; i < password.length(); i++) {
                        data[i] = (byte) password.charAt(i);
                    }
                    //将密码转换为KeyB
                    for (int i = 0; i < password.length(); i++) {
                        data[i + password.length() + 4] = (byte) password.charAt(i);
                    }
                    //输入控制位
                    data[password.length()] = (byte) 0xff;
                    data[password.length() + 1] = (byte) 0x07;
                    data[password.length() + 2] = (byte) 0x80;
                    data[password.length() + 3] = (byte) 0x69;
                    //验证密码
                    boolean isOpen = mfc.authenticateSectorWithKeyA(a, bytes);
                    if (isOpen) {
                        int bIndex = mfc.sectorToBlock(a);
                        int bCount = mfc.getBlockCountInSector(a);
                        //写到扇区的最后一个块
                        mfc.writeBlock(bIndex + bCount - 1, data);
                        callback.isSusses(true);
                    } else {
                        callback.isSusses(false);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    callback.isSusses(false);
                } finally {
                    try {
                        mfc.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 返回监听类
         */
        public interface NFCCallback {
            /**
             * 返回是否成功
             *
             * @param flag
             */
            void isSusses(boolean flag);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189

    写入这里包含了密码修改的实现。

    NFC工具软件

    读写IC卡有个好使的软件推荐推荐,在开发的时候可以使用这个软件对比数据:
    NFC Reader Tool

    总结

    每一个新需求都是一次新的学习机会,这又是一个我未踏足过的知识面,希望自己无限进步,永远不会失去对新事物的好奇和热情。如果这篇博客对你有帮助,请点个赞。

  • 相关阅读:
    大数据下一代变革之必研究数据湖技术Hudi原理实战双管齐下-后续
    Java毕设项目基于的高校学生综合素质评价系统计算机(附源码+系统+数据库+LW)
    DevOps落地实践点滴和踩坑记录-(2) -聊聊平台建设
    使用“讯飞星火”快速生成高质量PPT文档
    ubuntu16.04 ros realsense 配置 2022.11.15
    数据结构与算法-第八章 插入排序
    SQL中为什么不要使用1=1
    操作系统实验四 进程间通信
    【已解决】设置SSH主机:VS Code-正在本地下载 VS Code 服务器
    Spring和Spring Boot的区别
  • 原文地址:https://blog.csdn.net/w752325717/article/details/127821350