---
title: ID驗證系列|身分證字號驗證
date: 2021-03-10
is_modified: true
disqus: cynthiahackmd
categories:
- "先備知識"
tags:
- "編碼規則"
- "Javascript"
- "regexp"
- "臺灣ID驗證系列"
---
{%hackmd @CynthiaChuang/Github-Page-Theme %}
<br>
最近看到一則[有趣的新聞](https://www.ctwant.com/article/61090),原來身分字號 `A123456789` 真有其人阿!決定來稍微了解一下,身分證字號是怎產生出來的,順便寫寫身分證字號檢查器...原本想寫產生器的,但想想還是算了 XD
<!--more-->
<p class="illustration">
    <img src="https://i.imgur.com/ggfZhwS.png" alt="中華民國身分證">
    中華民國身分證
</p>
## 編號規則
目前現行的身分證字號一共有 **10 碼**,包括起首的大寫的英文字母與接續的九個阿拉伯數字(如:A123456789),大抵可以將身分證字號分成五區:**區域碼**、**性別碼**、**身分碼**、**流水碼**跟**檢核碼**。
<table>
    <tbody>
    <tr>
      <th>區域碼</th>
      <th>性別碼</th>    
      <th>身分碼</th>     
      <th  colspan="6">流水碼 </th>
      <th>檢核碼</th>
    </tr>
    <tr>
        <td>A-Z</td>
        <td>男:1<br>
            女:2</td>    
        <td>其他:0-5<br>
            取得國籍之外國人:6<br>
            無戶籍國民:7<br>
            港澳居民:8<br>
            大陸地區人民:9
            </td>  
        <td  colspan="6">阿拉伯數字 </td>
        <td>阿拉伯數字</td>
    </tr>
    </tbody>
</table>
  
  
其中首碼的縣市代碼是以報戶口的地區來區分的、而性別代碼則是指首位數字,其中男性為1、女性為2,最後第三碼是身分碼,其中 0-5 是保留給國人,6-9 則是保留給歸化的外國人與中港澳人民使用。
關於第三碼的**身分碼**,在我 2020-08 第一次寫這篇的時候,我並沒有注意到身分碼的資料,但在今天為了寫[《【臺灣ID驗證系列】居留證驗證》](
https://hackmd.io/@CynthiaChuang/Check-Resident-Certificate-Number)在[查資料](http://www.academic.fcu.edu.tw/wSite/public/Attachment/f1582594331972.pdf)的時候,發現了這東西。不過再細查資料,這條規則是在內政部 92 年 4 月 24 日台內戶字第 0920063929 號函規定就被定下了,所以應該是我上次資料漏看了!?
<br>
整體來說,一個完整的身份證字號如下:
<table>
    <tbody>
    <tr>
      <th>區域碼</th>
      <th>性別碼</th>    
      <th>身分碼</th>     
      <th  colspan="6">流水碼 </th>
      <th>檢核碼</th>
    </tr>
    <tr>
      <td>A </td>
      <td>1 </td>
      <td>2 </td>
      <td>3 </td>
      <td>4 </td>
      <td>5 </td>
      <td>6 </td>
      <td>7 </td>
      <td>8 </td>
      <td>9 </td>
    </tr>
    </tbody>
</table>
<br>
在進行編碼檢查時,會將縣市代碼轉換成相對應的數值,如 A 就會被轉換成 `10`:  
|A|B|C|D|E|F|G|H|I|J|
|---|---|---|---|---|---|---|---|---|---|
|10|11|12|13|14|15|16|17|34|18|
|K|L|M|N|O|P|Q|R|S|T|
|---|---|---|---|---|---|---|---|---|---|
|19|20|21|22|35|23|24|25|26|27|
|U|V|W|X|Y|Z|
|---|---|---|---|---|---|
|28|29|32|30|31|33|
<br>
將轉換完成的數值,乘上相對應的權重後進行加總:  
|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 的倍數**,即為有效的驗證碼。
<br>
若改寫成數學判斷式:  
$$
(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
$$ 
<br>
將 `A123456789` 轉換成 `10123456789` 後套入公式如下:  
$$
\begin{aligned}	
&(1\times 1+0\times 9+1\times 8+2\times 7+3\times 6+4\times 5+5\times 4+6\times 3+7\times 2+8\times 1+9\times 1)\%10 \\
&= (1+0+8+14+18+20+20+18+14+8+9)\%10\\
&= 130\%10\\
&= 0
\end{aligned}
$$ 
餘數為 0,表有效的 ID。
 
## 程式碼
有點久沒寫 js 了,順便寫寫 js 練練手好了。把上面的規則寫成程式,如下:  
```javascript=
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"));
```
<br>
是說如果不要顯示 log , Regular Expression 可以涵蓋前半段的檢查:
```javascript=
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. **性別**:  
    當然就是檢查第二碼來確認啦。
    
2. **縣市代碼**與戶籍地的對照:  
    不過這個用到的機會不大,鮮少有情境是輸入戶籍地,多數時候都是輸入通訊地 XD
    
3. **縣市代碼**與出生日期的比較:  
    因為縣市合併的關係,目前有部份**縣市代碼**已不再賦配。所以可以比較出生日期與停發日期做進一步檢查。  
    | 縣市代碼 | 原行政區     | 停發日期   |
    | -------- | ------------ | ---------- |
    | L        | 臺中縣       | 2010/12/25 |
    | R        | 臺南縣       | 2010/12/25 |
    | S        | 高雄縣       | 2010/12/25 |
    | Y        | 陽明山管理局 | 1974/01/01 |
    
## 參考資料 
1. 林姸君 (2020-07-10)。[身分證A123456789真有人 一條龍伯「信用破產」冤跑法庭:別再害我了](https://www.ctwant.com/article/61090)。檢自 ctwant (2020-07-10)。
2. (2006-02-17)。[身分證「A123456789」老被冒用](https://blog.xuite.net/sinner66/blog/5201507-%E8%BA%AB%E5%88%86%E8%AD%89%E3%80%8CA123456789%E3%80%8D%E8%80%81%E8%A2%AB%E5%86%92%E7%94%A8)。檢自 ctwant阿特拉斯的部落格 (2020-07-10)。
3. 協同撰寫。[中華民國國民身分證](https://zh.wikipedia.org/wiki/%E4%B8%AD%E8%8F%AF%E6%B0%91%E5%9C%8B%E5%9C%8B%E6%B0%91%E8%BA%AB%E5%88%86%E8%AD%89#%E9%A9%97%E8%AD%89%E8%A6%8F%E5%89%87)。檢自 維基百科 (2020-07-10)。
4. 內政部 (2019-10)。[「外來人口統一證號格式專案」修正計畫(核定本)](http://www.academic.fcu.edu.tw/wSite/public/Attachment/f1582594331972.pdf)。檢自 內政部 (2021-03-10)。
## 更新紀錄
:::spoiler 最後更新日期:2021-03-10
- 2021-03-10 更新:新增身分碼資料
- 2020-08-25 更新:新增 Regular expression
- 2020-08-10 發布
- 2020-07-13 完稿
- 2020-07-10 起稿
:::
{%hackmd @CynthiaChuang/Github-Page-Footer %}