Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constant time to protect against timing attacks #20

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ return `true` or `false` to indicate that the credentials were approved or not:
app.use(basicAuth( { authorizer: myAuthorizer } ))

function myAuthorizer(username, password) {
// Take care to use a timing attack safe compare function in real code
return username.startsWith('A') && password.startsWith('secret')
}
```
Expand All @@ -79,6 +80,31 @@ This will authorize all requests with credentials where the username begins with
`'A'` and the password begins with `'secret'`. In an actual application you would
likely look up some data instead ;-)

### Timing attacks

When using a custom validation callback, even when comparing hashes, make sure
to **avoid standard string compare (`==`)**. Use a "constant time compare
function". This protects you against [timing
attacks](https://en.wikipedia.org/wiki/Timing_attack).

For example, using
[safe-compare](https://www.npmjs.com/package/safe-compare):

```js
const compare = require('safe-compare');
app.use(basicAuth( { authorizer: myAuthorizer } ))

function myAuthorizer(username, password) {
// Use non-short-circuiting evaluation to ensure constant time comparison
return compare("admin", username) & compare("admin-password", password);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there's a typo in this code. I believe @hraban meant a &&

Suggested change
return compare("admin", username) & compare("admin-password", password);
return compare("admin", username) && compare("admin-password", password);

Copy link
Author

@hraban hraban Mar 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially made the same mistake, but someone pointed out to me that using a short-circuiting conjunction operator (&&) will leak information about the username. Using an eager operator (&) will be actually constant time.

I added a comment to the readme to elaborate on it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂️
yes let's document to prevent same mistakes in the future! ;-)

}
```

To avoid leaking information about the username, a
non-[short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation)
and operator is used; `&`. This guarantees both username and password are always
compared.

### Custom Async Authorization

Note that the `authorizer` function above is expected to be synchronous. This is
Expand Down
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const auth = require('basic-auth')
const assert = require('assert')
const compare = require('safe-compare');

function ensureFunction(option, defaultValue) {
if(option == undefined)
Expand All @@ -24,7 +25,7 @@ function buildMiddleware(options) {

function staticUsersAuthorizer(username, password) {
for(var i in users)
if(username == i && password == users[i])
if(compare(username, i) & compare(password, users[i]))
return true

return false
Expand Down
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
},
"homepage": "/~https://github.com/LionC/express-basic-auth#readme",
"dependencies": {
"basic-auth": "^2.0.1"
"basic-auth": "^2.0.1",
"safe-compare": "^1.1.3"
},
"devDependencies": {
"@types/express": "^4.16.0",
Expand Down