node.js and ruby 1.8 different hmac sha1 result - ruby

I have ruby 1.8.7 code that create hmac with sha1
key= '123'
digest = Digest::SHA1.new
digest << 'test string'
digest << key
result = digest.hexdigest
# "c1bdfd602e1581f1ab91928e2c3fd371a1e63a5c"
I want to replicate this with node.js:
key= '123';
myhmac = crypto.createHmac('sha1', key);
result = myhmac.update('test string').digest('hex');
// 'a145f4d366e9e4e96b80bc427144ba77b3c7151a'
But the result is different.
What should I do in nodejs to have the same result as from ruby?

You are comparing a plain SHA1 digest in your Ruby code with a HMAC (using SHA1 as its hash function) in your Node code. These are different things, although the HMAC makes use of SHA1.
Usually you would want to use the HMAC over the plain SHA1. To do that in Ruby you could do something like:
require 'openssl'
key = '123'
data = 'test string'
digest = OpenSSL::Digest::SHA1.new
# See how HMAC uses SHA1 here:
result = OpenSSL::HMAC.hexdigest(digest, key, data)
# => "a145f4d366e9e4e96b80bc427144ba77b3c7151a", same as your node result
To reproduce your Ruby results in Node (calculating the SHA1 of the message + key), you want something like this:
const crypto = require('crypto');
const hash = crypto.createHash('sha1'); // Just SHA1, no HMAC
hash.update('test string');
hash.update('123'); // The Ruby code is hashing the concatenation of
// the data and key
result = hash.digest('hex');
// => 'c1bdfd602e1581f1ab91928e2c3fd371a1e63a5c', same as Ruby code

To make it works for both (nodejs and ruby), please make sure :
(important) make it in the same format.
ruby:
payload = "{'name': 'james kachiro sarumaha', 'data': [], 'is_available': true}"
payload = payload.to_json
nodejs:
payload = "{'name': 'james kachiro sarumaha', 'data': [], 'is_available': true}"
payload = JSON.stringify(payload)
have a correct key. (32)
ruby:
key = "v1tg3cOvfNdxh4TXxtdVmeB106doeQFS"
nodejs:
key = "v1tg3cOvfNdxh4TXxtdVmeB106doeQFS"
conversion
ruby :
digest = OpenSSL::Digest::SHA256.new
result = OpenSSL::HMAC.hexdigest(digest, password, payload)
#bd0724a05fab03e64e0112d09ceb11b6c1cbd8f9629a1d303e8d395d36cce396
nodejs:
crypto.createHmac('sha256', key).update(payload).digest('hex')
//bd0724a05fab03e64e0112d09ceb11b6c1cbd8f9629a1d303e8d395d36cce396
crypto from crypto module (nodejs core).
in my case, I'm using SHA256 but you can change it to SHA1. it will have the same result also.
tx

Related

Verify signed data vault offline

