DEV Community

Sam Leung
Sam Leung

Posted on • Edited on

HKID (Part 1) - Understanding the 🇭🇰Hong Kong🇭🇰 Identity Card: Symbols, Prefixes, and Check Digit Calculation

 

🔥🔥🔥This blog post is periodically updated to ensure the information remains current.

 

Why I Wrote This?

 
I am currently handling a project that needs to check whether a user-entered HKID is correct or not, and also to generate HKID numbers for testing purposes. To achieve this, I needed to learn and clarify the HKID rules in detail, from the structure and meaning of its symbols and prefixes, to the logic behind its check digit calculation.

 
This blog post documents what I’ve learned, how the check digit system works, and how you can validate or generate HKIDs in your own projects.


IMPORTANT❗❗❗
The HKID check digit algorithm here is based on commonly shared internet sources, not official documentation, so results may not be 100% accurate.

 
IMPORTANT❗❗❗
All the sample code will be written in Rust, so some basic familiarity with Rust (such as functions, variables, and basic control flow) will help you follow along.

 
IMPORTANT❗❗❗
For simplicity, I’ll use [ to represent a space character in HKID examples and logic throughout this post.


1. HKID Symbols and Prefixes: What Do They Mean?

 

1.1 HKID Number Format Explained

 
An HKID number typically looks like this: X123456(A), where:

  • X is one or two letters (the prefix)
  • 123456 is a six-digit serial number (each digit is 0–9)
  • A is the check digit (in brackets)

 

1.2 Prefix Meanings

 
The prefix letters indicate the period or category of issuance. Here is a comprehensive list:

Prefix Explanation / Issuance
A Original ID cards, issued between 1949 and 1962, most holders were born before 1950
B Issued between 1955 and 1960 in city offices
C Issued between 1960 and 1983 in NT offices, if a child most born between 1946 and 1971, principally HK born
D Issued between 1960 and 1983 at HK Island office, if a child most born between, principally HK born
E Issued between 1955 and 1969 in Kowloon offices, if a child most born between 1946 and 1962, principally HK born
F First issue of a card commencing from 24 February 2020
G Issued between 1967 and 1983 in Kowloon offices, if a child most born between 1956 and 1971
H Issued between 1979 and 1983 in HK Island offices, if a child most born between 1968 and 1971, principally HK born
J Consular officers
K First issue of an ID card between 28 March 1983 and 31 July 1990, if a child most born between 1972 and 1979
L Issued between 1983 and 2003, used when computer system malfunctioned, held by very few people
M First issue of ID card between 1 August 2011 and 23 February 2020
N Birth registered in Hong Kong after 1 June 2019
P First issue of an ID card between 1 August 1990 and 27 December 2000, if a child most born between July and December 1979
R First issue of an ID card between 28 December 2000 and 31 July 2011
S Birth registered in Hong Kong between 1 April 2005 and 31 May 2019
T Issued between 1983 and 1997, used when computer system malfunctioned, very few people hold
V Child under 11 issued with a "Document of Identity for Visa Purposes" between 28 March 1983 and 31 August 2003
W First issue to a foreign labourer or foreign domestic helper between 10 November 1989 and 1 January 2009
Y Birth registered in Hong Kong between 1 January 1989 and 31 March 2005
Z Birth registered in Hong Kong between 1 January 1980 and 31 December 1988

Double Letters:

Prefix Explanation / Issuance
EC Issued between 1993 and 2003, for European Community officers in Hong Kong (and dependents)
WX First issue to a foreign labourer or foreign domestic helper since 2 January 2009
XA/XB/XC/XD/XE/XG/XH ID card issued to person without a Chinese name before 27 March 1983

 

1.3 Symbols on the Card

 
Symbols under the date of birth have the following meanings:

Symbol Meaning
*** Age 18 or over, eligible for Hong Kong Re-entry Permit
* Age 11–17, eligible for Hong Kong Re-entry Permit
A Right of abode in Hong Kong
B Birth date/place changed since first registration
C Stay in HKSAR is limited
N Name changed since first registration
O Born outside Hong Kong, Mainland China, or Macau
R Right to land in HKSAR
U Stay in HKSAR not limited
W Born in Macau
X Born in Mainland China
Y Birth date confirmed by Immigration
Z Born in Hong Kong
AO, AW, AX, AZ Right of abode in Hong Kong and place of birth is Other, Macau, Mainland China, or Hong Kong, respectively

Examples:

Example Meaning
***AZ Aged 18 or over, eligible for re-entry permit (***), right of abode in Hong Kong (A), born in Hong Kong (Z)
*AZ Aged 11–17, eligible for re-entry permit (*), right of abode in Hong Kong (A), born in Hong Kong (Z)
AW Right of abode in Hong Kong (A), born in Macau (W)

 

For further details, see Wikipedia: Hong Kong identity card – Meaning of first letter(s)


2. (Optional) What You Need to Know Before Verifying a Hong Kong Identity Card (HKID): Weight Function and Check Digit

 
Before you can accurately verify a Hong Kong Identity Card (HKID) number, it's important to understand two key concepts:

  • Weight Function:
    This is used to calculate a weighted sum by multiplying each digit (or character) of the HKID number by a specific weight. The result is used in the check digit calculation process.

  • Check Digit:
    This is a special digit added to the end of the HKID number, specifically designed to detect errors.

Mastering these two pieces of knowledge will allow you to understand and implement the HKID verification process correctly and confidently.

 

2.1 Understanding the Weight Function: The Concept and a Simple Example

 

⚠️⚠️⚠️ To understand this section, you may need high school-level math, such as vectors and the summation (Σ) formula.

 
According to Wikipedia’s article on Weight Function, a weight function is a mathematical tool used when performing a sum, integral, or average, to give some elements more "weight" (or influence) than others. The result is called a weighted sum or a weighted average.

Although weight functions can be applied to both weighted sums, weighted averages, weighted integrals, etc., ⚠️For the purposes of the HKID check digit calculation, it is sufficient to focus only on the concept of the weighted sum. Understanding how to calculate a weighted sum is all you need for this context, knowledge of weighted averages and other applications is not necessary.

Before we dive into the concept of a weighted sum, let’s look at a simple example to understand how weighted calculations work in everyday situations.

 
Simple Example

Suppose you have three test scores:

  • Chinese: 85
  • English: 92
  • Math: 78

Your teacher thinks Chinese is the most important and assigns different weights:

  • Chinese: 3
  • English: 2
  • Math: 1

The weighted sum is calculated as:
Weighted Sum = 85 × 3 + 92 × 2 + 78 × 1 = 255 + 184 + 78 = 517

 
This means the Chinese score contributes the most to the total.

 
It’s pretty straightforward, isn’t it? 😊 Now, let’s see how we can express this process using a mathematical formula.

 
Mathematical Representation

For a vector of values X and a vector of weights W, where
X=[𝑥1, 𝑥2, …, 𝑥n],
W=[𝑤1, 𝑤2, …, 𝑤n]

the weighted sum is:

Weighted Sum=i=1nwixi=WX \text{Weighted Sum} = \sum_{i=1}^{n} w_i x_i = \mathbf{W} \cdot \mathbf{X}

The dot product notation (W⋅X) is a compact way of expressing the sum of products of corresponding elements in the two vectors. This is equivalent to:

w1x1+w2x2++wnxn w_1 x_1 + w_2 x_2 + \cdots + w_n x_n

This means:
Multiply each value by its corresponding weight, then sum all the results.

 
Why Do We Need to Know the Weighted Sum? 🤔

Understanding the weighted sum is important because the check digit formula for things like HKID actually uses this concept at its core. The check digit isn't just a random number—it's calculated based on the sum of all the digits and letters in the ID, but each one is first multiplied by a different weight before being added together.

 

2.2 Exploring Common Check Digit Algorithm

 

⚠️⚠️⚠️ To understand this section, you may need high school-level math, such as Modular arithmetic and the summation (Σ) formula.

 
I found many documents online about the HKID check digit 📄, but there is no official documentation explaining how to verify or calculate it 😔. Many sources suggest the method is similar to the ISBN-10 check digit formula, so understanding ISBN-10 is useful for this purpose.

 
A check digit is added to identification numbers to detect errors. Common methods include:

  • UPC/EAN/GTIN: Odd-position digits × 3, add even-position digits, sum, then (sum mod 10). If not zero, subtract from 10.
  • ISBN-10: Each digit × its position (right to left), sum, then (sum mod 11) should be 0. If 10, use 'X'.
  • ISBN-13: Odd-position digits + (even-position digits × 3), sum, then (sum mod 10). If not zero, subtract from 10.
  • NCDA: Used for special identifiers, can detect single-character and transposition errors.

⚠️For HKID, just focus on the ISBN-10 method.

For more details, see Check digit.

 
ISBN-10

ISBN-10 is a 10-digit identifier used for books published before 2007. The last digit is a check digit used to detect errors. The check digit is calculated so that the weighted sum of all digits is a multiple of 11.

The formula for a valid ISBN-10 is:

i=110(11i)xi0(mod11) \sum_{i=1}^{10} (11-i) x_i \equiv 0 \pmod{11}

This means each digit is multiplied by a descending weight from 10 to 1, and the sum is evaluated using modular arithmetic.

 
Based on our study of weighted sum math knowledge in 2.1, we can simplify the formula to:

10x1+9x2+8x3+7x4+6x5+5x6+4x7+3x8+2x9+x100(mod11) 10x_1 + 9x_2 + 8x_3 + 7x_4 + 6x_5 + 5x_6 + 4x_7 + 3x_8 + 2x_9 + x_{10} \equiv 0 \pmod{11}

 
Modular Arithmetic

Modular arithmetic is a system for computing with integers, where numbers “wrap around” after reaching a certain fixed value called the modulus. This means, just like the hours on a clock reset after reaching 12, numbers in modular arithmetic reset after reaching the modulus. Calculations are always performed with respect to this modulus.

 
Congruence in Modular Arithmetic

Congruence is the mathematical relation that expresses when two numbers are considered equivalent in modular arithmetic. Specifically, two integers (a) and (b) are said to be congruent modulo (m) if they have the same remainder when divided by (m). This relationship is written as:

ab(modm) a \equiv b \pmod{m}

This means that the difference (a - b) is divisible by (m), or equivalently, (a) and (b) belong to the same position (congruence class) in the modular system with modulus (m).

Note:

The parentheses in "a ≡ b (mod m)" mean that "mod m" applies to the whole equation, not just to the right side.

This is different from "b mod m", which gives the remainder when b is divided by m. In other words, "b mod m" is the unique r such that:

0r<mandrb(modm) 0 \leq r < m \quad \text{and} \quad r \equiv b \pmod{m}

So, "a ≡ b (mod m)" shows a relationship, while "b mod m" gives a specific remainder.

 
Example Calculation

Suppose you want to compute (17 mod 5):

  • Divide 17 by 5: 17 ÷ 5 = 3 remainder 2
  • So, 17 ≡ 2 (mod 5)

Alternatively, 17 − 2 = 15, and 15 is a multiple of 5.

 
Summary Table

Expression Meaning
a ≡ b (mod m) a and 𝑏 leave the same remainder when divided by 𝑚
17 ≡ 2 (mod 5) Both 17 and 2 leave remainder 2 when divided by 5

Modular arithmetic and congruence are deeply linked: modular arithmetic sets the rules for “wrapping around,” and congruence gives us the formal way to describe equality in this system.

For more, see Modular arithmetic - Wikipedia.


3. (Optional) Where Does the HKID Check Digit Formula Come From?

 
If you’ve forgotten the ISBN-10 check digit formula, don’t worry 😊—this guide will remind you and show how it transforms into the HKID check digit formula.

The ISBN-10 check digit formula works by multiplying each digit by a specific weight, summing them, and ensuring the total is divisible by 11. The Standard mathematical expression is:

i=110(11i)xi0(mod11) \sum_{i=1}^{10} (11-i) x_i \equiv 0 \pmod{11}

When expanded, this becomes:

10x1+9x2+8x3+7x4+6x5+5x6+4x7+3x8+2x9+x100(mod11) 10x_1 + 9x_2 + 8x_3 + 7x_4 + 6x_5 + 5x_6 + 4x_7 + 3x_8 + 2x_9 + x_{10} \equiv 0 \pmod{11}

If we focus on the last digit (the check digit), the formula can be rearranged as:

S+(1110)x10=S+1x10 S + (11-10)x_{10} = S + 1 \cdot x_{10}

which simplifies to:

S+x100(mod11) S + x_{10} \equiv 0 \pmod{11}

where (S) is the weighted sum of the first nine digits.

This means that when you add the weighted sum (S) of the first nine digits to the check digit 𝑥10​ , the result must be a multiple of 11.

 
Deriving the Check Digit Formula Using Modular Arithmetic

Step-by-step conversion:

  1. Rearrange for the check digit:

    S+x100(mod11) S + x_{10} \equiv 0 \pmod{11}
    x10S(mod11) x_{10} \equiv -S \pmod{11}
  2. Express -S (mod 11) as a positive remainder:
    In modular arithmetic, a negative remainder can be rewritten as:

    S(mod11)=(11(Smod11))mod11 -S \pmod{11} = (11 - (S \bmod{11})) \bmod{11}

    This is because adding the modulus (11) to a negative number brings it back into the range of 0…10.

  3. Substitute and simplify:

    x10=(11(Smod11))mod11 x_{10} = (11 - (S \bmod{11})) \bmod{11}

 
Summary

  • S + x10 ≡ 0 (mod 11) leads to x10 ≡ -S (mod 11).
  • In computer code, to always get a non-negative remainder, we use (11 - (S mod 11)) mod 11.
  • Therefore, the check digit formula becomes (11 - (sum % 11)) % 11.

4. (Optional) Comparing Online HKID Check Digit Formulas: Common Practices vs. the ISBN-10 Method

 

4.1 Common Practices

The majority of online resources present the HKID check digit calculation formula as 11 - (sum % 11), typically without providing any underlying rationale. However, this approach can yield results ranging from 0 to 10, while the check digit field permits only a single digit or, in some cases, the character 'A'. Therefore, additional logic is required to map the result to a valid check digit. This mapping can be expressed as follows:

  • If the result is 0, the check digit is '0'.
  • If the result is 1, the check digit is 'A'.
  • If the result is between 2 and 10, the check digit is the corresponding digit: 11 - (sum % 11).

Formally, this can be written as:

let remainder = sum % 11;

if (remainder == 0) {
    check_digit = '0';
} else if (remainder == 1) {
    check_digit = 'A';
} else {
    check_digit = 11 - remainder;
}
Enter fullscreen mode Exit fullscreen mode

As a result, many implementations use conditional statements to handle these cases, increasing the complexity of the logic.

 

4.2 The ISBN-10 Method

The ISBN-10 method applies the formula (11 - (sum % 11)) % 11 for HKID check digit calculation. This approach is structurally similar to the commonly used method, but it incorporates an additional modulo operation at the final step.

The purpose of this extra % 11 is to ensure that when sum % 11 equals 0, the calculation yields a check digit of 0, rather than 11. This guarantees that all possible results are valid single-digit values (0–10) and removes the need for separate handling of results outside this range.

Based on the output, only the value 10 needs to be mapped to 'A', while all other digits can be used directly as the check digit. This mapping makes the implementation simpler and easier to understand.

A typical implementation can be represented as follows:

let check_digit = (11 - sum % 11) % 11;

match check_digit {
    10 => Some('A'),
    digit => char::from_digit(digit, 10),
}
Enter fullscreen mode Exit fullscreen mode

 

4.3 Why the Common Practices and the ISBN-10 Method Return the Same Result

Both the Common Practices 11 - (sum % 11) and the ISBN-10 Method (11 - (sum % 11)) % 11 are used for check digit calculation. Although the formulas differ slightly, they produce the same result when the proper mapping is applied. The process can be described as follows:

  1. Calculate the Remainder
    Both methods start by calculating the remainder:
    remainder = sum % 11

  2. Subtract from 11
    The next step is to subtract the remainder from 11:
    check_digit = 11 - remainder
    This yields results ranging from 1 to 11.

  3. Result Mapping in Common Practices
    According to the Common Practices approach, the mapping is as follows:

    • If remainder is 0, then check_digit is '0'
    • If remainder is 1, then check_digit is 'A'
    • If remainder is between 2 and 10, then check_digit is 11 - remainder (i.e., a digit from 9 to 1)
  4. Result Mapping in the ISBN-10 Method
    The ISBN-10 method applies an additional modulo operation:
    check_digit = (11 - (sum % 11)) % 11
    The mapping is:

    • If check_digit is 10, return 'A'
    • Otherwise, return the digit itself (0 to 9)
  5. Equivalence of Both Methods
    Both methods ensure the check digit is:

    • '0' when the remainder is 0 (resulting in 11, mapped to 0 by ISBN-10 method's % 11)
    • 'A' when the remainder is 1 (resulting in 10)
    • Digits 1 to 9 for other remainders

    The only difference is that the Common Practices approach uses explicit conditional mapping, while the ISBN-10 method uses a modulo operation to automatically handle the special case when the result is 11.

 
Example: Result Comparison

The table below demonstrates that both methods produce the same check digits for a variety of weighted sum values:

Weighted Sum (sum) sum % 11 Common Practices Result ISBN-10 (11 - sum % 11) % 11 Final Check Digit
113 3 else → 8 (11-3)%11 = 8 8
245 3 else → 8 (11-3)%11 = 8 8
311 3 else → 8 (11-3)%11 = 8 8
402 6 else → 5 (11-6)%11 = 5 5
156 2 else → 9 (11-2)%11 = 9 9
230 10 else → 1 (11-10)%11 = 1 1
407 0 if 0 → '0' (11-0)%11 = 0 0
372 9 else → 2 (11-9)%11 = 2 2
454 3 else → 8 (11-3)%11 = 8 8
187 0 if 0 → '0' (11-0)%11 = 0 0

As shown, the check digit results from both the Common Practices and ISBN-10 Method are always the same.

 
Conclusion:

When the special mappings are correctly applied, both the Common Practices and the ISBN-10 Method yield identical check digit results for any valid HKID number.


5. Alphabet-to-Number Mapping

 
For reference, here’s how letters are converted to numbers in the HKID algorithm:

Letter Value Letter Value Letter Value Letter Value
A 10 B 11 C 12 D 13
E 14 F 15 G 16 H 17
I 18 J 19 K 20 L 21
M 22 N 23 O 24 P 25
Q 26 R 27 S 28 T 29
U 30 V 31 W 32 X 33
Y 34 Z 35 [ 36

 

Why Alphabet-to-Number Mapping is Needed

 
In the HKID check digit calculation, each character in the card number, whether it is a letter, number, or a space, must be converted to a numeric value before performing calculations. This process is called alphabet-to-number mapping.

 
The HKID number typically starts with one or two letters (the prefix), followed by six digits and a check digit in parentheses. To calculate or verify the check digit, the HKID algorithm multiplies each character by a position-based weight and sums the results. Since mathematical operations can only be performed on numbers, any letter in the HKID must first be converted to its corresponding numeric value.

  • Letters are mapped from A = 10 up to Z = 35.
  • A space (used for padding single-letter prefixes) is mapped to 36.
  • Digits keep their natural values (0–9).

6. HKID Check Digit Calculation: Step-by-Step Explanation

 
Let’s formally get started with today’s topic 📚. In the following section, we’ll break down the HKID check digit calculation step by step so you can easily understand the process 🧮✨.

 
The HKID check digit helps verify the validity of an HKID number. The calculation has two parts:

  • First: Compute a weighted sum.
  • Second: Apply a modulus formula to get the check digit.

 

Step 1: Calculate the Weighted Sum

  1. Pad the HKID if needed:
    • If the prefix is a single letter (e.g., A123456), pad it by adding a space in front:
      • "[A123456"
    • If the prefix is two letters (e.g., AZ123456), use as-is.
  2. Convert each character to a number:
    • For letters: A = 10, B = 11, ..., Z = 35
    • For digits: use the digit itself
    • For a [: 36
  3. Apply positional weights:
    • The weights, from left to right, are: 9, 8, 7, 6, 5, 4, 3, 2
  4. Calculate the weighted sum:
    • Multiply each character’s numeric value by its corresponding weight and sum the results.

 

Worked Example

Suppose the HKID is A123456:

Character Value Weight Sum
[ 36 9 324
A 10 8 80
1 1 7 7
2 2 6 12
3 3 5 15
4 4 4 16
5 5 3 15
6 6 2 12

 

Weighted sum:

324 + 80 + 7 + 12 + 15 + 16 + 15 + 12 = 481

 
Code implementation (Rust):

const WEIGHTS: [u32; 8] = [9, 8, 7, 6, 5, 4, 3, 2];

let values = padded_body.chars().map(Self::char_to_value).collect::<Option<Vec<u32>>>()?;
let sum = values.iter().zip(WEIGHTS.iter()).map(|(v, w)| v * w).sum::<u32>();
Enter fullscreen mode Exit fullscreen mode

 

Step 2: Calculate the Check Digit

Use the weighted sum from Step 1 in ISBN-10 check digit formula:

check_digit = (11 - (sum % 11)) % 11
Enter fullscreen mode Exit fullscreen mode
  • If the result is 10, the check digit is A
  • If the result is 0, the check digit is 0
  • Otherwise, the check digit is the digit itself

 
Code implementation (Rust):

match check_digit {
    10 => 'A',
    digit => char::from_digit(check_digit, 10).unwrap(),
}
Enter fullscreen mode Exit fullscreen mode

 

Continuing the Example

  • Weighted sum: 481
  • sum % 11 = 481 % 11 = 8
  • 11 - 8 = 3
  • 3 % 11 = 3

So, the check digit is 3 (the complete HKID is A123456(3)).

 

Core Logic Implementation (Rust)

The following is the core logic used to calculate the HKID check digit. For full details functions, please visit my GitHub repo: hkid_ops/rust_hkid_ops.

const WEIGHTS: [u32; 8] = [9, 8, 7, 6, 5, 4, 3, 2];

fn char_to_value(c: char) -> Option<u32> {
    let c = c.to_ascii_uppercase() as u8;

    match c {
        b'A'..=b'Z' => Some(u32::from(c - b'A' + 10)),
        b'0'..=b'9' => Some(u32::from(c - b'0')),
        b' ' => Some(36),
        _ => None,
    }
}

pub fn calculate_check_digit(&self, hkid_body: &str) -> Option<char> {
  if !VALID_HKID_BODY_REGEX.is_match(hkid_body) {
      return None;
  }

  let padded_body = format!("{hkid_body:>8}");
  let values = padded_body.chars().map(HKIDOps::char_to_value).collect::<Option<Vec<u32>>>()?;
  let sum = values.iter().zip(WEIGHTS.iter()).map(|(v, w)| v * w).sum::<u32>();
  let check_digit = (11 - sum % 11) % 11;

  match check_digit {
      10 => Some('A'),
      digit => char::from_digit(digit, 10),
  }
}

pub fn validate_hkid(&self, hkid_full: &str, must_exist_in_enum: bool) -> Result<bool, String> {
    let cleaned = hkid_full.chars()
        .filter(|&c| c != '(' && c != ')')
        .collect::<String>();

    let caps = HKID_FULL_REGEX.captures(&cleaned)
        .ok_or_else(|| "Invalid HKID format: incorrect structure.".to_string())?;

    let prefix = caps.get(1).ok_or("Missing prefix in HKID")?.as_str();
    let digits = caps.get(2).ok_or("Missing digits in HKID")?.as_str();
    let provided_digit = caps.get(3).ok_or("Missing check digit in HKID")?.as_str();

    if must_exist_in_enum {
        let parsed_prefix = HKIDPrefix::parse(prefix);
        if !parsed_prefix.is_known() {
            return Err(format!("Prefix '{prefix}' is not recognized."));
        }
    }

    let hkid_body = format!("{prefix}{digits}");
    let calculated_digit = self.calculate_check_digit(&hkid_body)
        .ok_or_else(|| "Failed to calculate check digit".to_string())?;

    let provided_digit = provided_digit.chars().next().ok_or_else(|| "Missing check digit".to_string())?;

    Ok(calculated_digit == provided_digit)
}
Enter fullscreen mode Exit fullscreen mode

7. How to Generate and Validate Hong Kong Identity Card Numbers

 
To generate a Hong Kong Identity Card (HKID) number, begin by providing an optional prefix, which must consist of one or two uppercase letters.

If the prefix is supplied and validation against known HKID prefixes is required, the function checks whether the prefix is recognized. If no prefix is provided, a random prefix is generated; this prefix is selected from known values if validation is required, or chosen as any one or two-letter uppercase combination otherwise.

Six random digits are then appended to the chosen prefix to form the HKID body. A check digit is calculated based on this body, and the complete HKID is constructed in the format dddddd(C), where d represents a digit and C is the computed check digit.

The function returns the generated HKID string or an error if validation fails.

 
Example: Generating an HKID with a Specific Prefix

Suppose the goal is to generate an HKID with the prefix "A" and require that the prefix must be recognized (i.e., must_exist_in_enum = true).

  1. The function checks that the prefix "A" is in the correct format (one or two uppercase letters) and is recognized.
  2. Six random digits are generated, for example: 123456.
  3. The HKID body is formed by combining the prefix and digits: A123456.
  4. A check digit is calculated for the body. Suppose the result is 3.
  5. The final HKID is constructed in the format: A123456(3).

Example Code:

use hkid_ops::hkid_ops::HKIDOps;

let ops = HKIDOps::new();
let hkid = ops.generate_hkid(Some("A"), true).unwrap();

println!("Generated HKID: {}", hkid); // Output: e.g. "A123456(3)"
Enter fullscreen mode Exit fullscreen mode

 
Example: Generating an HKID with a Random Prefix

Suppose the goal is to generate an HKID without specifying a prefix. In this case, the function will use a random prefix. If must_exist_in_enum is set to true, the prefix will be chosen from known valid HKID prefixes.

  1. No prefix is supplied, so the function selects a random known prefix, for example: "M".
  2. Six random digits are generated, for example: 482951.
  3. The HKID body is formed by combining the random prefix and digits: M482951.
  4. A check digit is calculated for the body. Suppose the result is A.
  5. The final HKID is constructed in the format: M482951(A).

Example Code:

use hkid_ops::hkid_ops::HKIDOps;

let ops = HKIDOps::new();
let hkid = ops.generate_hkid(None, true).unwrap();

println!("Generated HKID: {}", hkid); // Output: e.g., "M482951(A)"
Enter fullscreen mode Exit fullscreen mode

 

Core Logic Implementation (Rust)

The following is the core logic used to generate the HKID number. For full details and functions, please visit my GitHub repo: hkid_ops/rust_hkid_ops.

fn random_known_prefix() -> &'static str {
    let idx = fastrand::usize(..KNOWN_PREFIXES.len());
    KNOWN_PREFIXES[idx]
}

fn random_prefix() -> String {
    let len = if fastrand::bool() { 1 } else { 2 };
    let mut s = String::with_capacity(len);

    for _ in 0..len {
        s.push(Self::random_uppercase_letter());
    }

    s
}

pub fn generate_hkid(&self, prefix: Option<&str>, must_exist_in_enum: bool) -> Result<String, String> {
    if let Some(px) = prefix {
        if !VALID_PREFIX_REGEX.is_match(px) {
            return Err(format!("Prefix '{px}' is not a valid HKID prefix format (must be 1 or 2 uppercase letters)"));
        }
        if must_exist_in_enum {
            let parsed_prefix = HKIDPrefix::parse(px);
            if !parsed_prefix.is_known() {
                return Err(format!("Prefix '{px}' is not recognized"));
            }
        }
    }

    let prefix_str = match (prefix, must_exist_in_enum) {
        (Some(px), true | false) => HKIDPrefix::parse(px).as_str().to_string(),
        (None, true) => Self::random_known_prefix().to_string(),
        (None, false) => Self::random_prefix(),
    };

    let digits = (0..6).map(|_| fastrand::u8(0..10).to_string()).collect::<String>();
    let hkid_body = format!("{prefix_str}{digits}");
    let check_digit = self.calculate_check_digit(&hkid_body).ok_or("Failed to calculate check digit")?;

    Ok(format!("{hkid_body}({check_digit})"))
}
Enter fullscreen mode Exit fullscreen mode

8. Rust Implementation: hkid_ops

 
To make HKID validation and generation easier and more reliable in software projects, I have implemented the entire HKID logic in Rust and published it as both open-source code and a reusable crate.

  • GitHub: hkid_ops/rust_hkid_ops

    You can explore the codebase, check out the implementation details, and contribute or raise issues if you find any edge cases or want to request features.

  • Crates.io: hkid_ops

    Easily include HKID validation and generation in your own Rust projects by adding hkid_ops to your dependencies.

What Does the Rust Crate Provide?

  • Generate valid HKID numbers
  • Verify HKID check digits
  • Parse and handle HKID prefixes and symbols according to the official rules

 

9. Conclusion

 
The Hong Kong Identity Card (HKID) format encodes information about the cardholder and the history of card issuance through specific prefixes and symbols. The check digit is calculated using a weighted sum of the card’s characters and a modulus formula based on the ISBN-10 algorithm. This process produces a check digit value of 0–9 or ‘A’, which is used for error detection in HKID numbers.

 
Understanding the structure, prefix meanings, character-to-number mapping, and check digit calculation enables implementation of HKID validation and generation in software systems. The hkid_ops crate provides functions for generating and verifying HKID numbers according to these rules.

 
For implementation details and sample code, refer to the hkid_ops/rust_hkid_ops repository.

 

References:

Top comments (0)