combined RC4 RSA encrypt/decrypt for long messages Javascript - javascript

NOTE: Yes, I understand there is a lot of code in this message, but you do encourage us to show prior research and how we've been trying.
Let me preface this by saying, I am not interested in the security of this function. All I want is to encrypt and decrypt arbitrarily long messages using RSA. Usually to do this, the message is encrypted using a block cipher (such as AES) and encrypting the key with the RSA cipher. However, I am just trying to find the easiest way to encrypt/decrypt long messages, irregardless of security. Hence why I am using RC4 in place of the block cipher.
Now, I can encrypt properly using the following code:
function encryptLong(signedCert, msg) {
var key256Bits = CryptoJS.SHA256("password");
var ciphertext = CryptoJS.RC4.encrypt(msg, key256Bits);
key = new RSAKey();
var m = CryptoJS.SHA256("password").toString(CryptoJS.enc.Hex);
m = new BigInteger(m, 16);
key.setPublic(signedCert.msg.subject.pk.n, signedCert.msg.subject.pk.e);
var ctxt = key.doPublic(m).toString(16);
var cipherstring = ciphertext + ":" + ctxt;
var obj = { "type": "CTXT-LONG", "encrypted": cipherstring };
return JSON.stringify(obj);
}
The message and the key are encrypted properly. I tested them individually using these functions.
function encryptRSA(signedCert, msg) {
//create a new RSA key object
var key = new RSAKey();
//convert ASCII message to hex
var m = asciiToHex(msg);
// create new BigInterger from m
m = new BigInteger(m, 16);
// set the values for the public key
key.setPublic(signedCert.msg.subject.pk.n, signedCert.msg.subject.pk.e);
// compute the RSA public key operation, and convert to a hex value
var ctxt = key.doPublic(m).toString(16);
//enter ctxt into the JSON obj
var obj = { "type": "CTXT-SHORT", "c": ctxt };
return JSON.stringify(obj);
}
And...
function encryptRSA(password, message) {
var key256Bits = CryptoJS.SHA256(password);
var ciphertext = CryptoJS.RC4.encrypt(CryptoJS.enc.Utf8.parse(message), key256Bits);
return ciphertext;
}
Now, here is our decryption code:
function decryptLong(sk, ctxt) {
key = new RSAKey();
encryptedStuff = JSON.stringify(ctxt.encrypted);
log(encryptedStuff);
splitEncryptedstuff = encryptedStuff.split(":");
rsaencryption = splitEncryptedstuff[1];
log(rsaencryption);
rc4encryption = splitEncryptedstuff[0];
log(rc4encryption);
c = new BigInteger(rsaencryption, 16);
key.setPrivate(sk.n, sk.e, sk.d);
var key256Bits = key.doPrivate(c).toString(16);
log(key256Bits);
// RC4 decryption
var message = CryptoJS.RC4.decrypt(rc4encryption, key224Bits);
// var ptxt = CryptoJS.enc.Utf8.stringify(message);
// log(ptxt);
return CryptoJS.enc.Utf8.stringify(message);
}
This code doesn't decrypt properly, but I know parts of it work. For example, where I have
log(key356Bits);
it returns the key exactly. So I know that at least the RSA decryption works. What I don't understand is, I followed the decryption function that I have exactly. Which is as follows.
function decryptRC4(password, ciphertext) {
var key256Bits = CryptoJS.SHA256(password);
var message = CryptoJS.RC4.decrypt(ciphertext, key256Bits);
return CryptoJS.enc.Utf8.stringify(message);
}
Well not exactly, I don't have to take the Hash of the password to get the key, as I already have the key. But, I still don't understand what is not working. When we decrypt our ciphertext using this individual function, the plaintext is correct.
Any assistance in this matter would be greatly appreciated.
Knowing my luck, it's probably just something annoying like it's in the wrong encoding type thing.

Related

Uncaught URIError: URI malformed error with jQuery

