Django password in node.js - django

I'm trying to do some authentication from my previous django web app in node. I got PBKDF2-sha256 working but I'm not able to get the BCryptSHA256PasswordHasher working in Node. I tried the following:
var Bcrypt = require('bcrypt');
var sha256 = require('sha256');
var pass = sha256("test password")
// from django ("bcrypt_sha256$$2b$12$mUg9hoKn0tt2/VwWaNb6Euie4.jtQjfU6.CY1pT0EH8GPORqAsh66")
var hash = "$2b$12$mUg9hoKn0tt2/VwWaNb6Euie4.jtQjfU6.CY1pT0EH8GPORqAsh66"
Bcrypt.compare(pass, hash, function (err, isMatch) {
if (err) {
return console.error(err);
}
console.log('do they match?', isMatch);
});
Is there something i'm missing with the above? I'm taking the sha256 of the password and testing with bcrypt. The corresponding code in Django is below:
def verify(self, password, encoded):
algorithm, data = encoded.split('$', 1)
assert algorithm == self.algorithm
bcrypt = self._load_library()
# Hash the password prior to using bcrypt to prevent password truncation
# See: https://code.djangoproject.com/ticket/20138
if self.digest is not None:
# We use binascii.hexlify here because Python3 decided that a hex encoded
# bytestring is somehow a unicode.
password = binascii.hexlify(self.digest(force_bytes(password)).digest())
else:
password = force_bytes(password)
# Ensure that our data is a bytestring
data = force_bytes(data)
# force_bytes() necessary for py-bcrypt compatibility
hashpw = force_bytes(bcrypt.hashpw(password, data))
return constant_time_compare(data, hashpw)
UPDATE
I have no idea why, but when I change the salt slightly to the following:
var hash = "$2a$12$mUg9hoKn0tt2/VwWaNb6Euie4.jtQjfU6.CY1pT0EH8GPORqAsh66"
everything works! I changed the 2b to 2a at the beginning. Why is this working and the other isn't? Is there something i'm missing?

From the excellent Passlib library:
ident (str) – Specifies which version of the BCrypt algorithm will be used when creating a new hash. Typically this option is not needed,
as the default ("2a") is usually the correct choice. If specified, it
must be one of the following:
"2" - the first revision of BCrypt, which suffers from a minor security flaw and is generally not used anymore. "2a" - some
implementations suffered from a very rare security flaw. current
default for compatibility purposes.
"2y" - format specific to the crypt_blowfish BCrypt implementation, identical to "2a" in all but name.
"2b" - latest revision of the official BCrypt algorithm (will be default in Passlib 1.7).

Related

Typeform Security API and Django: Not Verifiying Hash Correctly

