The ID-number Masking Flaw on Train Tickets Can Reveal Your ID Number

Everyone is familiar with train tickets — we've all taken the train. Now tickets are sold under real-name rules, and the ticket carries your ID number, name, and other real-name information. The ID number is masked with asterisks for four digits, but do you think it's safe to throw it away? As a programmer, I want to tell you that a program can quickly compute your possible ID numbers.

Everyone is familiar with train tickets — we’ve all taken the train. Now tickets are sold under real-name rules, and the ticket carries your ID number, name, and other real-name information. The ID number is masked with asterisks for four digits, but do you think it’s safe to throw it away? As a programmer, I want to tell you that a program can quickly compute your possible ID numbers.

How an ID Number Is Composed

First, we need to understand how an ID number is composed. From left to right: a six-digit address code, an eight-digit birth-date code, a three-digit sequence code, and a one-digit check code. The six-digit address code indicates which province, city, and district/county you are from; then comes the birth date; the three sequence digits can be treated as random; and the last digit is the check digit, computed from the preceding information via an algorithm.

Check Algorithm

The Java logic for computing the last check digit from the first 17 digits:

package net.renfei.util;

public class IDNumberUtil {
    private int[] weight = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};           // Weights for the 17-digit base code
    private char[] validate = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};      // mod11, corresponding check-code character values
    public char getValidateCode(String id17) {
        int sum = 0;
        int mode = 0;
        for (int i = 0; i < id17.length(); i++) {
            sum = sum + Integer.parseInt(String.valueOf(id17.charAt(i))) * weight[i];
        }
        mode = sum % 11;
        return validate[mode];
    }
}

Enumerating the Masked Part

Train ticket

The four digits masked on the train ticket are the month and day, so there are about 365 days in a year — 365 possibilities. We first enumerate all 365 possible first-17-digit ID numbers, i.e., dates from 0101 to 1231, which is easy to iterate through a program. Then we compute the last check digit; if it matches, we mark it as a suspected ID number. Taking the train ticket of “Liu Daming” in the image above as an example, my code is:

package net.renfei.util;

import lombok.extern.slf4j.Slf4j;
import net.renfei.TestApplication;
import org.junit.Test;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

@Slf4j
public class IDNumberUtilTest extends TestApplication {
    @Test
    public void idNumberTest() throws ParseException {
        String idPre = "6221261980", idPost = "314", idCheck = "4";
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date birthDate = dateFormat.parse("1980-01-01");
        List<String> allID = new ArrayList<>();
        for (; ; ) {
            Calendar c = Calendar.getInstance();
            c.setTime(birthDate);
            c.add(Calendar.DAY_OF_MONTH, 1); // Add 1 day
            birthDate = c.getTime();
            if (c.get(Calendar.YEAR) == 1981) {
                break;
            }
            int month = c.get(Calendar.MONTH)+1;
            int day = c.get(Calendar.DAY_OF_MONTH);
            allID.add(idPre + (month < 10 ? "0" + month : month) + (day < 10 ? "0" + day : day) + idPost);
        }
        IDNumberUtil idNumberUtil = new IDNumberUtil();
        List<String> suspectedID = new ArrayList<>();
        for (String id : allID
        ) {
            log.info("== Check: {}", id + idCheck);
            if (idCheck.equals(idNumberUtil.getValidateCode(id) + "")) {
                suspectedID.add(id + idCheck);
            }
        }
        for (String id : suspectedID
        ) {
            log.info("== Found suspected ID: {}", id);
        }
    }
}

The result is 34 suspected ID numbers:

  • == Found suspected ID: 622126198001023144
  • == Found suspected ID: 622126198001103144
  • == Found suspected ID: 622126198001293144
  • == Found suspected ID: 622126198002093144
  • == Found suspected ID: 622126198002173144
  • == Found suspected ID: 622126198002253144
  • == Found suspected ID: 622126198003053144
  • == Found suspected ID: 622126198003133144
  • == Found suspected ID: 622126198003213144
  • == Found suspected ID: 622126198004013144
  • == Found suspected ID: 622126198004283144
  • == Found suspected ID: 622126198005083144
  • == Found suspected ID: 622126198005163144
  • == Found suspected ID: 622126198005243144
  • == Found suspected ID: 622126198006043144
  • == Found suspected ID: 622126198006123144
  • == Found suspected ID: 622126198006203144
  • == Found suspected ID: 622126198007193144
  • == Found suspected ID: 622126198007273144
  • == Found suspected ID: 622126198008073144
  • == Found suspected ID: 622126198008153144
  • == Found suspected ID: 622126198008233144
  • == Found suspected ID: 622126198008313144
  • == Found suspected ID: 622126198009033144
  • == Found suspected ID: 622126198009113144
  • == Found suspected ID: 622126198010093144
  • == Found suspected ID: 622126198010173144
  • == Found suspected ID: 622126198010253144
  • == Found suspected ID: 622126198011053144
  • == Found suspected ID: 622126198011133144
  • == Found suspected ID: 622126198011213144
  • == Found suspected ID: 622126198012013144
  • == Found suspected ID: 622126198012283144

Verifying the ID Number and Name

After computing the suspected ID numbers, we still cannot determine which one corresponds to “Liu Daming’s” ID number. At this point there is another vulnerability on the 12306 website we can exploit: the “add frequent contact” feature. We try the ID numbers and names one by one, and can quickly find the correct ID-number-and-name combination.

12306