I'm working with a project that currently is doing encryption in a salesforce apex class (using the Crypto library) and that logic needs to be moved into a javascript file. The node.js package I'm trying to use to do the encryption is node-rsa.
Here's the code that currently exists in apex:
String algName = 'RSA';
blob signature;
String signGen = '';
String pKey = 'MIIEvgIBADANBgkqhkiG<rest of key snipped>';
String payload = 'some payload';
blob privateKey = EncodingUtil.base64Decode(pKey);
blob input = Blob.valueOf(payload);
signature = Crypto.sign(algName, input, privateKey);
signGen = EncodingUtil.base64Encode(signature);
And here's the initial javascript implementation:
var tmp = forge.util.decode64(pKey);
var privateKey2 = new NodeRSA(tmp);
payload = 'some payload
var encrypted = key.encrypt(payload, 'base64');
The problem I'm having is that the line:
var privateKey2 = new NodeRSA(tmp);
is causing the following error:
Invalid PEM format
The private key that the node-rsa uses in their example has markets at the beginning and end of the key of:
---- BEGIN RSA PRIVATE KEY -----
---- END RSA PRIVATE KEY -----
So I'm not sure if I have to somehow indicate to the node-rsa library that this key is in a different format. Or maybe there's another RSA javascript library I could try using?
I left you a response for how to do this using forge here: https://github.com/digitalbazaar/forge/issues/150
var pkey = 'some base64-encoded private key';
var pkeyDer = forge.util.decode64(pkey);
var pkeyAsn1 = forge.asn1.fromDer(pkeyDer);
var privateKey = forge.pki.privateKeyFromAsn1(pkeyAsn1);
// above could be simplified if pkey is stored in standard PEM format, then just do this:
// var pkey = 'some private key in pem format';
// var privateKey = forge.pki.privateKeyFromPem(pkey);
var payload = 'some string payload';
var md = forge.md.sha1.create();
md.update(payload, 'utf8');
var signature = privateKey.sign(md);
var signature64 = forge.util.encode64(signature);
// signature64 is now a base64-encoded RSA signature on a SHA-1 digest
// using PKCS#1v1.5 padding... see the examples for other padding options if necessary
Related
I'm generating signatures in C# using the Bouncy Castle library as follows:
var privateKeyBase64 = "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgg8/MbvGGTDMDpfje8lQBZ8st+l3SK7jRl7OWlyUl/VagCgYIKoZIzj0DAQehRANCAARkQIUpkKbxmJJicvG450JH900JjmJOGdlMCZl3BIXvPBBKkaTMsQc6l3O4vJA6Yc23nr3Ox/KwFUl6gdo5iTqV";
var publicKeyBase64 = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZECFKZCm8ZiSYnLxuOdCR/dNCY5iThnZTAmZdwSF7zwQSpGkzLEHOpdzuLyQOmHNt569zsfysBVJeoHaOYk6lQ==";
var plainText = "aaa";
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
// Sign
var privateKey = PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKeyBase64));
var signer = SignerUtilities.GetSigner(X9ObjectIdentifiers.ECDsaWithSha512.Id);
signer.Init(true, privateKey);
signer.BlockUpdate(plainTextBytes, 0, plainTextBytes.Length);
var signature = signer.GenerateSignature();
var signatureBase64 = Convert.ToBase64String(signature);
Console.WriteLine("Signature base64: {0}", signatureBase64);
// Verify
Console.WriteLine("-------------------- Verifying signature ");
Console.WriteLine("Public key base64: {0}", publicKeyBase64);
var publicKey = PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKeyBase64));
var verifier = SignerUtilities.GetSigner(X9ObjectIdentifiers.ECDsaWithSha512.Id);
verifier.Init(false, publicKey);
verifier.BlockUpdate(plainTextBytes, 0, plainTextBytes.Length);
Console.WriteLine("Signature valid?: {0}", verifier.VerifySignature(Convert.FromBase64String(signatureBase64)));
// Prints: MEUCIBEcfv2o3UwqwV72CVuYi7HbjcoiuSQOULY5d+DuGt3UAiEAtoNrdNWvjfdz/vR6nPiD+RveKN5znBtYaIrRDp2K7Ks=
On the node.js app, I'm using jsrsasign to verify the generated signature on same payload as follows:
let rs = require('jsrsasign');
let pem = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZECFKZCm8ZiSYnLxuOdCR/dNCY5iThnZTAmZdwSF7zwQSpGkzLEHOpdzuLyQOmHNt569zsfysBVJeoHaOYk6lQ== -----END PUBLIC KEY-----`;
let plainText = 'aaa';
let signature = 'MEUCIBEcfv2o3UwqwV72CVuYi7HbjcoiuSQOULY5d+DuGt3UAiEAtoNrdNWvjfdz/vR6nPiD+RveKN5znBtYaIrRDp2K7Ks=';
let signatureHex = Buffer.from(signature, 'base64').toString('hex');
var sig = new rs.Signature({alg: 'SHA512withECDSA'});
sig.init(pem);
sig.updateString(plainText);
var isValid = sig.verify(signatureHex);
console.log('Is signature valid: ', isValid); // <--- returns false always!
I'd be grateful if you could assist me in identifying what the issue could be.
I'd also accept suggestions for other Node.js libraries that can validate signatures generated using ECDSA with SHA512.
This is very likely a bug in the jsrsasign library where it generates wrong ECDSA signatures with hash functions that have an output larger than the bit length of n in bits. Awaiting the answer of the author, see more details here https://github.com/kjur/jsrsasign/issues/394.
Using another package elliptic curves package and generating + truncating the hash of the payload manually, I was able to verify that the signatures generated by Bouncy Castle in C# are valid:
let elliptic = <any>window.require('elliptic');
let hash = <any>window.require('hash.js')
let ec = new elliptic.ec('p256');
// Same key from my original post, just hex encoded
let keyPair = ec.keyFromPrivate("83CFCC6EF1864C3303A5F8DEF2540167CB2DFA5DD22BB8D197B396972525FD56");
let pubKey = keyPair.getPublic();
// The first 32 bytes (256 bits) of the SHA521 hash of the payload "aaa"
// sha512('aaa') => d6f644b19812e97b5d871658d6d3400ecd4787faeb9b8990c1e7608288664be77257104a58d033bcf1a0e0945ff06468ebe53e2dff36e248424c7273117dac09
let msgHash = 'd6f644b19812e97b5d871658d6d3400ecd4787faeb9b8990c1e7608288664be7'
// Same signature from original post above
let signatureBase64 = 'MEUCIBEcfv2o3UwqwV72CVuYi7HbjcoiuSQOULY5d+DuGt3UAiEAtoNrdNWvjfdz/vR6nPiD+RveKN5znBtYaIrRDp2K7Ks='
let signatureHex = Buffer.from(signatureBase64, 'base64').toString('hex');
let validSig = ec.verify(msgHash, signatureHex, pubKey);
console.log("Signature valid?", validSig); // <------- prints TRUE
I have following code in Java.
String secretString = 'AAABBBCCC'
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
securerandom.setSeed(secretString.getBytes());
kgen.init(256, securerandom);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] byteContent = content.getBytes("utf-8");
byte[] cryptograph = cipher.doFinal(byteContent);
String enc1 = Base64.getEncoder().encodeToString(cryptograph);
return enc1;
I need to implement it in JavaScript/Node.js, however I can only figure out the last half in js like below
'use strict';
const crypto = require('crypto');
const ALGORITHM = 'AES-256-ECB';
const secretString = 'AAABBBCCC'
// missing part in JS (how to convert secretString to key)
function encrypt(plaintext, key) {
const cipher = crypto.createCipheriv(ALGORITHM, key, Buffer.alloc(0));
return cipher.update(plaintext, 'utf8', 'base64') + cipher.final('base64');
}
For the previous Java part( from secretString to key generated by KeyGenerator), I don't know how to implement it in JavaScript, neither do I know whether there is a thing like KeyGenerator in JavaScript world could help me to do the heavy lifting work.
I think this is what you're after:
const crypto = require("crypto-js");
// Encrypt
const ciphertext = crypto.AES.encrypt('SOMETHING SECRET', 'secret key 123');
// Decrypt
const bytes = crypto.AES.decrypt(ciphertext.toString(), 'secret key 123');
const decryptedData = bytes.toString(crypto.enc.Utf8);
console.log(decryptedData);
https://runkit.com/mswilson4040/5b74f914d4998d0012cccdc0
UPDATE
JavaScript does not have a native equivalent for key generation. The answer is to create your own or use a third party module. I would recommend something like uuid for starters.
You can use crypto.randomBytes().
As per its documentation:
Generates cryptographically strong pseudo-random data. The size argument is a number indicating the number of bytes to generate.
Also, it uses openssl's RAND_bytes API behind scenes
I need send a password from Angular App to Spring boot backend and i need encrypt this password. I try to use AES to encrypt the password and RSA to encrypt the AES generated key, but i dont know how do this.
My code:
Angular 2 Side:
EncryptService:
public generateRandomKey( keyLength: number){
let chars =
`0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmn
opqrstuvwxyz*&-%/!?*+=()`;
let stringKey = "";
for (let i=0; i < keyLength; i++) {
var rnum = Math.floor(Math.random() * chars.length);
stringKey += chars.substring(rnum,rnum+1);
}
return stringKey;
}
public aesEncrypt( phrase: string ){
let key = this.generateRandomKey(50);
let aesEncrypt = cryptojs.AES.encrypt(phrase, key);
let aesKey = aesEncrypt.key
let aesIv = aesEncrypt.iv;
let encryptMessage = aesEncrypt.toString();
return [encryptMessage, aesKey, aesIv];
}
public buildPubkFromPem( pem: string ){
return forge.pki.publicKeyFromPem(pem);
}
Component:
let pubKey = this.encryptService.buildPubkFromPem(environment.publicKey);
let data = this.encryptService.aesEncrypt(map.passwd);
let passwdEncrypt = data[0];
let aesKey = data[1];
let encryptAesKey = pubKey.encrypt(aesKey.toString());
this.pendingRequestService
.send({'passwd': passwdEncrypt, 'encryptAesKey': encryptAesKey)
.finally(()=>{this.popupSignService.isLoading = false;})
.subscribe(
result => {
console.log("OK", result);
}
);
}
Backend Side:
Controller:
public ResponseEntity signDocs(
#RequestParam(value = "passwd") String passwd,
#RequestParam(value = "encryptAesKey") String encryptAesKey,
){
try{
signatureService.decryptMessage(passwd, encryptAesKey);
}catch( Exception ex ){
ex.printStackTrace();
}
Service:
public String decryptMessage( String encrypMsg, String encryptAesKey ){
// Load private key from P12
ClassPathResource pkcs12 = new ClassPathResource("ssl/file.p12");
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(pkcs12.getInputStream(), p12Password.toCharArray());
String alias = (String)keystore.aliases().nextElement();
PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, p12Password.toCharArray());
// Decrypt
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.PRIVATE_KEY, privateKey);
System.out.println(encryptAesKey);
byte[] doFinal = cipher.doFinal(encryptAesKey.getBytes());
String aesKey = new String(cipher.doFinal(Base64.decodeBase64(encryptAesKey)), "UTF-8");
return aesKey;
}
when i try to decrypt the AES encrypted key:
javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes
I think it's related when I encrypted the AES key.
Can somebody help me?
Thanks in advance.
EDIT
If i encrypt the string "Some text" with the public key using forge, and send the encrypted text to the backend does not work. If i ecrypt and decrypt using the same public and private key work from javascript to javascript and java to java.
Javascript code:
let encryptedText = pubKey.encrypt(this.forgeService.encodeUTF8("Some text"));//<-- Encrypt "Some text"
I dont know if something is loosing (changing) when i send the encrypted text in the network.
I believe you have basic problem with encoding data (along others)
let encryptAesKey = pubKey.encrypt(aesKey.toString());
You are encrypting string representation of the key (not the key itself) and I believe JS returns the toString() in the hex format (please correct me if I am wrong). The result would be twice as long as the original key.
while i Java you are trying to decrypt the encrypted hex string rssulting in a byte array, which doesn't fit into expected 256 bit size
edit:
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.PRIVATE_KEY, privateKey);
is wrong, you have to use Cipher. DECRYPT_MODE.
next :
String aesKey = new String(cipher.doFinal(.. DO NOT make a string from the key (encode when you want to print it), keep it as byte array. You are loosing nonprintable values.
you should be specific with the encryption mode
Cipher cipher = Cipher.getInstance("rsa/ecb/nopadding");
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.
So I have a piece of code which encrypts a string with a passphrase. It uses the CryptoJS AES encrypt function (CryptoJS.AES.encrypt) and looks like this...
CryptoJS.AES.encrypt(data, password).toString();
Going forward, I don't want to be using CryptoJS, as it's officially deprecated/not maintained, and I would instead like to use Forge.js. I've attempted to read through the Forge.js docs on GitHub to find a solution, but haven't been able to find anything which uses passphrases instead of manually creating the key & IV.
I've taken a look at the CryptoJS archive at https://code.google.com/archive/p/crypto-js/ and it seems that if the encrypt function is passed a string as the second argument (key) it's used as a passphrase to derive a key and IV. But it doesn't detail how it does this.
It seems that base64 decoding the result gives a string that starts with Salted__ then a comma and then the encrypted blob of binary text, and I'm unsure even how I would pass the "salt" through to Forge.
How would I go about decrypting this blob of data using Forge.js only?
CryptoJS supports OpenSSL's EVP_BytesToKey function, which derives a key and IV from a freshly generated salt and password with one round of MD5. There is an example on the forge documentation page:
Using forge in node.js to match openssl's "enc" command line tool
(Note: OpenSSL "enc" uses a non-standard file format with a custom key
derivation function and a fixed iteration count of 1, which some
consider less secure than alternatives such as OpenPGP/GnuPG):
var forge = require('node-forge');
var fs = require('fs');
// openssl enc -des3 -in input.txt -out input.enc
function encrypt(password) {
var input = fs.readFileSync('input.txt', {encoding: 'binary'});
// 3DES key and IV sizes
var keySize = 24;
var ivSize = 8;
// get derived bytes
// Notes:
// 1. If using an alternative hash (eg: "-md sha1") pass
// "forge.md.sha1.create()" as the final parameter.
// 2. If using "-nosalt", set salt to null.
var salt = forge.random.getBytesSync(8);
// var md = forge.md.sha1.create(); // "-md sha1"
var derivedBytes = forge.pbe.opensslDeriveBytes(
password, salt, keySize + ivSize/*, md*/);
var buffer = forge.util.createBuffer(derivedBytes);
var key = buffer.getBytes(keySize);
var iv = buffer.getBytes(ivSize);
var cipher = forge.cipher.createCipher('3DES-CBC', key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(input, 'binary'));
cipher.finish();
var output = forge.util.createBuffer();
// if using a salt, prepend this to the output:
if(salt !== null) {
output.putBytes('Salted__'); // (add to match openssl tool output)
output.putBytes(salt);
}
output.putBuffer(cipher.output);
fs.writeFileSync('input.enc', output.getBytes(), {encoding: 'binary'});
}
// openssl enc -d -des3 -in input.enc -out input.dec.txt
function decrypt(password) {
var input = fs.readFileSync('input.enc', {encoding: 'binary'});
// parse salt from input
input = forge.util.createBuffer(input, 'binary');
// skip "Salted__" (if known to be present)
input.getBytes('Salted__'.length);
// read 8-byte salt
var salt = input.getBytes(8);
// Note: if using "-nosalt", skip above parsing and use
// var salt = null;
// 3DES key and IV sizes
var keySize = 24;
var ivSize = 8;
var derivedBytes = forge.pbe.opensslDeriveBytes(
password, salt, keySize + ivSize);
var buffer = forge.util.createBuffer(derivedBytes);
var key = buffer.getBytes(keySize);
var iv = buffer.getBytes(ivSize);
var decipher = forge.cipher.createDecipher('3DES-CBC', key);
decipher.start({iv: iv});
decipher.update(input);
var result = decipher.finish(); // check 'result' for true/false
fs.writeFileSync(
'input.dec.txt', decipher.output.getBytes(), {encoding: 'binary'});
}
This example is shown for Triple DES, but it works in the same way for AES. You just have to change the ivSize to 16.