I am trying to use Typeform's security for their webhooks. This involves
1) Receiving the signed packets and extracting the signature
2) Getting the body of the requst
3) Creating a hash with a secret key on the payload
4) Matching the hash with the received signature
My web framework is Django (Python based). I am following the example at the TypeForm link here: https://developer.typeform.com/webhooks/secure-your-webhooks/.
For the life of me, I can't figure out what's going on. I've tried it in both Python and Ruby, and I can't get the hash right. I call a Ruby script from Python to match the output, but they are different and neither work. Does anyone have any insight? I'm starting to think that it might have something to do with the way that Django sends request bodies. Does anyone have any input?
Python implementation:
import os
import hashlib
import hmac
import base64
import json
class Typeform_Verify:
# take the request body in and encrypt with string
def create_hash(payload):
# convert the secret string to bytes
file = open("/payload.txt", "w")
# write to a payload file for the ruby script to read later
file.write(str(payload))
# access the secret string
secret = bytearray(os.environ['DT_TYPEFORM_STRING'], encoding="utf-8")
file.close()
# need to have the ruby version also write to a file
# create a hash with payload as the thing
# and the secret as the key`
pre_encode = hmac.new(secret,
msg=payload, digestmod=hashlib.sha256).digest()
post_encode = base64.b64encode(pre_encode)
return post_encode
# another approach is to make a ruby script
# that returns a value and call it from here
def verify(request):
file = open("/output.txt", "w")
# check the incoming hash values
received_hash = request.META["HTTP_TYPEFORM_SIGNATURE"]
# create the hash of the payload
hash = Typeform_Verify.create_hash(request.body)
# call ruby script on it
os.system(f"ruby manager/ruby_version.rb {received_hash} &> /oops.txt")
# concatenate the strings together to make the hash
encoded_hash = "sha256=" + hash.decode("utf-8")
file.write(f"Secret string: {os.environ['DT_TYPEFORM_STRING']}\n")
file.write(f"My hash : {encoded_hash}\n")
file.write(f"Their hash : {received_hash}\n")
file.close()
return received_hash == encoded_hash
Ruby script (called from Python)
require 'openssl'
require 'base64'
require 'rack'
def verify_signature(received_signature, payload_body, secret)
hash = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, payload_body)
# the created signature
actual_signature = 'sha256=' + Base64.strict_encode64(hash)
# write created signature to the file
out_file = File.new("/output.txt", "a")
out_file.write("Ruby output: ")
out_file.write(actual_signature)
out_file.close()
return 500, "Signatures don't match!" unless Rack::Utils.secure_compare(actual_signature, received_signature)
end
# MAIN EXECUTION
# get the hash from the python scriupt
received_hash = ARGV[0]
# read the content of the file into the f array
# note that this is the json payload from the python script
f = IO.readlines("/payload.txt")
# declare the secret string
secret = "SECRET"
# call the funtion with the recieved hash, file data, and key
result = verify_signature(received_hash, f[0], secret)
Code output:
Typeform hash: sha256=u/A/F6u3jnG9mr8KZH6j8/gO+Uny6YbSYFz7+oGmOik=
Python hash: sha256=sq7Kl2qBwRrwgGJeND6my4UPli8rseuwaK+f/sl8dko=
Ruby output: sha256=BzMxPZGmxgOMeJ236eAxSOXj85rEWI84t+6CtQBYliA=
UPDATED First see this github article as the one you referred to may be based on it.
The idea is that your requests should be signed. Here is a more basic pure ruby example which should illustrate how this should work.
# test.rb
ENV['SECRET_TOKEN'] = 'foobar'
require 'openssl'
require 'base64'
require 'rack'
def stub_request(body)
key = ENV['SECRET_TOKEN']
digest = OpenSSL::Digest.new('sha256')
hmac_signature = OpenSSL::HMAC.hexdigest(digest, key, body)
{ body: body, hmac_signature: hmac_signature }
end
def verify_signature(payload_body, request_signature)
digest = OpenSSL::Digest.new('sha256')
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
if Rack::Utils.secure_compare(request_signature, hmac)
puts "They match"
else
puts "They don't match"
end
puts "request_signature: #{request_signature}"
puts " hmac: #{hmac}"
puts " body: #{payload_body}"
end
request = stub_request(ARGV[0])
verify_signature(request[:body], request[:hmac_signature])
Now to test this, just run:
ruby test.rb 'this is some random body string'
Here is a Python version of the same code. But this is vulnerable to timing attack vulnerability. There is probably a Python equivalent somewhere to mitigate this but I didn't do the research to find it. It shouldn't be hard to write something like the Ruby Rack version here in Python if your server doesn't already have something like it.
#test.py
import sys
import hashlib
import binascii
import hmac
import base64
KEY = 'foobar'
def stub_request(body):
key = bytes(KEY, 'utf-8')
body_bytes = bytes(body, 'utf-8')
hmac_signature = hmac.new(key,
msg=body_bytes, digestmod=hashlib.sha256).digest()
return {'body': body, 'hmac_signature': hmac_signature}
def verify_signature(payload_body, request_signature):
key = bytes(KEY, 'utf-8')
hmac_sig = hmac.new(key, msg=bytes(payload_body,'utf-8'), digestmod=hashlib.sha256).digest()
if hmac_sig == request_signature:
print("They match")
else :
print("They don't match")
print(f"request_signature: {binascii.hexlify(request_signature)}")
print(f" hmac: {binascii.hexlify(hmac_sig)}")
print(f" body: {payload_body}")
return request_signature
body = sys.argv[-1]
request = stub_request(body)
verify_signature(request['body'], request['hmac_signature'])
I ended up figuring it out. The Python implementation I had worked fine. The problem was in how I was saving the secret string. Apparently, environment variables in Python will not allow characters like $ or *. My Ruby implementation started working when I hardcoded my secret into the code, which led me to believe that the problem was in how I was saving the secret string. I recommend the Python implementation to anyone trying to do this kind of authentication. Cheers!

