You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(

解决方案

核心问题说明

  1. 不需要单独使用公钥:签名JWT只需要私钥(或Key Vault中的托管私钥),公钥仅用于验证JWT有效性,签名环节完全不需要。
  2. 代码错误根源:调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 20:30:55