使用 Google Cloud Key Management Service 签署 JSON Web Tokens

Posted

技术标签:

【中文标题】使用 Google Cloud Key Management Service 签署 JSON Web Tokens【英文标题】:Using Google Cloud Key Management Service to sign JSON Web Tokens 【发布时间】:2019-06-12 12:20:14 【问题描述】:

编辑:我找到了答案。滚动到该问题的底部。

我正在使用 NodeJS 身份验证服务器,我想使用 google 签名签署 JSON Web 令牌 (JWT)。

我正在使用 Google Cloud Key Management Service (KMS),并创建了一个密钥环和一个非对称签名密钥。

这是我获取签名的代码:

signatureObject = await client.asymmetricSign( name, digest )

signature = signatureObject["0"].signature

我的 Google 签名对象如下所示:

我的问题:如何使用 Google 签名签署 JWT?

或者换句话说,如何将 Google 签名连接到 JWT 的 (header.payload)?

JWT 应该如下所示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. (GoogleSignature)

我正在使用的代码:

签名:

async function sign(message, name) 
  hashedMessage = crypto.createHash('sha256').update(message).digest('base64');
  digest =  'sha256': hashedMessage 

  signatureObject = await client.asymmetricSign( name, digest ).catch((err) => console.log(err))
  signature = signatureObject["0"].signature
  signJWT(signature)

创建 JWT:

function signJWT(signature) 
  header = 
    alg: "RS256",
    typ: "JWT"
  

  payload = 
    sub: "1234567890",
    name: "John Doe",
    iat: 1516239022
  

  JWT = base64url(JSON.stringify(header)) + "." +
        base64url(JSON.stringify(payload)) + "." + 
        ???signature??? ; // what goes here?

验证:

async function validateSignature(message, signature) 
  // Get public key
  publicKeyObject = await client.getPublicKey( name ).catch((err) => console.log(err))
  publicKey = publicKeyObject["0"].pem

  //Verify signature
  var verifier = crypto.createVerify('sha256');
  verifier.update(message)
  var ver = verifier.verify(publicKey, signature, 'base64')

  // Returns either true for a valid signature, or false for not valid.
  return ver


答案:

我可以像这样使用 toString() 方法:

signatureString = signature.toString('base64');

然后我可以使用

获得原始签名八位字节流
var buffer = Buffer.from(theString, 'base64');

【问题讨论】:

您使用什么库进行签名?您是否考虑过使用 JWT 库? @JohnHanley 我不使用库来签署 JWT,因为没有支持 Google 签名的 JWT 库。 您能解释一下“Google 签名”是什么意思吗? @JohnHanley 使用“Google 签名”是指 Google KMS 使用我的非对称签名密钥进行的签名,如上述问题中所述。 cloud.google.com/kms/docs/digital-signatures @JimvanLienden 感谢您的帮助,我在***.com/questions/55828435/…得到了答案 【参考方案1】:

您没有在问题中发布代码,所以我不知道您是如何构建 JWT 以进行签名的。

[将代码添加到问题后于 2019 年 1 月 18 日编辑]

您的代码正在反向执行签名。您正在创建一个签名并尝试将其附加到 JWT Headers + Payload。您希望改为使用 JWT Headers + Payload 并对数据进行签名,然后将签名附加到 JWT 以创建 Signed-JWT。

使用您的源代码的伪代码:

body_b64 = base64url(JSON.stringify(header)) + "." + base64url(JSON.stringify(payload))

signature = sign(body_b64, name);

jwt = body_b64 + '.' + base64url(signature)

注意:我不确定signatureObject["0"].signature 返回的签名是什么数据格式。在转换为 base64 之前,您可能必须先转换它。

[结束编辑]

示例数据:

JWT 标头:


    alg: RS256
    kid: 0123456789abcdef62afcbbf01234567890abcdef
    typ: JWT

JWT 负载:


  "azp": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
  "aud": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
  "sub": "123456789012345678901",
  "scope": "https://www.googleapis.com/auth/cloud-platform",
  "exp": "1547806224",
  "expires_in": "3596",
  "email": "someone@example.com.com",
  "email_verified": "true",
  "access_type": "offline"

算法:

SHA256withRSA

创建签名 JWT (JWS):

