TryGhost / Ghost

Independent technology for modern publishing, memberships, subscriptions and newsletters.
https://ghost.org
MIT License
46.98k stars 10.22k forks source link

Login Takes Too Much Time. #1207

Closed lfuelling closed 10 years ago

lfuelling commented 10 years ago

When I log in (or sign up) on my Ghost installation, it takes up to three minutes until the page reloads and I'm logged in.

I use Ghost on my Raspberry Pi (not the most powerful device, I know) and I don't think this should last that long.

jayay commented 10 years ago

Yeah, same here. node index slows down the pi.

lfuelling commented 10 years ago

The strange thing is that this only affects the sign up/login process. Creating a post or updating an image works normal.

jayay commented 10 years ago

Exactly. CPU usage increases to 100 percent at login/server start.

sebgie commented 10 years ago

Just an uneducated guess, but is it possible that this is caused due to cryptographic operations during signup/login/server start?

lfuelling commented 10 years ago

Yes, this is what I think. But which cryptographic operations in a login operation need to take 3 Minutes? That's a bit long...

sebgie commented 10 years ago

As far as I know, there are no exotic operations involved. Hash calculation for passwords (bcrypt-nodejs) and encryption of sessions (express).

jgillich commented 10 years ago

Anther one having that issue.

But I don't think this is directly related to the Pi's performance. I run Ghost on a rather slow Celeron 1007U with 1.5 GHz and login is super quick (~1 sec).

What OS do you run on your Pi? Maybe you can compare your setup with @halfdan, I guess he had no such issues. :)

I will also try Ghost on my Cubieboard soon, might as well be related to the ARM architecture.

jayay commented 10 years ago

I'm running it on Raspbian. But I think IT might be caused by SQLite. I'll try it with MySQL.

lfuelling commented 10 years ago

I'm using Raspbian, the Hard-Float version. I thought SQLite was a bit perfomance friendlier compared to MySQL...

halfdan commented 10 years ago

@jgillich I actually have the same issues, just didn't bother to look into it yet. I'll check what's happening later today and provide a fix if possible.

halfdan commented 10 years ago

The issue really comes from bcrypt being horribly slow on the Raspberry Pi. I think there's not much you can do about it since both the bcryptjs and nodejs-bcrypt libraries are relatively slow (as intended by bcrypt).

ErisDS commented 10 years ago

Thanks for looking into this @halfdan

Unfortunately this means there is no bug to be resolved. The slow speed of bcrypt is completely intentional, so if nothing else is slowing things down there's nothing to fix.

mirceanis commented 10 years ago

I managed to get much better performance using bcrypt instead of bcrypt-nodejs. The API is almost the same so there are very few changes to make in core/server/models/user.js

ErisDS commented 10 years ago

@mirceanis Thanks for this useful piece of info.

It makes sense that bcrypt would be faster as it is a binding for the C++ module, rather than a JavaScript implementation. Unfortunately this is also the reason why we can't use bcrypt in the core of Ghost - it requires anyone installing Ghost to compile a native binary for that module, which requires a number of complex dependencies and would be a significant barrier to the average person trying to install Ghost.

However, what we can do is consider how we can make bcrypt easily replaceable with an App so that people who are comfortable building a binary can benefit from the improved performance of bcrypt.

mirceanis commented 10 years ago

Yeah, I figured that would be the case but I felt that maybe that info would benefit people who are running Ghost on very slow machines (like I am). On my Raspberry Pi it takes minutes to do a password compare with the pure javascript implementation of bcrypt and it's almost instant with the c++ module.

ErisDS commented 10 years ago

@mirceanis Definitely interesting - well worth considering how we can make this configurable with an App.

halfdan commented 10 years ago

This really defeats the point of using bcrypt. Bcrypt is supposed to take an significant amount of time to compute a hash in order to prevent/slow down brute force attacks.

What you can do however is decrease the number of rounds (currently by default at 2^10).

mirceanis commented 10 years ago

