目录

  • 关于IC卡的基本介绍
    • MIFARE Classic S50内部结构
      • 扇区密码块数据结构详解
  • 需求分析
    • 代码实现
      • 读取数据
      • 写入数据
    • NFC工具软件
  • 总结

关于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" />

读取数据

/*** @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 {//链接NFCmfc.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();}
}

写入数据

/*** @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 {//连接NFCmfc.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;}//将密码转换为keyAfor (int i = 0; i < password.length(); i++) {data[i] = (byte) password.charAt(i);}//将密码转换为KeyBfor (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);}
}

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

NFC工具软件

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

总结

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

Android 关于IC卡的读写和加密相关推荐

  1. 电动吞吐式ISO14443ISO15693多功能IC卡磁条读写器M100接口说明

    1.RS232串口通讯的线路连接图 A.将TTCE产品所携带的串口通讯线DB9线公头插在电脑串口座上. B.再将TTCE产品所携带串口通讯线的另一端3PIN插头(红线是TXD,蓝线是RXD,黑线是GN ...

  2. android关于IC卡读写及加密解密

    byte[] key = {(byte) 0xA0, (byte) 0xB7, (byte) 0xA5, (byte) 0xC5, (byte) 0x80, (byte) 0x88}; Tag tag ...

  3. Android针对IC卡读写的NFC开发

    菜鸟进场,方圆十里,寸草不生 这两天研究了NFC功能,网上查了很多的资料,不过感觉别人讲的都大同小异,但都缺了那么一点点火候,因为第一次接触有些概念是不清楚的,所以代码看上去很吃力,这个博客呢就是想整 ...

  4. 树莓派使用RFID-RC522A读卡器对IC卡进行读写(通过 python)

    1. 配置树莓派 首先需要运行raspi-config,开启树莓派的spi接口,因为RFID-RC522A读卡器与树莓派之间是通过spi协议进行通信的  https://www.raspberrypi ...

  5. 使用Java驱动ACR122U对IC卡进行读写,总结

    1.站在他的肩膀上,快速的看完,动手自己实战了下.对过程写下总结.总历时3.5小时. 2.手上有一个ACR122U,读卡器.不贵有条件的买一个,毕竟是神器,很好用. 3.那文中提示的JavaCard文 ...

  6. Vb6荣士Udp Rtu通讯IC卡网络读写源码

    网络读写器介绍: https://item.taobao.com/item.htm?spm=a1z10.5-c.w4002-17663462238.25.454be728NKUIW8&id=2 ...

  7. BS结构中,web如何将数据进行DES加密并写道IC卡中

    在IC卡应用系统中,一般都要对IC卡数据进行DES加密,以保证数据的安全.友我科技RFID读写器云服务2.0充分考虑了这个需求,只需要软件工程师简单的配置即可实现数据的加解密并且写到数据块中.如下图所 ...

  8. 加密IC卡保险柜控制器的设计

    随着社会的进步和为民生活水平的提高, 为们出差.旅游和度假的机会日益增加.在宾馆.饭店等居住场所都需要一保险柜来保存贵重物品和易失物品,即便在家里,也往往需要有一个地方来保存一些单据等物品.传统的手段 ...

  9. IC-14W网络IC卡读写器_银河麒麟桌面操作系统V10适配测试报告

    银河麒麟操作系统产品NeoCertify 认证测试报告 系统版本:银河麒麟桌面操作系统V10 厂商名称:广州荣士电子有限公司 认证产品:IC-14W网络IC卡读写器 测试日期:2022-11-04 麒 ...

  10. 公交IC卡读写器设计指南

    采用PHILIPS公司的Mifaue卡作IC卡,设计以射频技术为核心,以单片机为控制器的IC卡读写器在公交自动收费系统中的应用.制作的IC卡读写器可以实现制卡.售卡.自动收费等功能,具有安全.实用.方 ...

最新文章

  1. php7+的php-fpm参数配置,注意事项
  2. 设置允许远程连接MySQL (Ubuntu为例)
  3. ibatis.net:第六天,QueryForList
  4. conda 删除env_软件包与环境管理神器之conda
  5. php preg_split,php汉字截取函数_preg_split()
  6. sdio stm32理解 205 sdio调试 stm32f205
  7. iOS捷径(Workflow 2.0)拓展
  8. 3D打印机Ender-3 V2 CR-10S CR10S PRO Ender-3 Ender 3PRO Ender 5更换BMG挤出机,挤出电机的脉冲值或传动值E如何修改
  9. 人工智能技术发展现状分析,阿发狗22年最新
  10. 大专计算机知识,大专计算机应用基础试题及答案
  11. 利用Xming X Server使用服务器上使用kettle
  12. 客户满意度调查表怎么做?
  13. 一家国营老化工厂的数字化三级跳|案例解析
  14. SQL常用的一些关键字
  15. 音视频技术开发周刊 79期
  16. IOS 滑动tableview,导航栏渐渐显示,搜索框显示在导航栏上
  17. python绘制日历图
  18. 手把手教你从头开始搭建友善之臂ARM-tiny4412开发环境(史上最详细!!)
  19. 前端常用时间工具 -- Moment.js常见用法总结
  20. 完成英语第一阶段的学习,明天开始第二阶段

热门文章

  1. c语言贪吃蛇设计实验报告引言,C语言实现贪吃蛇游戏设计
  2. 谷粒学院权限管理模块
  3. android 菜鸟面单打印_菜鸟Android
  4. vue项目中使用cn打印组件
  5. SQL基本语句(整理)
  6. 【python高阶编程】python线程池简单应用
  7. python编写俄罗斯方块代码详解_python如何写个俄罗斯方块
  8. python清屏命令-python清屏命令
  9. 「业务架构」商业模式画布
  10. js调用摄像头拍照,js调用摄像头在线拍照,js调用电脑摄像头拍照