Having trouble grasping how to securely sign JWT with Private Key - javascript

I'm looking at this example here which refers to the javascript functionality of JWT
I am trying to use javasrcipt to sign a piece of data. However, it says I have to use a Private RSA Key and it doesn't allow you to use a public key.
My goal was once a form is submitted via PHP, call this javascript function and encrypt the data.
Pardon my ignorance, but how can you use a private RSA key in javascript and keep it private at the same time?
It appears that you have to give it a private key somehow and wouldn't that private key be visible to a user using simple developer tools in the web browser?
function _genJWS() {
var sHead = '{"alg":"RS256"}';
var sPayload = '{"data":"HI","exp":1300819380}';
var sPemPrvKey = document.form1.pemprvkey1.value;
var jws = new KJUR.jws.JWS();
var sResult = null;
try {
sResult = jws.generateJWSByP1PrvKey(sHead, sPayload, sPemPrvKey);
document.form1.jwsgenerated1.value = sResult;
} catch (ex) {
alert("Error: " + ex);
}
}

What your are looking for is not JWS (signed), but JWE (encrypted).
If you want to send secured data to a server using JWE, you must :
get the public key of the server
encrypt your data using this public key and produce a JWE
send your JWE to the server.
As far as I know, there is no javascript library able to produce JWE (I may be wrong, but I found nothing).

Related

UnityWebRequest.Put doesn't send JSON string

I'm looking to make a PUT or POST request to an API I've deployed to Vercel built with NEXT JS. It does other things besides being an API but that's not important...
So, I have a Unity Project where I would like to log the time that my player completed the level they're on.
Originally I wanted to use Firebase but discovered that I can't as the Windows Unity builds don't support it so I ended up just making an API that send the Data to MongoDB and does some other stuff, again not important.
So getting to the guts of the problem:
I have this IEnumerator to send the request:
IEnumerator logPlayerTime()
{
string url = "http://some-vercel-cloud-function/api/new";
var requestBody = new PlayerRequestBody();
requestBody.firstName = "John";
requestBody.lastName = "Doe";
requestBody.email = "email#email.com";
requestBody.marketing = false;
requestBody.time = 200000; // this actually come from somewhere else but for now this is fine
string json = JsonUtility.ToJson(requestBody);
UnityWebRequest request = UnityWebRequest.Put(url, json);
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("Accept", "*/*");
request.SetRequestHeader("Accept-Encoding", "gzip, deflate, br");
request.SetRequestHeader("Connection", "keep-alive");
request.useHttpContinue = false;
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.Log(request.error);
}
else
{
Debug.Log("Time Logged! " + json);
}
}
According to the Docs I should have two arguments, the first being my URL and the second Being my Data.
As for the JSON I want to send I made this struct
[Serializable]
private struct PlayerRequestBody
{
public string firstName;
public string lastName;
public string email;
public bool marketing;
public float time;
}
Then, I'm using JsonUtiltiy.ToJson to turn my json variable into a JSON string, which seems to work.
All of this said, no matter what I change in the request, even typing the JSON string out manually escaped. It just sends a request body of an empty JSON object like this:
{}
So if we take a look at what the API receives the logs look like this:
[PUT] /api/new 01:44:25:13 {}
I'm sure it's something small but I just don't have enough experience with Unity or C# to find the issue (and I want to go to bed).
As for how this gets called, In one of my Scripts that manages the game I have a handler method that is subscribed to GameState changes, so when my GameState === GameState.Victory then I would run the handler. So the code for that, with bits removed for readability looks like this:
// ...
private void Awake()
{
GameManager.OnGameStateChanged += GameManager_OnGameStateChanged;
}
// ...
private void GameManager_OnGameStateChanged(GameState state)
{
if (state == GameState.Victory)
{
handleVictory();
}
}
That handleVictory method just runs StartCoroutine on the IEnumerator at the moment
Any ideas? Thanks in advance
So for anyone who ever somehow runs into this, the answer is... Next is weird.
I made another API using Express JS and boom instant data from the Player.
I don't have time to look into it but there must be something about Unitys UnityWebRequest that Next doesn't agree with. Long and short I'm gonna pull the API out of next and then move over my components to React and host it on a server less function :)

