JWT Private / Publi c Key Confusion - PullRequest
       0

JWT Private / Publi c Key Confusion

1 голос
/ 05 марта 2020

Я пытаюсь понять логику c использования JSON веб-токенов с закрытыми / publi c ключами (RS512) при подписывании полезной нагрузки данных, отправляемых клиентом (в данном случае React Native) Приложение) на мой сервер.

Я думал, что весь смысл закрытых / publi c ключей заключается в том, чтобы private key был закрыт (на моем сервере) и передавал publi c ключ к человеку, который успешно вошел в приложение.

Я думал, что при каждом запросе API к моему серверу аутентифицированный пользователь приложения будет использовать publi c Ключ для создания JWT (на стороне клиента) и сервер будут использовать ключ private для проверки подписи / полезной нагрузки из запроса API.

Кажется, у меня это задом наперед, потому что везде, где я читаю, вы должны подписывать JWT с личным ключом - но это противоречит моему пониманию того, кто владеет ключами .

В зависимости от того, как создаются ключи, некоторые закрытые ключи могут иметь пароль, который должен быть секретным! Итак, если секретный ключ и секретный ключ открыт (в коде на стороне клиента), насколько это может быть безопасно?

И откуда приходит шифрование? Если пользователь приложения отправляет конфиденциальные данные в API, я должен зашифровать полезную нагрузку и подписать ее с помощью JWT на стороне клиента, а затем позволить серверу проверить подпись JWT и расшифровать данные?

Этот учебник был полезен https://medium.com/@siddharthac6 / json -web-token-jwt-правильный путь реализации-с- node-js -65b8915d550e , но, похоже, в обратном направлении.

Любое объяснение определенно поможет, потому что все онлайн-уроки не имеют смысла.

Спасибо.

Ответы [ 2 ]

0 голосов
/ 05 марта 2020

Решения для JWT и JWE в React Native и Node Backend

Самым сложным было найти метод, который работает как в RN, так и в Node, потому что я не могу просто использовать любую библиотеку Node в RN, так что крипто и Пакеты jwt, которые я устанавливаю в приложении и на сервере, должны работать вместе.

Я передаю все вызовы API через HTTPS, так что часть шифрования в любом случае, вероятно, избыточна, как упомянул Флорент.

Метод № 1 чище, но я предпочитаю метод № 2, потому что он дает мне больше контроля над тем, что зашифровано и как создается JWT. Это позволяет мне проверить JWT до Я пытаюсь расшифровать полезную нагрузку.

Метод # 1

Создать JWE для одновременного шифрования токена и полезной нагрузки.

Плюсы:

  1. Соответствует принятым стандартам шифрования / безопасности.
  2. Гораздо меньше кода.
  3. Не нужно отслеживать секретных паролей, потому что мы используем только пару открытых / закрытых ключей, известных только серверу, и приложение, созданное при входе в систему.

Минусы:

  1. Я не вижу способа настроить JWT. При создании простого JWT я мог установить явное время истечения 60 с или меньше. Я не вижу способа сделать это с JWE.
  2. Ошибки, возникающие при расшифровке JWE, не так полезны. Это будет либо работать, либо потерпеть неудачу. При дешифровке JWT вы получаете хорошие сообщения об ошибках, если что-то не работает или недействительно.

Реагируйте собственный код приложения

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

}

Node Backend

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

}

Метод # 2

Создайте JWT и зашифруйте полезную нагрузку самостоятельно.

Ниже JS показывает, как я создаю JWT и шифрую данные в React Native, а затем проверяю токен и расшифровать данные в бэкэнде Node.

Когда пользователь войдет в систему, я передам обратно секретный ключ для JWT и отдельный секретный ключ для шифрования (при необходимости), который действует только в течение сеанса. После того, как они выйдут из системы, ключи будут уничтожены.

Я также сохраняю время истечения JWT до 60 секунд, чтобы быть в безопасности.

Плюсы:

Вы имеете больший контроль над JWT с точки зрения установки времени истечения.

Минусы:

Может быть медленнее, потому что вам нужно создать JWT и зашифруйте полезную нагрузку отдельными шагами.

React Native App

import jwt from 'react-native-pure-jwt';
import CryptoJS from 'react-native-crypto-js';

/**
 * Create JWT Signature from payload for API call
 *
 * @param payload {object|string} - payload to add to JWT
 * @param encrypt {boolean} - encrypt the payload
 *
 */
