首页 > 解决方案 > 如何从 Aadhaar 卡的安全 QR 码验证手机号码和电子邮件 ID

问题描述

如何从 UIDAI Aadhaar 卡的安全二维码读取二维码数据的官方文档:https ://uidai.gov.in/images/resource/User_manulal_QR_Code_15032019.pdf

使用 zxing 扫描仪扫描 Secure QR 码,并通过 Github 中的关注项目获得 aadhar 卡的详细信息: https ://github.com/dimagi/AadharUID

在文档说明的帮助下,我如何弄清楚如何从转换后的字节数组中提取照片和 bit_indicator_value

但我无法从 Aadhar 卡字节数组的安全 QR 码中获得电子邮件和手机的精确哈希值来验证

我对第 6 页中上述文档的这一行感到有些困惑:

  1. 如果它是 3,则首先从索引(字节数组长度 - 1-256)读取移动设备,然后以相反的顺序从索引(字节数组长度 - 1-256-32)读取电子邮件。每个值的固定大小为 32 字节。
  2. 如果 Email_mobile_present_bit_indicator_value 为 1,则仅存在移动设备。
  3. 如果 Email_mobile_present_bit_indicator_value 为 2,则仅存在电子邮件。
  4. 如果 Email_mobile_present_bit_indicator_value 为 0,则不存在手机或电子邮件。

我还准备了文档第 6 页的最后一步,但用户移动/电子邮件哈希值和文档移动/电子邮件字节数组哈希值永远不匹配

