與 JOSE 戰(zhàn)斗的日子 - 寫給 iOS 開發(fā)者的密碼學(xué)入門手冊 (基礎(chǔ))

image

概述

事情的緣由很簡單,工作上在做 LINE SDK 的開發(fā),在拿 token 的時候有一步額外的驗證:從 Server 會發(fā)回一個 JWT (JSON Web Token),客戶端需要對這個 JWT 進行簽名和內(nèi)容的驗證,以確保信息沒有被人篡改。

推薦閱讀:iOS開發(fā)——2019 最新 BAT面試題合集(持續(xù)更新中)

Server 在簽名中使用的算法類型會在 JWT 中寫明,驗證簽名所需要的公鑰 ID 也可以在 JWT 中找到。這個公鑰是以 JWK (JSON Web Key) 的形式公開,客戶端拿到 JWK 后即可在本地對收到的 JWT 進行驗證。用一張圖的話,大概是這樣:

image

作為一個開發(fā)者,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:638302184,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗,討論技術(shù), 大家一起交流學(xué)習(xí)成長!

群內(nèi)提供數(shù)據(jù)結(jié)構(gòu)與算法、底層進階、swift、逆向、整合面試題等免費資料
附上一份收集的各大廠面試題(附答案) ! 群文件直接獲取
各大廠面試題

步驟

如果你現(xiàn)在對下面說步驟不理解的話 (這挺正常的,畢竟這篇文章都還沒正式開始 ??),可以先跳過這部分,等我們有一些基礎(chǔ)知識以后再回頭看看就好。如果你很清楚這些步驟的話,那真是好棒棒,你應(yīng)該能無壓力閱讀該系列剩余部分內(nèi)容了。

LINE SDK 里使用 JWT 驗證用戶的邏輯如下:

  1. 向登錄服務(wù)器請求 access token,登錄服務(wù)器返回 access token,同時返回一個 JWT。
  2. JWT 中包含應(yīng)該使用的算法和密鑰的 ID。通過密鑰 ID,去找預(yù)先定義好的 Host 拿到 JWK 形式的該 ID 的密鑰。
  3. 將 1 的 JWT 和 2 的密鑰轉(zhuǎn)換為 Security.framework 接受的形式,進行簽名驗證。

這個過程想法很簡單,但會涉及到一系列比較基礎(chǔ)的密碼學(xué)知識和標(biāo)準的閱讀,難度不大,但是枯燥乏味。另外,由于 iOS 并沒有直接將 JWK 轉(zhuǎn)換為 native 的 SecKey 的方式,自己也沒有任何密碼學(xué)的基礎(chǔ),所以在處理密鑰轉(zhuǎn)換上也花了一些工夫。為了后來者能比較順利地處理相關(guān)內(nèi)容 (包括 JWT 解析驗證,JWK 特別是 RSA 和 EC 算法的密鑰轉(zhuǎn)換等),也為了過一段時間自己還能有地方回憶這些內(nèi)容,所以將一些關(guān)鍵的理論知識和步驟記錄下來。

系列文章的內(nèi)容

整個系列會比較長,為了閱讀壓力小一些,我會分成三個部分:

  1. 基礎(chǔ) - 什么是 JWT 以及 JOSE (本文)
  2. 理論 - JOSE 中的簽名和驗證流程
  3. 實踐 - 如何使用 Security.framework 處理 JOSE 中的驗證

全部讀完的話應(yīng)該能對網(wǎng)絡(luò)相關(guān)的密碼學(xué)有一個膚淺的了解,特別是常見的簽名算法和密鑰種類,編碼規(guī)則,怎么處理拿到的密鑰,怎么做簽名驗證等等。如果你在工作中有相關(guān)需求,但不知道如何下手的話,可以仔細閱讀整個系列,并參看開源的 LINE SDK Swift 的相關(guān)實現(xiàn),甚至直接 copy 部分代碼 (如果可以的話,也請順便點一下 star)。如果你只是感興趣想要簡單了解的話,可以只看 JOSE 和 JWT 的基礎(chǔ)概念和理論流程部分的內(nèi)容,作為知識面的擴展,等以后有實際需要了再回頭看實踐部分的內(nèi)容。

在文章結(jié)尾,我還列舉了一些常見的問題,包括筆者自己在學(xué)習(xí)時的思考和最后的選擇。如果您有什么見解,也歡迎發(fā)表在評論里,我會繼續(xù)總結(jié)和補充。

聲明:筆者自身對密碼學(xué)也是初學(xué),而本文介紹的密碼學(xué)知識也都是自己的一些理解,同時盡量不涉及過于原理性的內(nèi)容,一切以普通工程師實用為目標(biāo)原則。其中可以想象在很多地方會有理解的錯誤,還請多包涵。如您發(fā)現(xiàn)問題,也往不吝賜教指正,感激不盡。

