【问题标题】:JWT Private / Public Key ConfusionJWT 私钥/公钥混淆
【发布时间】:2020-06-17 15:40:48
【问题描述】:

在对从客户端(在本例中为 React Native 应用程序)发送到我的服务器的数据有效负载进行签名时,我试图了解使用带有私钥/公钥 (RS512) 的 JSON Web 令牌的逻辑。

我认为私钥/公钥的全部意义在于将 private 密钥保密(在我的服务器上)并将 public 密钥交给成功登录的人进入应用程序。

我认为,对于我的服务器的每个 API 请求,经过身份验证的应用用户将使用 public 密钥创建 JWT(在客户端),服务器将使用 private 密钥,用于验证 API 请求的签名/有效负载。

似乎我把它弄反了,因为在我阅读的任何地方,您都应该使用 private 密钥签署 JWT ——但这与我对谁拥有密钥的理解背道而驰。

根据创建密钥的方式,一些私钥可以有一个本应保密的密码!因此,如果私钥和秘密是公开的(在客户端代码中),那么安全性如何?

加密从何而来?如果应用程序的用户在 API 中发送敏感数据,我是否应该在客户端加密有效负载并使用 JWT 对其进行签名,然后让服务器验证 JWT 签名并解密数据?

本教程很有帮助https://medium.com/@siddharthac6/json-web-token-jwt-the-right-way-of-implementing-with-node-js-65b8915d550e,但似乎倒退了。

任何解释肯定会有所帮助,因为所有在线教程都没有意义。

谢谢。

【问题讨论】:

  • JWT 通常不用于加密/解密有效负载。它们被郑重地用于认证和授权。您链接到的网站正确描述了该过程。用户在授权服务器上登录并接收到使用授权服务器的 private 密钥签名的 JWT 令牌。然后,用户将这个 JWT 附加到每个发送的请求中。然后,授权服务器通过 public 密钥验证其签名来验证 JWT 是否由该授权服务器颁发,以及它是否被某人篡改。
  • 我认为您对两个不同的术语感到困惑;签名和加密。数据使用公钥加密并使用私钥解密。另一方面,您使用您的私钥对您的数据进行签名并使用您的公钥对其进行验证。
  • 好的,但是为什么jwt.io 除了在 JWT 令牌(也就是签名)的第三部分中的公共密钥之外还包含私钥?
  • 同意,在我的第二个示例中,不应使用 JWT 私钥。这种解决方案是不可接受的。我试图创建一种通过每个 API 调用刷新密钥的方法,但这并不理想。我将删除这部分答案。
  • @Lucian jwt.io 是一个检查、验证和创建令牌的工具。我猜想 除了 JWT 第三部分中的公共密钥之外,还包含私钥,您指的是右栏中的输入字段。您可以在此处插入私钥以签署令牌。当左侧有现有令牌时,只需在右侧插入公钥即可验证令牌,但如果添加私钥,则使用该私钥对令牌进行签名。

标签: jwt


【解决方案1】:

使用 JWT,密钥材料的拥有和使用与发生密码操作的任何其他上下文完全相同。

签名:

  • 私钥归发行者所有。
  • 公钥可以与需要验证签名的各方共享。

对于加密:

  • 私钥归收件人所有
  • 可以将公钥共享给任何想要将敏感数据发送给收件人的方。

JWT 很少使用加密。大多数时候 HTTPS 层就足够了,令牌本身只包含一些不敏感的信息(数据时间、ID ......)。 令牌的颁发者(身份验证服务器)具有用于生成签名令牌 (JWS) 的私钥。这些令牌被发送到客户端(API 服务器、Web/本机应用程序...)。 客户端可以使用公钥验证令牌。

如果您有不应向第三方披露的敏感数据(电话号码、个人地址...),则强烈建议使用加密令牌 (JWE)。 在这种情况下,每个客户端(即令牌的接收者)都应该有一个私钥,并且令牌的发行者必须使用每个接收者的公钥加密令牌。这意味着令牌的颁发者可以为给定的客户端选择适当的密钥。