代码片段:

    public ScanResult(String input) {
        rawString = input;

        // copied from http://www.java-samples.com/showtutorial.php?tutorialid=152
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        Document dom;
        try {

            //Using factory get an instance of document builder
            DocumentBuilder db = dbf.newDocumentBuilder();
            // Replace </?xml... with <?xml...
            if (input.startsWith("</?")) {
                input = input.replaceFirst("</\\?", "<?");
            }
            // Replace <?xml...?"> with <?xml..."?>
            input = input.replaceFirst("^<\\?xml ([^>]+)\\?\">", "<?xml $1\"?>");
            //parse using builder to get DOM representation of the XML file
            dom = db.parse(new ByteArrayInputStream(input.getBytes("UTF-8")));

        } catch (ParserConfigurationException | SAXException | IOException e) {
            dom = null;
        }

        if (rawString.matches("[0-9]*")) {
            type = QR_CODE_TYPE_SECURE;
            byte[] msgInBytes = null;
            try {
                msgInBytes = decompressByteArray(new BigInteger(rawString).toByteArray());
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (msgInBytes != null) {
                int[] delimiters = locateDelimiters(msgInBytes);
                String referenceId = getValueInRange(msgInBytes, delimiters[0] + 1, delimiters[1]);
                uid = referenceId.substring(0, 4);
                name = getValueInRange(msgInBytes, delimiters[1] + 1, delimiters[2]);
                dob = formatDate(getValueInRange(msgInBytes, delimiters[2] + 1, delimiters[3]),
                        new String[] {"dd-MM-yyyy", "dd/MM/yyyy"});
                yob = dob.substring(0, 4);
                gender = getValueInRange(msgInBytes, delimiters[3] + 1, delimiters[4]);
                co = getValueInRange(msgInBytes, delimiters[4] + 1, delimiters[5]);
                dist = getValueInRange(msgInBytes, delimiters[5] + 1, delimiters[6]);
                lm = getValueInRange(msgInBytes, delimiters[6] + 1, delimiters[7]);
                house = getValueInRange(msgInBytes, delimiters[7] + 1, delimiters[8]);
                loc = getValueInRange(msgInBytes, delimiters[8] + 1, delimiters[9]);
                pc = getValueInRange(msgInBytes, delimiters[9] + 1, delimiters[10]);
                po = getValueInRange(msgInBytes, delimiters[10] + 1, delimiters[11]);
                state = getValueInRange(msgInBytes, delimiters[11] + 1, delimiters[12]);
                street = getValueInRange(msgInBytes, delimiters[12] + 1, delimiters[13]);
                subdist = getValueInRange(msgInBytes, delimiters[13] + 1, delimiters[14]);
                vtc = getValueInRange(msgInBytes, delimiters[14] + 1, delimiters[15]);
                statusCode = STATUS_SUCCESS;
            } else {
                statusCode = STATUS_PARSE_ERROR;
                uid = "";
                name = "";
                gender = "";
                yob = "";
                co = "";
                house = "";
                street = "";
                lm = "";
                loc = "";
                vtc = "";
                po = "";
                dist = "";
                subdist = "";
                state = "";
                pc = "";
                dob = "";
            }
        } else {
            type = QR_CODE_TYPE_UNKNOWN;
            statusCode = STATUS_PARSE_ERROR;
            uid = "";
            name = "";
            gender = "";
            yob = "";
            co = "";
            house = "";
            street = "";
            lm = "";
            loc = "";
            vtc = "";
            po = "";
            dist = "";
            subdist = "";
            state = "";
            pc = "";
            dob = "";
        }

        dobGuess = getDobGuess(dob, yob);
        statusText = getStatusText(statusCode);
    }

private static int[] locateDelimiters(byte[] msgInBytes) {
        int[] delimiters = new int[NUMBER_OF_PARAMS_IN_SECURE_QR_CODE + 1];
        int index = 0;
        int delimiterIndex;
        for (int i = 0; i <= NUMBER_OF_PARAMS_IN_SECURE_QR_CODE; i++) {
            delimiterIndex = getNextDelimiterIndex(msgInBytes, index);
            delimiters[i] = delimiterIndex;
            index = delimiterIndex + 1;
        }
        return delimiters;
    }


    private static String getValueInRange(byte[] msgInBytes, int start, int end) {
        return new String(Arrays.copyOfRange(msgInBytes, start, end), Charset.forName("ISO-8859-1"));
    }

    private static int getNextDelimiterIndex(byte[] msgInBytes, int index) {
        int i = index;
        for (; i < msgInBytes.length; i++) {
            if (msgInBytes[i] == -1) {
                break;
            }
        }
        return i;
    }

    private static byte[] decompressByteArray(byte[] bytes) throws IOException {
        java.io.ByteArrayInputStream bytein = new ByteArrayInputStream(bytes);
        java.util.zip.GZIPInputStream gzin = new GZIPInputStream(bytein);
        java.io.ByteArrayOutputStream byteout = new ByteArrayOutputStream();

        int res = 0;
        byte buf[] = new byte[1024];
        while (res >= 0) {
            res = gzin.read(buf, 0, buf.length);
            if (res > 0) {
                byteout.write(buf, 0, res);
            }
        }
        return byteout.toByteArray();
    }

    private String formatDate(String rawDateString, String[] possibleFormats) {
        if (rawDateString.equals("")) {
            return "";
        }
        SimpleDateFormat toFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        ParseException parseException = null;
        for (String fromFormatPattern : possibleFormats) {
            try {
                SimpleDateFormat fromFormat = new SimpleDateFormat(fromFormatPattern);
                date = fromFormat.parse(rawDateString);
                break;
            } catch (ParseException e) {
                parseException = e;
            }
        }
        if (date != null) {
            return toFormat.format(date);
        } else if (parseException != null) {
            System.err.println("Expected dob to be in dd/mm/yyyy or yyyy-mm-dd format, got " + rawDateString);
            return rawDateString;
        } else {
            throw new AssertionError("This code is unreachable");
        }
    }

    @NonNull
    protected String formatGender(String gender) throws ParseException {
        String lowercaseGender = gender.toLowerCase();
        if (lowercaseGender.equals("male") || lowercaseGender.equals("m")) {
            return "M";
        } else if (lowercaseGender.equals("female") || lowercaseGender.equals("f")) {
            return "F";
        } else if (lowercaseGender.equals("other") || lowercaseGender.equals("o")) {
            return "O";
        } else {
            throw new ParseException("404 gender not found", 0);
        }
    }

    @NonNull
    private String getStatusText(int statusCode) {
        switch (statusCode) {
            case ScanResult.STATUS_SUCCESS:
                return "✓&quot;;
            default:
                return "✗&quot;;
        }
    }

    @NonNull
    private String getDobGuess(String dob, String yob) {
        if (dob.equals("")) {
            Integer yearInt;
            try {
                yearInt = Integer.parseInt(yob);
            } catch (NumberFormatException e) {
                return "";
            }
            // June 1 of the year
            return Integer.toString(yearInt) + "-06-01";
        } else {
            return dob;
        }
    }

Github 代码链接

标签: javaandroid

解决方案


刚刚得到解决方案,从字节数组中获取准确的移动和电子邮件哈希值

首先从字节数组中删除最后 256 个字节,然后,

如果只有移动设备存在,则从移动设备的字节数组中获取最后 32 个字节,即(array.length - 32 到 array.length)

否则,如果仅存在电子邮件,则从字节数组中获取电子邮件的最后 32 个字节,即(array.length - 32 到 array.length)

否则,如果电子邮件和移动设备都存在,则从字节数组中获取移动设备的最后 32 个字节,为电子邮件获取下一个最后 32 个字节,即(mobile = array.length - 32 to array.length 和 email = array.length - 64 to array.length - 32)


推荐阅读