JWT 以及 JOSE

什么是 JWT

估計大部分 Swift 的開發(fā)者對 JWT 會比較陌生,所以先簡單介紹一下它是什么,以及可以用來做什么。JWT (JSON Web Token) 是一個編碼后的字符串,比如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

一個典型的 JWT 由三部分組成,通過點號 . 進行分割。每個部分都是經(jīng)過 Base64Url 編碼的字符串。第一部分 (Header) 和第二部分 (Payload) 在解碼后應(yīng)該是有效的 JSON,最后一部分 (簽名) 是通過一定算法作用在前兩部分上所得到的簽名數(shù)據(jù)。接收方可以通過這個簽名數(shù)據(jù)來驗證 token 的 Header 及 Payload 部分的數(shù)據(jù)是否可信。

為了視覺上看起來輕松一些,在上面的 JWT 例子中每個點號后加入了換行。實際的 JWT 中不應(yīng)該存在任何換行的情況。

嚴格來說,JWT 有兩種實現(xiàn),分別是 JWS (JSON Web Signature) 和 JWE (JSON Web Encryption)。由于 JWS 的應(yīng)用更為廣泛,所以一般說起 JWT 大家默認會認為是 JWS。JWS 的 Payload 是 Base64Url 的明文,而 JWE 的數(shù)據(jù)則是經(jīng)過加密的。相對地,相比于 JWS 的三個部分,JWE 有五個部分組成。本文中提到 JWT 的時候,所指的都是用于簽名認證的 JWS 實現(xiàn)。

關(guān)于 Base64Url 編碼和處理,在本文后面部分會再提到。

Header

Header 包含了 JWT 的一些元信息。我們可以嘗試將上面的 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 這個 Header 解碼,得到:

{"alg":"HS256","typ":"JWT"}

關(guān)于在數(shù)據(jù)的不同格式之間互相轉(zhuǎn)換 (明文,Base64,Hex Bytes 等),我推薦這個非常不錯的 web app。

在 JWT Header 中,”alg” 是必須指定的值,它表示這個 JWT 的簽名方式。上例中 JWT 使用的是 HS256 進行簽名,也就是使用 SHA-256 作為摘要算法的 HMAC。常見的選擇還有 RS256,ES256 等等??偨Y(jié)一下:

  • HSXXX 或者說 HMAC:一種對稱算法 (symmetric algorithm),也就是加密密鑰和解密密鑰是同一個。類似于我們創(chuàng)建 zip 文件時設(shè)定的密碼,驗證方需要知道和簽名方同樣的密鑰,才能得到正確的驗證結(jié)果。
  • RSXXX:使用 RSA 進行簽名。RSA 是一種基于極大整數(shù)做因數(shù)分解的非對稱算法 (asymmetric algorithm)。相比于對稱算法的 HMAC 只有一對密鑰,RSA 使用成對的公鑰 (public key) 和私鑰 (private key) 來進行簽名和驗證。大多數(shù) HTTPS 中驗證證書和加密傳輸數(shù)據(jù)使用的是 RSA 算法。
  • ESXXX:使用 橢圓曲線數(shù)字簽名算法 (ECDSA) 進行簽名。和 RSA 類似,它也是一種非對稱算法。不過它是基于橢圓曲線的。ECDSA 最著名的使用場景是比特幣的數(shù)字簽名。
  • PSXXX: 和 RSXXX 類似使用 RSA 算法,但是使用 PSS 作為 padding 進行簽名。作為對比,RSXXX 中使用的是 PKCS1-v1_5 的 padding。

如果你對這些介紹一頭霧水,也不必擔(dān)心。關(guān)于各個算法的一些更細節(jié)的內(nèi)容,會在后面實踐部分再詳細說明?,F(xiàn)在,你只需要知道 Header 中 “alg” key 為我們指明了簽名所使用的簽名算法和散列算法。我們之后需要依據(jù)這里的指示來驗證簽名。

除了 “alg” 外,在 Header 中發(fā)行方還可以放入其他有幫助的內(nèi)容。JWS 的標(biāo)準定義了一些預(yù)留的 Header key。在本文中,除了 “alg” 以外,我們還會用到 “kid”,它用來表示在驗證時所需要的,從 JWK Host 中獲取的公鑰的 key ID?,F(xiàn)在我們先集中于 JWT 的構(gòu)造,之后在 JWK 的部分我們再對它的使用進行介紹。

Payload

Payload 是想要進行交換的實際有意義的數(shù)據(jù)部分。上面例子解碼后的 Payload 部分是:

{"sub":"1234567890","name":"John Doe","iat":1516239022}

