阅读二代居民身份证的流程如下:

  1. C1, 打开阅读器(切换到身份证模式): SDT_OpenPort;
  2. C2, 找卡: SDT_StartFindIDCard; 如果失败, 跳转到 C5
  3. C3, 选卡: SDT_SelectIDCard; 如果失败, 跳转到 C5
  4. C4, 读取身份证: SDT_ReadBaseMsgToFile 或者 SDT_ReadBaseMsg
  5. C5, 如果要读取下一个身份证跳转到 C2
  6. 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 即可



硬件      JNA 读取身份证

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!