#27468 closed Cleanup/optimization (fixed)
Move utils.crypto.salted_hmac() from SHA1 toward SHA256
Reported by: | Tim Graham | Owned by: | Claude Paroz |
---|---|---|---|
Component: | Utilities | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Simon Charette | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
The salted_hmac()
function uses SHA-1 in two places, for key derivation and as the hash function of the HMAC algorithm. HMAC-SHA1 is still considered secure, but it would be nice to move toward SHA256. Some work is needed to ensure backward compatibility (like we've done with password storage).
Ramifications:
- Changing the algorithm will make all the old HMACs invalid. This means that when users upgrade, old sessions, signed cookies, and session authentication hashes will be rejected as invalid and replaced (hence the need for a backwards-compatibility layer of some sort).
- SHA-256 is slower than SHA-1, though hopefully no Django servers are bottlenecked on hashing speed.
Thanks Predrag Gruevski (obi1kenobi on Github) for the report.
Attachments (1)
Change History (33)
comment:1 by , 8 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:2 by , 7 years ago
Owner: | changed from | to
---|
comment:3 by , 7 years ago
comment:4 by , 7 years ago
Has patch: | set |
---|
comment:5 by , 7 years ago
Needs tests: | set |
---|---|
Patch needs improvement: | set |
There are a number of review comments (from multiple reviewers) on the PR that need to be addressed in order to move this patch forwards.
comment:6 by , 5 years ago
Needs tests: | unset |
---|---|
Owner: | changed from | to
Patch needs improvement: | unset |
In this PR, I did the first step of allowing different hash algorithms in the salted_hmac()
function, and tried to apply the new capability to the contrib.messages CookieStorage. We can then work on other salted_hmac()
usages if that approach is validated.
follow-up: 8 comment:7 by , 5 years ago
Maybe there could be a SIGNING_HASHERS
similar to PASSWORD_HASHERS
? Add a "sha256:" prefix to new signatures, and a signature without an algorithm should be assumed to be SHA1
, similar to how a password without an algorithm prefix is assumed to be md5
.
That should provide a backwards-compatible transition path.
The default SIGNING_HASHERS
could be something like ['django.core.signing.hashers.SHA256', 'django.core.signing.hashers.SHA1']
(or maybe just ['sha256', 'sha1']
)
You could still override by passing in the algoritm name in the function signature.
comment:8 by , 5 years ago
Replying to Collin Anderson:
...Add a "sha256:" prefix to new signatures, and a signature without an algorithm should be assumed to be
SHA1
, similar to how a password without an algorithm prefix is assumed to bemd5
.
That's the approach I choose for the CookieStorage part of my patch, I also think it's a good upgrade path.
The default
SIGNING_HASHERS
could be something like['django.core.signing.hashers.SHA256', 'django.core.signing.hashers.SHA1']
(or maybe just['sha256', 'sha1']
)
Your idea about SIGNING_HASHERS
could be handled separately. I'm not yet convinced about use cases for that, but let's keep it in mind.
comment:11 by , 5 years ago
Should we keep this only ticket to track all Django usage of salted_hmac
or should we create tickets for each one:
- User session auth hash (django.contrib.auth.base_user)
Password rest tokens (django.contrib.auth.tokens)PRCookie storage of messages (django.contrib.messages.storage.cookie)(After #27604 is resolved, will useSigner
)- Session hashes (django.contrib.sessions.backends.base)
- Signing utility (django.core.signing)
I think that depending on the usage, different backwards-compatibility strategies may be involved.
comment:12 by , 5 years ago
Cc: | added |
---|
I'd definitely want to see django.core.signing
default to SHA256
, happy to submit a ticket and patch if it's not already on your plate Claude?
comment:13 by , 5 years ago
comment:16 by , 5 years ago
We have now two proposed strategies for handling an algorithm change in django.core.signing.Signer
:
- the first one was to include the signer algorithm in the signature, so when unsigning, we use that algorithm taken from the signature.
That approach was not approved by @ubernostrum, so base on his comments, an alternative is:
- set the algorithm in the Signer class (with an optional
legacy_algorithm
value to handle the transition periods), so unsigning is forced using that algorithm, which avoids trusting a possibly mangled algorithm in the signature itself.
comment:19 by , 5 years ago
Has patch: | unset |
---|
comment:20 by , 5 years ago
Claude, are you going to work on the last point, i.e. user session auth hash? I would really like to close it in Django 3.1. I can prepare it if you're busy, just let me know. Thanks.
comment:21 by , 5 years ago
Mariusz, I attached a "very" WIP patch (no tests, no docs) I was working on some days ago. It might not even be the good approach, but feel free to get some inspiration if it has any merit. I'll happily review your version.
comment:22 by , 5 years ago
Has patch: | set |
---|
Thanks! I don't think we need to persist legacy sessions in login()
, what's more flushing them should help not using them anymore.
comment:24 by , 5 years ago
Resolution: | → fixed |
---|---|
Status: | assigned → closed |
A WIP PR has been raised here - https://github.com/django/django/pull/8773