async function createJWT(payload, encrypt = false) {

  try {

    const signature = await jwt.sign({

        // REQUIRED: Payload
        // Any data you want to pass in the JWT
        // Two options:
        // 1. Send the payload without encryption.
        //      No need to stringify payload. Just send it as-is.
        // 2. Send the payload as an encrypted string.
        //      Use the CryptoJS library with a 'another-secret-key' that the App and the server share.
        //      Payload needs to be stringified.

        data: encrypt ? CryptoJS.AES.encrypt(JSON.stringify(payload), 'another-secret-key').toString() : payload,

        // REQUIRED: Expires Time
        // Milliseconds since 1970 when the token can no longer be decoded.
        // Here, I've set the value to however many seconds I want the token to last.
        // The clock starts ticking from the moment this token was created.  Afer the time has expired,
        // the token can't be decoded by anyone.

        exp: new Date().getTime() + 60 * 1000,

        // OPTIONAL: Issuer Claim
        // String that identifies whoever issued this JWT.
        // The JWT is coming from my App so I'll use the App name here.

        iss: 'APP ID',

        // OPTIONAL: Subject Claim
        // String that uniquely identifies the subject of the JWT.
        // The I'll probably use the Username or User ID of the person using the App.

        sub: 'UNIQUE USER ID',

      },

      // REQUIRED: Secret Key
      // This is the string that is used to encode/decode the JWT.
      // Here, I'll probably use something that's unique to the user's account on the App such
      // as a password or strong token that's stored in the user's Keychain and that's
      // also in a database the server can retrieve.

      'my-secret-key',

      {

        // REQUIRED: Algorithm
        // What algorithm is being used to encode the JWT
        // The supported algorithms by react-native-pure-jwt are HS256, HS384, HS512.
        // The higher the number, the better the security but it will also take longer to encode/decode the token.
        // I would prefer to use RS256 or RS512 but those algorithms aren't supported
        // by the react-native-pure-jwt package as far as I can tell.

        alg: 'HS256',

      },
    );

    return signature;

  } catch (err) {

    throw new Error(err.message);

  }

}

Узел Backend

const jwt = require('jsonwebtoken');
const CryptoJS = require('crypto-js');

/**
 * Decode JWT Signature from API call
 *
 * @param token {string}
 * @param decrypt {boolean} - decrypt the payload
 *
 */
async function decodeJWT(token, decrypt = false) {

  // Use the same options to decode the JWT that were used to encode the JWT.

  const options = {
    issuer:  'APP ID',
    subject: 'UNIQUE USER ID',
  };

  // Use the same Secret Key that was used to encode the JWT.
  // This should be a password or some hidden value that is only
  // known by the App and the Server such as the user's password or previously
  // agreed upon strong token shared after login and discarded after logout.

  const secretJWTKey = 'my-secret-key';

  // If decrypting an encrypted payload, you'll also need the encryption secret
  // which should ideally not be the same as the secretJWTKey.
  // It can also be some hidden value that is only known by the App and the Server such
  // an agreed upon strong token shared after login and discarded after logout.

  const secretDecryptKey = 'another-secret-key';

  return new Promise((resolve, reject) => {

    try {

      // First check if the token is valid. Otherwise, it will throw an error.

      const verified = jwt.verify(token, secretJWTKey, options);

      let data = verified.data;

      if (decrypt) {

        // If the payload is encrypted, unwind it into an object or string.

        const bytes = CryptoJS.AES.decrypt(data, secretDecryptKey);
        data = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));

      }

      resolve(data);

    } catch (err) {

      // If the token is not valid or the decryption failed.

      reject(err.message);

    }

  });

}

Пример

async function test() {

  // Create the JWT and encrypt the payload (optional) in React Native

  const tokenSignature = await createJWT({
    name: 'Marc',
    password: 'P@55w0rd',
    cc: 'Visa',
    cc_num: '4400-0000-0000-0000'
    phone: '704-000-0000'
    info: {
      birthdate: '1970-00-00',
      ssn: '000-00-0000',
    },
  }, true).catch(err => {

    console.log('Error creating signature', err);

  });

  // Use the JWT signature to make the https api call.

  // On the server...
  // Decode the JWT and decrypt the payload (optional)

  const data = await decodeJWT(tokenSignature, true).catch(err => {

    console.log('Error retrieving data', err);

  });

  // Use the data to update the database, etc.

  console.log({data});

}
0 голосов
/ 05 марта 2020

С JWT владение и использование ключевых материалов точно такое же, как и в любых других контекстах, где происходят операции шифрования.

Для подписи:

  • Закрытый ключ принадлежит эмитенту.
  • Ключ publi c может быть предоставлен всем сторонам, которым необходимо проверить подпись.

Для шифрования:

  • Закрытый ключ принадлежит получателю
  • Ключ publi c может быть предоставлен любому лицу, которое хочет отправить конфиденциальные данные получателю.

Шифрование редко используется с JWT. В большинстве случаев уровень HTTPS является достаточным, а сам токен содержит только немного информации, которая не является конфиденциальной (время данных, идентификаторы ...). Эмитент токена (сервер аутентификации) имеет закрытый ключ для генерации подписанных токенов (JWS). Эти токены отправляются клиентам (сервер API, веб-приложение / собственное приложение ...). Клиенты могут проверить токен с помощью ключа publi c.

Если у вас есть конфиденциальные данные, которые не будут переданы третьим лицам (номера телефонов, личный адрес ...), то зашифрованные токены ( JWE) настоятельно рекомендуется. В этом случае каждый клиент (т. Е. Получатель токена) должен иметь закрытый ключ, и эмитент токена должен зашифровать токен, используя ключ publi c каждого получателя. Это означает, что эмитент токена может выбрать соответствующий ключ для данного клиента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...