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 %}