Is there a way to generate RSA key pair client side with javascript?

I used Cryptico library and it is working fine, but the public key from Cryptico is not compatible with OpenSSL (meaning I can not use it to encrypt data with PHP as an example). I am asking how can I generate a key pair client side with the public key being compatible with OpenSSL. The goal is being able to encrypt data with the public key on IOS, Android or PHP and decrypt it on Javascript (meaning it is compatible cross platforms).
You may use jsbn library in the link below:
http://www-cs-students.stanford.edu/~tjw/jsbn/
and you may see the demo here:
http://www-cs-students.stanford.edu/~tjw/jsbn/rsa2.html
This is the most popular library you may find and you have the chance to customize it based on your requirement.
Also, you have another option with jsencrypt which is available here:
https://github.com/travist/jsencrypt
It is also compatible with openssl.
// Encrypt with the public key...
var encrypt = new JSEncrypt();
encrypt.setPublicKey($('#pubkey').val());
var encrypted = encrypt.encrypt($('#input').val());
// Decrypt with the private key...
var decrypt = new JSEncrypt();
decrypt.setPrivateKey($('#privkey').val());
var uncrypted = decrypt.decrypt(encrypted);
// Now a simple check to see if the round-trip worked.
if (uncrypted == $('#input').val()) {
alert('It works!!!');
}
else {
alert('Something went wrong....');
}

Decryption Issue with Node Package "node-rsa"

I am attempting to implement simple public key cryptography with this library's RSA functions, but decryption seems to be broken.
I have two "users", Alice and Bob. Both Alice and Bob (code in separate files) create a new empty key via const key = new nodeRSA(). Then, they both generate a 2048 bit public and private key pair via the function key.generateKeyPair(2048). They both then give each other their public keys by exporting them from the key with key.exportKey('pkcs8-public-pem') and storing them into separate files and reading them in with fs. Alice then attempts to write a message to bob by passing both the string message and bob's public key into the function below
module.exports.writeMessage = (message, key) => {
const k = new rsa(key, 'pkcs8-public-pem')
const cipherText = k.encrypt(message, 'hex');
console.log('Saving "${cipherText}" to ctext.txt');
fs.writeFileSync('ctext.txt', cipherText);
};
Then, when bob goes to read the message, he passes in his full key and decodes the message from ctext.txt as shown in the function below
module.exports.readMessage = key => {
const encryptedMessage = fs.readFileSync('ctext.txt');
const message = key.decrypt(encryptedMessage, 'utf8');
return message;
};
Encryption works just fine, and Alice is able to send the ciphertext to ctext. The problem comes when bob calls the readMessage function and attempts to decipher the text. Both the Alice and Bob programs were activated and their keys remained unchanged throughout this process. The below error occurs on deciphering:
Error: Error during decryption (probably incorrect key). Original error: Error: Incorrect data or key
at NodeRSA.module.exports.NodeRSA.$$decryptKey (/Users/jisacf1/College/SeniorYear/Spring2019/CompSec/HW3/node_modules/node-rsa/src/NodeRSA.js:301:19)
at NodeRSA.module.exports.NodeRSA.decrypt (/Users/jisacf1/College/SeniorYear/Spring2019/CompSec/HW3/node_modules/node-rsa/src/NodeRSA.js:249:21)
at Object.module.exports.readMessage.key [as readMessage] (/Users/jisacf1/College/SeniorYear/Spring2019/CompSec/HW3/Part2/rsaReadWrite.js:7:25)
at inquirer.prompt.then (/Users/jisacf1/College/SeniorYear/Spring2019/CompSec/HW3/Part2/bob.js:42:43)
at processTicksAndRejections (internal/process/next_tick.js:81:5)
I really cannot see how the system thinks it is the incorrect key, since Alice encrypted the message using Bob's public key, and Bob is decoding the message using is private key. I've tried changing padding schemes to no avail as well. Any help would be appreciated greatly. For reference, the library's github is here: https://github.com/rzcoder/node-rsa
As mentioned by Maarten, the issue was that writeFileSync was encoding my cipher text in utf8 rather than the format the cipher text was in. This resulted in reading back incorrect cipher text, causing the key or data mismatch exception. Changing the default encoding for the function to hex solved the issue.

