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
Related
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 have been trying to figure out how to decrypt strings using javascript that were encoded using codeiginter's encryption library.
So far I found this as a guide php to js-mcrypt
But I could not figure out how to supply the iv variable.
Because codeiginter randomly generates it upon encryption.
My sample code is
//PHP Side
$this->encrypt->encode('apple','1234567');
//The result is : 2lek4Q1mz4CJtTy2ot/uJWlfeGKuGiUKuKkR5Utkwc1nSWjf3JqG8gOhNmS13mt25QVbgP/2QOuffpn7rhIOmQ==
//JS Side
var encrypted = '2lek4Q1mz4CJtTy2ot/uJWlfeGKuGiUKuKkR5Utkwc1nSWjf3JqG8gOhNmS13mt25QVbgP/2QOuffpn7rhIOmQ==';
var key = 'fcea920f7412b5da7be0cf42b8c93759';//md5 version of "1234567"
var iv = 'some 32 length string';// I don't know how to get the IV because it constantly change in PHP
var decrypted = mcrypt.Decrypt(atob(encrypted), iv, key, 'rijndael-256', 'cbc');
console.log(decrypted);
A random iv is generally pre-pended to the encrypted data.
Simple encryption of 5 bytes ('apple') with padding using 'rijndael-256' would produce 32 bytes of output. In this case the encrypted output is 88-bytes so the iv is probably there along with something else.
Also mcrypt is somewhat brain-dead in that it does not support the standard PKCS#7 (AKA PKCS#5) padding so that is also an interoperability problem.
Note: 'rijndael-256' means a block size of 256-bits, not a key size and AES is essentially Rijndael with a block size of 128-bits, it is best to use a block size of 128-bits and be compatible with AES.
I have used RNCryptor successfully in Objective-C, and now need to encrypt data for my iOS app from Javascript. But, this simple test fails...
<script type="text/javascript" src="js/utils/sjcl.js"></script>
<script type="text/javascript" src="js/utils/rncryptor.js"></script>
function testEncodeEncrypt_RN(plaintext) {
var secret = "rosebud";
var encrypted = RNCryptor.Encrypt(secret, plaintext);
var decrypted = RNCryptor.Decrypt(secret, encrypted);
console.log("decrypted to " + decrypted);
}
...with the error "Uncaught CORRUPT: pkcs#5 padding corrupt" thrown by sjcl.js.
I've tried various options objects, but I figure that no options ought to work for both Encrypt and Decrypt. Varying the input string and secret do no good, either. Any ideas?
/*
Takes password string and plaintext bitArray
options:
iv
encryption_salt
html_salt
Returns ciphertext bitArray
*/
RNCryptor.Encrypt = function(password, plaintext, options) {
Is plaintext a bitArray? If you're passing a UTF-8 string, you'll need to convert it with sjcl.codec.utf8String.toBits. There are also codecs for hex and base64 encoding. See the SJCL docs.
The latest versions of SJCL will accept strings and autoconvert them to bitArrays, but I probably won't touch the JS implementation myself again until I finish work on the v4 format (should be before the end of 2015). I am happy to accept pull requests.
Note that RNCryptor-js is not fully compatible with any of the other implementations (including the ObjC implementation). JavaScript is too slow to handle the 10,000 PBKDF2 iterations that are required for the v3 format, so it defaults to using 1,000. That means you have to modify the code on the other side to match (or configure JS to use 10,000, but it'll take 10x as long to deal with passwords). Look for the two .rounds configuration settings in RNCryptor.h.
One of the major goals of the v4 format is to make iteration count configurable to make JavaScript capable of interoperating (unfortunately by dramatically reducing the security of the encryption, but it's all JavaScript can handle today).
I'm trying to convert an ASP/VBScript OAuth library to VBA. One of the challenges is this line of code:
Get_Signature = b64_hmac_sha1(strSecret, strBaseSignature)
This function, b64_hmac_sha1 is actually a function contained in a JavaScript library. It appears to me that calling a JavaScript function from VBA is fairly impractical.
Because I know so little about encryption, it's not even clear to me what this b64_hmac_sha1 function does. Is HMAC SHA1 different from SHA1?
I half suspect I might be able to find some VBA code online to do what I need to do if I just understood what this function is actually doing. If I do not find an existing function, I could possibly write one that would use the .NET Cryptography library (you can actually call the .NET cryptography libraries from VBA if you know how).
I'm not looking for someone to convert this JavaScript to VBA. I'm only trying to understand what it is that this b64_hmac_sha1 function is outputting so I can try to find ways to achieve the same output in VBA if possible.
A copy of this JavaScript library is visible on this website. You'll have to scroll down past the VBScript to the JavaScript section.
http://solstice.washington.edu/solstice/ASP_Signing_REST_Example
Edit1:
OK, so here's the functions I ended up writing and using:
Public Function Base64_HMACSHA1(ByVal sTextToHash As String, ByVal sSharedSecretKey As String)
Dim asc As Object, enc As Object
Dim TextToHash() As Byte
Dim SharedSecretKey() As Byte
Set asc = CreateObject("System.Text.UTF8Encoding")
Set enc = CreateObject("System.Security.Cryptography.HMACSHA1")
TextToHash = asc.Getbytes_4(sTextToHash)
SharedSecretKey = asc.Getbytes_4(sSharedSecretKey)
enc.Key = SharedSecretKey
Dim bytes() As Byte
bytes = enc.ComputeHash_2((TextToHash))
Base64_HMACSHA1 = EncodeBase64(bytes)
Set asc = Nothing
Set enc = Nothing
End Function
Private Function EncodeBase64(ByRef arrData() As Byte) As String
Dim objXML As MSXML2.DOMDocument
Dim objNode As MSXML2.IXMLDOMElement
Set objXML = New MSXML2.DOMDocument
' byte array to base64
Set objNode = objXML.createElement("b64")
objNode.DataType = "bin.base64"
objNode.nodeTypedValue = arrData
EncodeBase64 = objNode.Text
Set objNode = Nothing
Set objXML = Nothing
End Function
Using this function:
Debug.Print Base64_HMACSHA1("abc", "123")
VAsMU9SSWDe9krP3Gr56nXC2dsQ=
HMAC is a construct for turning a hash function, like SHA1, into a Message Authentication Code (MAC).
Normal hash functions don't have any secret data associated with it. This means that anyone can compute the digest, assuming they have the original input. HMAC uses a secret key, so that only those in possession of the key can compute outputs.
Suppose I have a file, file.txt. I want to send this to you, and we need to make sure nobody tampers with it. Sorry, I have no clever way to represent this with just text.
me -> file.txt -> you
me -> SHA1(file.txt) -> you
Then you verify the result by computing your own SHA1 digest, and verifying it matches what I sent you.
Now suppose an attacker was in the middle. Unfortunately, because there is no secret involved, the attacker can modify the file, and compute his own file/digest pair. When you compute your version, it'll match what he sent you, and you'll be none the wiser.
me -> file.txt -> attacker -> modified.txt -> you
me -> SHA1(file.txt) -> attacker -> SHA1(modified.txt) -> you
With HMAC, we add a secret key to the computation.
me -> file.txt -> you
me -> SHA1_HMAC(file.txt, our_secret) -> you
When you compute your version, you apply the secret key as well, and the result matches. The attacker, without knowledge of the key, can't replace the digest.
me -> file.txt -> attacker -> modified.txt -> you
me -> SHA1(file.txt) -> attacker -> SHA1_HMAC(modified.txt, // DOESN'T KNOW KEY) -> you
HMAC is a very specific way of adding the secret key. Unfortunately, simple methods of just concatenating a key to the end of the file, or pre-pending it before hashing, are vulnerable to different attacks (length extension attacks, for example).
The B64 is Base64 encoding the output, to make it pretty.
What this code is ultimately doing is taking some input, and some secret key, and computing a 160-bit digest, and base64 encoding the result.
There is an implementation of SHA1 HMAC in .NET
This looks like an implementation of Base64 for VBA
I hope this answers it well enough, or clear enough. If the text is confusing, please let me know. I tried a couple routes of how to express it, and none of them seemed that clear.
You have written:
It appears to me that calling a JavaScript function from VBA is fairly impractical.
That is a misjudgment.
Javascript can be easily packaged as a Windows Script Component (WSC) and then invokved via COM, from VBA, Perl, VB6, or what-have-you.
Here's an example of packaging Javascript as a WSC and invoking it: https://stackoverflow.com/a/849970/48082
Therefore, your problem should be easily solvable.
I am using the following code to read in a binary file in VBScript and store it in a byte array which I then access from Javascript and copy to a JS array, basically just a sneaky way (the only way!) i've found of reading binary data in my JS.
Function readBinaryFile(fileName)
dim inStream,buff
set inStream=CreateObject("ADODB.Stream")
inStream.Open
inStream.type=1
inStream.LoadFromFile fileName
buff=inStream.Read()
inStream.Close
Dim byteArray()
Dim i
Dim len
len = LenB(buff)
ReDim byteArray(len)
For i = 1 To len
byteArray(i-1) = AscB(MidB(buff, i, 1))
Next
readBinaryFile=byteArray
End Function
It appears to work exactly as expected, the only problem being it seems extremely slow. For example, reading in a 300kb file can take over 2 minutes. I am expecting to read files up to around 2meg.
Could anyone explain why this is such a slow operation and if there's anything I can do to speed it up?
Thanks.
The problem is the loop. Try using disconnected recordset to do the conversion:
Function RSBinaryToString(xBinary)
'Antonin Foller, http://www.motobit.com
'RSBinaryToString converts binary data (VT_UI1 | VT_ARRAY Or MultiByte string)
'to a string (BSTR) using ADO recordset
Dim Binary
'MultiByte data must be converted To VT_UI1 | VT_ARRAY first.
If vartype(xBinary)=8 Then Binary = MultiByteToBinary(xBinary) Else Binary = xBinary
Dim RS, LBinary
Const adLongVarChar = 201
Set RS = CreateObject("ADODB.Recordset")
LBinary = LenB(Binary)
If LBinary>0 Then
RS.Fields.Append "mBinary", adLongVarChar, LBinary
RS.Open
RS.AddNew
RS("mBinary").AppendChunk Binary
RS.Update
RSBinaryToString = RS("mBinary")
Else
RSBinaryToString = ""
End If
End Function
Function MultiByteToBinary(MultiByte)
'© 2000 Antonin Foller, http://www.motobit.com
' MultiByteToBinary converts multibyte string To real binary data (VT_UI1 | VT_ARRAY)
' Using recordset
Dim RS, LMultiByte, Binary
Const adLongVarBinary = 205
Set RS = CreateObject("ADODB.Recordset")
LMultiByte = LenB(MultiByte)
If LMultiByte>0 Then
RS.Fields.Append "mBinary", adLongVarBinary, LMultiByte
RS.Open
RS.AddNew
RS("mBinary").AppendChunk MultiByte & ChrB(0)
RS.Update
Binary = RS("mBinary").GetChunk(LMultiByte)
End If
MultiByteToBinary = Binary
End Function
In your case have readBinaryFile return the "ASCII contents" of the file and use it instead of the array: readBinaryFile = RSBinaryToString(buf)
I think its because you are using a high level scripting language to emulate something that should be done by low-level compiled languages. I guess there's a reason scripts don't support binary data. They are not designed to deal with data one byte at a time. Looping through 300,000 bytes of data would take a noticable amount of time in many languages, but a non-compiled (scripting) language makes it even worse. The only things I can suggest are using a compiled language instead, or using some ActiveX object created in a compiled language that supports the operations you want to perform without having to perform them byte-by-byte in script. Do you have the option of using compiled components or other languages?
Still not found a solution to this, but it's a side issue now and does work as it is (if very slowly in some circumstances) so not got time to look at it any further unfortunately.