Using PassLib to Verify Hash from Flask User Passwords

I'm currently trying to migrate my Flask Users over to a Django Backend.
However, when I'm using passlib to verify the hash, I can't figure out why it won't verify.
Our flask app settings
SECURITY_PASSWORD_HASH = "pbkdf2_sha512"
SECURITY_PASSWORD_SALT = "stackoverflow" # this is an example
An example of a hash I pulled from a database
flask_hash =
"$pbkdf2sha512$12000$ZQyhNEbIOSfk/J/T2vs/Bw$j.yxtixV.DqAcpsY9XTnJZZb3lCkR2fMWmV329Uc7Y/vz5Z0yMshEkYlUsE2Y9xm8TICwYkG55RgAplzZzLl7g"
So I created a custom pbkdf2_sha512 with the the rounds and salt
from passlib.hash import pbkdf2_sha512
rounds = 12000
salt = "stackoverflow".encode() # assume I swapped this out with the right salt
custom_pbkdf2 = pbkdf2_sha512.using(rounds=rounds, salt=salt)
verify_result = custom_pbkdf2.verify(hash=flask_hash, secret=password)
print (verify_result) # false
But if I create a new hash ... it does work
test_hash = custom_pbkdf2.hash('testing-if-this-works')
test_hash_confirm = custom_pbkdf2.verify('testing-if-this-works', hash=test_hash)
Is there something I'm missing? Thank you so much for any help here ... I know the password to this -- it's a dummy account I used for testing.
I was struck in exactly the same situation, luckily found this reddit thread, which had the explanation.
Basically, what you have to do verify the user is:
from flask_security.utils import verify_password
verify_password(<plain text password>, <password hash>)
More details here

Migrating passwords from web2py to Django

