阅读二代居民身份证的流程如下:
- C1, 打开阅读器(切换到身份证模式): SDT_OpenPort;
- C2, 找卡: SDT_StartFindIDCard; 如果失败, 跳转到 C5
- C3, 选卡: SDT_SelectIDCard; 如果失败, 跳转到 C5
- C4, 读取身份证: SDT_ReadBaseMsgToFile 或者 SDT_ReadBaseMsg
- C5, 如果要读取下一个身份证跳转到 C2
- C6, 关闭读卡器 SDT_ClosePort
定义接口
public interface IdCardService extends StdCallLibrary {
/**
* 打开端口
* @param iPort
* @return
*/
int SDT_OpenPort(int iPort);
/**
* 关闭端口
* @param iPort
* @return
*/
int SDT_ClosePort(int iPort);
/**
* 0x9F 成功
* 其他 失败
* @param iPort
* @param iIfOpen
* @return
*/
int SDT_StartFindIDCard(int iPort, Pointer pRAPDU, int iIfOpen);
/**
* 0x90 成功
* 其他 失败
* @param iPort
* @param iIfOpen
* @return
*/
int SDT_SelectIDCard(int iPort, Pointer pRAPDU, int iIfOpen);
/**
* 0x90 成功
* 其他 失败
* @param iPort
* @param iIfOpen
* @return
*/
int SDT_ReadBaseMsg(int iPort, byte[] pucCHMsg, IntByReference puiCHMsgLen, byte[] pucPHMsg, IntByReference puiPHMsgLen, int iIfOpen);
int SDT_ReadBaseMsgToFile(int iPort, String pucCHMsgFile, IntByReference puiCHMsgLen, String pucPHMsgFile, IntByReference puiPHMsgLen, int iIfOpen);
int SDT_ReadNewAppMsg(int iPort, String pucAppMsg, int puiAppMsgLen, int iIfOpen);
}
读取实现
@Slf4j
@Service
public class IdCardServiceImpl {
private static final String MULTI_BYTE_ENCODING = "UTF-16LE";
private IdCardService idCardService;
@PostConstruct
public void init(){
idCardService = Native.load("sdtapi", IdCardService.class);
}
public IdCardResult readIdCard(){
IdCardResult result = new IdCardResult();
int m_nOpenPort = 0;
int textLen = 512;
int faceLen = 1024;
byte[] pucAppMsg = new byte[textLen];
byte[] pRAPDU = new byte[faceLen];
IntByReference textReference = new IntByReference(textLen);
IntByReference faceReference = new IntByReference(faceLen);
int resultCode = 0;
// 打开端口
for (int iPort = 1001; iPort < 1017; iPort = iPort + 1)
{
resultCode = idCardService.SDT_OpenPort(iPort);
if (0x90 == resultCode)
{
m_nOpenPort = iPort;
log.debug("初始化身份证阅读器成功,port={}",iPort);
break;
}
}
if (0 == m_nOpenPort) {
log.error("初始化身份证阅读器失败");
return null;
}
// 找卡
resultCode = idCardService.SDT_StartFindIDCard(m_nOpenPort,null, 0);
if (0x9F != resultCode) {
log.error("寻找卡失败,resultCode={}",resultCode);
return null;
}
log.debug("寻找卡成功,resultCode={}",resultCode);
// 选卡
resultCode=idCardService.SDT_SelectIDCard(m_nOpenPort, null, 0);
if (0x90 != resultCode) {
log.error("选卡失败,resultCode={}",resultCode);
return null;
}
log.debug("选卡成功,resultCode={}",resultCode);
// 读卡
resultCode = idCardService.SDT_ReadBaseMsg(m_nOpenPort,pucAppMsg,textReference,pRAPDU,faceReference,0);
if(0x90 != resultCode){
log.error("读身份证失败,resultCode={}", resultCode);
return null;
}
try {
log.debug("读身份证成功,resultCode={},info={}",resultCode,new String(pucAppMsg,MULTI_BYTE_ENCODING));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 关闭端口
resultCode=idCardService.SDT_ClosePort(m_nOpenPort);
log.debug("关闭身份证阅读器,resultCode={}",resultCode);
try {
FileUtils.writeByteArrayToFile(new File("c:\\sj.jpeg"),pRAPDU);
} catch (IOException e) {
e.printStackTrace();
}
pucAppMsg = Arrays.copyOf(pucAppMsg, textReference.getValue());
// 解析数据
result.setCardNumber(getCardNumber(pucAppMsg));
result.setSex(getSex(pucAppMsg));
result.setBirthday(getBirthDay(pucAppMsg));
result.setName(getName(pucAppMsg));
return result;
}
private String getString(byte[] data) {
String str = null;
try {
str = new String(data, MULTI_BYTE_ENCODING);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (StringUtils.isNotEmpty(str)) {
str=str.trim();
}
return str;
}
private String getCardNumber(byte[] data) {
byte[] idcarddata = Arrays.copyOfRange(data, 122, 122 + 36);
return getString(idcarddata);
}
private String getSex(byte[] data) {
char sex = (char)data[30];
if (sex == '1') {
return "男";
}else {
return "女";
}
}
private String getBirthDay(byte[] data){
byte[] birthData = Arrays.copyOfRange(data, 36, 36 + 16);
return getString(birthData);
}
private String getName(byte[] data){
byte[] name = Arrays.copyOfRange(data, 0, 30);
return getString(name);
}
public static void main(String[] args) {
IdCardServiceImpl idCardService = new IdCardServiceImpl();
idCardService.init();
System.out.println(idCardService.readIdCard());
}
}
注意点
1. 要设置 native 库的加载位置,否则无法加载dll
设置虚拟机参数
-Djna.library.path=C:\lib
2. 输入型整形指针和输出型宽字节解码
C 接口定义
STDAPI_API int WINAPI SDT_ReadBaseMsg(
int iPort,
unsigned char * pucCHMsg,
unsigned int * puiCHMsgLen,
unsigned char * pucPHMsg,
unsigned int * puiPHMsgLen,
int iIfOpen
);
其中 pucCHMsg和pucPHMsg是输出型参数,jna 定义使用 byte数组接收
puiCHMsgLen和puiPHMsgLen 整形的地址,这时jna接口不能用 int,要用 IntByReference
JNA 接口定义
int SDT_ReadBaseMsg(int iPort, byte[] pucCHMsg, IntByReference puiCHMsgLen, byte[] pucPHMsg, IntByReference puiPHMsgLen, int iIfOpen);
宽字节转换
SDT_ReadBaseMsg 的读取信息保存在 pucCHMsg字节数组,厂商放入的是宽字节码
private static final String MULTI_BYTE_ENCODING = "UTF-16LE";
byte[] nameData = Arrays.copyOfRange(data, 0, 30);
String name = new String(data, MULTI_BYTE_ENCODING);
这样能正确解出中文姓名
传入宽字节使用 jna 的 WString 即可
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!