【讨论】:

    【解决方案2】:

    JWE 在 React Native 和 Node 后端的解决方案

    最难的部分是找到一种适用于 RN 和 Node 的方法,因为我不能只在 RN 中使用任何 Node 库。

    我正在通过 HTTPS 传输所有 API 调用。

    创建一个 JWE 以同时加密令牌和有效负载。

    反应原生应用代码

    import {JWK, JWE} from 'react-native-jose';
    
    /**
     * Create JWE encrypted web token
     *
     * @param payload
     * @returns {Promise<string>}
     */
    async function createJWEToken(payload = {}) {
    
      // This is the Public Key created at login. It is stored in the App.  
      // I'm hard-coding the key here just for convenience but normally it 
      // would be kept in a Keychain, a flat file on the mobile device, or 
      // in React state to refer to before making the API call.
    
      const publicKey = `-----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApl9FLYsLnP10T98mT70e
    qdAeHA8qDU5rmY8YFFlcOcy2q1dijpgfop8WyHu1ULufJJXm0PV20/J9BD2HqTAK
    DZ+/qTv4glDJjyIlo/PIhehQJqSrdIim4fjuwkax9FOCuFQ9nesv32hZ6rbFjETe
    QSxUPjNzsYGOuULWSR3cI8FuV9InlSZQ7q6dEunLPRf/rZujxiAxGzY8zrMehjM5
    LNdl7qDEOsc109Yy3HBbOwUdJyyTg/GRPwklLogw9kkldz5+wMvwOT38IlkO2rCr
    qJpqqt1KmxdOQNbeGwNzZiGiuYIdiQWjilq5a5K9e75z+Uivx+G3LfTxSAnebPlE
    LwIDAQAB
    -----END PUBLIC KEY-----`;
    
      try {
    
        const makeKey = pem => JWK.asKey(pem, 'pem');
        const key = await makeKey(publicKey);
    
        // This returns the encrypted JWE string
    
        return await JWE.createEncrypt({
          zip:    true,
          format: 'compact',
        }, key).update(JSON.stringify(payload)).final();
    
      } catch (err) {
        throw new Error(err.message);
      }
    
    }
    

    节点后端

    const keygen = require('generate-rsa-keypair');
    const {JWK, JWE} = require('node-jose');
    
    /**
     * Create private/public keys for JWE encrypt/decrypt
     *
     * @returns {Promise<object>}
     *
     */
    async function createKeys() {
    
      // When user logs in, create a standard RSA key-pair.
      // The public key is returned to the user when he logs in.
      // The private key stays on the server to decrypt the message with each API call.
      // Keys are destroyed when the user logs out.
    
      const keys = keygen();
      const publicKey = keys.public;
      const privateKey = keys.private;
    
      return {
        publicKey,
        privateKey
      };
    
    }
    
    /**
     * Decrypt JWE Web Token
     *
     * @param input
     * @returns {Promise<object>}
     */
    async function decryptJWEToken(input) {
    
      // This is the Private Key kept on the server.  This was
      // the key created along with the Public Key after login.
      // The public key was sent to the App and the Private Key
      // stays on the server.
      // I'm hard-coding the key here just for convenience but 
      // normally it would be held in a database to 
      // refer during the API call.
    
      const privateKey = `-----BEGIN RSA PRIVATE KEY-----
    MIIEpAIBAAKCAQEApl9FLYsLnP10T98mT70eqdAeHA8qDU5rmY8YFFlcOcy2q1di
    jpgfop8WyHu1ULufJJXm0PV20/J9BD2HqTAKDZ+/qTv4glDJjyIlo/PIhehQJqSr
    dIim4fjuwkax9FOCuFQ9nesv32hZ6rbFjETeQSxUPjNzsYGOuULWSR3cI8FuV9In
    lSZQ7q6dEunLPRf/rZujxiAxGzY8zrMehjM5LNdl7qDEOsc109Yy3HBbOwUdJyyT
    g/GRPwklLogw9kkldz5+wMvwOT38IlkO2rCrqJpqqt1KmxdOQNbeGwNzZiGiuYId
    iQWjilq5a5K9e75z+Uivx+G3LfTxSAnebPlELwIDAQABAoIBAQCmJ2FkMYhAmhOO
    LRMK8ZntB876QN7DeT0WmAT5VaE4jE0mY1gnhp+Zfn53bKzQ2v/9vsNMjsjEtVjL
    YlPY0QRJRPBZqG3wX5RcoUKsMaxip3dckHo3IL5h0YVJeucAVmKnimIbE6W03Xdn
    ZG94PdMljYr4r9PsQ7JxLOHrFaoj/c7Dc7rd6M5cNtmcozqZsz6zVtqO1PGaNa4p
    5mAj9UHtumIb49e3tHxr//JUwZv2Gqik0RKkjkrnUmFpHX4N+f81RLDnKsY4+wyI
    bM5Gwq/2t8suZbwfHNFufytaRnRFjk+P6crPIpcfe05Xc+Y+Wq4yL62VY3wSS13C
    EeUZ2FXpAoGBANPtw8De96TXsxdHcbmameWv4uepHUrYKq+7H+pJEGIfJf/1wsJ0
    Gc6w2AE69WJVvCtTzP9XZmfiIze2sMR/ynhbUl9wOzakFpEh0+AmJUG+lUHOy4k2
    Mdmu6GmeIM9azz6EXyfXuSZ39LHowS0Es1xaWRuu5kta73B5efz/hz2tAoGBAMj4
    QR87z14tF6dPG+/OVM/hh9H5laKMaKCbesoXjvcRVkvi7sW8MbfxVlaRCpLbsSOs
    cvAkc4oPY+iQt8fJWSJ1nwGJ0g7iuObLJh9w6P5C3udCGLcvqNbmQ9r+edy1IDBr
    t7pdrFKiPFvaEEqYl06gVSsPCg041N6bRTJ1nEzLAoGAajSOVDqo6lA6bOEd6gDD
    PSr+0E+c4WQhSD3Dibqh3jpz5aj4uFBMmptfNIaicGw8x43QfuoC5O6b7ZC9V0wf
    YF+LkU6CLijfMk48iuky5Jao3/jNYW7qXofb6woWsTN2BoN52FKwc8nLs9jL7k6b
    wB166Hem636f3cLS0moQEWUCgYABWjJN/IALuS/0j0K33WKSt4jLb+uC2YEGu6Ua
    4Qe0P+idwBwtNnP7MeOL15QDovjRLaLkXMpuPmZEtVyXOpKf+bylLQE92ma2Ht3V
    zlOzCk4nrjkuWmK/d3MzcQzu4EUkLkVhOqojMDZJw/DiH569B7UrAgHmTuCX0uGn
    UkVH+wKBgQCJ+z527LXiV1l9C0wQ6q8lrq7iVE1dqeCY1sOFLmg/NlYooO1t5oYM
    bNDYOkFMzHTOeTUwbuEbCO5CEAj4psfcorTQijMVy3gSDJUuf+gKMzVubzzmfQkV
    syUSjC+swH6T0SiEFYlU1FTqTGKsOM68huorD/HEX64Bt9mMBFiVyA==
    -----END RSA PRIVATE KEY-----`;
    
      try {
    
        const makeKey = pem => JWK.asKey(pem, 'pem');
        const key = await makeKey(privateKey);
    
        // This returns the decrypted data
    
        return await JWE.createDecrypt(key).decrypt(input);
    
      } catch (err) {
        throw new Error(err.message);
      }
    
    }
    

    【讨论】:

      【解决方案3】:

      jwt.io 很好地解释了签署 JWT 的方法不止一种。用户可以使用单个密钥进行签名和验证,也可以使用公钥/私钥对分别进行验证/签名。

      JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全传输信息。此信息可以验证和信任,因为它是数字签名的。 JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。

      虽然 JWT 可以加密以提供保密性 各方,我们将专注于签名代币。签名的令牌可以验证 其中包含的声明的完整性,而加密的令牌 向其他方隐藏这些声明。当令牌使用签名时 公钥/私钥对,签名还证明只有 持有私钥的一方就是签署它的一方。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-08-16
        • 2017-01-27
        • 2016-10-09
        • 2016-10-14
        • 2016-12-01
        • 2017-10-20
        • 2018-12-14
        相关资源
        最近更新 更多