概述
這是關于 JOSE 和密碼學的三篇系列文章中的最后一篇,你可以在下面的鏈接中找到其他部分:
- 基礎 - 什么是 JWT 以及 JOSE
- 理論 - JOSE 中的簽名和驗證流程
- 實踐 - 如何使用 Security.framework 處理 JOSE 中的驗證 (本文)
推薦閱讀:iOS開發——2019 最新 BAT面試題合集(持續更新中)
這一篇中,我們會在 JOSE 基礎篇和理論篇的知識架構上,使用 iOS (或者說 Cocoa) 的相關框架來完成對 JWT 的解析,并利用 JWK 對它的簽名進行驗證。在最后,我會給出一些我自己在實現和學習這些內容時的思考,并把一些相關工具和標準列舉一下。
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:638302184,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!
群內提供數據結構與算法、底層進階、swift、逆向、整合面試題等免費資料
附上一份收集的各大廠面試題(附答案) ! 群文件直接獲取
各大廠面試題
解碼 JWT
JWT,或者更精確一點,JWS 中的 Header 和 Payload 都是 Base64Url 編碼的。為了獲取原文內容,先需要對 Header 和 Payload 解碼。
Base64Url
Base64 相信大家都已經很熟悉了,隨著網絡普及,這套編碼有一個很大的“缺點”,就是使用了 +
,/
和 =
。這些字符在 URL 里是很不友好的,在作為傳輸時需要額外做 escaping。Base64Url 就是針對這個問題的改進,具體來說就是:
- 將
+
替換為-
; - 將
/
替換為_
; - 將末尾的
=
干掉。
相關代碼的話非常簡單,為 Data
和 String
分別添加 extension 來相互轉換就好:
extension Data {
// Encode `self` with URL escaping considered.
var base64URLEncoded: String {
let base64Encoded = base64EncodedString()
return base64Encoded
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
}
extension String {
// Returns the data of `self` (which is a base64 string), with URL related characters decoded.
var base64URLDecoded: Data? {
let paddingLength = 4 - count % 4
// Filling = for %4 padding.
let padding = (paddingLength < 4) ? String(repeating: "=", count: paddingLength) : ""
let base64EncodedString = self
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
+ padding
return Data(base64Encoded: base64EncodedString)
}
}
結合使用 JSONDecoder
和 Base64Url 來處理 JWT
因為 JWT 的 Header 和 Payload 部分實際上是有效的 JSON,為了簡單,我們可以利用 Swift 的 Codable 來解析 JWT。為了簡化處理,可以封裝一個針對以 Base64Url 表示的 JSON 的 decoder:
class Base64URLJSONDecoder: JSONDecoder {
override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
guard let string = String(data: data, encoding: .ascii) else {
// 錯誤處理
}
return try decode(type, from: string)
}
func decode<T>(_ type: T.Type, from string: String) throws -> T where T : Decodable {
guard let decodedData = string.base64URLDecoded else {
// 錯誤處理
}
return try super.decode(type, from: decodedData)
}
}
Base64URLJSONDecoder
將 Base64Url 的轉換封裝到解碼過程中,這樣一來,我們只需要獲取 JWT,將它用 .
分割開,然后使用 Base64URLJSONDecoder
就能把 Header 和 Payload 輕易轉換了,比如:
struct Header: Codable {
let algorithm: String
let tokenType: String?
let keyID: String?
enum CodingKeys: String, CodingKey {
case algorithm = "alg"
case tokenType = "typ"
case keyID = "kid"
}
}
let jwtRaw = "eyJhbGciOiJSUzI1NiI..." // JWT 字符串,后面部分省略了
let rawComponents = text.components(separatedBy: ".")
let decoder = Base64JSONDecoder()
let header = try decoder.decode(Header.self, from: rawComponents[0])
guard let keyID = header.keyID else { /* 驗證失敗 */ }
在 Header 中,我們應該可以找到指定了驗證簽名所需要使用的公鑰的 keyID
。如果沒有的話,驗證失敗,登錄過程終止。
對于簽名,我們將解碼后的原始的 Data
保存下來,稍后使用。同樣地,我們最好也保存一下 {Header}.{Payload}
的部分,它在驗證中也會被使用到:
let signature = rawComponents[2].base64URLDecoded!
let plainText = "\(rawComponents[0]).\(rawComponents[1])"
這里的代碼基本都沒有考慮錯誤處理,大部分是直接讓程序崩潰。實際的產品中驗證簽名過程中的錯誤應該被恰當處理,而不是粗暴掛掉。
在 Security.framework 中處理簽名
我們已經準備好簽名的數據和原文了,萬事俱備,只欠密鑰。
處理密鑰
通過 keyID
,在預先設定的 JWT Host 中我們應該可以找到以 JWK 形式表示的密鑰。我們計劃使用 Security.framework 來處理密鑰和簽名驗證,首先要做的就是遵守框架和 JWA 的規范,通過 JWK 的密鑰生成 Security 框架喜歡的 SecKey
值。
在其他大部分情況下,我們可能會從一個證書 (certificate,不管是從網絡下載的 PEM 還是存儲在本地的證書文件) 里獲取公鑰。像是處理 HTTPS challenge 或者 SSL Pinning 的時候,大部分情況下我們拿到的是完整的證書數據,通過 SecCertificateCreateWithData
使用 DER 編碼的數據創建證書并獲取公鑰:
guard let cert = SecCertificateCreateWithData(nil, data as CFData) else {
// 錯誤處理
return
}
let policy = SecPolicyCreateBasicX509()
var trust: SecTrust? = nil
SecTrustCreateWithCertificates(cert, policy, &trust)
guard let t = trust, let key: SecKey = SecTrustCopyPublicKey(t) else {
// 錯誤處理
return
}
print(key)
但是,在 JWK 的場合,我們是沒有 X.509 證書的。JWK 直接將密鑰類型和參數編碼在 JSON 中,我們當然可以按照 DER 編碼規則將這些信息編碼回一個符合 X.509 要求的證書,然后使用上面的方法再從中獲取證書。不過這顯然是畫蛇添足,我們完全可以直接通過這些參數,使用特定格式的數據來直接生成 SecKey
。
有可能有同學會迷惑于“公鑰”和“證書”這兩個概念。一個證書,除了包含有公鑰以外,還包含有像是證書發行者,證書目的,以及其他一些元數據的信息。因此,我們可以從一個證書中,提取它所存儲的公鑰。
另外,證書本身一般會由另外一個私鑰進行簽名,并由頒發機構或者受信任的機構進行驗證保證其真實性。
使用 SecKeyCreateWithData
就可以直接通過公鑰參數來生成了:
func SecKeyCreateWithData(_ keyData: CFData,
_ attributes: CFDictionary,
_ error: UnsafeMutablePointer<Unmanaged<CFError>?>?) -> SecKey?
第二個參數 attributes
需要的是密鑰種類 (RSA 還是 EC),密鑰類型 (公鑰還是私鑰),密鑰尺寸 (數據 bit 數) 等信息,比較簡單。
關于所需要的數據格式,根據密鑰種類不同,而有所區別。在這個風馬牛不相及的頁面 以及 SecKey 源碼 的注釋中有所提及:
The method returns data in the PKCS #1 format for an RSA key. For an elliptic curve public key, the format follows the ANSI X9.63 standard using a byte string of 04 || X || Y. … All of these representations use constant size integers, including leading zeros as needed.
The requested data format depend on the type of key (kSecAttrKeyType) being created:
kSecAttrKeyTypeRSA PKCS#1 format, public key can be also in x509 public key format kSecAttrKeyTypeECSECPrimeRandom ANSI X9.63 format (04 || X || Y [ || K])
JWA - RSA
簡單說,RSA 的公鑰需要遵守 PKCS#1,使用 X.509 編碼即可。所以對于 RSA 的 JWK 里的 n
和 e
,我們用 DER 按照 X.509 編碼成序列后,就可以扔給 Security 框架了:
extension JWK {
struct RSA {
let modulus: String
let exponent: String
}
}
let jwk: JWK.RSA = ...
guard let n = jwk.modulus.base64URLDecoded else { ... }
guard let e = jwk.exponent.base64URLDecoded else { ... }
var modulusBytes = [UInt8](n)
if let firstByte = modulusBytes.first, firstByte >= 0x80 {
modulusBytes.insert(0x00, at: 0)
}
let exponentBytes = [UInt8](e)
let modulusEncoded = modulusBytes.encode(as: .integer)
let exponentEncoded = exponentBytes.encode(as: .integer)
let sequenceEncoded = (modulusEncoded + exponentEncoded).encode(as: .sequence)
let data = Data(bytes: sequenceEncoded)
關于 DER 編碼部分的代碼,可以在這里找到。對于
modulusBytes
,首位大于等于0x80
時需要追加0x00
的原因,也已經在第一篇中提及。如果你不知道我在說什么,建議回頭仔細再看一下前兩篇的內容。
使用上面的 data
就可以獲取 RSA 的公鑰了:
let sizeInBits = data.count * MemoryLayout<UInt8>.size
let attributes: [CFString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits: NSNumber(value: sizeInBits)
]
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(data as CFData, attributes as CFDictionary, &error) else {
// 錯誤處理
}
print(key)
// 一切正常的話,打印類似這樣:
// <SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4,
// block size: 1024 bits, exponent: {hex: 10001, decimal: 65537},
// modulus: DD95AB518D18E8828DD6A238061C51D82EE81D516018F624...,
// addr: 0x6000027ffb00>
JWA - ECSDA
按照說明,對于 EC 公鑰,期望的數據是符合 X9.63 中未壓縮的橢圓曲線點座標:04 || X || Y
。不過,雖然在文檔說明里提及:
All of these representations use constant size integers, including leading zeros as needed.
但事實是 SecKeyCreateWithData
并不喜歡在首位追加 0x00
的做法。這里的 X
和 Y
必須是滿足橢圓曲線對應要求的密鑰位數的整數值,如果在首位大于等于 0x80
的值前面追加 0x00
,反而會導致無法創建 SecKey
。所以,在組織數據時,不僅不需要添加 0x00
,我們反而最好檢查一下獲取的 JWK,如果首位有不必要的 0x00
的話,應該將其去除:
extension JWK {
struct RSA {
let x: String
let y: String
}
}
let jwk: JWK.RSA = ...
guard let decodedXData = jwk.x.base64URLDecoded else { ... }
guard let decodedYData = jwk.y.base64URLDecoded else { ... }
let xBytes: [UInt8]
if decodedXData.count == curve.coordinateOctetLength {
xBytes = [UInt8](decodedXData)
} else {
xBytes = [UInt8](decodedXData).dropFirst { $0 == 0x00 }
}
let yBytes: [UInt8]
if decodedYData.count == curve.coordinateOctetLength {
yBytes = [UInt8](decodedYData)
} else {
yBytes = [UInt8](decodedYData).dropFirst { $0 == 0x00 }
}
let uncompressedIndicator: [UInt8] = [0x04]
let data = Data(bytes: uncompressedIndicator + xBytes + yBytes)
創建公鑰時和 RSA 類似:
let sizeInBits = data.count * MemoryLayout<UInt8>.size
let attributes: [CFString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits: NSNumber(value: sizeInBits)
]
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(data as CFData, attributes as CFDictionary, &error) else {
// 錯誤處理
}
print(key)
// 一切正常的話,打印類似這樣:
// <SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3,
// key type: ECPublicKey, version: 4, block size: 256 bits,
// y: 3D4F8B27B29E5C77FCF877367245F3D75C2FBA806C54A0A0C05807E1B536E68A,
// x: FFB00CF903B79BB0F6C049208A59C448049BE0A2A1AF4692C486085CBD9057EF,
// addr: 0x7fcafd80ced0>
驗證簽名
Security 框架中為使用公鑰進行簽名驗證準備了一個方法:SecKeyVerifySignature
:
func SecKeyVerifySignature(_ key: SecKey,
_ algorithm: SecKeyAlgorithm,
_ signedData: CFData,
_ signature: CFData,
_ error: UnsafeMutablePointer<Unmanaged<CFError>?>?) -> Bool
key
我們已經拿到了,signedData
就是之前我們準備的 {Header}.{Payload}
的字符串的數據表示 (也就是 plainText.data(using: .ascii)
。注意,這里的 plainText
不是一個 Base64Url 字符串,JWS 簽名所針對的就是這個拼湊后的字符串的散列值)。我們需要為不同的簽名算法指定合適的 SecKeyAlgorithm
,通過訪問 SecKeyAlgorithm
的靜態成員,就可以獲取 Security 框架預先定義的算法了。比如常用的:
let ecdsa256 = SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
let rsa256 = SecKeyAlgorithm.rsaSignatureDigestPKCS1v15SHA256
你可以在 Apple 的文檔里找到所有支持的算法的定義,但是不幸的是,這些算法都只有名字,沒有具體說明,也沒有使用范例。想要具體知道某個算法的用法,可能需要在源碼級別去參考注釋。為了方便,對于簽名驗證相關的一些常用算法,我列了一個表說明對應關系:
算法 | 輸入數據 (signedData) | 簽名 (signature) | 對應 JWT 算法 |
---|---|---|---|
rsaSignatureDigestPKCS1v15SHA{x} | 原數據的 SHA-x 摘要 | PKCS#1 v1.5 padding 的簽名 | RS{x} |
rsaSignatureMessagePKCS1v15SHA{x} | 原數據本身,框架負責計算 SHA-x 摘要 | PKCS#1 v1.5 padding 的簽名 | RS{x} |
rsaSignatureDigestPSSSHA{x} | 原數據的 SHA-x 摘要 | 使用 PSS 的 PKCS#1 v2.1 簽名 | PS{x} |
rsaSignatureMessagePSSSHA{x} | 原數據本身,框架負責計算 SHA-x 摘要 | 使用 PSS 的 PKCS#1 v2.1 簽名 | PS{x} |
ecdsaSignatureDigestX962SHA{x} | 原數據的 SHA-x 摘要 | DER x9.62 編碼的 r 和 s | ES{x} |
ecdsaSignatureMessageX962SHA{x} | 原數據本身,框架負責計算 SHA-x 摘要 | DER x9.62 編碼的 r 和 s | ES{x} |
不難看出,這些簽名算法基本就是 {算法類型} + {數據處理方式} + {簽名格式}
的組合。另外還有一些更為泛用的簽名算法,像是 .ecdsaSignatureRFC4754
或者 .rsaSignatureRaw
,你需要按照源碼注釋給入合適的輸入,不過一般來說還是直接使用預設的散列的 __Message__SHA___
這類算法最為方便。
SecKeyAlgorithm
中除了簽名算法,也包括了使用 RSA 和 EC 進行加密的相關算法。整體上和簽名算法的命名方式類似,有興趣和需要相關內容的同學可以自行研究。
對于 JWT 來說,RS 算法的簽名已經是 PKCS#1 v1.5 padding 的了,所以直接將 signedData
和 signature
配合使用 rsaSignatureMessagePKCS1v15SHA{x}
就可以完成驗證。
var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(
key,
.rsaSignatureMessagePKCS1v15SHA256,
signedData as CFData,
signature as CFData, &error)
對于 ES 的 JWT 來說,事情要麻煩一些。我們收到的 JWT 里的簽名只是 {r, s} 的簡單連接,所以需要預先進行處理。按照 X9.62 中對 signature
的編碼定義:
ECDSA-Sig-Value ::= SEQUENCE {
r INTEGER,
s INTEGER }
因此,在調用 SecKeyVerifySignature
之前,先處理簽名:
let count = signature.count
guard count != 0 && count % 2 == 0 else {
// 錯誤,簽名應該是兩個等長的整數
}
var rBytes = [UInt8](signature[..<(count / 2)])
var sBytes = [UInt8](signature[(count / 2)...])
// 處理首位,我們已經做過很多次了。
if rBytes.first! >= UInt8(0x80) {
rBytes.insert(0x00, at: 0)
}
if sBytes.first! >= UInt8(0x80) {
sBytes.insert(0x00, at: 0)
}
// 完成簽名的 DER 編碼
let processedSignature = Data(bytes:
(rBytes.encode(as: .integer) + sBytes.encode(as: .integer))
.encode(as: .sequence))
var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(
key,
.ecdsaSignatureMessageX962SHA256,
signedData as CFData,
processedSignature as CFData, &error)
上面 RSA 和 ECDSA 的驗證,都假設了使用 SHA-256 作為散列算法。如果你采用的是其他的散列算法,記得替換。
驗證 Payload 內容
簽名正確完成驗證之后,我們就可以對 JWT Payload 里的內容進行驗證了:包括但不限于 “iss”,”sub”,”exp”,”iat” 這些保留值是否正確。當簽名和內容都驗證無誤后,就可以安心使用這個 JWT 了。
一些問題
至此,我們從最初的 JWT 定義開始,引伸出 JWA,JWK 等一系列 JOSE 概念。然后我們研究了互聯網安全領域的通用編碼方式和幾種最常見的密鑰的構成。最后,我們使用這些知識在 Security 框架的幫助下,完成了 JWT 的簽名驗證的整個流程。
事后看上去沒有太大難度,但是由于涉及到的名詞概念很多,相關標準錯綜復雜,因此初上手想要把全盤都弄明白,還是會有一定困難。希望這系列文章能夠幫助你在起步階段就建立相對清晰的知識體系,這樣在閱讀其他的相關信息時,可以對新的知識進行更好的分類整理。
最后,是一些我自己在學習和實踐中的考慮。在此一并列出,以供參考。如果您有什么指正和補充,也歡迎留言評論。
為什么不用已有的相關開源框架
現存的和這個主題相關的 iOS 或者 Swift 框架有一些,比如 JOSESwift,JSONWebToken.swift,Swift-JWT,vaper/jwt 等等。來回比較考察,它們現在 (2018 年 12 月) 或多或少存在下面的不足:
- 沒有一個從 JWK 開始到 JWT 的完整方案。JWT 相關的框架基本都是從本地證書獲取公鑰進行驗證,而我需要從 JWK 獲取證書
- 支持 JWK 的框架只實現了部分算法,比如只有 RSA,沒有 ECDSA 支持。
- 一些框架依賴關系太復雜,而且大部分實現是面向 Swift Server Side,而非 iOS 的。
在 LINE SDK 中,我們需要,且只需要在 iOS 上利用 Security 框架完成驗證。同時 Server 可能會變更配置,所以我們需要同時支持 RSA 和 ECDSA (當前默認使用 ECDSA)。另外,本身作為一個提供給第三方開發者的 SDK,我們不允許引入不可靠的復雜依賴關系 (最理想的情況是零依賴,也就是 LINE SDK 的現狀)。基于這些原因,我沒有使用現有的開源代碼,而是自己從頭進行實現。
為什么不把你做的相關內容整理開源
在 LINE SDK 中的方案是不完備的,它是 JOSE 中滿足我們的 JWT 解析和驗證需求的最小子集,因此沒有很高的泛用性,不適合作為單獨項目開源。不過因為 LINE SDK 整個項目是開源的,JOSE 部分的代碼其實也都是公開且相對獨立的。如果你感興趣,可以在 LINE SDK 的 Crypto 文件夾下找到所有相關代碼。
為什么要用非對稱算法,各算法之間有什么優劣
不少 JWT 使用 HS 的算法 (HMAC)。和 RSA 或 ECDSA 不同,HMAC 是對稱加密算法。對稱算法加密和解密比較簡單,因為密鑰相同,所以比較適合用在 Server to Server 這種雙方可信的場合。如果在客戶端上使用對稱算法,那就需要將這個密鑰存放在客戶端上,這顯然是不可接受的。對于 Client - Server 的通訊,非對稱算法應該是毋庸置疑的選擇。
相比與 RSA,ECDSA 可以使用更短的密鑰實現和數倍長于自己的 RSA 相同的安全性能。
For example, at a security level of 80 bits (meaning an attacker requires a maximum of about 2^80 operations to find the private key) the size of an ECDSA public key would be 160 bits, whereas the size of a DSA public key is at least 1024 bits.
由于 ECDSA 是專用的 DSA 算法,只能用于簽名,而不能用作加密和密鑰交換,所以它比 RSA 要快很多。另外,更小的密鑰也帶來了更小的計算量。這些特性對于減少 Server 負擔非常重要。關于 ECDSA 的優勢和它相對于 RSA 的對比,可以參考 Cloudflare 的這篇文章。
簽名的安全性
JWT 簽名的偽造一直是一個困擾人的問題。因為 JWT 的 Header 和 Payload 內容一旦確定的話,它的簽名也就確定了 (雖然 ECDSA 會產生隨機數使簽名每次都不同,但是這些簽名都可以通過驗證)。這帶來一個問題,攻擊者可以通過截取以前的有效的 JWT,然后把它作為新的響應發給用戶。這類 JWT 依然可以正確通過簽名驗證。
因此,我們必須每次生成不同的 JWT,來防止這種替換攻擊。最簡單的方式就是在內存中存儲隨機值,發送 JWT 請求時附帶這個隨機值,然后 Server 將這個隨機值嵌入在返回的 JWT 的 Payload 中。Client 收到后,再與內存中保存的值進行比對。這樣保證了每次返回的 JWT 都不相同,讓簽名驗證更加安全。
OpenSSL 版本的問題
macOS 上自帶的 OpenSSL 版本一般比較舊,而大部分 Linux 系統的 OpenSSL 更新一些。不同版本的 OpenSSL (或者其他的常用安全框架) 實現細節上會有差異,比如有些版本會在負數首位補 0x00
等。在測試時,最好讓 Server 的小伙伴確認一下使用的 OpenSSL 版本,這樣能在驗證和使用密鑰上避免一些不必要的麻煩。(請不要問我細節!都是淚)
JWT 可以用來做什么,應該用來做什么
JWT 最常見的使用場景有兩個:
- 授權:用戶登錄后,在后續的請求中帶上一個有效的 JWT,其中包含該用戶可以訪問的路徑或權限等。服務器驗證 JWT 有效性后對訪問進行授權。相比于傳統像是 OAuth 的 token 來說,服務器并不需要存儲這些 token,可以實現無狀態的授權,因此它的開銷較小,也更容易實現和理解。另外,由于 JWT 不需要依賴 Cookie 的特性,跨站或者跨服務依然可能使用,這讓單點登錄非常簡單。
- 信息交換:LINE SDK 中對用戶信息進行簽名和驗證,就屬于信息交換的范疇。依賴 JWT 的簽名特性,接收方可以確保 JWT 中的內容沒有被篡改,是一種安全的信息交換方式。
最近有非常多的關于反對使用 JWT 進行授權的聲音,比如這篇文章和這篇文章。JWT 作為授權 token 來使用,最大的問題在于無法過期或者作廢,另外,一些嚴格遵守標準的實現,反而可能引入嚴重的安全問題。
不過對于第二種用法,也就是信息交換來說,JWT 所提供的便捷和安全性是無人質疑的。
我也想讀讀看相關標準
如你所愿,我整理了一下涉及到的標準。祝武運昌隆!
關于編碼和算法
- X.680 - ASN.1 的標準和基本標注方式:ASN.1 是這套方法的名字,而對應的標準號是 X.680。
- X.690 - DER 編碼規則:也包括了其他的,比如 BER 和 CER 的編碼規則。
- RFC 3279 - 關于 X.509 如何編碼密鑰和簽名:在 X.509 應用層面上密鑰以及簽名的構成。
- SEC 2 - 關于橢圓曲線算法參數:ECDSA 的各種 OIDs 定義和橢圓曲線 G 值的表示方式。
- X9.62 - 橢圓曲線的應用和相關編碼方式:描述了 ECDSA 算法和密鑰的表示方式。它在 SEC 2 的基礎上添加了關于曲線點 (也就是實際的密鑰本身) 的定義。
- RFC 5480 - 橢圓曲線公鑰的信息:EC 公鑰的定義,表示方式,使用曲線和對應的密鑰位數及散列算法的關系。
- RFC 8017 - RSA 算法相關的標準:包括像是 RSA key 的 ASN.1 定義,所注冊的 OIDs 。
關于 JOSE
- RFC 7515 - JSON Web Signature (JWS)
- RFC 7516 - JSON Web Encryption (JWE)
- RFC 7517 - JSON Web Key (JWK)
- RFC 7518 - JSON Web Algorithms (JWA)
- RFC 7519 - JSON Web Token (JWT)
- RFC 7165 - JOSE 的使用例子和要求
雜項
- RFC 4648 - 關于 Base64Url 的編碼規則:JOSE 中的數據都是使用 Base64Url 進行編碼的。
- OpenID Connect Discovery:OpenID 相關的 profile 取得方式,以及其中鍵值對的定義。關于 Discovery Document 的更好的說明,可以參考 Google 的這個指南。
驗證和速查工具匯總
- ASN.1 解碼器:將一段 DER 數據解碼為可讀的 ASN.1 表示。
- 數據格式轉換:將數據在 Base64、文本和字節表示之間進行任意轉換。
- ASN.1 中的 OIDs 轉換:幫助解碼和編碼 OBJECT IDENTIFIER 值。
- JWK 和 PEM 相互轉換:將 JWK 或者 PEM 的密鑰相互轉換的工具。
你的這篇文章或者代碼好像有問題!
我是初學者,文章中的紕漏請不吝賜教指出!
關于代碼方面的不足,LINE SDK 歡迎各種 PR。但是如果您發現的問題涉及安全漏洞,或者會導致比較嚴重后果的話,還請先不要公開公布。如果能按照這里的說明給我們發送郵件聯系的話,實在感激不盡。