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

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ā)者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:638302184,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗,討論技術, 大家一起交流學習成長!

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

步驟

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

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

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

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

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

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

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

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

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

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

JWT 以及 JOSE

什么是 JWT

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

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

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

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

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

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

Header

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

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

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

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

  • HSXXX 或者說 HMAC:一種對稱算法 (symmetric algorithm),也就是加密密鑰和解密密鑰是同一個。類似于我們創(chuàng)建 zip 文件時設定的密碼,驗證方需要知道和簽名方同樣的密鑰,才能得到正確的驗證結果。
  • 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。

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

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

Payload

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

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

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

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

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

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

注意,在 JWS 里,Header 和 Payload 是 Base64Url 編碼的明文,所以你不應該用 JWS 來傳輸任何敏感信息。如果你需要加密,應該選擇 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) 定義了一系列標準,用來規(guī)范在網(wǎng)絡傳輸中使用 JSON 的方式。我們在上面介紹過了JWS 和 JWE,在這一系列概念中還有兩個比較重要,而且相互關聯(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 構成了密鑰最關鍵的數(shù)據(jù)部分,這兩部分都是 Base64Url 編碼的大數(shù)字。

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

如果你接觸過幾個 RSA 密鑰,可能會發(fā)現(xiàn) “e” 的值基本都是 “AQAB”。這并不是巧合,這是數(shù)字 65537 (0x 01 00 01) 的 Base64Url 表示。選擇 AQAB 作為指數(shù)已經(jīng)是業(yè)界標準,它同時兼顧了運算效率和安全性能。同樣,這部分內(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”。”x” 和 “y” 是選取的橢圓曲線點的座標值,根據(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 對應的是 SHA-512,不是 SHA-521 (不存在 521 位的散列算法 ??)

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

細心的同學可能會發(fā)現(xiàn)上面的 ECDSA 密鑰中 “y” 的值轉換為 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 中,是不符合標準的。如果你遇到了這種情況,可以和負責服務器的小伙伴商量一下讓他先處理一下,給你正確的 key。當然,你也可以自己在客戶端檢查和處理長度不符合預期的問題,以增強本地代碼的健壯性。

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

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 標準中定義的。它為如何使用 JWK,如何驗證 JWT 提供支持和指導。

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

小結

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

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

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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374