Even with reduced number of rounds, bcrypt-nodejs is increddibly slow. If the purpose is to slow down brute-force attacks, you can easily setTimeout() to a controllable delay.

halfdan commented 10 years ago

@mirceanis You're missing the point. setTimeoutwouldn't do any good here - it should be hard to find the password even if you have the hash. By using more rounds you make it hard for an attacker to brute force the hash even when he's running it on his own machine.

mirceanis commented 10 years ago

@halfdan I don't get it. You were the one suggesting a smaller number of rounds. What I tried to say was that bcrypt-nodejs is terribly slow even with reduced number of rounds but bcrypt is fast even with the default number of rounds. AND, if the attacker already has your hash, he most certainly would NOT use bcrypt-nodejs to brute-force it.

My suggestion for require('bcrypt') + setTimeout() was to create a "tar pit" for anyone trying different passwords remotely. In this case, the server would not waste CPU trying to compare brute-force passwords, It would do the computation fast and then delay the response. So if a burst of password attempts was made, the server would respond with "Too many logins".

halfdan commented 10 years ago

There already is a brute force protection in place - limiting the amount of requests that are processed.

We are talking about two different things here:

ksmithbaylor commented 10 years ago

I am having the same problem, and I traced it even further into the bcrypt-nodejs code. It seems that it is hanging in the decode_base64 function just after getting the character code for the unicode character represented by 65518. I don't know if this is just because of the particular password that I used, but I hope it helps somebody. I'm going to try to get the other bcrypt library working instead.

ghost commented 10 years ago

I'm encountering this issue too, on a raspberry pi. I hope there will be a workaround/fix for this, because I find this platform really beautiful/endearing, but I'm unable to use it so far.

ksmithbaylor commented 10 years ago

I couldn't figure out what the problem with bcrypt-nodejs is, but I made a temporary fix by using sha1 for user password encryption instead. Here's the gist: https://gist.github.com/ksmithbaylor/8232988. It runs very fast on my raspberry pi, and should be relatively secure. All I did was hash the password with sha1 instead of bcrypt.

ErisDS commented 10 years ago

There is, to my knowlege, no problem with bcrypt-nodejs, as mentioned in previous comments, bcrypt is intentionally slow - it is a security feature.

bcrypt is delibrately slow and resource intensive to try to make brute forcing more difficult, the JS-based implementation Ghost uses is even more slow and intensive.

What's happening is you're running an incredibly complex cryptographic operation on sub-standard hardware. Don't get me wrong, I love the Pi as much as the next person, but it is much less powerful than an average computer.

sha1 is not secure and should not be used for hashing passwords

Someone can use the might of amazon ec2 (another great way of running Ghost btw) and brute-force a sha1 hash of an 8-char password in less time than you are reporting it takes to log in with bcrypt.

voronoipotato commented 10 years ago

@mirceanis you seem to misunderstand the premise of having a more complicated hash. The higher bcrypt value is not to prevent brute force log-ins (although that is a side effect). The point of having a higher bcrypt value is that if your database is compromised, when someone tries to rainbow table, or crack passwords in your database it takes longer to generate those values. If you keep that value high enough it can be prohibitively expensive to do so (millions or even billions of dollars). @ksmithbaylor you would be better off simply reducing your bcrypt cost.

@ErisDS bcrypt is intentionally slow, but having it be slower without adding complexity does nothing, is there perhaps an optional DLL dependency? The thing is that an attacker will have a very, very fast bcrypt implementation (GPU etc), so every bit of speed counts.

ErisDS commented 10 years ago

@voronoipotato Ghost is using the js implementation because C++ binaries are impossible for the average user to install. If you want to use a different implementation of bcrypt feel free to hack Ghost, but introducing this for everyone does not make sense.

voronoipotato commented 10 years ago

@ErisDS Fair, I believe the team that is creating your library is working on some degree of parallelization so that will cause a speed increase. Is there a way in the set up process to change the bcrypt cost? I believe that would solve most of the average user's struggles, and would provide the ability for them to have higher security if they felt they were a higher profile target.