和 Header 類似,payload 中也有一些預(yù)先定義和保留的 key,我們稱它們?yōu)?claim。常見的預(yù)定義的 key 包括有:

  • “iss” (Issuer):JWT 的簽發(fā)者名字,一般是公司名或者項目名
  • “sub” (Subject):JWT 的主題
  • “exp” (Expiration Time):過期時間,在這個時間之后應(yīng)當(dāng)視為無效
  • “iat” (Issued At):發(fā)行時間,在這個時間之前應(yīng)當(dāng)視為無效

當(dāng)然,你還可以在 Payload 里添加任何你想要傳遞的信息。

我們在驗證簽名后,就可以檢查 Payload 里的各個條目是否有效:比如發(fā)行者名字是否正確,這個 JWT 是否在有效期內(nèi)等等。因為一旦簽名檢查通過,我們就可以保證 Payload 的東西是可靠的,所以這很適合用來進行消息驗證。

注意,在 JWS 里,Header 和 Payload 是 Base64Url 編碼的明文,所以你不應(yīng)該用 JWS 來傳輸任何敏感信息。如果你需要加密,應(yīng)該選擇 JWE。

Signature

一個 JWT 的最后一部分是簽名。首先對 Header 和 Payload 的原文進行 Base64Url 編碼,然后用 . 將它們連接起來,最后扔給簽名散列算法進行簽名,把簽名得到的數(shù)據(jù)再 Base64Url 編碼,就能得到這個簽名了。寫成偽代碼的話,是這樣的:

// 比如使用 RS256 簽名:
let 簽名數(shù)據(jù): Data = RS256簽名算法(Base64Url(string: Header).Base64Url(string: Payload), 私鑰)
let 簽名: String = Base64Url(data: 簽名數(shù)據(jù))

最后,把編碼后的 Header,Payload 和 Signature 都用 . 連在一起,就是我們收發(fā)的 JWT 了。

什么是 JOSE

JWT 其實是 JOSE 這個更大的概念中的一個組成部分。JOSE (Javascript Object Signing and Encryption) 定義了一系列標(biāo)準,用來規(guī)范在網(wǎng)絡(luò)傳輸中使用 JSON 的方式。我們在上面介紹過了JWS 和 JWE,在這一系列概念中還有兩個比較重要,而且相互關(guān)聯(lián)的概念:JWK 和 JWA。它們一起組成了整個 JOSE 體系。

image

JWK

不管簽名驗證還是加密解密,都離不開密鑰。JWK (JSON Web Key) 解決的是如何使用 JSON 來表示一個密鑰這件事。

RSA 的公鑰由模數(shù) (modulus) 和指數(shù) (exponent) 組成,一個典型的代表 RSA 公鑰的 JWK 如下:

{
  "alg": "RS256",
  "n": "ryQICCl6NZ5gDKrnSztO3Hy8PEUcuyvg_ikC-VcIo2SFFSf18a3IMYldIugqqqZCs4_4uVW3sbdLs_6PfgdX7O9D22ZiFWHPYA2k2N744MNiCD1UE-tJyllUhSblK48bn-v1oZHCM0nYQ2NqUkvSj-hwUU3RiWl7x3D2s9wSdNt7XUtW05a_FXehsPSiJfKvHJJnGOX0BgTvkLnkAOTdOrUZ_wK69Dzu4IvrN4vs9Nes8vbwPa_ddZEzGR0cQMt0JBkhk9kU_qwqUseP1QRJ5I1jR4g8aYPL_ke9K35PxZWuDp3U0UPAZ3PjFAh-5T-fc7gzCs9dPzSHloruU-glFQ",
  "use": "sig",
  "kid": "b863b534069bfc0207197bcf831320d1cdc2cee2",
  "e": "AQAB",
  "kty": "RSA"
}

模數(shù) n 和指數(shù) e 構(gòu)成了密鑰最關(guān)鍵的數(shù)據(jù)部分,這兩部分都是 Base64Url 編碼的大數(shù)字。

關(guān)于 RSA 的原理,不在本文范圍內(nèi),你可以在其他很多地方找到相關(guān)信息。

如果你接觸過幾個 RSA 密鑰,可能會發(fā)現(xiàn) “e” 的值基本都是 “AQAB”。這并不是巧合,這是數(shù)字 65537 (0x 01 00 01) 的 Base64Url 表示。選擇 AQAB 作為指數(shù)已經(jīng)是業(yè)界標(biāo)準,它同時兼顧了運算效率和安全性能。同樣,這部分內(nèi)容也超出了本文范疇。

類似地,一個典型的 ECDSA 的 JWK 內(nèi)容如下:

