I have code that is working in my PHP app. In the PHP I sign the url with the following code:
private static function __getHash($string)
{
return hash_hmac('sha1', $string, self::$__secretKey, true);
}
I am attempting to sign the URL in the same way in a Node.js application. This is what I'm trying:
S3.prototype.getHash = function(string){
var key = this.secret_key;
var hmac = crypto.createHash('sha1', key);
hmac.update(string);
return hmac.digest('binary');
};
However, I am getting the following error:
The request signature we calculated does not match the signature you provided. Check your key and signing method.
Do these pieces of code do the same thing? Am I missing something?
This answer from Chris is good if you are porting hash_hmac with the last parameter being true. In this case, binary is produced, as is the case with Chris's javascript.
To add to that, this example:
$sign = hash_hmac('sha512', $post_data, $secret);
Would be ported with a function like so in nodejs:
const crypto = require("crypto");
function signHmacSha512(key, str) {
let hmac = crypto.createHmac("sha512", key);
let signed = hmac.update(Buffer.from(str, 'utf-8')).digest("hex");
return signed
}
The difference here being that when you leave off the last argument to hash_hmac (or set it to something not true), it behaves as defined in the PHP docs:
When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
In order to do this with node.js we use digest('hex') as you can see in the snippet.
The primary problem here is that you are using createHash which creates a hash, rather than createHmac which creates an HMAC.
Change createHash to createHmac and you should find it produces the same result.
This is the output you should expect:
chris /tmp/hmac $ cat node.js
var crypto = require('crypto');
var key = 'abcd';
var data = 'wxyz';
function getHash(string){
var hmac = crypto.createHmac('sha1', key);
hmac.update(string);
return hmac.digest('binary');
};
process.stdout.write(getHash(data));
chris /tmp/hmac $ cat php.php
<?php
$key = "abcd";
$data = "wxyz";
function __getHash($string)
{
global $key;
return hash_hmac('sha1', $string, $key, true);
}
echo utf8_encode(__getHash($data));
chris /tmp/hmac $ node node.js | base64
WsOKw4xgw4jDlFHDl3jDuEPDuCfCmsOFwoDCrsK/w6ka
chris /tmp/hmac $ php php.php | base64
WsOKw4xgw4jDlFHDl3jDuEPDuCfCmsOFwoDCrsK/w6ka
Related
My goal is to use NodeJS to create LDAP password hashes that are similar to what comes out of the slappasswd command-line tool.
Here's how LDAP passwords can be produced with command-line:
slappasswd -h '{SSHA}' -s 'P#ssw0rd'
{SSHA}1RHPt8m4AWLjK8Px1MT6FEBJOBJpdzqT
The result is a base64 encoded, salted SHA1 password.
Here's what I tried initially to recreate it:
#!/usr/bin/env node
import sha1 from 'crypto-js/sha1.js';
let password = 'P#ssW0rd';
let salt = btoa(0xA5); // Not random, just a proof of concept temporary value.
let hash = sha1(password + salt);
console.log('{SSHA}' + btoa(hash));
But, I got a much longer string than what the slappasswd command produced and I'm not sure why.
{SSHA}NDVkN2JjODQ2ZDk3Yjc2YmViNTU3MzUzYjBiNzExN2ZmYzMxYWY5ZA==
I did some digging around on the net and found this on an LDAP password generator web page:
<script src="lib/cryptojs/core.js"></script>
<script src="lib/cryptojs/sha1.js"></script>
<script src="lib/cryptojs/enc-base64.js"></script>
<script>
function slappasswd(password) {
var salt = CryptoJS.lib.WordArray.random(128/8).toString().substr(0,4);
var hash = CryptoJS.SHA1(password + salt);
var base = CryptoJS.enc.Latin1.parse(hash.toString(CryptoJS.enc.Latin1) + salt).toString(CryptoJS.enc.Base64);
return '{SSHA}' + base;
}
...
The web page produces a string that is the same length as what comes out of slappasswd, so I assume it's an accurate recreation of the slappasswd logic.
Using this information, my next attempt looks like this:
#!/usr/bin/env node
import * as CryptoJS from 'crypto-js';
let password = 'P#ssW0rd';
let salt = CryptoJS.lib.WordArray.random(128/8).toString().substr(0,4);
let hash = sha1(password + salt);
let base = CryptoJS.enc.Latin1.parse(hash.toString(CryptoJS.enc.Latin1) + salt).toString(CryptoJS.enc.Base64);
console.log('{SSHA}' + base);
However, I get errors.
First, there is TypeError: Cannot read properties of undefined (reading 'WordArray')
If I replace let salt = with let salt = btoa(0xA5) from my first attempt code, I then get the error: ReferenceError: sha1 is not defined
My feeling is that I've got the import wrong somehow.
I'm trying to do the ES6 module equivalent of var CryptoJS = require("crypto-js");, but failing somewhere.
So my question is two-fold:
Can my first attempt be made to produce a string length similar to what slappassword outputs?
If not, what can I do to fix the errors I'm getting in the second attempt?
Ideally, I'd like to understand where I went wrong in my first attempt rather than simply copying and pasting someone else's code (second attempt) without fully grasping it.
Here is alternative of python/php implementations for NodeJS.
Import Crypto module
const crypto = require('crypto');
It will be used to create LDAP password hashes (SSHA)
function generate_hash(passwd, salt) {
if (!salt) {
const buf = crypto.randomBytes(4);
salt = buf.toString('base64');
}
let ctx = crypto.createHash('sha1');
ctx.update(passwd, 'utf-8');
ctx.update(salt, 'binary');
let digest = ctx.digest('binary');
return '{SSHA}' + Buffer.from(digest + salt, 'binary').toString('base64');
}
It will be used to verify hash
function verify_hash(passwd, hash) {
let bhash = Buffer.from(hash.substr(6), 'base64');
let salt = bhash.toString('binary', 20);
let newssha = generate_hash(passwd, salt);
return hash === newssha;
}
Test it together
const hash = generate_hash("qwe1234");
let test = verify_hash("qwe1234", hash);
console.log(test); //Output: true
let test = verify_hash("XXXX", hash);
console.log(test); //Output: false
Hope it help you. Please let me know.
Try Now
I am trying to convert python code to node js. Need help in converting this code to JS.
```
import uuid
uuid_salt = '9909fa72-b690-55dd-ab71-a987953bb438'
x = 'hello'
uuid_salt = uuid.UUID(uuid_salt)
salted_uuid = lambda x: str(uuid.uuid5(uuid_salt, x))
print salted_uuid(x)
```
Expected Output - 3e735408-7f83-53cf-b7ce-f9ef69e5ca43
I tried writing this way but output does not match
var uuid_salt = '9909fa72-b690-55dd-ab71-a987953bb438'
var x = 'hello'
var hmac = crypto.createHmac('sha1', uuid_salt);
hmac.setEncoding('hex');
hmac.end(x, function () {
hash = hmac.read();
console.log('hash >>> ', hash);
});
here is the actual python function copy paste from library
def uuid5(namespace, name):
"""Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
from hashlib import sha1
hash = sha1(namespace.bytes + name).digest()
return UUID(bytes=hash[:16], version=5)
I guess the bytes part is missing i tried created bytes array in JavaScript/Node but still the output is off.
Please help.
Thanks in advance!
I was appointed the task of making a process in which a PowerShell script needs to make a call to Canvas servers in order to get data out of it for other uses that are outside the scope of this question.
The first thing I did was research how the Canvas API actually works. I eventually found this post holds everything I think I should know about the API. The API requires an HMAC SHA 256 hash.
I have decided to reverse engineer his the writer's code that makes the hash in order to make the same script in PowerShell.
Here is my slightly edited code (node.js)
var crypto = require('crypto')
var url = require('url')
var HMAC_ALG = 'sha256'
var apiAuth = module.exports = {
buildMessage: function(secret, timestamp, uri) {
var urlInfo = url.parse(uri, false);
var query = urlInfo.query ? urlInfo.query.split('&').sort().join('&') : '';
var parts = [ 'GET', urlInfo.host, '', '', urlInfo.pathname, query, timestamp, secret ]
console.log(parts);
return parts.join('\n');
},
buildHmacSig: function(secret, timestamp, reqOpts,message) {
//var message = apiAuth.buildMessage(secret, timestamp, reqOpts);
var hmac = crypto.createHmac(HMAC_ALG, new Buffer(secret))
hmac.update(message)
Console.log(message);
return hmac.digest('base64')
}
}
Here are the parameters that I put in the node js application
var canvas = require('[filepath]/new_canvas');
var secret = 'mycrazysecret';
var today = new Date();
var timestamp= today.toUTCString();
var regOpts = 'mycrazymessage';
var message = canvas.buildMessage(secret, timestamp, regOpts)
var hmac = canvas.buildHmacSig(secret, timestamp, regOpts,message);
the final code it this
'Oexq8/ulAGxSIQXGDVqoXyqk5x+n9cMrc3avcTW9aZk='
Here is my PowerShell file:
function buffer
{
param ($string)
$c=#()
Foreach ($element in $string.toCharArray()) {$c+= [System.Convert]::ToSByte($element)}
return $c
}
$message = 'GET\n\n\n\nmycrazymessage\n\nFri, 18 Nov 2016 15:29:52 GMT\nmycrazysecret'
$secret = 'mycrazysecret'
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = buffer -string $secret
$signature = $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($message))
$signature = [Convert]::ToBase64String($signature)
echo $signature
The final result is 'pF92zam81wclnnb8csDsscsSYNQ7it9qLrcJkRTi5rM='
I do not know getting the to produce the same results is even possible, but the question I am asking why aren't they producing to different results? (the keys are the same as well)
In PowerShell, the default escape sequence uses backticks ` rather than backslash \.
In order for the parser to recognize the escape sequence as not just a backtick character literal and the letter n, use an expandable string (" instead of '):
$message = "GET`n`n`n`nmycrazymessage`n`nFri, 18 Nov 2016 15:29:52 GMT`nmycrazysecret"
Other than that, your HMAC signature procedure is correct (it correctly outputs Oexq8/ulAGxSIQXGDVqoXyqk5x+n9cMrc3avcTW9aZk= after changing the $message value)
I can get a hmac sing using Python as following:
import hmac, base64, hashlib
def make_sign():
hash_data = "data"
secret = "this is secret"
sha512 = hashlib.sha512
hmac_obj = hmac.new(secret, hash_data, sha512)
str_hash = hmac_obj.digest()
sign = base64.b64encode(str_hash)
hex_hash = hmac_obj.hexdigest()
hex_sign = base64.b64encode(hex_hash)
print "correct_sign:",sign
print "hex_digest_sign:",hex_sign
make_sign()
output:
correct_sign: Lg4pXNCIpitNQt2DLU19qWb+FxdsYZlK4LLncfkTzSidrYoFJLNolUziRqh09B5HyRdCTEP7enZp6/Te34FK1g==
hex_digest_sign: MmUwZTI5NWNkMDg4YTYyYjRkNDJkZDgzMmQ0ZDdkYTk2NmZlMTcxNzZjNjE5OTRhZTBiMmU3NzFmOTEzY2QyODlkYWQ4YTA1MjRiMzY4OTU0Y2UyNDZhODc0ZjQxZTQ3YzkxNzQyNGM0M2ZiN2E3NjY5ZWJmNGRlZGY4MTRhZDY=
but with js, I can get hex_digest_sign, but I need to get correct_sign for web request.
function make_request() {
hash_data = "data"
secret = "this is secret"
hmac = hmac_512(hash_data, secret)
var sign = $.base64.encode(hmac),
console.log("js_sign="+sign);
}
function hmac_512(message, secret) {
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA512, secret);
hmac.update(message);
var hash = hmac.finalize();
return hash;
}
js output:
js_sign="MmUwZTI5NWNkMDg4YTYyYjRkNDJkZDgzMmQ0ZDdkYTk2NmZlMTcxNzZjNjE5OTRhZTBiMmU3NzFmOTEzY2QyODlkYWQ4YTA1MjRiMzY4OTU0Y2UyNDZhODc0ZjQxZTQ3YzkxNzQyNGM0M2ZiN2E3NjY5ZWJmNGRlZGY4MTRhZDY="
the correct sign is correct_sign: Lg4pXNCIpitNQt2DLU19qWb+FxdsYZlK4LLncfkTzSidrYoFJLNolUziRqh09B5HyRdCTEP7enZp6/Te34FK1g==
how to get it in js?
I suspect that you are running into trouble with types and encoding. According to the CryptoJS source, the iterative hashing style that you are using returns a WordArray once you call finalize().
With that, once you go to print the results, you are printing the contents of the WordArray.
The purpose for itterative hashing is typically if you have a large input, you can break it into chunks to work on one piece at a time. Try the below edit I made that removes this as it does not look like you need to iterate.
function hmac_512(message, secret) {
var newHMAC = CryptoJS.HmacSHA256(message, secret);
return newHMAC;
}
The above will simply return the HMAC in string form which, once Base64 encoded, should match the result you see in Python.
Hope this helps!
I'm trying to create a websocket server written in node.js
To get the server to work I need to get the SHA1 hash of a string.
What I have to do is explained in Section 5.2.2 page 35 of the docs.
NOTE: As an example, if the value of the "Sec-WebSocket-Key"
header in the client's handshake were "dGhlIHNhbXBsZSBub25jZQ==", the server would append thestring "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" to form the
string "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11". The server would then take the SHA-1 hash of this string, giving the value 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea. This value is then base64-encoded, to give the value "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", which would be returned
in the "Sec-WebSocket-Accept" header.
See the crypto.createHash() function and the associated hash.update() and hash.digest() functions:
var crypto = require('crypto')
var shasum = crypto.createHash('sha1')
shasum.update('foo')
shasum.digest('hex') // => "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
Obligatory: SHA1 is broken, you can compute SHA1 collisions for 45,000 USD (and even less since this answer was written). You should use sha256:
var getSHA256ofJSON = function(input){
return crypto.createHash('sha256').update(JSON.stringify(input)).digest('hex')
}
To answer your question and make a SHA1 hash:
const INSECURE_ALGORITHM = 'sha1'
var getInsecureSHA1ofJSON = function(input){
return crypto.createHash(INSECURE_ALGORITHM).update(JSON.stringify(input)).digest('hex')
}
Then:
getSHA256ofJSON('whatever')
or
getSHA256ofJSON(['whatever'])
or
getSHA256ofJSON({'this':'too'})
Official node docs on crypto.createHash()
Tips to prevent issue (bad hash) :
I experienced that NodeJS is hashing the UTF-8 representation of the string. Other languages (like Python, PHP or PERL...) are hashing the byte string.
We can add binary argument to use the byte string.
const crypto = require("crypto");
function sha1(data) {
return crypto.createHash("sha1").update(data, "binary").digest("hex");
}
sha1("Your text ;)");
You can try with : "\xac", "\xd1", "\xb9", "\xe2", "\xbb", "\x93", etc...
Other languages (Python, PHP, ...):
sha1("\xac") //39527c59247a39d18ad48b9947ea738396a3bc47
Nodejs:
sha1 = crypto.createHash("sha1").update("\xac", "binary").digest("hex") //39527c59247a39d18ad48b9947ea738396a3bc47
//without:
sha1 = crypto.createHash("sha1").update("\xac").digest("hex") //f50eb35d94f1d75480496e54f4b4a472a9148752
You can use:
const sha1 = require('sha1');
const crypt = sha1('Text');
console.log(crypt);
For install:
sudo npm install -g sha1
npm install sha1 --save
Please read and strongly consider my advice in the comments of your post. That being said, if you still have a good reason to do this, check out this list of crypto modules for Node. It has modules for dealing with both sha1 and base64.
Answer using the new browser compatible, zero dependency SubtleCrypto API added in Node v15
const crypto = this.crypto || require('crypto').webcrypto;
const sha1sum = async (message) => {
const encoder = new TextEncoder()
const data = encoder.encode(message)
const hashBuffer = await crypto.subtle.digest('SHA-1', data)
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
return hashHex;
}
sha1sum('foo')
.then(digestHex => console.log(digestHex))
// "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
Node Sandbox: https://runkit.com/hesygolu/61564dbee2ec8600082a884d
Sources:
https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
https://nodejs.org/api/webcrypto.html#webcrypto_class_subtlecrypto