ksmithbaylor commented 10 years ago

I understand that bcrypt is intentionally slow. However, the problem is not its speed, but the fact that on ARM hardware, this particular implementation seems to hang for an arbitrary amount of time (up to 75s on my machine) during one iteration of the loop in decode_base64 just after executing the charCodeAt function and returning from getByte. The rest of the algorithm runs perfectly fine, and indeed other implentations of bcrypt would not be orders of magnitude faster if it were not an issue with this particular implantation.

Sha1 was not a good choice, I agree. I am going to replace my example with a different bcrypt library later today.

mirceanis commented 10 years ago

@voronoipotato You have completely misunderstood what I said. I was offering a solution to a problem. I'm not criticizing bcrypt as an algorithm and I am not suggesting the use of lighter encryption, not even less number of rounds with bcrypt.

All I'm saying is that the pure javascript implementation is too slow for cheap hardware like the raspberry PI. It takes 2 minutes to verify a password and during that time, CPU usage is at 100%. My suggestion for people encountering that problem was to use the C++ implementation of bcrypt which has a similar API and is much faster (milliseconds instead of minutes).

At the same time, I understand the need to avoid "complicated" dependencies so @ErisDS has a point.

ksmithbaylor commented 10 years ago

It seems I must apologize for being wrong. I just looked into it and you are 100% correct, @ErisDS. Chalk it up to my inexperience with node and debugging asynchronous code, but all the other pure javascript implementations of bcrypt performed about the same.

I'm still confused about your claims that the C++ implementation (at https://npmjs.org/package/bcrypt) is hard to install for the average user. I thought npm took care of building the binaries for any dependencies. Running 'npm install bcrypt' seems to compile the code just fine and let me use it out of the box. Am I missing something?

voronoipotato commented 10 years ago

@ksmithbaylor Windows users will need visual studio configured, also the installation of OpenSSL etc.

ErisDS commented 10 years ago

Before Ghost was public, when there were only ~200 users, sqlite3 didn't have pre-built binaries. It was hell to install, for all users on all systems except for the luckiest developers who already had everything. It is vastly improved now but still not as good as I'd like.

joelekstrom commented 10 years ago

I too had this issue and would like to emphasize once again how much slower the JS version of bcrypt actually is. I'm having a hard time accepting that taking 50+ seconds compared to the few milliseconds of a native binary is an "intended" slowdown. The Pi might be slower, but it isn't that slow (which is proved by using a native binary). This is much more likely due to an edge issue with JS optimization on ARM architectures.

I'm pressing for this issue to be opened again. It's not a direct issue with Ghost itself, but rather an issue with the JS implementation of bcrypt used. Or, since apparently more bcrypt implementations in JS have the same problem, a block of JS used by all these implementations that is seriously bogged down on ARM architectures.

And while this may not directly be an issue with Ghost, it's directly affecting its end users, and if it's not solvable maybe a solution could be proposed when installing or something like that. The binary version may be harder to install, but better that than users abandoning Ghost since they think the admin login is broken.

joelekstrom commented 10 years ago

Also, maybe I was lucky for having correct dependencies - but for all you others trying to fix this, this solved it for me:

npm install bcrypt

In core/server/models/user.js, change

bcrypt         = require('bcryptjs')

into:

bcrypt         = require('bcrypt')
ErisDS commented 10 years ago

The binary version may be harder to install, but better that than users abandoning Ghost since they think the admin login is broken.

Installing the binary would affect every single Ghost user, many of whom have no technical knowledge. The issue with slow down on login affects just a few people who by their own selection have at least some technical knowledge. It really isn't that hard to swap out the library as you have discovered.

joelekstrom commented 10 years ago

Installing the binary would affect every single Ghost user, many of whom have no technical knowledge. The issue with slow down on login affects just a few people who by their own selection have at least some technical knowledge. It really isn't that hard to swap out the library as you have discovered.

