概述
事情的緣由很簡(jiǎn)單,工作上在做 LINE SDK 的開(kāi)發(fā)搀愧,在拿 token 的時(shí)候有一步額外的驗(yàn)證:從 Server 會(huì)發(fā)回一個(gè) JWT (JSON Web Token)救拉,客戶端需要對(duì)這個(gè) JWT 進(jìn)行簽名和內(nèi)容的驗(yàn)證追驴,以確保信息沒(méi)有被人篡改焚志。
推薦閱讀:iOS開(kāi)發(fā)——2019 最新 BAT面試題合集(持續(xù)更新中)
Server 在簽名中使用的算法類型會(huì)在 JWT 中寫(xiě)明嘱腥,驗(yàn)證簽名所需要的公鑰 ID 也可以在 JWT 中找到录别。這個(gè)公鑰是以 JWK (JSON Web Key) 的形式公開(kāi)朽色,客戶端拿到 JWK 后即可在本地對(duì)收到的 JWT 進(jìn)行驗(yàn)證。用一張圖的話组题,大概是這樣:
作為一個(gè)開(kāi)發(fā)者葫男,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要,這是一個(gè)我的iOS交流群:638302184崔列,不管你是小白還是大牛歡迎入駐 梢褐,分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù)盈咳, 大家一起交流學(xué)習(xí)成長(zhǎng)耿眉!
群內(nèi)提供數(shù)據(jù)結(jié)構(gòu)與算法、底層進(jìn)階鱼响、swift鸣剪、逆向、整合面試題等免費(fèi)資料
附上一份收集的各大廠面試題(附答案) ! 群文件直接獲取
各大廠面試題
步驟
如果你現(xiàn)在對(duì)下面說(shuō)步驟不理解的話 (這挺正常的热押,畢竟這篇文章都還沒(méi)正式開(kāi)始 ??)西傀,可以先跳過(guò)這部分,等我們有一些基礎(chǔ)知識(shí)以后再回頭看看就好桶癣。如果你很清楚這些步驟的話拥褂,那真是好棒棒,你應(yīng)該能無(wú)壓力閱讀該系列剩余部分內(nèi)容了牙寞。
LINE SDK 里使用 JWT 驗(yàn)證用戶的邏輯如下:
- 向登錄服務(wù)器請(qǐng)求 access token饺鹃,登錄服務(wù)器返回 access token,同時(shí)返回一個(gè) JWT间雀。
- JWT 中包含應(yīng)該使用的算法和密鑰的 ID悔详。通過(guò)密鑰 ID,去找預(yù)先定義好的 Host 拿到 JWK 形式的該 ID 的密鑰惹挟。
- 將 1 的 JWT 和 2 的密鑰轉(zhuǎn)換為 Security.framework 接受的形式茄螃,進(jìn)行簽名驗(yàn)證。
這個(gè)過(guò)程想法很簡(jiǎn)單连锯,但會(huì)涉及到一系列比較基礎(chǔ)的密碼學(xué)知識(shí)和標(biāo)準(zhǔn)的閱讀归苍,難度不大,但是枯燥乏味运怖。另外拼弃,由于 iOS 并沒(méi)有直接將 JWK 轉(zhuǎn)換為 native 的 SecKey
的方式,自己也沒(méi)有任何密碼學(xué)的基礎(chǔ)摇展,所以在處理密鑰轉(zhuǎn)換上也花了一些工夫吻氧。為了后來(lái)者能比較順利地處理相關(guān)內(nèi)容 (包括 JWT 解析驗(yàn)證,JWK 特別是 RSA 和 EC 算法的密鑰轉(zhuǎn)換等)咏连,也為了過(guò)一段時(shí)間自己還能有地方回憶這些內(nèi)容盯孙,所以將一些關(guān)鍵的理論知識(shí)和步驟記錄下來(lái)。
系列文章的內(nèi)容
整個(gè)系列會(huì)比較長(zhǎng)祟滴,為了閱讀壓力小一些镀梭,我會(huì)分成三個(gè)部分:
- 基礎(chǔ) - 什么是 JWT 以及 JOSE (本文)
- 理論 - JOSE 中的簽名和驗(yàn)證流程
- 實(shí)踐 - 如何使用 Security.framework 處理 JOSE 中的驗(yàn)證
全部讀完的話應(yīng)該能對(duì)網(wǎng)絡(luò)相關(guān)的密碼學(xué)有一個(gè)膚淺的了解,特別是常見(jiàn)的簽名算法和密鑰種類踱启,編碼規(guī)則报账,怎么處理拿到的密鑰研底,怎么做簽名驗(yàn)證等等。如果你在工作中有相關(guān)需求透罢,但不知道如何下手的話榜晦,可以仔細(xì)閱讀整個(gè)系列,并參看開(kāi)源的 LINE SDK Swift 的相關(guān)實(shí)現(xiàn)羽圃,甚至直接 copy 部分代碼 (如果可以的話乾胶,也請(qǐng)順便點(diǎn)一下 star)。如果你只是感興趣想要簡(jiǎn)單了解的話朽寞,可以只看 JOSE 和 JWT 的基礎(chǔ)概念和理論流程部分的內(nèi)容识窿,作為知識(shí)面的擴(kuò)展,等以后有實(shí)際需要了再回頭看實(shí)踐部分的內(nèi)容脑融。
在文章結(jié)尾喻频,我還列舉了一些常見(jiàn)的問(wèn)題,包括筆者自己在學(xué)習(xí)時(shí)的思考和最后的選擇肘迎。如果您有什么見(jiàn)解甥温,也歡迎發(fā)表在評(píng)論里,我會(huì)繼續(xù)總結(jié)和補(bǔ)充妓布。
聲明:筆者自身對(duì)密碼學(xué)也是初學(xué)姻蚓,而本文介紹的密碼學(xué)知識(shí)也都是自己的一些理解,同時(shí)盡量不涉及過(guò)于原理性的內(nèi)容匣沼,一切以普通工程師實(shí)用為目標(biāo)原則狰挡。其中可以想象在很多地方會(huì)有理解的錯(cuò)誤,還請(qǐng)多包涵释涛。如您發(fā)現(xiàn)問(wèn)題加叁,也往不吝賜教指正,感激不盡枢贿。
JWT 以及 JOSE
什么是 JWT
估計(jì)大部分 Swift 的開(kāi)發(fā)者對(duì) JWT 會(huì)比較陌生殉农,所以先簡(jiǎn)單介紹一下它是什么刀脏,以及可以用來(lái)做什么局荚。JWT (JSON Web Token) 是一個(gè)編碼后的字符串,比如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
一個(gè)典型的 JWT 由三部分組成愈污,通過(guò)點(diǎn)號(hào) .
進(jìn)行分割耀态。每個(gè)部分都是經(jīng)過(guò) Base64Url 編碼的字符串。第一部分 (Header) 和第二部分 (Payload) 在解碼后應(yīng)該是有效的 JSON暂雹,最后一部分 (簽名) 是通過(guò)一定算法作用在前兩部分上所得到的簽名數(shù)據(jù)首装。接收方可以通過(guò)這個(gè)簽名數(shù)據(jù)來(lái)驗(yàn)證 token 的 Header 及 Payload 部分的數(shù)據(jù)是否可信。
為了視覺(jué)上看起來(lái)輕松一些杭跪,在上面的 JWT 例子中每個(gè)點(diǎn)號(hào)后加入了換行仙逻。實(shí)際的 JWT 中不應(yīng)該存在任何換行的情況驰吓。
嚴(yán)格來(lái)說(shuō),JWT 有兩種實(shí)現(xiàn)系奉,分別是 JWS (JSON Web Signature) 和 JWE (JSON Web Encryption)檬贰。由于 JWS 的應(yīng)用更為廣泛,所以一般說(shuō)起 JWT 大家默認(rèn)會(huì)認(rèn)為是 JWS缺亮。JWS 的 Payload 是 Base64Url 的明文翁涤,而 JWE 的數(shù)據(jù)則是經(jīng)過(guò)加密的。相對(duì)地萌踱,相比于 JWS 的三個(gè)部分葵礼,JWE 有五個(gè)部分組成。本文中提到 JWT 的時(shí)候并鸵,所指的都是用于簽名認(rèn)證的 JWS 實(shí)現(xiàn)鸳粉。
關(guān)于 Base64Url 編碼和處理,在本文后面部分會(huì)再提到能真。
Header
Header 包含了 JWT 的一些元信息赁严。我們可以嘗試將上面的 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
這個(gè) Header 解碼,得到:
{"alg":"HS256","typ":"JWT"}
關(guān)于在數(shù)據(jù)的不同格式之間互相轉(zhuǎn)換 (明文粉铐,Base64疼约,Hex Bytes 等),我推薦這個(gè)非常不錯(cuò)的 web app蝙泼。
在 JWT Header 中程剥,”alg” 是必須指定的值,它表示這個(gè) JWT 的簽名方式汤踏。上例中 JWT 使用的是 HS256
進(jìn)行簽名织鲸,也就是使用 SHA-256 作為摘要算法的 HMAC。常見(jiàn)的選擇還有 RS256
溪胶,ES256
等等搂擦。總結(jié)一下:
-
HSXXX
或者說(shuō) HMAC:一種對(duì)稱算法 (symmetric algorithm)哗脖,也就是加密密鑰和解密密鑰是同一個(gè)瀑踢。類似于我們創(chuàng)建 zip 文件時(shí)設(shè)定的密碼,驗(yàn)證方需要知道和簽名方同樣的密鑰才避,才能得到正確的驗(yàn)證結(jié)果橱夭。 -
RSXXX
:使用 RSA 進(jìn)行簽名。RSA 是一種基于極大整數(shù)做因數(shù)分解的非對(duì)稱算法 (asymmetric algorithm)桑逝。相比于對(duì)稱算法的 HMAC 只有一對(duì)密鑰棘劣,RSA 使用成對(duì)的公鑰 (public key) 和私鑰 (private key) 來(lái)進(jìn)行簽名和驗(yàn)證。大多數(shù) HTTPS 中驗(yàn)證證書(shū)和加密傳輸數(shù)據(jù)使用的是 RSA 算法楞遏。 -
ESXXX
:使用 橢圓曲線數(shù)字簽名算法 (ECDSA) 進(jìn)行簽名茬暇。和 RSA 類似首昔,它也是一種非對(duì)稱算法。不過(guò)它是基于橢圓曲線的糙俗。ECDSA 最著名的使用場(chǎng)景是比特幣的數(shù)字簽名沙廉。 -
PSXXX
: 和RSXXX
類似使用 RSA 算法,但是使用 PSS 作為 padding 進(jìn)行簽名臼节。作為對(duì)比撬陵,RSXXX
中使用的是 PKCS1-v1_5 的 padding。
如果你對(duì)這些介紹一頭霧水网缝,也不必?fù)?dān)心巨税。關(guān)于各個(gè)算法的一些更細(xì)節(jié)的內(nèi)容,會(huì)在后面實(shí)踐部分再詳細(xì)說(shuō)明》垭現(xiàn)在草添,你只需要知道 Header 中 “alg” key 為我們指明了簽名所使用的簽名算法和散列算法。我們之后需要依據(jù)這里的指示來(lái)驗(yàn)證簽名扼仲。
除了 “alg” 外远寸,在 Header 中發(fā)行方還可以放入其他有幫助的內(nèi)容。JWS 的標(biāo)準(zhǔn)定義了一些預(yù)留的 Header key屠凶。在本文中驰后,除了 “alg” 以外,我們還會(huì)用到 “kid”矗愧,它用來(lái)表示在驗(yàn)證時(shí)所需要的灶芝,從 JWK Host 中獲取的公鑰的 key ID。現(xiàn)在我們先集中于 JWT 的構(gòu)造唉韭,之后在 JWK 的部分我們?cè)賹?duì)它的使用進(jìn)行介紹夜涕。
Payload
Payload 是想要進(jìn)行交換的實(shí)際有意義的數(shù)據(jù)部分。上面例子解碼后的 Payload 部分是:
{"sub":"1234567890","name":"John Doe","iat":1516239022}
和 Header 類似属愤,payload 中也有一些預(yù)先定義和保留的 key女器,我們稱它們?yōu)?claim。常見(jiàn)的預(yù)定義的 key 包括有:
- “iss” (Issuer):JWT 的簽發(fā)者名字住诸,一般是公司名或者項(xiàng)目名
- “sub” (Subject):JWT 的主題
- “exp” (Expiration Time):過(guò)期時(shí)間驾胆,在這個(gè)時(shí)間之后應(yīng)當(dāng)視為無(wú)效
- “iat” (Issued At):發(fā)行時(shí)間,在這個(gè)時(shí)間之前應(yīng)當(dāng)視為無(wú)效
當(dāng)然只壳,你還可以在 Payload 里添加任何你想要傳遞的信息俏拱。
我們?cè)隍?yàn)證簽名后暑塑,就可以檢查 Payload 里的各個(gè)條目是否有效:比如發(fā)行者名字是否正確吼句,這個(gè) JWT 是否在有效期內(nèi)等等。因?yàn)橐坏┖灻麢z查通過(guò)事格,我們就可以保證 Payload 的東西是可靠的惕艳,所以這很適合用來(lái)進(jìn)行消息驗(yàn)證搞隐。
注意,在 JWS 里远搪,Header 和 Payload 是 Base64Url 編碼的明文劣纲,所以你不應(yīng)該用 JWS 來(lái)傳輸任何敏感信息。如果你需要加密谁鳍,應(yīng)該選擇 JWE癞季。
Signature
一個(gè) JWT 的最后一部分是簽名。首先對(duì) Header 和 Payload 的原文進(jìn)行 Base64Url 編碼倘潜,然后用 .
將它們連接起來(lái)绷柒,最后扔給簽名散列算法進(jìn)行簽名,把簽名得到的數(shù)據(jù)再 Base64Url 編碼涮因,就能得到這個(gè)簽名了废睦。寫(xiě)成偽代碼的話,是這樣的:
// 比如使用 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 其實(shí)是 JOSE 這個(gè)更大的概念中的一個(gè)組成部分购披。JOSE (Javascript Object Signing and Encryption) 定義了一系列標(biāo)準(zhǔn),用來(lái)規(guī)范在網(wǎng)絡(luò)傳輸中使用 JSON 的方式肩榕。我們?cè)谏厦娼榻B過(guò)了JWS 和 JWE今瀑,在這一系列概念中還有兩個(gè)比較重要,而且相互關(guān)聯(lián)的概念:JWK 和 JWA点把。它們一起組成了整個(gè) JOSE 體系橘荠。
JWK
不管簽名驗(yàn)證還是加密解密,都離不開(kāi)密鑰郎逃。JWK (JSON Web Key) 解決的是如何使用 JSON 來(lái)表示一個(gè)密鑰這件事哥童。
RSA 的公鑰由模數(shù) (modulus) 和指數(shù) (exponent) 組成,一個(gè)典型的代表 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)信息朵你。
如果你接觸過(guò)幾個(gè) RSA 密鑰,可能會(huì)發(fā)現(xiàn) “e” 的值基本都是 “AQAB”揣非。這并不是巧合抡医,這是數(shù)字 65537 (0x 01 00 01) 的 Base64Url 表示。選擇 AQAB 作為指數(shù)已經(jīng)是業(yè)界標(biāo)準(zhǔn)早敬,它同時(shí)兼顧了運(yùn)算效率和安全性能忌傻。同樣大脉,這部分內(nèi)容也超出了本文范疇。
類似地水孩,一個(gè)典型的 ECDSA 的 JWK 內(nèi)容如下:
{
"kty":"EC",
"alg":"ES256",
"use":"sig",
"kid":"3829b108279b26bcfcc8971e348d116",
"crv":"P-256",
"x":"EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84",
"y":"AJBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G"
}
決定一個(gè) ECDSA 公鑰的參數(shù)有三個(gè): “crv” 定義使用的密鑰所使用的加密曲線镰矿,一般可能值為 “P-256”,”P(pán)-384” 和 “P-521”俘种〕颖辏”x” 和 “y” 是選取的橢圓曲線點(diǎn)的座標(biāo)值,根據(jù)曲線 “crv” 的不同宙刘,這個(gè)值的長(zhǎng)度也會(huì)有區(qū)別抛杨;另外,推薦使用的散列算法也會(huì)隨著 “crv” 的變化有所不同:
crv | x/y 的字節(jié)長(zhǎng)度 | 散列算法 |
---|---|---|
P-256 | 32 | SHA-256 |
P-384 | 48 | SHA-384 |
P-521 | 66 | SHA-512 |
注意
P-521
對(duì)應(yīng)的是SHA-512
荐类,不是SHA-521
(不存在 521 位的散列算法 ??)
同樣怖现,使用的曲線也決定了簽名的長(zhǎng)度。在使用 ECDSA 對(duì)數(shù)據(jù)簽名時(shí)玉罐,通過(guò)橢圓曲線計(jì)算得到 r 和 s 兩個(gè)值屈嗤。這兩個(gè)值的字節(jié)長(zhǎng)度也應(yīng)該符合上表。
細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn)上面的 ECDSA 密鑰中 “y” 的值轉(zhuǎn)換為 hex 表示后是 33 個(gè)字節(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ù)饶号,但是在某些安全框架的實(shí)現(xiàn) (比如一些版本的 OpenSSL) 中,使用的會(huì)是普通的整數(shù)類型 (Int)季蚂,而非無(wú)符號(hào)整數(shù) (UInt)茫船。而如果一個(gè)數(shù)字首 bit 為 1 的話,在有符號(hào)的整數(shù)系統(tǒng)中會(huì)被認(rèn)為是負(fù)數(shù)扭屁。在這里算谈,”y” 原本第一個(gè) byte 其實(shí)是
0x90
(bit 表示是 0b_1001_0000),首 bit 為 1料滥,為了避免被誤認(rèn)為負(fù)數(shù)然眼,有的實(shí)現(xiàn)會(huì)在前面添加0x00
。但是實(shí)際上把這樣一個(gè) 33 byte 的值作為 “y” 放在 JWK 中葵腹,是不符合標(biāo)準(zhǔn)的高每。如果你遇到了這種情況,可以和負(fù)責(zé)服務(wù)器的小伙伴商量一下讓他先處理一下践宴,給你正確的 key鲸匿。當(dāng)然,你也可以自己在客戶端檢查和處理長(zhǎng)度不符合預(yù)期的問(wèn)題阻肩,以增強(qiáng)本地代碼的健壯性带欢。在這個(gè)例子中,如果服務(wù)器在生成 JWK 時(shí)就幫我們處理了
0x00
的問(wèn)題的話,那么 “y” 的值應(yīng)該是
kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY
我們還會(huì)在后面看到更多的處理
0x00
添加或刪除的情況洪囤,對(duì)于首字節(jié)是0x80
(0b_1000_0000
) 或者以上的值,我們可能都需要考慮具體實(shí)現(xiàn)是接受 Int 還是 UInt 的問(wèn)題撕氧。
JWA
JWA (JSON Web Algorithms) 定義的就是在 JWT 和 JWK 中涉及的算法了瘤缩,它為每種算法定義了具體可能存在哪些參數(shù),和參數(shù)的表示規(guī)則伦泥。比如上面 JWK 例子中的 “n”剥啤,”e”,”x”不脯,”y”府怯,”crv” 都是在 JWA 標(biāo)準(zhǔn)中定義的。它為如何使用 JWK防楷,如何驗(yàn)證 JWT 提供支持和指導(dǎo)牺丙。
除了 RSA 和 ECDSA 以外,JWA 里還定義了 AES 相關(guān)的加密算法复局,不過(guò)這部分內(nèi)容和 JWS 沒(méi)什么關(guān)系冲簿。另外,在簽名算法定義的后面亿昏,也附帶了如果使用簽名和如何進(jìn)行驗(yàn)證的簡(jiǎn)單說(shuō)明峦剔。我們?cè)谥髸?huì)對(duì) JOSE 中的簽名和驗(yàn)證過(guò)程進(jìn)行更詳細(xì)的解釋。
小結(jié)
本文簡(jiǎn)述了 JWT 和 JOSE 的相關(guān)基礎(chǔ)概念角钩。您現(xiàn)在對(duì) JWT 是什么吝沫,JOSE 有哪些組成部分,以及它們大概長(zhǎng)什么樣有一定了解递礼。
你可以訪問(wèn) JWT.io 來(lái)實(shí)際試試看創(chuàng)建和驗(yàn)證一個(gè) JWT 的過(guò)程惨险。如果你想要更深入了解 JWT 的內(nèi)容和定義的話,JWT.io 還提供了免費(fèi)的 JWT Handbook脊髓,里面有更詳細(xì)的介紹平道。我們?cè)谙盗形恼碌淖詈筮€會(huì)對(duì) JWT 的應(yīng)用場(chǎng)景,適用范圍和存在的風(fēng)險(xiǎn)進(jìn)行補(bǔ)充說(shuō)明供炼。
系列文章后面兩篇一屋,會(huì)分別針對(duì) JOSE 中的簽名和驗(yàn)證過(guò)程以及作為 iOS 開(kāi)發(fā)者如何使用 Security.frame 來(lái)處理 JOSE 相關(guān)的概念實(shí)踐進(jìn)行更詳細(xì)的說(shuō)明。