第 1 步: 获取 JWT 标头并转换为 Base-64。我们称之为 hdr_b64。

第 2 步: 获取 JWT Payload 并转换为 Base-64。我们称之为payload_b64。

第 3 步: 用一个点 . 连接编码的标头和有效负载:hdr_b64 + '.' +有效载荷_b64`。我们称之为 body_b64。

第 4 步: 通常,JWS 使用 SHA256withRSA 进行签名,通常称为“RS256”,使用私钥:

signature = sign(body_b64, RS256, private_key)

现在将签名转换为 Base-64。让我们称之为 signature_b64。

创建最终的 JWS:

jws = body_b64 + '.' + 签名_b64。

建议:

您想使用 KMS 创建签名 JWT 吗?我不会推荐这个。访问存储在 KMS 中的密钥是有成本的。签名 JWT 使用私钥签名并使用公钥进行验证。你将如何发布公钥?您在访问私钥和公钥时需要什么性能级别(您多久进行一次签名和验证)?

当您在 Google Cloud Platform 中创建服务帐号时,系统会为您创建一个密钥对。此密钥对的 ID 包含 Internet 上可用的公钥,并且私钥存在于服务帐户 Json 凭据文件中。我会使用服务帐户来创建签名 JWT,而不是 KMS 中的密钥对。

在 Python 中创建和签名的示例代码:

def create_signed_jwt(pkey, pkey_id, email, scope):
    '''
    Create a Signed JWT from a service account Json credentials file
    This Signed JWT will later be exchanged for an Access Token
   '''

    import jwt

    # Google Endpoint for creating OAuth 2.0 Access Tokens from Signed-JWT
    auth_url = "https://www.googleapis.com/oauth2/v4/token"

    issued = int(time.time())
    expires = issued + expires_in   # expires_in is in seconds

    # Note: this token expires and cannot be refreshed. The token must be recreated

    # JWT Headers
    headers = 
        "kid": pkey_id, # This is the service account private key ID
        "alg": "RS256",
        "typ": "JWT"    # Google uses SHA256withRSA
    

    # JWT Payload
    payload = 
            "iss": email,           # Issuer claim
            "sub": email,           # Issuer claim
            "aud": auth_url,        # Audience claim
            "iat": issued,          # Issued At claim
            "exp": expires,         # Expire time
            "scope": scope          # Permissions
    

    # Encode the headers and payload and sign creating a Signed JWT (JWS)
    sig = jwt.encode(payload, pkey, algorithm="RS256", headers=headers)

    return sig

【讨论】:

KMS 发布公钥。你的建议没有意义。您建议下载服务帐户的私钥,然后...然后呢?把它交给你的开发人员? KMS 正是为这种情况而设计的。 @NikolaMihajlović - 开发人员将如何访问 KMS?您的开发人员需要凭据才能访问 KMS,这意味着服务帐户或用户凭据。如果我的建议对您没有意义,请创建一个新问题并参考此答案。不,KMS 无法创建服务帐户凭据。 KMS 可以存储服务帐户 JSON 密钥,然后需要另一个凭据(服务帐户或用户)才能访问它们。 应使用服务帐户访问 KMS。开发人员不需要访问 KMS,只有生产需要。您使用服务帐户签署令牌的建议在开发中有意义,但在生产中没有意义。 @NikolaMihajlović - 哇,这是一个有趣的观点。发布另一个答案。 同意,这只是关于 KMS 签署 JWT 的可行性的评论。我相信是的——每 10k 个签名的成本是 0.03 美元。如果您预计一个月内有 100 万次登录,则需要花费 3 美元。但是通过使用 KMS,任何地方都不会暴露私钥用于生产,因此根本不需要为服务帐户生成私钥。所以我可以放心,任何员工在被解雇时都无法窃取私钥。

以上是关于使用 Google Cloud Key Management Service 签署 JSON Web Tokens的主要内容,如果未能解决你的问题,请参考以下文章

Google Cloud 获取 API Key 的日志使用信息

如何通过HTTP请求验证Google Cloud Vision

通过 API Key 访问 Google Cloud Function

使用 Google Cloud Load Balancer 设置 SSL 证书

保存Google Cloud Platform服务帐户凭据的位置

Google Cloud Pub/Sub,使用 HTTP PUSH 请求发布