Verifying JWT using jsonwebtoken in node.js with a token generated by jose4j fails

I'm trying to verify a json web token generated by jose4j using jsonwebtoken in node.js and I see the following error:
[Error: PEM_read_bio_PUBKEY failed]
The jose4j code is basically lifted straight from the example:
RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
key.setKeyId("global.authenticated");
byte[] raw = key.getKey().getEncoded();
Base64.Encoder encoder = Base64.getEncoder();
System.out.printf("Public Key [%s]\n", encoder.encodeToString(raw));
JwtClaims claims = new JwtClaims();
claims.setIssuer("global.gen");
claims.setAudience("global.cons");
claims.setExpirationTimeMinutesInTheFuture(12 * 60);
claims.setGeneratedJwtId();
claims.setIssuedAtToNow();
claims.setNotBeforeMinutesInThePast(2);
claims.setSubject("nim");
claims.setClaim("role", "tester");
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setKey(key.getPrivateKey());
jws.setKeyIdHeaderValue(key.getKeyId());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
String token = jws.getCompactSerialization();
System.out.printf("Generated Token [%s]\n", token);
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime() // the JWT must have an expiration time
.setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
.setRequireSubject() // the JWT must have a subject claim
.setExpectedIssuer("global.gen") // whom the JWT needs to have been issued by
.setExpectedAudience("global.cons") // to whom the JWT is intended for
.setVerificationKey(key.getKey()) // verify the signature with the public key
.build(); // create the JwtConsumer instance
try {
// Validate the JWT and process it to the Claims
JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
System.out.println("JWT validation succeeded! " + jwtClaims);
} catch (InvalidJwtException e) {
// InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
// Hopefully with meaningful explanations(s) about what went wrong.
System.out.println("Invalid JWT! " + e);
}
So internally the token validates fine. However when I copy the token and the key (for example below is from a run), the above error is reported:
var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
var key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nYvN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZUy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOKn3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCSaQIDAQAB';
var token = 'eyJraWQiOiJnbG9iYWwuYXV0aGVudGljYXRlZCIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJnbG9iYWwuZ2VuIiwiYXVkIjoiZ2xvYmFsLmNvbnMiLCJleHAiOjE0NDMwNjMyMDgsImp0aSI6InpweF9ERW8tX1h2Q1hnZmNZTUpiZ0EiLCJpYXQiOjE0NDMwMjAwMDgsIm5iZiI6MTQ0MzAxOTg4OCwic3ViIjoibmltIiwicm9sZSI6InRlc3RlciJ9.inEebSQ8jYPQsTpHnvw-gMpoNbJl5ErUkS8FtkDagWrwijUgG8XYYP8FLi2ZCpdgDqUsP6nE1iG0_2wWuL7B7C7wUpZlrqR2bEOG2cXK9s26VqNAXu8I7BTDaZBKmdOt1aFVWozGsN8iUCsQ7Yt9-GfvNRP1yeOoMgpOxf_wVa0QVzsV18aVi_oSeiMqOkQ_6n7JOjFVdiURm0ew4vh5TBaMcEcS35a9jtPxuFR_Z_FaLUk0g06PDVKcdsK1-FYRAGBlRGDkea8Hs9Zh-ZIxgcs2QfWzq5PSsIKum1dWqNLW04ullWmlbAO-5d0V0NAnkh4FFoi3N7AedvkILJgbqA';
jwt.verify(token, key, { algorithms: ['RS256'] }, function(err, decoded) {
if (err)
console.log(err);
else
console.log(decoded);
});
Is there some bit of magic that I'm missing (either in jose4j or jsonwebtoken) that will allow the generated token to be validated by the public key?
On a side note, pasting the token into jwt.io decodes the header and payload correctly, however the signature fails to be verified using the same public key. I'm guessing the problem really is in the jose4j side - but not sure.
I'll add this as an answer incase someone hits something similiar. The problem stems from the format of the key passed into jsonwebtoken. It can't just be a plain text public key, it has to follow the PEM format specifically. So once I convert the key to a PEM file, specficially by making the following changes:
byte[] raw = key.getKey().getEncoded();
Base64.Encoder encoder = Base64.getMimeEncoder(64, new byte[]{'\n'});
And then wrapping with
-----BEGIN PUBLIC KEY-----
<Key>
-----END PUBLIC KEY-----
Then passing the resulting file into jsonwebtoken allows the authentication to proceed. I didn't realize that the certificate passed into the validation step should be formatted so strictly (i.e. with the line sizes and the wrappers!)
NOTE: the bouncycastle package has a PemObject class and a PemWriter which should make writing the file somewhat easier - however I didn't want to pull in another package just for that. Maybe the maintainers of jose4j can add a little class for that to their package..
It seems as if jsonwebtoken is very strict not only about the -----BEGIN PUBLIC KEY----- header and footer, but also about the line breaks.
A key like
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nYvN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZUy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOKn3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCSaQIDAQAB
has to look like this for it to work with jsonwebtoken (mind the header, footer and line breaks):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X
8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34
YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nY
vN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZ
Uy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOK
n3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCS
aQIDAQAB
-----END PUBLIC KEY-----
This function does the job for me:
function base64toPem(base64)
{
for(var result="", lines=0;result.length-lines < base64.length;lines++) {
result+=base64.substr(result.length-lines,64)+"\n"
}
return "-----BEGIN PUBLIC KEY-----\n" + result + "-----END PUBLIC KEY-----";
}

