我试图理解使用带有私钥/公钥(RS512)的JSON Web令牌的逻辑,当将从客户端(在本例中是React Native App)发送到服务器的数据有效载荷签名时。
我认为私钥/公钥的全部意义在于将私钥保持为私钥(在我的服务器上),并将公钥交给成功登录应用程序的人。
我认为,对于我的服务器的每个API请求,经过身份验证的应用程序用户将使用公钥创建JWT(在客户端),服务器将使用私钥验证来自API请求的签名/有效负载。
我似乎把它倒过来了,因为我读到的每一个地方,你都应该用私钥签署JWT——但这违背了我对谁拥有这些密钥的理解。
根据密钥的创建方式,一些私钥可以具有应该是秘密的密码!因此,如果私钥和秘密公开(在客户端代码中),那会有多安全?
加密是从哪里来的?如果应用程序的用户在API中发送敏感数据,我是否应该加密负载并在客户端使用JWT签名,然后让服务器验证JWT签名并解密数据?
本教程很有帮助https://medium.com/@Siddhartac6/json-web-token-jwt-the-right-way-of-implementation-with-node-js-65b8915d550e,但它似乎是倒退的。
任何解释肯定会有帮助,因为所有的在线教程都没有意义。
非常感谢。
最难的部分是找到一种同时适用于RN和节点的方法,因为我不能只在RN中使用任何节点库,所以我在应用程序和服务器上安装的加密和jwt包必须一起工作。
我正在通过HTTPS传输所有的应用编程接口调用,所以加密部分可能是过度杀伤性的,正如弗洛伦特所提到的。
方法#1更干净,但我更喜欢方法#2,因为它让我可以更好地控制什么是加密的,以及如何创建JWT。这让我在尝试解密有效载荷之前验证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);
}
}
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);
}
}
下面的JS展示了我如何创建JWT并在React Native中加密数据,然后验证令牌并在节点后端解密数据。
当用户登录时,我将为JWT传回一个密钥,并为加密(如果需要)传回一个单独的密钥,该密钥仅在会话期间有效。他们注销后,密钥被销毁。
为了安全起见,我还将JWT的过期时间保持在60秒左右。
赞成的意见:
在设置过期时间方面,您可以对JWT进行更多的控制。
欺骗:
可能会更慢,因为您必须在单独的步骤中创建JWT并加密有效负载。
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);
}
}
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});
}
使用JWT,关键材料的拥有和使用与发生密码操作的任何其他上下文完全相同。
用于签署:
用于加密:
加密很少用于JWT。大多数时候,HTTPS层已经足够了,令牌本身只包含一些不敏感的信息(数据、标识...)。令牌的发行者(身份验证服务器)拥有用于生成签名令牌(JWS)的私钥。这些令牌被发送到客户端(API服务器、Web/本地应用程序...)。客户端可以使用公钥验证令牌。
如果您有不应向第三方披露的敏感数据(电话号码、个人地址……),然后强烈建议使用加密令牌(JWE)。在这种情况下,每个客户端(即令牌的接收者)应具有私钥,令牌的发行者必须使用每个接收者的公钥加密令牌。这意味着令牌的颁发者可以为给定的客户端选择适当的密钥。
Auth0提供了两个JWT库,一个用于Node:Node jsonwebtoken,另一个用于Java:Java JWT。 我创建了私有/公钥对,并在Node中成功地使用了node-jsonwebToken: 但是我发现没有办法在java-jwtJava做同样的事情。 有人有一个如何在Java中为JWT使用私钥/公钥的工作示例吗?
我明白JWT规范没有涵盖这一点:但我很想知道在实际的JWT使用中有哪些常见的做法。 当然,这个问题与HTTPS中涉及的证书无关:我只是在讨论JWT签名和加密中使用的密钥。
我正在实施类似于:https://login.microsoftonline.com/common/discovery/v2.0/keys Spring boot JWT应用程序,用于生成和验证JWT令牌。我将生成许多公钥/私钥(我不想用一个密钥生成所有令牌。一个密钥对将生成生命周期较短的令牌,第二个密钥对将生成更长的令牌…)我将使用公钥创建endpoint/密钥。问题是:如何在我的应用程序中连接
我和JWT有一些安全问题。事情是这样的: 首先是创建一个只有一个密钥的令牌,并使用它来注册和验证令牌-(这是许多开发使用的规则)。 第二种是使用私钥和带有私钥的公钥进行注册,创建新的令牌,并且只使用公钥进行令牌验证。点击这里查看第二条规则中的图像 所以我的问题是哪两种方式更安全?第二条安全规则真的必要吗?谢谢
Auth0提供了两个JWT库,一个用于Node:Node jsonwebtoken,另一个用于Java:Java JWT。事实证明,JavaJWT不支持公钥/私钥对。 然而,另一个java库jjwt库声称支持该特性。但是,该文档没有显示如何在jjwt中使用自己的公钥/私钥对。 我创建了私有/公钥对,并在Node中成功地使用了node-jsonwebToken: 但是我发现在Java中用JWT无法做
我使用secp192r1曲线生成了ECC公钥和私钥。公共密钥数组长度为75,私有密钥数组长度125。为什么私钥比公钥长?为什么私钥的长度不是公钥的两倍?为什么由于secp192r1私钥不是192位=24字节?