{
  "kty":"EC",
  "alg":"ES256",
  "use":"sig",
  "kid":"3829b108279b26bcfcc8971e348d116",
  "crv":"P-256",
  "x":"EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84",
  "y":"AJBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G"
}

決定一個 ECDSA 公鑰的參數(shù)有三個: “crv” 定義使用的密鑰所使用的加密曲線,一般可能值為 “P-256”,”P-384” 和 “P-521”?!眡” 和 “y” 是選取的橢圓曲線點的座標(biāo)值,根據(jù)曲線 “crv” 的不同,這個值的長度也會有區(qū)別;另外,推薦使用的散列算法也會隨著 “crv” 的變化有所不同:

crv x/y 的字節(jié)長度 散列算法
P-256 32 SHA-256
P-384 48 SHA-384
P-521 66 SHA-512

注意 P-521 對應(yīng)的是 SHA-512,不是 SHA-521 (不存在 521 位的散列算法 ??)

同樣,使用的曲線也決定了簽名的長度。在使用 ECDSA 對數(shù)據(jù)簽名時,通過橢圓曲線計算得到 r 和 s 兩個值。這兩個值的字節(jié)長度也應(yīng)該符合上表。

細心的同學(xué)可能會發(fā)現(xiàn)上面的 ECDSA 密鑰中 “y” 的值轉(zhuǎn)換為 hex 表示后是 33 個字節(jié):

00 90 67 b9 0e 04 88 c9 c2 a9 f3 0f 5a 26 6a 07 84 
1d 6c 07 74 13 ba 07 e7 45 69 b9 9d 4f d3 ce c6

我們知道,在密鑰中 “x” 和 “y” 都是大的整數(shù),但是在某些安全框架的實現(xiàn) (比如一些版本的 OpenSSL) 中,使用的會是普通的整數(shù)類型 (Int),而非無符號整數(shù) (UInt)。而如果一個數(shù)字首 bit 為 1 的話,在有符號的整數(shù)系統(tǒng)中會被認為是負數(shù)。在這里,”y” 原本第一個 byte 其實是 0x90 (bit 表示是 0b_1001_0000),首 bit 為 1,為了避免被誤認為負數(shù),有的實現(xiàn)會在前面添加 0x00。但是實際上把這樣一個 33 byte 的值作為 “y” 放在 JWK 中,是不符合標(biāo)準的。如果你遇到了這種情況,可以和負責(zé)服務(wù)器的小伙伴商量一下讓他先處理一下,給你正確的 key。當(dāng)然,你也可以自己在客戶端檢查和處理長度不符合預(yù)期的問題,以增強本地代碼的健壯性。

在這個例子中,如果服務(wù)器在生成 JWK 時就幫我們處理了 0x00 的問題的話,那么 “y” 的值應(yīng)該是

kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY

我們還會在后面看到更多的處理 0x00 添加或刪除的情況,對于首字節(jié)是 0x80 (0b_1000_0000) 或者以上的值,我們可能都需要考慮具體實現(xiàn)是接受 Int 還是 UInt 的問題。

JWA

JWA (JSON Web Algorithms) 定義的就是在 JWT 和 JWK 中涉及的算法了,它為每種算法定義了具體可能存在哪些參數(shù),和參數(shù)的表示規(guī)則。比如上面 JWK 例子中的 “n”,”e”,”x”,”y”,”crv” 都是在 JWA 標(biāo)準中定義的。它為如何使用 JWK,如何驗證 JWT 提供支持和指導(dǎo)。

除了 RSA 和 ECDSA 以外,JWA 里還定義了 AES 相關(guān)的加密算法,不過這部分內(nèi)容和 JWS 沒什么關(guān)系。另外,在簽名算法定義的后面,也附帶了如果使用簽名和如何進行驗證的簡單說明。我們在之后會對 JOSE 中的簽名和驗證過程進行更詳細的解釋。

小結(jié)

本文簡述了 JWT 和 JOSE 的相關(guān)基礎(chǔ)概念。您現(xiàn)在對 JWT 是什么,JOSE 有哪些組成部分,以及它們大概長什么樣有一定了解。

你可以訪問 JWT.io 來實際試試看創(chuàng)建和驗證一個 JWT 的過程。如果你想要更深入了解 JWT 的內(nèi)容和定義的話,JWT.io 還提供了免費的 JWT Handbook,里面有更詳細的介紹。我們在系列文章的最后還會對 JWT 的應(yīng)用場景,適用范圍和存在的風(fēng)險進行補充說明。

系列文章后面兩篇,會分別針對 JOSE 中的簽名和驗證過程以及作為 iOS 開發(fā)者如何使用 Security.frame 來處理 JOSE 相關(guān)的概念實踐進行更詳細的說明。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容