其它內容

-

一篇文章搞懂 bcrypt、JWT、jsonwebtoken、簽章與加密

this.web

一篇文章搞懂 bcrypt、JWT、簽章與加密

如果你是軟體工程師,但從來沒有搞懂登入系統是怎麼設計的,這篇文章想先帶你了解幾個觀念,包括 bcrypt、JWT、jsonwebtoken、簽章與加密。

讓你不管是遇到登入需求、要串接前後端驗證機制,還是要讓 AI 幫你開發時,都能分得清楚每個名詞到底在做什麼、彼此的關係是什麼,以及哪些地方最容易搞混。

這篇文章會用登入流程的角度,依序講解:

  • bcrypt
  • JWT
  • Base64URL
  • jsonwebtoken
  • stateless / stateful
  • session

那我們就開始吧!

一、bcrypt 是什麼?不是加密

bcrypt 的角色

bcrypt 用來處理密碼的,

在資料庫中,我們不會直接儲存用戶的密碼,因為如果被有心人士入侵資料庫,那所有人的資料都會外洩,

因此,我們需要 bcrypt 幫我們做一個不可逆的密碼轉換。

比如密碼是:123456,經過 bcrypt 後,密碼就會變成類似這樣:$2b$10$8QvJYF9m1G0nQmRz0XlS3eN1QvWmK7x0wM4Jf3mY9nG5xT2cL8p6W

這種把任意資料轉換成一段不可逆字串的運算方式,被稱為** Hash(雜湊)**,bcrypt 就是一種 password hash function,專門用來實現密碼雜湊的工具。

只不過 bcrypt 除了雜湊以外,還會搭配加鹽,加鹽的意思是加入一段隨機字串和密碼一起進行 hash,概念類似於:hash(password + salt),如果沒有加鹽,那相同的密碼會 hash 出相同的結果。

不過我們不需要看懂這串字在幹嘛,只要知道資料庫裡存的不是原始密碼,而是這串文字

假設註冊流程是這樣:

  1. 使用者註冊時輸入密碼 123456
  2. 後端用 bcrypt 把它轉成一串文字
  3. 資料庫儲存的是這段被轉換後的文字,不是明文密碼

到了登入時:

  1. 使用者再次輸入密碼 123456
  2. 後端不是拿明文密碼去解密
  3. 而是用 bcrypt 的 compare 機制,檢查這次輸入的密碼能不能對上資料庫那串文字
  4. 對得上就登入成功,對不上就失敗

也就是說,除了你自己,系統和開發者根本不需要知道你的原始密碼,我們只需要知道你這次輸入的密碼,能不能和之前存下來的資料對上就好了。

注意 bcrypt 不是加密,因為它不能被解密,即使拿到雜湊值也無法還原密碼。

二、JWT 是什麼?也不是加密

JWT(JSON Web Token)是一種 Token 的格式與規範,用來:

  • 承載身分資訊(claims)
  • 證明這份資料沒有被竄改
  • 證明 token 是由可信的一方簽發

JWT 預設是不加密內容的,它由三段組成,分別是:

header.payload.signature

其中只有 signature 是安全核心,前兩段通常長這樣:

header 用來描述這顆 JWT 的基本資訊,常見內容例如:

{
"alg": "HS256",
"typ": "JWT"
}
  • alg:簽章演算法,例如 HS256RS256
  • typ:通常是 JWT

payload

payload 用來放 claims,也就是這顆 token 想傳遞的資料,例如:

{
"sub": "123",
"role": "admin",
"iat": 1710000000,
"exp": 1710003600
}
  • sub:這顆 token 代表誰,通常是 user id
  • role:角色或權限資訊
  • iat:簽發時間
  • exp:過期時間

要注意的是,payload可被讀取的資料,它可不是什麼保險箱,任何人拿到 JWT 都能解碼出攜帶的資料(payload)。

因此它適合放識別與授權判斷需要的資訊,例如用戶名稱,千萬不要放密碼、身分證字號、信用卡等敏感內容。

補充:更準確應該說,多數 JWT 實作是 JWS(簽章),不是加密。但 JWT 規範也支援 JWE(加密)。

三、Base64URL 在 JWT 中的角色

那 JWT 裡的 header 和 payload 是怎麼被儲存的呢?

這就要講到 Base64URL。

不過在講 Base64URL 之前,要先知道什麼是 Base64。

什麼是 Base64?

Base64 是一種編碼方式,作用是把原本的文字資料,轉成只包含英數字和少數符號的字串。

它不是在保護資料,只是換一種比較方便傳輸的寫法

例如一段 JSON:

{
"sub": "123",
"role": "admin"
}

可以被編碼成一串 Base64 文字,這樣在網路傳輸時會比較方便處理。

而 Base64URL 是把被編碼成 Base64 文字的資料:變成可以安全放在 HTTP header / cookie / URL 裡的字串

可以把 Base64URL 理解成「Base64 的 URL / Header 安全版本」。