Issues running crypto-js on Parse Cloud Code for iOS application

I'm using Parse as my backend for my iOS application and would like to encrypt all the data that's sent between Parse and my iOS device. As such, I'm using Parse Cloud Code in hopes of being able to perform server-side encryption & decryption to process all data it sends and receives.
Apparently Parse has a 'crypto' module by default, but since I've been unable to find any documentation for it, I've gone ahead and tried using crypto-js by copying the appropriate files for AES encryption + decryption into my Parse Cloud Code /cloud folder.
The issue I'm running into is that I'm not sure what class of object is being returned by crypto-js's AES encryption / decryption function. I *seem* to be getting back an NSDictionary object but have no idea what to do with it. I would have guessed that I would receive an NSString or NSData object, but I seem to have guessed wrong.
Please let me know what additional information I can provide or what incorrect assumptions I may have made.
I needed to encrypt / decrypt on the server side, here is my cloud code which is similar to nodeJS code:
var crypto = require('crypto');
var cryptoAlgorithm = "aes-128-cbc"; //or whatever you algorithm you want to choose see http://nodejs.org/api/crypto.html
var cryptoPassword = "theLongAndRandomPassphrase";
var cipher = crypto.createCipher(cryptoAlgorithm,cryptoPassword);
var decipher = crypto.createDecipher(cryptoAlgorithm,cryptoPassword);
exports.myCiphering = {
encrypt:function(text){
var encrypted = cipher.update(text,'utf8','hex')
encrypted += cipher.final('hex');
return encrypted;
},
decrypt: function(text){
var decrypted = decipher.update(text,'hex','utf8')
decrypted += decipher.final('utf8');
return decrypted;
}
};
If this snippet has been saved in "cloud/ciphering.js", you can then use the ciphering tool like this anywhere in cloud code:
var text = "encryptMe";
var ciphering = require("cloud/ciphering.js").myCiphering;
var encrypted = ciphering.encrypt(text);
var decrypted = ciphering.decrypted(encrypted);
if (decrypted == text){
//the "password" is correct
}
Since Parse uses SSL all data is sent encrypted, SSL is enough to secure the communications.
You may want to encrypt the data so that it is protected on the server but unless you really understand cryptographic security don't.
Plain or encrypted passwords should never be stored, store a properly salted and hashed version of the password.
If you feel your data is substantially valuable enough have a security domain expert design it. Getting security right is hard, one mistake will invalidate it all.

Categories