I'm sending to a client a public_key and a payload plus a signature.
I'm trying to verify the signature on the client using the public_key and payload but I can't find a way to get the data verified. Every time I receive false as a response regardless of what I'm doing.
I'm using vault to sign the payload
def sign(box_identifier, data) do
Vaultex.Client.write("transit/sign/#{box_identifier}",
%{
input: data,
hash_algorithm: "sha2-256"
},
#authentication_strategy,
{#token})
end
Vault.Transit.sign("coucou", Base.encode64("test"))
_____________________________________________________________
digest = OpenSSL::Digest::SHA256.new
key = OpenSSL::PKey::RSA.new File.read 'key.pem'
key.public_key.verify digest, signature, "test"
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzj0qLxEc0Qu9g9nxdMRe
jBaUD0+GuQITiAPEDOrjScJTznJrR9hXqO14BqepuEmcz4irv4hEkBEBfqZ1XnP9
2fc9zG4A20lepqDRwPhkEdI4D71KRPSxv/a+O2HrAhTYH17NbsYDtpkFCdepC6FC
01aso679d3kAZiZ+GD2OLDWifreBVPE2aXacJYXZZ4kTkchsevY3PnAcOG4LmM6b
kUoF1qfP6tJ/VItJXyqSC2PI9Io28zFhwOf6cPLEQCBCTNCNwHunqHW8olcE/Xfn
b2toym0/UvW3kH/P4h+TE1ZoCV5FWwcx9hcAy1TC0zm4D5Xwt0/4Pgj1GiXFGOY4
WKGyuDK8gs7QsSDqu+B5p0xiDg7226bpplVxR/P87CohYTtYZj/lO03G2ZEj3pCr
/juxThdzTgO5xUcPV5GFbPjlK1TIIb5XVZZzW6+sviB6cYZ5T/Xp9dtbR8G9Zt9n
gLlaKU7U/DDQxRv2uiCZy5U/DUpfxY56r4Y73Ir2YgmZY1PKLC2a5/w3wScVZILN
fnxnVYOzSPPaKxAJWbZsZjxXJS0veE8RgGFHgWfe8+qPCEnx81Jf2NzupQHO1KIk
UnlYGcPESk/90psDNsmISSdtF2D6j4k28k0ncViTu2eMKBX81W8TgTeHtQa3zR0S
upN2o25b3Wi2oQU14kTdOCcCAwEAAQ==
-----END PUBLIC KEY-----
Am I even capable of validating the signature offline when its signed by vault ?
Yes, it's possible to verify the signature offline. My answer is going to mention some Ruby specifics, since I didn't notice this was Elixir until after I started researching a bit more.
By default, when signing with a RSA key, Vault uses the PSS algorithm for the signature. The verify method on OpenSSL::PKey::PKey is expecting PKCS #1 v1.5. Here's some useful info about the pros and cons of the two.
In addition to this algorithm difference, you need to strip off the vault:v1: from the returned signature from Vault.
Here's some sample Ruby code to sign and then verify the signature:
transit_key = "test_key"
message = "test"
# Returns something like:
# vault:v1:B3reNpf8e/WyAYzBzyWz3oSUM...
signature = Vault.logical.write(
"transit/sign/#{transit_key}/sha2-256",
input: Base64.encode64(message),
signature_algorithm: "pkcs1v15"
).data[:signature]
signature = signature.split(":")[2]
# Gives us the PEM encoded public key
# -----BEGIN PUBLIC KEY-----
# ...
public_key = Vault.logical.read("transit/keys/#{transit_key}").data[:keys][:"1"][:public_key]
public_key = OpenSSL::PKey::RSA.new(public_key)
digest = OpenSSL::Digest::SHA256.new
puts public_key.verify(digest, Base64.decode64(signature), message) # returns true
puts public_key.verify(digest, Base64.decode64(signature), message + "modified") # returns false
Looks like as of Ruby 2.5, there's a new verify_pss method on the OpenSSL::PKey::RSA class.
signature = Vault.logical.write(
"transit/sign/#{transit_key}/sha2-256",
input: Base64.encode64(message),
signature_algorithm: "pss"
).data[:signature].split(":")[2]
puts public_key.verify_pss(digest, Base64.decode64(signature), message, salt_length: :auto, mgf1_hash: "SHA256") # returns true
puts public_key.verify_pss(digest, Base64.decode64(signature), message + "modified", salt_length: :auto, mgf1_hash: "SHA256") # returns false

As a NodeJS user, how do I use the ruby OpenSSL library for crypto?

I'm Node developer, but every once in awhile I get to play around with ERB templates. I really love pulling out as much ruby as I can in these templates when I can and this idea caught my eye especially.
I have this configuration value, which should be encrypted, but is coming in plain text. The program would decrypt it like so:
var crypto = require('crypto');
var decipher = crypto.createDecipher('aes256', 'e20jhciwjf90u2r9u9ujj');
var decrypted = crypto.update('4ufujj90u19ru90u109u') + crypto.final();
I was wondering how I might go about creating an encrypted string for the above to decrypt using ruby?
So far I have:
require 'openssl'
cipher = OpenSSL::Cipher::Cipher.new('aes256');
cipher.key= 'e20jhciwjf90u2r9u9ujj'
encrypted = cipher.update('my cat is yellow and very pretty.') + cipher.final
Two problems I have:
I often get a Key length not long enough error on the ruby side.
Ruby outputs a bunch of crazy hex, whereas node seems to always take/want utf8.
Am I encrypting/decrypting safely?
Is there a way to universally translate/work laterally with these two APIs?
AES-256 uses a key of 256bits, and by default ruby uses utf-8 encoding, so each ansi character is 8bit long. So the key string must be 32 bytes.
Explicitly use an aes mode (e.g. aes-256-cbc).
Set the same iv(initial vector) on both sides
I finally succeeded with the above methods.
Here is my code:
Ruby side:
require 'openssl'
require 'base64'
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
cipher.iv = 'a'*16;
cipher.key = '01234567890123456789012345678901' # should be 32 characters, 32*8=256 bits
enc = Base64.strict_encode64(cipher.update('01234567890123456789012345678901') + cipher.final)
puts enc
Javascript side:
var encrypted = new Buffer(base64Data, 'base64');
var crypto = require('crypto');
var decipher = crypto.createDecipheriv('aes-256-cbc', '01234567890123456789012345678901', 'aaaaaaaaaaaaaaaa');
var dec = decipher.update(encrypted);
console.log(Buffer.concat([dec, decipher.final()]));

Decrypting salted AES file generated on command line with Ruby

I would like to decrypt a text file within a ruby 2.1 script which was previously encrypted using OpenSSL's commandline tools:
openssl enc -aes-256-cbc -a -salt -in my_file
As seen in the command, the file is AES-256-CBC encrypted, salted and base64 encoded.
The password is known, but not the IV nor the key, which are required to follow this code snippet, taken from the ruby documentation:
decipher = OpenSSL::Cipher::AES.new 256, :CBC
decipher.decrypt
decipher.key = key
decipher.iv = iv
plain = decipher.update(encrypted_text) + decipher.final
While trying to find an answer, I found the gem AESCrypt gem which supposedly simplifies en- and decrypting, yet the currently released version is not compatible with ruby 2.1.
Looking at it's source code, I found that the key was retrieved by digesting the password, and the IV is just left as nil.
So I tried to get the following running:
encoded_and_encrypted_text = File.read my_file_path
encrypted_text = Base64.decode64 encoded_and_encrypted_text.to_s.strip
decipher = OpenSSL::Cipher::AES.new 256, :CBC
decipher.decrypt
decipher.key = OpenSSL::Digest::SHA256.new(my_password).digest
plain_text = decipher.update(encrypted_text) + decipher.final
But this results in OpenSSL::Cipher::CipherError: bad decrypt.
Do I need to somehow specifically handle that the file is salted? I have read in the OpenSSL documentation for the enc function that the IV, if not specified while encrypting the file, is generated from the password. Do I need to manually reconstruct the IV somehow?
Any advice would be highly appreciated :)
OpenSSL uses a custom header and key derivation routine. Security.SE has a good description of the header and the docs for EVP_BytesToKey describe the key derivation.
We can modify your code to use this weird and somewhat broken key derivation as follows:
encoded_and_encrypted_text = File.read my_file_path
encrypted_text = Base64.decode64 encoded_and_encrypted_text.to_s.strip
header = encrypted_text[0,8]
salt = encrypted_text[8,8]
payload = encrypted_text[16..-1]
decipher = OpenSSL::Cipher::AES.new 256, :CBC
decipher.decrypt
D_1 = OpenSSL::Digest::MD5.new(my_password + salt).digest
D_2 = OpenSSL::Digest::MD5.new(D_1 + my_password + salt).digest
D_3 = OpenSSL::Digest::MD5.new(D_2 + my_password + salt).digest
decipher.key = (D_1 + D_2)
decipher.iv = D_3
plain_text = decipher.update(payload) + decipher.final

Calculate an RFC 2104-compliant HMAC with the SHA256 hash algorithm in ruby

I was going through the Amazon Product Advertising API REST signature docs and I got stuck at #8
Calculate an RFC 2104-compliant HMAC with the SHA256 hash algorithm using the string above with our "dummy" Secret Access Key: 1234567890. For more information about this step, see documentation and code samples for your programming language.
I managed to get it on one more try with the help of Calculating a SHA hash with a string + secret key in python.
The following creates the correct signature:
require 'openssl'
secret_key = '1234567890'
query = 'AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-06'
data = ['GET', 'ecs.amazonaws.com', '/onca/xml', query].join("\n")
sha256 = OpenSSL::Digest::SHA256.new
sig = OpenSSL::HMAC.digest(sha256, secret_key, data)
signature = Base64.encode64(sig)
Adding to AJcodez answer:
I would do:
...
signature_raw = Base64.strict_encode64(sig)
signature = CGI::escape(signature_raw)
encode64adds a newline at the end, strict_encode64() does not.
https://stackoverflow.com/a/2621023/2760406
Amazon wants you to "URL encode the plus (+) and equal (=) characters in the signature" #9 - won't work now if you don't.
http://docs.aws.amazon.com/AWSECommerceService/latest/DG/rest-signature.html#rest_detailedexample
You can calculate a keyed-hash message authentication code (HMAC-SHA256) signature with your secret access key by using cryptoJs
First install cryptoJs locally in your system by typing
npm install crypto-js
to install it globally you node a flag -g to the above command. Then add this code and run it.
var CryptoJS = require("crypto-js");
// Calculate an RFC 2104-compliant HMAC with the SHA256 hash algorithm
var exampleString =
"GET\n" +
"webservices.amazon.com\n" +
"/onca/xml\n" +
"AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&AssociateTag=mytag-20&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=Images%2CItemAttributes%2COffers%2CReviews&Service=AWSECommerceService&Timestamp=2014-08-18T12%3A00%3A00Z&Version=2013-08-01";
var signature = CryptoJS.HmacSHA256(exampleString, "1234567890");
console.log("test signature", signature.toString(CryptoJS.enc.Base64));

AES-256-CBC with Digest from Ruby to NodeJS

I am hoping someone can shed some light on a problem that has been vexing me for the last few hours.
I am trying to decode a string that has been encoded in Ruby thus:
#!/usr/bin/env ruby
require 'base64'
require 'openssl'
require 'openssl/cipher'
require 'openssl/digest'
aes = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
aes.encrypt
aes.key = Digest::SHA256.digest('IHazSekretKey')
p Base64.encode64( aes.update('text to be encrypted') << aes.final )
Executing the above spits out: "3P86KyOrN2QJ/HFxxo3b7kAsxTgpDMMjROUPclsuXj0=\n"
I now try to decrypt this string in NodeJS 0.6.17
#!/usr/bin/env node
var crypto = require('crypto');
function decrypto(toDecryptStr) {
var result,
encoded = new Buffer(toDecryptStr, 'base64'),
decodeKey = crypto.createHash('sha256').update('IHazSekretKey', 'ascii').digest(),
decipher = crypto.createDecipher('aes-256-cbc', decodeKey);
result = decipher.update(encoded);
result += decipher.final();
return result;
};
console.log(decrypto('3P86KyOrN2QJ/HFxxo3b7kAsxTgpDMMjROUPclsuXj0='));
console.log(decrypto('3P86KyOrN2QJ/HFxxo3b7kAsxTgpDMMjROUPclsuXj0=\n')
The second script yields:
nazar#xfce:~/tmp/tst$ ./js_decrypt
Å'{ H£V)ÜB
Å'{ H£V)ÜB
Any help would be very much appreciated as my only remaining option now is to drown myself in a barrel of [Jamerson || Kirin Ichiban] (I'm only kidding)
PS there is a similar question on SO here, which sadly hasn't yielded any inspiration for my case.
The critical missing piece is the IV, which is required when encryption/decryption is to be made across language boundaries as apparently the encrypter will generate a random IV (or something like that - still don't understand how Ruby decrypts the string without an IV.... but then what do I know....), if one is not provided.
The following snippets show how to encrypt a string in Ruby and decrypt in NodeJS.
#!/usr/bin/env ruby
require 'openssl'
require 'base64'
require 'openssl/cipher'
require 'openssl/digest'
aes = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
aes.encrypt
aes.key = Digest::SHA256.digest('IHazSekretKey')
aes.iv = '1234567890123456'
p Base64.encode64( aes.update('text to be encrypted') << aes.final )
The above prints: "eiLbdhFSFrDqvUJmjbUgwD8REjBRoRWWwHHImmMLNZA=\n"
#!/usr/bin/env node
var crypto = require('crypto');
function decrypto(toDecryptStr) {
var result,
encoded = new Buffer(toDecryptStr, 'base64'),
decodeKey = crypto.createHash('sha256').update('IHazSekretKey', 'ascii').digest(),
decipher = crypto.createDecipheriv('aes-256-cbc', decodeKey, '1234567890123456');
result = decipher.update(encoded);
result += decipher.final();
return result;
}
console.log(decrypto('eiLbdhFSFrDqvUJmjbUgwD8REjBRoRWWwHHImmMLNZA=\n'))
The JS script now properly decrypts the string.
One unfortunate side effect is that existing encrypted data will need to be decrypted and then re-encrypted with an IV that is then used in the decrypting implementation.
A PITA but nonetheless a working solution.

Resources