JWT 私钥/公钥混淆

Mar*_*arc 12 jwt

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

我认为私有/公共密钥的整点是保持私有密钥私人(我的服务器上)和手的公共密钥来谁是成功登录到应用程序的人。

我想,每个API请求到我的服务器,应用程序的身份验证的用户将使用公共密钥创建JWT(在客户端),服务器将使用私人密钥来验证从API请求签名/净荷.

似乎我把它倒退了,因为在我读到的任何地方,你都应该用私钥签署 JWT——但这违背了我对谁拥有密钥的理解。

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

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

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

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

谢谢你。

Sas*_*shi 27

希望这个数字能增加一些清晰度。两种 JWT 签名场景

  • 一图胜千言! (6认同)

Flo*_*lli 16

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

签名:

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

对于加密:

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

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

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


Mar*_*arc 10

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

最难的部分是找到一种同时适用于 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);
  }

}
Run Code Online (Sandbox Code Playgroud)

节点后端

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);
  }

}
Run Code Online (Sandbox Code Playgroud)