Python / jwk & kwe === ###### tags: `Python` ###### tags: `Python`, `pyjwt`, `JWT`, `Json Web Token`, `jwk`, `kwe` <br> [TOC] <br> ## 術語 - JWE 的全名為 JSON Web Encryption - JWK 的全名為 JSON Web Key。 --- ## 安裝 ```bash pip install jwcrypto ``` --- ## 討論 ### JWK 與 JWE 在 Python 中的使用說明 - ### gpt-o3-mini-high (2025/04/02) 本文整理了如何在 Python 中使用 JWK 與 JWE 來加密與解密敏感資料(例如 GitHub PAT token)的相關資訊,包含安裝方式、加密/解密範例以及參數說明。 ### 安裝 首先,請使用 pip 安裝 `jwcrypto` 套件: ```bash pip install jwcrypto ``` ### 加密與解密範例 下面提供一個範例,示範如何使用 Python 的 `jwcrypto` 套件來進行 JWE 加密與解密敏感 token 的操作: ```python from jwcrypto import jwk, jwe from jwcrypto.common import json_encode # 產生一個對稱金鑰 (使用 256 位元金鑰適用於 A256GCM) key = jwk.JWK.generate(kty='oct', size=256) # 要加密的敏感 token(例如 GitHub PAT token) sensitive_token = "github_pat_11ADRCFMY0qfyQc8iyTtcS_ZA******" # 定義 JWE 的保護性標頭 (protected header) # "alg": "dir" 表示直接使用共享的對稱金鑰作為內容加密金鑰 (CEK) # "enc": "A256GCM" 指定加密演算法 protected_header = { "alg": "dir", "enc": "A256GCM" } # 加密 token:建立一個 JWE 物件,並以金鑰加入接收者 jwetoken = jwe.JWE(sensitive_token.encode('utf-8'), protected=protected_header) jwetoken.add_recipient(key) # 使用 compact 格式序列化 JWE token(所有資訊合併成單一字串,各部分以句點分隔) encrypted_token = jwetoken.serialize(compact=True) print("加密後的 JWE token:") print(encrypted_token) # ----------------------------- # 解密範例:使用相同的金鑰來解密 token jwe_received = jwe.JWE() jwe_received.deserialize(encrypted_token, key) decrypted_token = jwe_received.payload.decode('utf-8') print("\n解密後的 token:") print(decrypted_token) ``` ### 參數說明 #### 1. Key Type (`kty`) - **`kty`** 代表 **Key Type**(金鑰類型),是 JWK(JSON Web Key)規範中的一個參數。 - 常見的 key type 包含: - **"oct"**: - **全名:** Octet Sequence - **用途:** 用於對稱金鑰(共享密鑰),例如在直接加密 (direct encryption) 模式中使用。 - **"RSA"**: - **用途:** 表示 RSA 金鑰,常用於非對稱加密演算法。 - **"EC"**: - **用途:** 表示橢圓曲線金鑰,用於非對稱加密。 - **"OKP"**: - **用途:** 表示 Octet Key Pair,適用於某些特殊的非對稱金鑰(例如 Edwards 曲線)。 當設定 `kty="oct"` 時,代表使用對稱金鑰,這通常用於直接加密的情境。 #### 2. 演算法 (`alg`) - **`alg`** 參數在 JWE 標頭中用來指定管理金鑰的演算法。 - **"dir" (Direct Encryption)**: - **用途:** 表示直接使用共享的對稱金鑰作為內容加密金鑰 (Content Encryption Key, CEK),可簡化金鑰包裝的流程,降低計算複雜度。 - 其他常見的 `alg` 值包括: - **RSA-OAEP**、**RSA-OAEP-256**、**RSA1_5**:用於 RSA 金鑰包裝。 - **ECDH-ES**、**ECDH-ES+A128KW**、**ECDH-ES+A256KW**:用於橢圓曲線 Diffie-Hellman (ECDH) 金鑰交換。 - **A128KW**、**A256KW**:用於對稱金鑰包裝。 - **PBES2-HS256+A128KW**:基於密碼的金鑰包裝演算法。 使用 **"dir"** 的好處在於當雙方已共享同一個對稱金鑰時,可以直接使用該金鑰進行加密,而不需要額外的金鑰交換或包裝步驟。 ### 結論 直接在 JWT 中儲存敏感的 PAT token 風險極高,因為 JWT(僅簽章而未加密)僅經 Base64 編碼,很容易被解碼。若需要將敏感資訊包含在 token 中,建議使用 JWE 來加密該資訊,並使用 JWK 管理金鑰。藉由正確設定 `kty` 與 `alg` 參數,可以實現安全且有效的加密/解密機制。 此文件可作為記錄,方便後續查閱 JWK 與 JWE 的相關使用方式與參數解釋。 --- ### 如果使用非對稱式加密,有哪幾行 code 要修改? - ### gpt-o3-mini-high (2025/04/02) > 非對稱式加密修改說明 以下整理了使用非對稱加密時需修改的程式碼部分,包含金鑰生成與保護性標頭的修改,其他部分則維持不變。 ### 1. 金鑰生成 原本使用對稱金鑰的程式碼: ```python key = jwk.JWK.generate(kty='oct', size=256) ``` 改為使用非對稱金鑰(例如 RSA 金鑰): ```python key = jwk.JWK.generate(kty='RSA', size=2048) ``` 此修改會產生一組 RSA 金鑰對,包含公鑰與私鑰。 ### 2. 保護性標頭 (Protected Header) 原本使用直接加密模式的標頭: ```python protected_header = { "alg": "dir", "enc": "A256GCM" } ``` 改為使用非對稱加密,例如 RSA-OAEP: ```python protected_header = { "alg": "RSA-OAEP", "enc": "A256GCM" } ``` 在這裡,`"alg": "RSA-OAEP"` 表示使用 RSA-OAEP 演算法來進行金鑰加密。 ### 3. 加解密流程 加密與解密的其他流程保持不變: - `jwetoken.add_recipient(key)` 會自動使用金鑰中的公鑰進行加密。 - 解密時使用包含私鑰的同一個 `key` 物件即可完成解密。 這樣修改後,即可利用非對稱加密方式來保護敏感資料。 --- ### 問題是當 client 收到 jwe token 後,怎麼知道要用哪種 alg, key type 解密? - ### gpt-o3-mini-high (2025/04/02) > JWE Token 解密流程與金鑰選擇 當 client 收到 JWE token 後,需要知道用哪種 `alg`(金鑰管理演算法)及金鑰類型(`kty`)來解密。以下整理了解決此問題的處理方式: ### 處理步驟 1. **解析 Protected Header** - JWE token 的 compact serialization 格式由五個部分組成,格式為: `<protected header>.<encrypted key>.<iv>.<ciphertext>.<tag>` - 第一部分即為經 Base64URL 編碼的 protected header。client 需先解碼此部分以取得 JSON 格式的 header,內含以下資訊: - `"alg"`:金鑰管理演算法(例如 `"RSA-OAEP"`, `"dir"` 等) - `"enc"`:內容加密演算法(例如 `"A256GCM"`) - `"kid"`(可選):金鑰識別碼,若系統中有多組金鑰,可以依此選擇正確的金鑰 2. **根據 Header 決定解密方式** - 根據 `"alg"` 的值,client 會知道如何處理加密用的金鑰: - 如果 `"alg"` 為 `"RSA-OAEP"`,client 必須使用 RSA 私鑰來解密 CEK(內容加密金鑰)。 - 如果 `"alg"` 為 `"dir"`,則表示 CEK 就是直接共享的對稱金鑰,client 直接使用該金鑰來解密。 - 同時,`"enc"` 告訴 client 應用哪個演算法來解密實際的 ciphertext(例如 `"A256GCM"`)。 3. **選取對應金鑰** - 如果 header 中包含 `"kid"`,client 可以根據該金鑰識別碼從自身儲存的金鑰庫中選取正確的金鑰。 - 否則,client 必須根據預先協商好的方式(例如內建金鑰或從安全後端獲取)來決定使用哪個金鑰。 4. **使用加解密庫處理** - 實務上,許多 JWE 加解密庫(例如 Python 的 `jwcrypto`)能自動解析 header 並根據 header 資訊選擇正確的解密流程。 - 只要提供正確的金鑰物件,庫就能處理後續的解密細節。 ### 手動解析 Header 範例 以下範例示範如何手動解析 JWE token 的 protected header,並取得 `alg`、`enc`、`kid` 等資訊: ```python= import base64 import json def parse_protected_header(jwe_token): # JWE token 格式:<protected header>.<encrypted key>.<iv>.<ciphertext>.<tag> parts = jwe_token.split('.') if len(parts) < 5: raise ValueError("JWE token 格式不正確") protected_header_encoded = parts[0] # 補足 Base64URL padding rem = len(protected_header_encoded) % 4 if rem > 0: protected_header_encoded += '=' * (4 - rem) # 解析 Base64URL 編碼後取得 header JSON header_json = base64.urlsafe_b64decode(protected_header_encoded) header = json.loads(header_json) return header # 假設有一個 JWE token jwe_token = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.XXXX.YYYY.ZZZZ.AAAA" header = parse_protected_header(jwe_token) print("解析出的 Header:", header) ``` ### 總結 - **解析 JWE token 的 protected header:** 將 token 的第一部分進行 Base64URL 解碼,取得包含 `"alg"`、`"enc"`、`"kid"` 等資訊的 JSON 物件。 - **根據 Header 選擇解密方式:** 利用 header 中的 `"alg"` 決定使用何種金鑰(例如 RSA 私鑰或對稱金鑰)解密加密用的內容金鑰(CEK),再使用 `"enc"` 指定的演算法解密內容。 - **選取適當金鑰:** 可依據 `"kid"` 從金鑰庫中選擇正確的金鑰,或使用預先協定的金鑰。 這些步驟使得 client 能夠正確解析與處理解密資訊,並依據 token 本身所提供的資訊來選擇正確的解密演算法與金鑰類型。 --- {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}