一般 Base64 會用到 +/= 這些字元,但這些字元放進 URL、cookie、HTTP header 時有時會造成額外處理上的麻煩,因此 JWT 改用 Base64URL:

  • + 換成
  • / 換成 _
  • 常常省略尾端的 =

所以它本質上只是編碼格式,用途是讓字串更適合在網路傳輸,不是拿來保護內容。

它和 JWT 的演算法是什麼關係?

這裡很容易搞混,因為 JWT 裡同時出現了 Base64URL 和 alg,但這兩者做的是完全不同的事

  • Base64URL:負責編碼 header 和 payload,讓它們變成適合傳輸的字串
  • alg:負責簽章演算法,例如 HS256RS256,用來產生與驗證 signature

也就是說,Base64URL 不是加密演算法也不是簽章演算法,只是把資料格式轉成字串格式。

真正和安全性直接相關的,是後面用 alg 產生出來的 signature,不是 Base64URL 本身。

四、簽章(Signature)是什麼?和加密差在哪

在上面有提到, JWT 中的 header 有 alg 用來表示簽章算法,不知道你有沒有想過,這個簽章和加密有什麼不同呢?

加密(Encryption)

目的:保密

  • 沒有金鑰,就看不懂內容
  • 可以解密還原
  • 用在 HTTPS、資料保護

簽章(Signature)

目的:完整性 + 真實性

  • 內容看得到
  • 但不能被偷改
  • 能確認是誰簽的

更精確地說,簽章讓接收方能驗證內容是否被修改過

如果有人改了 header 或 payload,原本的 signature 通常就會失效,伺服器在驗證時就能發現這顆 token 不合法,然後拒絕使用它。

所以白話一點說:JWT 不是把內容藏起來,而是在內容下面蓋一個防偽章

大家都看得到內容,但如果有人偷偷改內容,這個防偽章就會對不上,伺服器一驗就知道這張 token 是假的。

五、jsonwebtoken 這個 library 在做什麼?

那我們要怎麼做出 JWT 呢?

當然不可能自己去寫簽章算法啦,所以就可以使用 jsonwebtoken ,他是一個 JWT 規範的實作工具,會幫我們處理:

1. 簽發 token(sign)

  • JSON 序列化
  • Base64URL 編碼
  • 使用指定演算法產生 signature
  • 組合成 JWT 字串

2. 驗證 token(verify)

  • 驗 signature 是否正確(內容有無被修改過)
  • 確保 token 未被竄改、未過期

3. 解析 token(decode)

  • 不驗證
  • 只用於反向解析出 payload 的內容

六、session 是什麼?

除了 bcrypt、JWT 等,還有一個名詞經常出現在登入系統中,那就是 session。

session 可以把它理解成:使用者登入後,伺服器替這次登入建立的一份「登入狀態的紀錄」。

典型流程是:

  1. 使用者登入成功
  2. 伺服器建立一筆 session,存到記憶體、Redis 或資料庫
  3. 伺服器把 session id 回傳給瀏覽器,通常放在 cookie
  4. 之後每次 request,瀏覽器都帶上 session id
  5. 伺服器根據 session id 找回這位使用者的登入狀態
  6. 這樣我們就能判斷用戶有沒有登入

session 很常搭配 cookie 使用,瀏覽器會自動帶上 session id;JWT 則不一定,有些系統會把 JWT 放在 cookie,也有些會放在 Authorization header。

所以更精確地說,重點不是「它放在哪裡」,而是:

  • session:後端拿到 session id 後,要回頭查 server 端保存的狀態
  • JWT:後端拿到 token 後,先驗簽,再決定能不能相信其中的內容

而 session 和 JWT 最大的差異在於:

  • session:狀態直接存在 server 中
  • JWT:狀態直接放進 token,由 server 驗簽後信任內容

因此 session 就是所謂的 stateful, 而 JWT 常被稱為 stateless

所謂 stateful,意思是:伺服器自己有記住「這個人目前的登入狀態」。

例如 session id 只是鑰匙,真正的使用者資料、登入時間、權限或其他狀態,都還存放在 server 資料庫中。每次 request 來時,server 都要根據這把鑰匙把那份狀態找出來。

相對地,JWT 之所以常被叫做 stateless,是因為伺服器理論上不一定需要另外保存這份登入狀態,只要驗簽成功,就能直接相信 token 裡的內容。

不過實務上只要你加入 refresh token 等機制後,通常又會回到某種程度的 stateful。

總結

相信看到這邊,你對這些在登入、驗證系統中常見的名詞,有一定程度的了幾了,這邊最後總結一下:

  • bcrypt:驗證你知不知道密碼
  • JWT:證明你已登入,且資料沒被改
  • jsonwebtoken:JWT 的生產與驗證工具
  • Base64URL:把資料變成適合放進 URL、cookie、header 的格式,但不是安全機制
  • 簽章 ≠ 加密:簽章用來判斷資料有沒有被修改過,加密用來保護資料

希望這篇文章,有幫你了解關於驗證、授權等系統的相關概念~!

你可能會感興趣的文章 👇