I'm using node forge to encrypt a form before sending it to the server using AES.
The code for the crypto part for now is
const bigInt = require("big-integer");
const forge = require('node-forge');
function generateParams() {
// Cryptographic random number generator
var array = new Uint32Array(2);
var _key = bigInt(window.crypto.getRandomValues(array)[0]).toString();
var _iv = bigInt(window.crypto.getRandomValues(array)[1]).toString();
// generate random key and IV
var key = forge.util.encode64(_key);
var iv = forge.util.encode64(_iv);
const params = {
key: key,
iv: iv
}
return params;
}
function encrypt(params) {
var cipher = forge.rc2.createEncryptionCipher(params.key);
cipher.start(params.iv);
// Encrypting "testing"
cipher.update(forge.util.createBuffer("testing"));
cipher.finish();
return cipher.output;
}
function decrypt(params, encrypted) {
var cipher = forge.rc2.createDecryptionCipher(params.key);
cipher.start(params.iv);
cipher.update(encrypted);
cipher.finish();
return cipher.output;
}
and the jQuery function is (not posting yet)
$('#recordForm').submit(function(event) {
// Stop form from submitting normally
event.preventDefault();
// Grab form data
// Crypto
const params = generateParams();
const encryptedForm = {
test: encrypt(params),
}
console.log("Encrypted: " + encryptedForm.test);
const decryptedForm = {
test: decrypt(params, encryptedForm.id).data,
}
console.log("Decrypted: " + decryptedForm.test);
});
My problem is that I keep getting back (cryptob.js is the name of my file, generated with browserify)
Uncaught URIError: URI malformed
at decodeURIComponent (<anonymous>)
at Object.util.decodeUtf8 (cryptob.js:24437)
at ByteStringBuffer.util.ByteStringBuffer.toString (cryptob.js:23490)
at HTMLFormElement.<anonymous> (cryptob.js:1282)
at HTMLFormElement.dispatch (jquery-3.1.1.slim.min.js:3)
at HTMLFormElement.q.handle (jquery-3.1.1.slim.min.js:3)
when calling encrypt().
There is this answer here which recommends including a special meta tag. I have done that but it still doesn't work. Since some resources online say it is related to UTF-8 encoding, I tried replacing
cipher.update(forge.util.createBuffer("testing"));
with
cipher.update(forge.util.createBuffer(encodeURIComponent("testing")));
or
cipher.update(forge.util.createBuffer("testing", 'utf8'));
but it didn't work either (based on encodeURIComponent(str)).
You can test forge here, and if you run this code (which is essentially what I'm doing)
var forge = require("node-forge")
// generate a random key and IV
var key = forge.util.encode64("12354523465");
var iv = forge.util.encode64("2315");
// encrypt some bytes
var cipher = forge.rc2.createEncryptionCipher(key);
cipher.start(iv);
cipher.update(forge.util.createBuffer("testing"));
cipher.finish();
var encrypted = cipher.output;
console.log(encrypted);
// decrypt some bytes
var cipher = forge.rc2.createDecryptionCipher(key);
cipher.start(iv);
cipher.update(encrypted);
cipher.finish();
console.log(cipher.output.data)
it works fine.
How can I solve this?
It looks like this error is actually happening in the toString, where you generate your _key and _iv.
Try testing with some hard-coded strings, as used in the example code you posted. Then, use a method to generate random byte strings for the key and IV.
Also, for AES-256, the key should have 32 bytes (not bits) of entropy. The IV should have 16 bytes of entropy.

How to generate crypto.createCipheriv arguments from static source

I am using the below code to encrypt strings in my node.js code.
I would like to understand how to generate KEY and HMAC_KEY from a static source. In my program, it's generated randomly as of now. As it's generated randomly, I am not able to encrypt my database password using the below algorithm.
crypto = require('crypto');
ALGORITHM = "AES-256-CBC";
HMAC_ALGORITHM = "SHA256";
KEY = crypto.randomBytes(32);
HMAC_KEY = crypto.randomBytes(32);
function (plain_text) {
var IV = new Buffer(crypto.randomBytes(16)); // ensure that the IV (initialization vector) is random
var cipher_text;
var hmac;
var encryptor;
encryptor = crypto.createCipheriv(ALGORITHM, KEY, IV);
encryptor.setEncoding('hex');
encryptor.write(plain_text);
encryptor.end();
cipher_text = encryptor.read();
hmac = crypto.createHmac(HMAC_ALGORITHM, HMAC_KEY);
hmac.update(cipher_text);
hmac.update(IV.toString('hex')); // ensure that both the IV and the cipher-text is protected by the HMAC
// The IV isn't a secret so it can be stored along side everything else
return cipher_text + "$" + IV.toString('hex') + "$" + hmac.digest('hex')
};
You have to split your code into two executions:
Code that generates your keys and presents them in a storable format
KEY = crypto.randomBytes(32);
HMAC_KEY = crypto.randomBytes(32);
console.log(KEY.toString('hex'));
console.log(HMAC_KEY.toString('hex'));
Code that uses the stored keys
KEY = Buffer.from('some key string', 'hex');
HMAC_KEY = Buffer.from('some other key string', 'hex');
You just have to make sure that your keys aren't actually in your code, but rather in some file, because hardcoding key in code and checking them into your version control system is a bad idea and might give your developers access to production systems which they probably shouldn't have.

Replace Java code with javascript to encrypt in AES/ECB/Pkcs5

I am coding in node.js where i want to use AES/ECB/Pkcs5 for encrypting my string with a particular key. Now i have java code which is given below but as i tried to code the same thing in javascript things became really messy, Firstly i saw a simple solution of using crypto whose code is given below and i tried it but the output i got was different from the one i got from java, then i switched from crypto to Crypto-js which is another library for encryption now i gain tried the same but i again got different output and then finally when i saw plethora of posts where one said to declare iv and the other one to use key and input_string as a buffer and the other one to use chunks to encrypt the data.
Now as you can imagine i have tried for two days straight to code all these possibilities but none of them worked out, now can anyone please enlighten me and tell me which is the right approach or where i am missing something.
Thanks
JAVA CODE
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(1, secretKeySpec);
byte[] aBytes = cipher.doFinal(inputString.getBytes());
BASE64Encoder encoder = new BASE64Encoder();
String base64 = encoder.encode(aBytes).toString();
base64 = URLEncoder.encode(base64, "UTF-8");
return base64;
CRYPTO-JS CODE
var encrypted = CryptoJS.DES.encrypt(input_string, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
var base64 = CryptoJS.enc.Base64.toString(encrypted.ciphertext);
// var base64 = CryptoJS.enc.Base64.stringify(encrypted);
var parsedStr = base64.toString(CryptoJS.enc.Utf8);
console.log('parsedStr: ', parsedStr);
Crypto CODE
var cipher = crypto.createCipher('aes-128-ecb', key);
var crypted = cipher.update(input_string,'utf8','base64')
crypted += cipher.final('base64');
Chunks Implementation
var crypto = require('crypto'),
iv = new Buffer(''),
key = new Buffer('abcgthdbgfhyjhuy', 'hex'),
cipher = cypto.createCipheriv('aes-128-ecb', key, iv),
chunks = [];
chunks.push(cipher.update(
new Buffer(JSON.stringify({key: "abcgthdbgfhyjhuy"}), 'utf8'),
'buffer', 'base64'));
chunks.push(cipher.final('base64'));
var encryptedString = chunks.join('');
console.log('encryptedString: ', encryptedString);
ONE WHERE I USED input_string AND key AS BUFFER
var key = new Buffer(key, "utf8");
var input_string = new Buffer(input_string, "utf8");
IV USAGE
var key = new Buffer(key, "utf8");
var input_string = new Buffer(input_string, "utf8");
var iv = new Buffer(16); // 16 byte buffer with random data
iv.fill(0); // fill with zeros
var cipher = crypto.createCipher('aes-128-ecb', key, iv);
var crypted = cipher.update(input_string,'utf8','base64')
crypted += cipher.final('base64');

SJCL not concatenating bit arrays

I am trying to use RNCryptor-JS which uses SJCL but for some reason, SJCL bit array concatenation does not seem to work.
var SALT_SIZE = 64/8;
var plaintext = "Hello, World!";
var password = "myPassword";
function keyForPassword(password, salt){
// Using CryptoJS for pbkdf2, aes, sha256, and random word arrays
var pbkdf2_key = CryptoJS.PBKDF2(
password,
salt,
{
keySize: 256/32,
iterations: 1000,
hasher: CryptoJS.algo.SHA256
}
);
return pbkdf2_key;
}
var encryption_salt = CryptoJS.lib.WordArray.random(SALT_SIZE);
var encryption_key = keyForPassword(password, encryption_salt);
var hmac_salt = CryptoJS.lib.WordArray.random(SALT_SIZE);
var hmac_key = keyForPassword(password, hmac_salt);
var iv = CryptoJS.lib.WordArray.random(128/8);
var version = sjcl.codec.hex.toBits("03");
var options = sjcl.codec.hex.toBits("01");
var message = sjcl.bitArray.concat(version, iv);
message = sjcl.bitArray.concat(message, encryption_salt);
message = sjcl.bitArray.concat(message, hmac_salt);
message = sjcl.bitArray.concat(message, iv);
// Progressive cipher
var aesEncryptor = CryptoJS.algo.AES.createEncryptor(encryption_key, {iv: iv});
var ciphertext = aesEncryptor.process(plaintext);
message = sjcl.bitArray.concat(message, ciphertext);
var hmac = new sjcl.misc.hmac(hmac_key).encrypt(message);
var encrypted_data = sjcl.bitArray.concat(message, hmac);
var output = sjcl.codec.hex.fromBits(encrypted_data);
console.log(output);
When I log the output of message after the first set of sjcl.bitArray.concat is done, all that returns is the first concatenation of version and iv. The final hex output is just that first concatenation and hmac concatenated. This reinforces my suspicion that it might be CryptoJS's fault because the output concatenation works and is between two sjcl variables.
I tried using SJCL random bit arrays but had some trouble. SJCL's generator, prng, did not work when using
new sjcl.prng.randomWords(32/4);
or
new sjcl.prng(32/4);
And sjcl.random.randomWords does not seem to work anymore.
CryptoJS (WordArray) and SJCL (bitArray) have different internal representations of data. You can't simply concatenate them.
The easiest way would be probably to encode it into an intermediate format such as Hex and let the other side decode into its internal format:
message = sjcl.bitArray.concat(version, sjcl.codec.hex.toBits(iv.toString()));
WordArray#toString() automatically uses Hex encoding. You would have to do this for all lines, but this is a little overkill, since you can concatenate Hex strings as strings:
message = sjcl.codec.hex.toBits("03" + iv + encryption_salt + hmac_salt + iv);
This should work as expected, because adding a WordArray such as iv to a string automatically calls its toString() function which in turn produces a big-endian hex-encoded string.
I wonder why you're using iv twice. Perhaps you meant options on one of them.
What needs to change:
function convert(wordArray){
return sjcl.codec.hex.toBits(wordArray.toString());
}
var message = "0301" + encryption_salt + hmac_salt + iv;
var ciphertext = CryptoJS.AES.encrypt(plaintext, encryption_key, {iv: iv}).ciphertext;
message += ciphertext;
message = sjcl.codec.hex.toBits(message);
var hmac = new sjcl.misc.hmac(convert(hmac_key)).encrypt(message);
var encrypted_data = sjcl.bitArray.concat(message, hmac);
var output = sjcl.codec.hex.fromBits(encrypted_data);
console.log(output);

Encrypting data with forgejs on the client side, and decrypting with ruby

For a given project, I'm looking to encrypt a piece of data with AES 256, and then RSA encrypt the key. I've been using Forge and the Encryptor gem in ruby, and i can't seem get matching encryption values:
var key = 'strengthstrengthstrengthstrength';
var iv = 'cakecakecakecakecakecakecakecake';
var cipher = forge.aes.createEncryptionCipher(key, 'CBC');
cipher.start(iv);
cipher.update(forge.util.createBuffer("some string"));
cipher.finish();
var encrypted = cipher.output;
console.log(btoa(encrypted.data)); // outputs: CjLmWObDO2Dlwa5tJnRBRw==
Then in IRB:
Encryptor.encrypt 'some string', :key => 'strengthstrengthstrengthstrength', :key => 'cakecakecakecakecakecakecakecake'
Base64.encode64 _
# outputs: C9Gtk9YfciVMJEsbhZrQTw==\n
Over using string values for Key & IV, tried:
var key = forge.random.getBytesSync(32);
var iv = forge.random.getBytesSync(32);
Then doing a btoa() call on each of them. Using the Base64.decode64 on the ruby side, before passing them to Encryptor.decrypt, but still no luck.
Any idea where i've gone wrong?
I managed to get it to work. Since i'm just using one key to encrypt one value, i just used the key as the IV & Salt as well. This is not recommended if you are using the key to encrypt multiple values. You will then need to generate proper salt & iv values.
Also the gen key value is a pretty poor way, as Math.random is not secure. Just ran out of time to do this properly, but works okay for this case.
var Encryption = (function () {
var api = {
getKey: function () {
var possible = "ABCDEFabcdef0123456789";
var key = '';
for (var i = 0; i < 32; i++) {
key += possible.charAt(Math.floor(Math.random() * possible.length));
}
return key;
},
encryptPII: function (rawKey, value) {
var salt = rawKey;
var iv = rawKey;
var key = forge.pkcs5.pbkdf2(rawKey, salt, 2000, 32);
var cipher = forge.aes.createEncryptionCipher(key, 'CBC');
cipher.start(iv);
cipher.update(forge.util.createBuffer(value));
cipher.finish();
return btoa(cipher.output.data);
}
};
return api;
})();
The rawKey is the value returned from getKey(). The value property is the the string to be encrypted. I use the rawkey for iv & salt values, generate a key in the same way that the Encryptor gem in ruby does. Use forge to then encrypt the string value.
If i take the base64, decode it in ruby, and pass the same rawKey value to the encryptor gem for key, salt and iv, it works.

Categories