Azure Function App无法正确使用Key Vault中.PEM密钥的问题排查
问题:Azure Function中使用Key Vault密钥签名JWT失败
我正尝试通过Azure Function App使用Python完成一项流程:利用已上传至Azure Key Vault作为Key的.PEM私钥,以及两个作为Secret的API密钥,声明若干claims后签名并返回JWT。
我在Function App中测试了170多种不同版本的实现,每次都能正常获取Secret,但Key部分始终失败。该密钥是通过Git BASH中的OpenSSL生成的,本地直接调用.PEM文件运行脚本时完全正常。
请问是否需要同时使用公钥?如果需要,该如何操作?
当前使用的Python代码
import azure.functions as func import logging import jwt import uuid from time import time from azure.identity import DefaultAzureCredential from azure.keyvault.secrets import SecretClient from azure.keyvault.keys import KeyClient from azure.keyvault.keys.crypto import CryptographyClient, SignatureAlgorithm async def main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') # Azure Key Vault settings key_vault_url = "<my key vault>.vault.azure.net/" credential = DefaultAzureCredential() # Initialize the KeyClient to fetch the key for signing key_client = KeyClient(vault_url=key_vault_url, credential=credential) key_name = "int-test1" # The name of the key in Azure Key Vault key = key_client.get_key(key_name) # Initialize the CryptographyClient for signing crypto_client = CryptographyClient(key, credential) # Initialize the SecretClient to fetch the 'sub' and 'iss' claims secret_client = SecretClient(vault_url=key_vault_url, credential=credential) sub_claim = secret_client.get_secret("int-sub").value iss_claim = secret_client.get_secret("int-iss").value # Your JWT claims claims = { "sub": sub_claim, "iss": iss_claim, "jti": str(uuid.uuid4()), "aud": "<api token endpoint>", "exp": int(time()) + 300 } additional_headers = {"kid": key_name, "alg": "RS512"} # Prepare the JWT to be signed encoded_jwt = jwt.encode( claims, "", # Empty string for key, as we will sign the JWT using the CryptographyClient algorithm="RS512", headers=additional_headers ) try: # Sign the JWT using the CryptographyClient result = crypto_client.sign(SignatureAlgorithm.rs512, encoded_jwt.encode('utf-8')) signature = result.signature # Append the signature to the JWT to complete the token signed_jwt = f"{encoded_jwt}.{signature.decode('utf-8')}" except Exception as e: logging.error(f"Error signing JWT: {e}") raise # Return the JWT in the HTTP response return func.HttpResponse(signed_jwt, status_code=200)
额外尝试与错误信息
我也曾尝试将格式正确(Unix格式,带有-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----头)的.PEM文本复制为Secret并在脚本中调用,但同样无法正常工作。
Function App已分配带有Key Vault Crypto Officer和Key Vault Secret Officer角色的托管标识,用于与Key Vault交互。
运行当前代码时,Application Insights中收到的错误:
Result: Failure Exception: ValueError: ('Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters).', [<OpenSSLError(code=75497580, lib=9, reason=108, reason_text=no start line)>]) Stack: File "/azure-functions-host/workers/python/3.11/LINUX/X64/azure_functions_worker/dispatcher.py", line 491, in _handle__invocation_request call_result = await self._run_async_func( ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/azure-functions-host/workers/python/3.11/LINUX/X64/azure_functions_worker/dispatcher.py", line 774, in _run_async_func return await ExtensionManager.get_async_invocation_wrapper( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/azure-functions-host/workers/python/3.11/LINUX/X64/azure_functions_worker/extension.py", line 147, in get_async_invocation_wrapper result = await function(**args) ^^^^^^^^^^^^^^^^^^^^^^ File "/home/site/wwwroot/Http_createSignedJWT/__init__.py", line 43, in main encoded_jwt = jwt.encode( ^^^^^^^^^^^ File "/home/site/wwwroot/.python_packages/lib/site-packages/jwt/api_jwt.py", line 73, in encode return api_jws.encode( ^^^^^^^^^^^^^^^ File "/home/site/wwwroot/.python_packages/lib/site-packages/jwt/api_jws.py", line 160, in encode key = alg_obj.prepare_key(key) ^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/site/wwwroot/.python_packages/lib/site-packages/jwt/algorithms.py", line 353, in prepare_key return cast(RSAPublicKey, load_pem_public_key(key_bytes)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/site/wwwroot/.python_packages/lib/site-packages/cryptography/hazmat/primitives/serialization/base.py", line 35, in load_pem_public_key return ossl.load_pem_public_key(data) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/site/wwwroot/.python_packages/lib/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 794, in load_pem_public_key self._handle_key_loading_error() File "/home/site/wwwroot/.python_packages/lib/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 984, in _handle_key_loading_error raise ValueError(
解决方案
核心问题说明
- 不需要单独使用公钥:签名JWT只需要私钥(或Key Vault中的托管私钥),公钥仅用于验证JWT有效性,签名环节完全不需要。
- 代码错误根源:调用
jwt.encode时传入空字符串作为密钥,PyJWT库会尝试解析这个空字符串为密钥,直接触发格式错误。另外,Key Vault的托管密钥对象无法直接作为PyJWT的密钥参数使用,必须通过CryptographyClient完成签名流程。
修正后的代码实现
手动构造JWT的前两部分(header + payload),用CryptographyClient签名后拼接完整JWT:
import azure.functions as func import logging import uuid from time import time from azure.identity import DefaultAzureCredential from azure.keyvault.secrets import SecretClient from azure.keyvault.keys import KeyClient from azure.keyvault.keys.crypto import CryptographyClient, SignatureAlgorithm import base64 import json async def main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') # Azure Key Vault settings key_vault_url = "<my key vault>.vault.azure.net/" credential = DefaultAzureCredential() # Initialize clients key_client = KeyClient(vault_url=key_vault_url, credential=credential) secret_client = SecretClient(vault_url=key_vault_url, credential=credential) # Fetch key and secrets key_name = "int-test1" key = key_client.get_key(key_name) crypto_client = CryptographyClient(key, credential) sub_claim = secret_client.get_secret("int-sub").value iss_claim = secret_client.get_secret("int-iss").value # Build claims and headers claims = { "sub": sub_claim, "iss": iss_claim, "jti": str(uuid.uuid4()), "aud": "<api token endpoint>", "exp": int(time()) + 300 } headers = {"kid": key_name, "alg": "RS512"} # 手动编码JWT的前两部分(header + payload) def base64url_encode(data): json_data = json.dumps(data).encode('utf-8') return base64.urlsafe_b64encode(json_data).decode('utf-8').rstrip('=') encoded_header = base64url_encode(headers) encoded_payload = base64url_encode(claims) jwt_to_sign = f"{encoded_header}.{encoded_payload}" try: # 使用Key Vault的CryptographyClient签名 result = crypto_client.sign(SignatureAlgorithm.rs512, jwt_to_sign.encode('utf-8')) # 对签名结果做Base64URL编码 encoded_signature = base64.urlsafe_b64encode(result.signature).decode('utf-8').rstrip('=') signed_jwt = f"{jwt_to_sign}.{encoded_signature}" except Exception as e: logging.error(f"Error signing JWT: {e}") return func.HttpResponse(f"Error signing JWT: {str(e)}", status_code=500) return func.HttpResponse(signed_jwt, status_code=200)
额外注意事项
- 确保Key Vault中的密钥是RSA类型,创建时选择RSA或RSA-HSM,且密钥操作包含
sign权限。 - 若将PEM私钥存为Secret,需确保内容无多余换行或空格,读取时直接使用
secret.value即可。 - 托管标识权限:除已分配的角色,确认Key Vault访问策略允许该标识对目标密钥执行
sign操作。
内容的提问来源于stack exchange,提问作者LostMary
相关产品推荐
相关产品推荐