I have passwords stored in web2py using SHA 512 algorithm. I am now migrating the models to django and hence need a way to hash passwords in django using SHA 512 in the same way as web2py does so that I can authenticate the old users with the same passwords.Please suggest some way.
According to this post a Python snippet to recreate the convention used in web2py would be the following:
from hashlib import md5
import hmac
hmac_key = '<your secret key>'
password = 'insecure'
thehash = hmac.new(hmac_key, password).hexdigest()
print thehash
web2py uses hmac (which is your secret + the plaintext of the user's password) as the final hash and not just a straight MD5/SHA hash (depending on your settings). So you would just need to swap out MD5 for SHA in the above example to get things working on your end. But this implementation is all you would need to implement in your new application to make them cross compatible as long as the secret key is the same.
According to the docs the hash is stored in the following format:
<algorithm>$<salt>$<hash>
so if there is a salt used then it's stored with the hash making it easy to grab the salt for use in your new application. The dollar signs make it easy to parse each value.
algo, salt, hash = password_hash.split("$")
UPDATE: I pulled the below code from the web2py source but what you need to do is update the variable hmac_key with the value that you have set for auth.settings.hmac_key. Hopefully when you run (after you update the hmac_key variable) this the hashes should match.
import hashlib
import hmac
from hashlib import sha512
h="sha512$b850ed44943b861b$c90901439983bce7fd512592b20d83f8e654632dee51de515773e70eabe609f62cebec64fed4df03acd54e6a627c9291e70fdf3a89996ffa796897c159e95c11"
algo,salt,hash = h.split("$")
print "crypted hash: %s"%hash
pwd = "pawan123"
##get this value from auth.settings.hmac_key
hmac_key = ""
def get_digest(value):
"""
Returns a hashlib digest algorithm from a string
"""
if not isinstance(value, str):
return value
value = value.lower()
if value == "md5":
return md5
elif value == "sha1":
return sha1
elif value == "sha224":
return sha224
elif value == "sha256":
return sha256
elif value == "sha384":
return sha384
elif value == "sha512":
return sha512
else:
raise ValueError("Invalid digest algorithm: %s" % value)
#hashed = simple_hash(self.password, key, salt, digest_alg)
def simple_hash(text, key='', salt='', digest_alg='md5'):
"""
Generates hash with the given text using the specified
digest hashing algorithm
"""
if not digest_alg:
raise RuntimeError("simple_hash with digest_alg=None")
elif not isinstance(digest_alg, str): # manual approach
h = digest_alg(text + key + salt)
elif digest_alg.startswith('pbkdf2'): # latest and coolest!
iterations, keylen, alg = digest_alg[7:-1].split(',')
return pbkdf2_hex(text, salt, int(iterations),
int(keylen), get_digest(alg))
elif key: # use hmac
digest_alg = get_digest(digest_alg)
h = hmac.new(key + salt, text, digest_alg)
else: # compatible with third party systems
h = get_digest(digest_alg)()
h.update(text + salt)
return h.hexdigest()
print "result hash: %s"%simple_hash(pwd, hmac_key, salt, "sha512")
I think your best solution is to write an auth backend that will authenticate the User against the web2py base, then ask him to change or confirm his password and build a new Django auth-passwords.
The hole idea of crypto-hashing passwords is that you or any hacker can't see them if you have access to the database.
Here is the Django documentation on writing an authentication backend.

Django-registation : Error on activate new user

I'm using Django registration inside my project on a development server.
When I register a new user, I use EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' to get the activation link.
When I try to put the activation link into the web browser, I have an error, and the account is not activated.
It is said :
Thank you.
This function is used to generate the key.
def create_profile(self, user):
"""
Create a ``RegistrationProfile`` for a given
``User``, and return the ``RegistrationProfile``.
The activation key for the ``RegistrationProfile`` will be a
SHA1 hash, generated from a combination of the ``User``'s
username and a random salt.
"""
salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
username = user.username
if isinstance(username, unicode):
username = username.encode('utf-8')
activation_key = hashlib.sha1(salt+username).hexdigest()
return self.create(user=user,
activation_key=activation_key)
I received that mail. But I use EMAIL_BACKEND'django.core.mail.backends.filebased.EmailBackend'.
I think the problem comes from here. But I can't test in production server.
I solved the problem actually It's because I generate the email to send inside a file thanks to the file email backends provided by django for development purpose. Inside this file, when there is a carriage return, it adds an = characters. And this is the case with the link to active the account.
You shouldn't have a = character in your activation key.
Although sergzach's answer will work, I'd be more interested in finding out why that = is there in the first place.
django-registration usually generates the key as follows:
salt = sha.new(str(random.random())).hexdigest()[:5]
activation_key = sha.new(salt+user.username).hexdigest()
Where are you generating yours?
The character '=' is not in the range of \w+. Use [\w=]+ instead of \w+.
Replace ?P<activation_key>\w+ to ?P<activation_key>[\w=]+

What is the cleanest way to add code to contrib.auth

I've migrated an old joomla installation over to django. The password hashes is an issue though. I had to modify the get_hexdigest in contrib.auth.models to have an extra if statement to reverse the way the hash is generated.
# Custom for Joomla
if algorithm == 'joomla':
return md5_constructor(raw_password + salt).hexdigest()
# Djangos original md5
if algorithm == 'md5':
return md5_constructor(salt + raw_password).hexdigest()
I also added the following to the User model to update the passwords after login if they have the old joomla style:
# Joomla Backwards compat
algo, salt, hsh = self.password.split('$')
if algo == 'joomla':
is_correct = (hsh == get_hexdigest(algo, salt, raw_password))
if is_correct:
# Convert the password to the new more secure format.
self.set_password(raw_password)
self.save()
return is_correct
Everything is working perfectly but I'd rather not edit this code directly in the django tree. Is there a cleaner way to do this in my own project?
Thanks
Your best bet would be to roll a custom auth backend and rewrite get_hexdigest in there. Never done it myself, but documentation on how to do so is available at http://docs.djangoproject.com/en/dev/topics/auth/#authentication-backends.
Thanks for the guidance. For anyone who needs to go the other way (DJango to Joomla) with DJ passwords, the DJ format is Sha1$salt$crypt.
Joomla standard auth plugin and joomla core JUserHelper do not implement the same SHA1 algorithum but it is fairly easy to patch into joomla.php in that plugin, where the plugin normally does an explode on ':'. Do a three-part explode with '$' and use salt = [1], compare that against $encrypted = sha1($salt.$plaintext), match that against the crypt [2].