Yes, I'm not suggesting this is applied for all Ghost installations, but rather that maybe the installer could drop a hint about it if it detects an ARM architecture or something like that. This will not only be a niche problem for Raspberry Pi in the near future as ARM hardware is starting to be used in servers.

ImTheDeveloper commented 10 years ago

@accatyyc your suggested fix works perfect on the Raspberry Pi for me.

pi@raspberrypi ~/ghost $ sudo npm install bcrypt
npm http GET https://registry.npmjs.org/bcrypt
npm http 200 https://registry.npmjs.org/bcrypt
npm http GET https://registry.npmjs.org/bcrypt/-/bcrypt-0.7.7.tgz
npm http 200 https://registry.npmjs.org/bcrypt/-/bcrypt-0.7.7.tgz
npm http GET https://registry.npmjs.org/bindings/1.0.0
npm http 200 https://registry.npmjs.org/bindings/1.0.0
npm http GET https://registry.npmjs.org/bindings/-/bindings-1.0.0.tgz
npm http 200 https://registry.npmjs.org/bindings/-/bindings-1.0.0.tgz

> bcrypt@0.7.7 install /home/pi/ghost/node_modules/bcrypt
> node-gyp rebuild

gyp WARN EACCES user "root" does not have permission to access the dev dir "/root/.node-gyp/0.10.24"
gyp WARN EACCES attempting to reinstall using temporary dev dir "/home/pi/ghost/node_modules/bcrypt/.node-gyp"
gyp http GET http://nodejs.org/dist/v0.10.24/node-v0.10.24.tar.gz
gyp http 200 http://nodejs.org/dist/v0.10.24/node-v0.10.24.tar.gz
make: Entering directory `/home/pi/ghost/node_modules/bcrypt/build'
  CXX(target) Release/obj.target/bcrypt_lib/src/blowfish.o
  CXX(target) Release/obj.target/bcrypt_lib/src/bcrypt.o
  CXX(target) Release/obj.target/bcrypt_lib/src/bcrypt_node.o
  SOLINK_MODULE(target) Release/obj.target/bcrypt_lib.node
  SOLINK_MODULE(target) Release/obj.target/bcrypt_lib.node: Finished
  COPY Release/bcrypt_lib.node
make: Leaving directory `/home/pi/ghost/node_modules/bcrypt/build'
bcrypt@0.7.7 node_modules/bcrypt
└── bindings@1.0.0
julianlam commented 10 years ago

Issue opened upstream. For reference, the NodeBB team ran into this recently. So kudos to you guys for doing the legwork to find that using the bcrypt package could be an acceptable workaround.

We also believe in pure-js dependencies, so if it could be fixed upstream, that would be best for us all.

ofstudio commented 9 years ago

Installing bcrypt completely solves this problem on Raspberry Pi (actually npm install bcrypt takes less time than single login using bcryptjs).

It would be nice if using bgcrypt instead of bcryptjs will be default behavior on ARM systems.

Dygear commented 9 years ago

It's still the case the bcrypt is not compiled on ARM system. This really needs to happen, otherwise ghost is useless on ARM platforms.

halfdan commented 9 years ago

@Dygear I am sorry you are having trouble. bryptjs however relies only on JavaScript and therefore works on any ARM platform. The only reason why it is slow on the Raspberry Pi is because the Pi lacks a proper FPU (but you can speed things up by replacing bcryptjs with the native brypt library as pointed out by @ofstudio). Ghost certainly isn't useless on ARM platforms.

Zepmann commented 8 years ago

The installation and configuration of bcrypt to replace bcryptjs as described in this comment is now mandatory when Ghost is hosted on a Raspberry Pi 1. With bcryptjs the login procedure will timeout with a 'There was a problem on the server' error. This was tested on stock CPU speed with Ghost 0.8.0-1 from the Arch Linux User Repository and nginx 1.10.1 as a reverse proxy server.