I am trying to create a RSA key pair in JavaScript using JSBN and transfer the public key to Crypto++. Then, I am trying to encrypt a message in Crypto++ and send it back to JavaScript and decrypt it.
But I am relatively new at this, so I think I am doing something wrong ( data is not getting decrypted ofc )
any help would be greatly appreciated :D
Here is my cpp code
Integer n(nStr->c_str()),
e("0x10001");
RSA::PublicKey pubKey;
pubKey.Initialize(n, e);
AutoSeededRandomPool rng;
if (!pubKey.Validate(rng, 3))
throw std::exception("Rsa private key validation failed");
////////////////////////////////////////////////
// Encryption
RSAES_PKCS1v15_Encryptor enc(pubKey);
StringSource ss1(data, true,
new PK_EncryptorFilter(rng, enc,
new StringSink(retStr)
));
std::string retData2 = "";
StringSource ss2((const byte*)retStr.data(), retStr.size(), true,
new Base64Encoder(
new StringSink(retData2)
));
retStr = retData2;
And my javascript code
// nStr in CPP is "0x" + localStorage.getItem("rsa_public") from javascript
// data in CPP is "secret"
var rsa = new RSAKey();
var publickey = localStorage.getItem("rsa_public");
var privatekey = localStorage.getItem("rsa_private");
rsa.setPrivate(publickey, "10001", privatekey);
alert(b64tohex(dec) + "\n" + rsa.encrypt("secret")); <-- these don't match at all .. and ofc rsa.decrypt returns null
dec in javascript is retStr from CPP
The PKCS#1 v1.5 Padding that is used by JSBN and your Crypto++ code is a random padding, so if you encrypt data with the same key it will look differently. You have to check whether your implementation works by encrypting on one end and decrypting on the other in both directions.
RSAES_PKCS1v15_Encryptor enc(pubKey);
StringSource ss1(data, true,
new PK_EncryptorFilter(rng, enc,
new StringSink(retStr)
));
...
StringSource ss2((const byte*)retStr.data(), retStr.size(), true,
new Base64Encoder(
new StringSink(retData2)
));
I'm not sure this is correct for interop'ing with Javascript and JSBN. It is correct stand alone, and it is correct for interop'ing with OpenSSL.
Crypto++ uses an early Base64 encoding scheme. It was seen in email and other standards of the time (the time was around the 1990s). The alphabet uses the plus (+) and forward slash (/) characters.
The Javascript and JSON technologies like JSON Web Keys (JWKs) tend to favor the Base64 encoding using the URL or Web Safe alphabet. The alphabet uses the minus (-) and underscore (_) characters.
Both the old and new Base64 encoding schemes are specified in RFC 4648, The Base16, Base32, and Base64 Data Encodings.
You should fetch the patch for the Base64URLEncoder, and apply it in-place over top of the Crypto++ sources. After you patch, you will have the existing Base6Encoder and the new Base64URLEncoder. Finally, recompile and reinstall the library. You have to patch it because its not part of the Crypto++ library as written and offered by Wei Dai.
Then, do the following:
RSAES_PKCS1v15_Encryptor enc(pubKey);
string encoded;
StringSource ss(data, true,
new PK_EncryptorFilter(prng, enc,
new Base64URLEncoder(
new StringSink(encoded)
)));
// Print encoded cipher text
cout << encoded << endl;
return encoded;
As for "these don't match at all..." - I think that's expected. RSA encryption uses randomized padding so that when you encrypt messages m1 and m2, the cipher text is different. Its called "semantic security" and its a strong (stronger?) notion of security. This way, the bad guy can't tell when the same message has been sent twice.
Related
I have a custom payment method on WordPress that processes the payment through a NodeJS API. For obvious reasons, I should encrypt some data, like credit card numbers and cvv2s.
This is how I encrypt my fields in PHP:
$cvv2 = openssl_encrypt(
$_POST['cvv2'],
'AES-256-CBC',
"2c2702d12c747661358dfe9b77425628", // Testing key, thats why its set like this
0
);
This is the output ciphered text: CRqJRoqHriB7X/u1bwSE1Q==
This is how I try to decrypt in JS
const key = "2cea02d12c747661358dfe9b77425628";
const dec = CryptoJS.AES.decrypt(cvv2,key);
console.log(dec.toString(CryptoJS.enc.Utf8))
But the output is always an empty string.
This is me decrypting it using an online tool
I am really frustrated as I cannot seem to make this work, no matter how much I read online.
What could be the problem?
In the CryptoJS code, the key must be passed as WordArray (using a suitable encoder) and the zero IV (16 0x00 values) used implicitly in the PHP code must be specified explicitly.
The ciphertext must be passed as CipherParams object or Base64 encoded (the latter is implicitly converted to a CipherParams object):
const key = CryptoJS.enc.Utf8.parse("2cea02d12c747661358dfe9b77425628");
const iv = CryptoJS.lib.WordArray.create([], 16);
const ciphertext = "CRqJRoqHriB7X/u1bwSE1Q==";
const dec = CryptoJS.AES.decrypt(ciphertext, key, {iv: iv});
console.log(dec.toString(CryptoJS.enc.Base64));
console.log(dec.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Note that a static IV is insecure.
I need to authenticate myself via PHP script on remote website, and website uses JS-based RSA encryption for passwords. Here's the code from website:
function rsa_encrypt(strPlainText) {
var strModulus = "some_random_string";
var strExponent = "10001";
var rsa = new RSAKey();
rsa.setPublic(strModulus, strExponent);
var res = rsa.encrypt(strPlainText);
if (res) {
return res;
}
return false;
}
Browsed a lot of topics on this website, and found that the recommended way is to use phpseclib (if there's another one, let me know). However, using basic example from http://phpseclib.sourceforge.net/rsa/examples.html#encrypt,enc2 I get just an empty page. I entered some_random_string into $rsa->loadKey('...'); - not sure if I did it right? However, I can't see a place to enter strExponent (which is 10001) in this example.
So I tried another solution - Encrypt and Decrypt text with RSA in PHP and modified my code to look the following:
include('Crypt/RSA.php');
$privatekey = "some_random_string";
$rsa = new Crypt_RSA();
$rsa->loadKey($privatekey);
$plaintext = new Math_BigInteger('10001');
echo $rsa->_exponentiate($plaintext)->toBytes();
However, I get this error:
Fatal error: Call to a member function abs() on null in Math\BigInteger.php on line 1675
The solution was posted some time ago, so I guess something got changed in phpseclib library during this time, and I'm just not sure how to re-modify my code.
Popular formats for RSA keys typically contain both the exponent and the modulus within them. See, for example, my answer to I understand the mathematics of RSA encryption: How are the files in ~/.ssh related to the theory? for a more detailed discussion of one particular type of key format.
If you have the exponent and modulo as distinct values try doing this:
$rsa->loadKey([
'e' => new Math_BigInteger('10001', 16),
'n' => new Math_BigInteger('some_random_string', 16);
]);
Note the , 16 bit. 65537 (10001 in hex) is a common RSA exponent. Math_BigInteger assumes, by default, that the number being passed to it is in base-10, unless you specifically tell it otherwise. One requirement of RSA is that e be coprime to either phi(n) or lcm(n). 65537 is trivially coprime because it is prime. 10001 is not prime. It can be factored into 73*137.
I am looking for a way, to encrypt a password in CryptoJS and then decrypt it in php. I have looked at other posts concerning the same subject, but I need someone to explain all that IV and key stuff.
My CryptoJS encryption code:
password = document.getElementById("usrp").value;
password = CryptoJS.AES.encrypt(password, <?php echo '"'.$_SESSION['adk'].'"'; ?>);
IV
You're using the CBC mode of operation which requires an IV. If you use a static IV for all your ciphertexts then you miss out on an important property of encryption which is semantic security. If you use the same IV, attackers may observe your ciphertext and determine whether you sent the same plaintext with the same key, because the ciphertext will be the same.
To prevent that, you can generate a random IV for each encryption you do. The IV doesn't have to be secret, but it has to be unpredictable. Since it doesn't have to be secret, you can simply prepend it to the ciphertext and slice it off before decryption or send it otherwise in a structured fashion. You need to use IV during decryption. Otherwise, the first block will be different from the original plaintext.
Keep in mind that CryptoJS' WordArray.random() uses Math.random() internally which is not cryptographically secure. It would be better to use a better randomness source. You can use this drop in replacement from my project of that function for semi-modern browsers which uses the WebCrypto API:
(function(C){
var WordArray = C.lib.WordArray;
var crypto = window.crypto;
var TypedArray = Int32Array;
if (TypedArray && crypto && crypto.getRandomValues) {
WordArray.random = function(nBytes){
var array = new TypedArray(Math.ceil(nBytes / 4));
crypto.getRandomValues(array);
return new WordArray.init(
[].map.call(array, function(word){
return word
}),
nBytes
);
};
} else {
console.log("No cryptographically secure randomness source available");
}
})(CryptoJS);
and use it like this:
var iv = CryptoJS.lib.WordArray.random(128/8);
Key
The key is trickier, because it needs to be kept confidential. The basic way is:
Let the user type in the password that is also present on the server and derive the key from the password by for example using PBKDF2 which CryptoJS also provides. Perfectly secure as long as you use TLS and the developers don't change the code.
I have successfully implemented Rob Napier's AES encryption method for iOS in one of my apps. I would now like to be able to encrypt and decrypt files from that app with my JavaScript implementation. I am using FileReader to get a local from from the user and loading it with
reader.readAsArrayBuffer(file);
When this is done the file gets encrypted using the Stanford JavaScript Crypto Library and finally the encrypted file can be downloaded:
reader.onloadend = function(e) {
var content = new Uint8Array(e.target.result);
var utf8 = "";
for (var i = 0, len = content.length; i < len; i++) {
utf8 += String.fromCharCode(content[i]);
}
var b64 = btoa(utf8);
//we finally encrypt it
var encrypted = sjcl.encrypt(password, b64,{ks:256});
var json = JSON.parse(encrypted);
var ciphertext = json.ct;
a.attr('href', 'data:application/octet-stream,' + ciphertext);
a.attr('download', file.name + '.encrypted');
step(4);
};
reader.readAsArrayBuffer(file);
The problem is that the encrypted file is much larger than the original. This is not the case in my iOS implementation which works just fine. And of course it can't be decrypted without error. In fact the resulting file will have 0 bytes of size.
I am hoping someone can point out the error in my code to me. That would really be great.
#Duncan:
Thank you. I looked into that but I'm not really sure about all the steps I have to take. Especially what they mean in code. Maybe somebody could help me out here. Thanks a lot!
Encryption
Generate a random encryption salt
Generate the encryption key using PBKDF2 (see your language docs for how to call this). Pass the password as a string, the random encryption salt, and 10,000 iterations.
Generate a random HMAC salt
Generate the HMAC key using PBKDF2 (see your language docs for how to call this). Pass the password as a string, the random HMAC salt, and 10,000 iterations.
Generate a random IV
Encrypt the data using the encryption key (above), the IV (above), AES-256, and the CBC mode. This is the default mode for almost all AES encryption libraries.
Pass your header and ciphertext to an HMAC function, along with the HMAC key (above), and the PRF "SHA-256" (see your library's docs for what the names of the PRF functions are; this might also be called "SHA-2, 256-bits").
Put these elements together in the format given above.
Encrypt the data using the encryption key (above), the IV (above), AES-256, and the CBC mode. This is the default mode for almost all AES encryption libraries.
This is a false assumption, according to this sjcl uses ccm as a default mode.
I want to encrypt some data in a ruby app and then decode it in a nodejs app. I have been trying to get this to work and now I am just trying to encrypt the same piece of data in both languages to get the same result but I can't seem to do it.
//js
var crypto = require('crypto');
var key = crypto.createHash('sha1').update('key').digest('hex');
console.log(key); // a62f2225bf70bfaccbc7f1ef2a397836717377de
var encrypted = "";
var cipher = crypto.createCipher('bf-cbc', key);
encrypted += cipher.update('text');
encrypted += cipher.final('hex');
console.log(encrypted); //outputs 4eafd5542875bd3c
So it looks like I get a hexadecimal string from the encoding.
#ruby
require 'openssl'
require 'digest/sha1'
c = OpenSSL::Cipher::Cipher.new("bf-cbc")
c.encrypt
# your pass is what is used to encrypt/decrypt
c.key = key = Digest::SHA1.hexdigest("key")
p key # a62f2225bf70bfaccbc7f1ef2a397836717377de
e = c.update("text")
e << c.final
p e # 皋?;??
Is there some sort of encoding issue that I am missing. I tried to base64 decode e but that didn't produce the same result as the node app. Any pointers?
UPDATE: So this is as close as a friend and I can get: https://gist.github.com/a880ea13d3b65a21a99d. Sheesh, I just want to encrypt something in ruby and decrypt it in node.
UPDATE2: Alright, the code in this issue gets me a lot of the way there: https://github.com/joyent/node/issues/1395
There are several subtle things that make this fail. The most important one - you are not specifying an IV in your code, so a random value will be generated for you. You would notice that you couldn't even decrypt your ciphertext within the same programming language this way.
So you need to provide an explicit IV to both implementations. But before I show you the code, some advice:
Key generation:
Blowfish operates on 64 bit blocks, its key size varies, but OpenSSL (which currently powers both Ruby's and node.js' cipher implementation) uses 128 bit by default, that is 16 bytes.
So your key violates two principles - the first: it's simply too long. It's the hex representation of a SHA-1 hash, which is 20 bytes * 2 = 40 bytes instead of 16. Most of the time this is fine, because the implementation truncates the values appropriately, but that is something you should not depend on.
The second mistake, much more severe, is that you use the hex representation instead of the raw bytes: big security issue! Hex characters are not random at all, so in effect you reduce the entropy of your input to half the length (because the underlying bytes were random).
A secure way to generate random keys is using OpenSSL::Random
key = OpenSSL::Random.random_bytes(cipher_key_len)
A third mistake is to keep your key hard-coded in the sources. It's a bad idea. The least you should do is to store it elsewhere on the file system, where access is tightly restricted. See also my answer to another question. The key should be stored out-of-band and only loaded dynamically within the application.
Cipher:
Blowfish grows old. It's still considered unbroken in the sense that brute-forcing it is the only way to break it. But a search space of 2^64 is not out of reach for resourceful attackers. So you should indeed move on to AES.
Padding:
OpenSSL pads using PKCS5Padding (also known as PKCS7Padding) by default. Ruby profits from this and my bet is node.js utilizes this, too - so you should be safe on this.
Now to the working solution. We need to generate an IV, Blowfish requires it to be 64 bit - 8 bytes. You will need rbytes to get secure random numbers in node. The IV may be hardcoded in your sources (it's public information, no security impact) - but it must be the same on both sides. You should pregenerate a value and use it for both node.js and Ruby.
/*node.js*/
var rbytes = require('rbytes');
var iv = rbytes.randomBytes(8);
/*see advice above - this should be out-of-band*/
var key = rbytes.randomBytes(16);
var encrypted = "";
var cipher = crypto.createCipheriv('bf-cbc', key, iv);
encrypted += cipher.update('text');
encrypted += cipher.final('hex');
Now the Ruby part:
require 'openssl'
c = OpenSSL::Cipher::Cipher.new("bf-cbc")
c.encrypt
# should be out-of-band again
c.key = OpenSSL::Random.random_bytes(16)
# may be public but has to be the same for Ruby and node
iv = OpenSSL::Random.random_bytes(8)
c.iv = iv
e = c.update("text")
e << c.final
puts e.unpack('H*')[0]
Your cyphertext will be some random looking bytes. Those bytes can be expressed as hex, Base64 or in other ways. It looks as if your ruby code is outputting the raw bytes. I suggest that you convert those raw bytes to hex to make your comparison.
Looking at your code, you should also change from Blowfish ("bf") to AES. Blowfish has a 64-bit block size and is now obsolete.
You would do well to explicitly specify padding, PKCS7 is common
OK. I want to thank everyone for helping me out. Basically this thread here answers my question: https://github.com/joyent/node/issues/1395. I am going to go ahead and post the two programs in case anyone else has to go through this rigamarole. Keep in mind this isn't mean to be hardcore secure, this is a stepping stone for ruby encrypting data and node decrypting it. You will have to take more steps to make sure higher security measures are taken.
The code is located at this gist: https://gist.github.com/799d6021890f34734470
These were run on ruby 1.9.2p290 and node 0.4.10