【臺灣ID驗證系列】身分證字號驗證

最近看到一則有趣的新聞,原來身分字號 A123456789 真有其人阿!決定來稍微了解一下,身分證字號是怎產生出來的,順便寫寫身分證字號檢查器…原本想寫產生器的,但想想還是算了 XD

中華民國身分證
中華民國身分證



編號規則

目前現行的身分證字號一共有 10 碼,包括起首的大寫的英文字母與接續的九個阿拉伯數字(如:A123456789),大抵可以將身分證字號分成四區:縣市代碼性別代碼流水碼檢核碼

縣市代碼 性別代碼 流水碼 檢核碼
A 1 2 3 4 5 6 7 8 9


其中首碼的縣市代碼以報戶口的地區來區分的。而性別代碼則是指首位數字,其中男性為 1、女性為 2。但在檢查檢核碼時,會將縣市代碼轉換成相對應的數值,如 A 就會被轉換成 10

10 11 12 13 14 15 16 17 34 18
19 20 21 22 35 23 24 25 26 27
28 29 32 30 31 33


將轉換完成的數值,乘上相對應的權重後進行加總:

Index $n_0$ $n_1$ $n_2$ $n_3$ $n_4$ $n_5$ $n_6$ $n_7$ $n_8$ $n_9$ $n_{10}$
權重 1 9 8 7 6 5 4 3 2 1 1

若總和為 10 的倍數,即為有效的驗證碼。

若改寫成數學判斷式:
\((n_0\times 1+n_1\times 9+n_2\times 8+n_3\times 7+n_4\times 6+n_5\times 5+n_6\times 4+n_7\times 3+n_8\times 2+n_9\times 1+n_{10}\times 1)\%10 = 0\)

A123456789 轉換成 10123456789 後套入公式如下:

\[\begin{aligned} &(1\times 1+2\times 9+3\times 8+4\times 7+5\times 6+6\times 5+7\times 4+8\times 3+9\times 2+10\times 1+11\times 1)\%10 \\ &= (2 +9 +8 +28+0+0+20+3+12+5+3)\%10\\ &= 90\%10\\ &= 0 \end{aligned}\]

餘數為 0,表有效的 ID。



程式碼

有點久沒寫 js 了,順便寫寫 js 練練手好了。把上面的規則寫成程式,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function verifyId(id) {
    id = id.trim();

    if (id.length != 10) {
        console.log("Fail,長度不正確");
        return false
    }


    let countyCode = id.charCodeAt(0);
    if (countyCode < 65 | countyCode > 90) {
        console.log("Fail,字首英文代號,縣市不正確");
        return false
    }

    let genderCode = id.charCodeAt(1);
    if (genderCode != 49 && genderCode != 50) {
        console.log("Fail,性別代碼不正確");
        return false
    }

    let serialCode = id.slice(2)
    for (let i in serialCode) {
        let c = serialCode.charCodeAt(i);
        if (c < 48 | c > 57) {
            console.log("Fail,數字區出現非數字字元");
            return false
        }
    }

    let conver = "ABCDEFGHJKLMNPQRSTUVXYWZIO"
    let weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1]

    id = String(conver.indexOf(id[0]) + 10) + id.slice(1);

    checkSum = 0
    for (let i = 0; i < id.length; i++) {
        c = parseInt(id[i])
        w = weights[i]
        checkSum += c * w
    }

    verification = checkSum % 10 == 0

    if (verification) {
        console.log("Pass");
    } else {
        console.log("Fail,檢核碼錯誤");
    }

    return verification
}

console.log(verifyId("A123456789"));


是說,如果不要顯示 log , Regular Expression 可以涵蓋前半段的檢查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function verifyId(id) {
    id = id.trim();
    
    <!--  js 中遇到反斜線要跳脫所以這邊用兩個反斜線 -->
    <!-- 如果你看到四個反斜線那是我為了讓 NexT.Mist 主題順利渲染所再做跳脫 -->
    verification = id.match("^[A-Z][12]\\d{8}$")
	if(!verification){
		return false
	}

    let conver = "ABCDEFGHJKLMNPQRSTUVXYWZIO"
    let weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1]

    id = String(conver.indexOf(id[0]) + 10) + id.slice(1);

    checkSum = 0
    for (let i = 0; i < id.length; i++) {
        c = parseInt(id[i])
        w = weights[i]
        checkSum += c * w
    }
	
    return checkSum % 10 == 0
}

console.log(verifyId("A123456789"));



進一步驗證規則

因為上述的驗證規則是用於僅輸入身分證字號的情境,若使用情境中有輸入戶籍地與出生日期的情況,可在新增:

  1. 縣市代碼與戶籍地的對照:
    不過這個用到的機會不大,鮮少有情境是輸入戶籍地,多數時候都是輸入通訊地 XD

  2. 縣市代碼與出生日期的比較:
    因為縣市合併的關係,目前有部份縣市代碼已不再賦配。所以可以比較出生日期與停發日期做進一步檢查。

    縣市代碼 原行政區 停發日期
    L 臺中縣 2010/12/25
    R 臺南縣 2010/12/25
    S 高雄縣 2010/12/25
    Y 陽明山管理局 1974/01/01



參考資料

  1. 林姸君 (2020-07-10)。身分證A123456789真有人 一條龍伯「信用破產」冤跑法庭:別再害我了 。檢自 ctwant (2020-07-10)。
  2. (2006-02-17)。身分證「A123456789」老被冒用 。檢自 ctwant阿特拉斯的部落格 (2020-07-10)。
  3. 協同撰寫。中華民國國民身分證。檢自 維基百科 (2020-07-10)。



更新紀錄

最後更新日期:2020-08-25
     
  • 2020-08-25 更新:新增 Regular expression
  •  
  • 2020-08-10 發布
  •  
  • 2020-07-13 完稿
  •  
  • 2020-07-10 起稿