Admin password in SHA1, normal password in plain text #2656

Open
opened 2026-02-28 01:16:52 -05:00 by deekerman · 16 comments
Owner

Originally created by @SuMaPa on GitHub (Nov 17, 2025).

Hi
Sha1 is a little better than plaintext.
So I changed the normal password to SHA1 in the config.py file. Works perfectly.
I haven't been able to switch to >SHA1 yet. Password generation works, but the login doesn't.
I was able to gather some insights. However, a bypass solution will then be needed.

Originally created by @SuMaPa on GitHub (Nov 17, 2025). Hi Sha1 is a little better than plaintext. So I changed the normal password to SHA1 in the config.py file. Works perfectly. I haven't been able to switch to >SHA1 yet. Password generation works, but the login doesn't. I was able to gather some insights. However, a bypass solution will then be needed.
Author
Owner

@MichaIng commented on GitHub (Nov 17, 2025):

When touching this, we'd need to keep supporting the old hashing algorithm for reading the password, but we may want to force users to set/change it once, to have it stored in new format ASAP. However, does not make much sense to use insecure SHA1. At best we switch to Argon2ID, or minimum bcrypt.

@MichaIng commented on GitHub (Nov 17, 2025): When touching this, we'd need to keep supporting the old hashing algorithm for reading the password, but we may want to force users to set/change it once, to have it stored in new format ASAP. However, does not make much sense to use insecure SHA1. At best we switch to Argon2ID, or minimum bcrypt.
Author
Owner

@MichaIng commented on GitHub (Nov 17, 2025):

Btw, regarding normal/surveillance user password, AFAIK the reason to store it in plain text was that it is reused as direct motion camera password, also used by motionEye to access those streams. Hence storing it hashed would break access. Not an awesome implementation, but I have no other great idea how to do better: motionEye could use some internal-only random password to access motion, that is independent of the surveillance user password used to authenticate at motionEye. But that would break direct access to those streams for users, bypassing motionEye interface. The idea surely was to keep it simple, having one surveillance user password to access motionEye read-only, as well as access motion camera streams directly. And AFAIK there is no way to have two passwords for a motion cam, to allow access via surveillance user password, while motionEye uses an own internal one that is not exposed in any UI.

@MichaIng commented on GitHub (Nov 17, 2025): Btw, regarding normal/surveillance user password, AFAIK the reason to store it in plain text was that it is reused as direct `motion` camera password, also used by motionEye to access those streams. Hence storing it hashed would break access. Not an awesome implementation, but I have no other great idea how to do better: motionEye could use some internal-only random password to access `motion`, that is independent of the surveillance user password used to authenticate at motionEye. But that would break direct access to those streams for users, bypassing motionEye interface. The idea surely was to keep it simple, having one surveillance user password to access motionEye read-only, as well as access `motion` camera streams directly. And AFAIK there is no way to have two passwords for a `motion` cam, to allow access via surveillance user password, while motionEye uses an own internal one that is not exposed in any UI.
Author
Owner

@zagrim commented on GitHub (Nov 17, 2025):

When touching this, we'd need to keep supporting the old hashing algorithm for reading the password

Different hash algorithms produce hashes of different lengths (and have even distinctly recognisable formats, see examples in Hashcat docs), so this shouldn't be too difficult 🤔

motionEye could use some internal-only random password to access motion, that is independent of the surveillance user password used to authenticate at motionEye. But that would break direct access to those streams for users, bypassing motionEye interface.

Yeah, not to mention that the password for accessing Motion (be it only used internally by ME or not) needs to be stored in plaintext anyway, so having a more secure read-only access to ME UI might not make much sense 🤔 I mean, if someone could read the surveillance user password from ME config, that same attacker could just as fine read the password from Motion config and anyway access Motion directly. The original design of Motion wasn't done with much regard for security, and that was pretty normal back then I guess so I'm not blaming anyone here. I guess it just is one of those things that needs to be guarded from outside access by all means and on all levels if security is a requirement.

@zagrim commented on GitHub (Nov 17, 2025): > When touching this, we'd need to keep supporting the old hashing algorithm for reading the password Different hash algorithms produce hashes of different lengths (and have even distinctly recognisable formats, see [examples in Hashcat docs](https://hashcat.net/wiki/doku.php?id=example_hashes)), so this shouldn't be too difficult 🤔 > motionEye could use some internal-only random password to access motion, that is independent of the surveillance user password used to authenticate at motionEye. But that would break direct access to those streams for users, bypassing motionEye interface. Yeah, not to mention that the password for accessing Motion (be it only used internally by ME or not) needs to be stored in plaintext anyway, so having a more secure read-only access to ME UI might not make much sense 🤔 I mean, if someone could read the surveillance user password from ME config, that same attacker could just as fine read the password from Motion config and anyway access Motion directly. The original design of Motion wasn't done with much regard for security, and that was pretty normal back then I guess so I'm not blaming anyone here. I guess it just is one of those things that needs to be guarded from outside access by all means and on all levels if security is a requirement.
Author
Owner

@SuMaPa commented on GitHub (Nov 17, 2025):

I'm not sure if I understand you correctly.
In the config.py file, I changed the user to sha1. The SHA1 password was created in the motion.conf file.
All other passwords continue to be stored in plain text. I can't find any restrictions.
Unfortunately, I haven't made any further progress. The SHA256 string is being saved correctly, but it's not being recognized upon login. There might be a hidden character limit somewhere that I haven't found yet.

:( ...postponed until next week

@SuMaPa commented on GitHub (Nov 17, 2025): I'm not sure if I understand you correctly. In the config.py file, I changed the user to sha1. The SHA1 password was created in the motion.conf file. All other passwords continue to be stored in plain text. I can't find any restrictions. Unfortunately, I haven't made any further progress. The SHA256 string is being saved correctly, but it's not being recognized upon login. There might be a hidden character limit somewhere that I haven't found yet. :( ...postponed until next week
Author
Owner

@SuMaPa commented on GitHub (Nov 19, 2025):

Image Image

user (anyway) and admin name can be changed, possibly a little more security??

@SuMaPa commented on GitHub (Nov 19, 2025): <img width="723" height="198" alt="Image" src="https://github.com/user-attachments/assets/3c98bec6-0fa5-4b0f-945a-d264d9feab27" /> <img width="515" height="110" alt="Image" src="https://github.com/user-attachments/assets/13723acf-6c0c-4e2e-a277-1cd3b1b50491" /> user (anyway) and admin name can be changed, possibly a little more security??
Author
Owner

@SuMaPa commented on GitHub (Nov 27, 2025):

I'm too stupid for that.

@SuMaPa commented on GitHub (Nov 27, 2025): I'm too stupid for that.
Author
Owner

@MichaIng commented on GitHub (Nov 27, 2025):

Sorry for the late reply. So you applied hashing in config.py here, but you could not login afterwards? Did you also implement support for the hash at the authentication part? Here I mean: https://github.com/motioneye-project/motioneye/blob/main/motioneye/handlers/base.py#L115-L135

Although, it seems to allow both already:

  • Assuming normal_password is a hash
  • normal_password in (up['password'], hashlib.sha1(up['password'].encode('utf-8')).hexdigest()) is true as it matches the 2nd entry: the hashed password header.
  • If it is in plain text, it machtes the 1st entry: the plain password header.
  • Alone the fact that this code is identical for admin and "normal" users means that a hash for the normal user should not cause any issue, as the same works for the admin.

If this does not work, and authentication fails there, it would need some debug code to find out why.

And yes, the usernames are variables internally already, but in many places of the code they are still hardcoded. Should not be too hard to find and adjust those places, and turn the currently read-only fields in the settings into read-write fields, to change those usernames.

I found the reason why this password is not hashed right now:

motion also has a webcontrol authentication option, but that is not currently used by motionEye:

  • https://motion-project.github.io/motion_config.html#webcontrol_auth_method
  • An open request about it: #1671
  • But that one would not necessarily interfere with password hashing: What could be done there is generating a long random password exclusively for this motion control port, and storing that separately, as digest or plain does not really matter, and using that for internal motion control API requests. It does not need to be visible in the GUI anywhere. So this would be just to protect the control port for attackers with access to that port, but cannot help against attackers with read-access to the motion or motionEye configs.
@MichaIng commented on GitHub (Nov 27, 2025): Sorry for the late reply. So you applied hashing in `config.py` [here](https://github.com/motioneye-project/motioneye/blob/main/motioneye/config.py#L791-L794), but you could not login afterwards? Did you also implement support for the hash at the authentication part? Here I mean: https://github.com/motioneye-project/motioneye/blob/main/motioneye/handlers/base.py#L115-L135 Although, it seems to allow both already: - Assuming `normal_password` is a hash - `normal_password in (up['password'], hashlib.sha1(up['password'].encode('utf-8')).hexdigest())` is true as it matches the 2nd entry: the hashed password header. - If it is in plain text, it machtes the 1st entry: the plain password header. - Alone the fact that this code is identical for admin and "normal" users means that a hash for the normal user should not cause any issue, as the same works for the admin. If this does not work, and authentication fails there, it would need some debug code to find out why. And yes, the usernames are variables internally already, but in many places of the code they are still hardcoded. Should not be too hard to find and adjust those places, and turn the currently read-only fields in the settings into read-write fields, to change those usernames. I found the reason why this password is not hashed right now: - https://motion-project.github.io/motion_config.html#stream_authentication - https://github.com/search?q=repo%3Amotioneye-project%2Fmotioneye+stream_authentication - So direct camera stream authentication can be enabled in which case the "normal" user credentials are used for simplicity. So this would break when hashing the password, as one would then need to enter the hash itself for viewing the direct camera stream. `motion` also has a webcontrol authentication option, but that is not currently used by motionEye: - https://motion-project.github.io/motion_config.html#webcontrol_auth_method - An open request about it: #1671 - But that one would not necessarily interfere with password hashing: What could be done there is generating a long random password exclusively for this motion control port, and storing that separately, as digest or plain does not really matter, and using that for internal motion control API requests. It does not need to be visible in the GUI anywhere. So this would be just to protect the control port for attackers with access to that port, but cannot help against attackers with read-access to the motion or motionEye configs.
Author
Owner

@SuMaPa commented on GitHub (Nov 27, 2025):

Please do not test on your live system.
It's easy to make the adminusername/ "admin" changeable.
templates/main.html

search: id="adminUsernameEntry" readonly="readonly"
remove: readonly="readonly"

It is also easy to encrypt the user password using SHA1.
config.py

search:     if ui.get('normal_password') is not None:
remove:     data['@normal_password'] = ui['normal_password']
paste:
        if ui['normal_password']:
            data['@normal_password'] = hashlib.sha1(
                ui['normal_password'].encode('utf-8')
            ).hexdigest()
        else:
            data['@normal_password'] = ''

I tested this on several test installations, and it works very well under Debian (12&13).
Please do not test on your live system.

Anything higher than SHA1 doesn't work.

Passwords are generated correctly (config.py).

Queries are apparently made via utils/init.py (on restart) and handlers/base.py.

Right now I'm just annoyed. Once I've calmed down, I'll add debug code. Maybe.

@MichaIng Everything's fine, we all have work to do.

@SuMaPa commented on GitHub (Nov 27, 2025): **Please do not test on your live system.** It's easy to make the adminusername/ "admin" changeable. templates/main.html ``` search: id="adminUsernameEntry" readonly="readonly" remove: readonly="readonly" ``` It is also easy to encrypt the user password using SHA1. config.py ``` search: if ui.get('normal_password') is not None: remove: data['@normal_password'] = ui['normal_password'] paste: if ui['normal_password']: data['@normal_password'] = hashlib.sha1( ui['normal_password'].encode('utf-8') ).hexdigest() else: data['@normal_password'] = '' ``` I tested this on several test installations, and it works very well under Debian (12&13). **Please do not test on your live system.** Anything higher than SHA1 doesn't work. Passwords are generated correctly (config.py). Queries are apparently made via utils/__init__.py (on restart) and handlers/base.py. Right now I'm just annoyed. Once I've calmed down, I'll add debug code. Maybe. @MichaIng Everything's fine, we all have work to do.
Author
Owner

@MichaIng commented on GitHub (Nov 27, 2025):

Anything higher than SHA1 doesn't work.

That makes sense unless you replace the algorithm everywhere it is used, i.e. in the base.py handler I linked above.

See here about updating the algorithm: #2467

@MichaIng commented on GitHub (Nov 27, 2025): > Anything higher than SHA1 doesn't work. That makes sense unless you replace the algorithm everywhere it is used, i.e. in the `base.py` handler I linked above. See here about updating the algorithm: #2467
Author
Owner

@SuMaPa commented on GitHub (Nov 28, 2025):

SHA1 user password works with video streaming.
Video streaming works even when it's switched off. :(

Image
@SuMaPa commented on GitHub (Nov 28, 2025): SHA1 user password works with video streaming. Video streaming works even when it's switched off. :( <img width="2149" height="807" alt="Image" src="https://github.com/user-attachments/assets/013db6ee-0163-4fa9-be99-d50b7ea601aa" />
Author
Owner

@MichaIng commented on GitHub (Nov 29, 2025):

Oh, that is odd. And indeed the code looks like it would just allow everything, hash, plain, or no authentication for normal/surveillance user. But that the direct stream cannot even be disabled raises the severance of that issue.

The [stream_auth_method[https://motion-project.github.io/motion_config.html#stream_auth_method] allows at least MD5 digest authentication, which hence would allow the normal user password to be stored this way, without breaking double-use of it. Not awesome, but better than plain text. However, inside the motion config, it seems to be still needed in plain text, needs testing.

Let me re-open this issue. This whole normal user and direct stream authentication needs some require at least.

@MichaIng commented on GitHub (Nov 29, 2025): Oh, that is odd. And indeed the code looks like it would just allow everything, hash, plain, or no authentication for normal/surveillance user. But that the direct stream cannot even be disabled raises the severance of that issue. The [stream_auth_method[https://motion-project.github.io/motion_config.html#stream_auth_method] allows at least MD5 digest authentication, which hence would allow the normal user password to be stored this way, without breaking double-use of it. Not awesome, but better than plain text. However, inside the `motion` config, it seems to be still needed in plain text, needs testing. Let me re-open this issue. This whole normal user and direct stream authentication needs some require at least.
Author
Owner

@MichaIng commented on GitHub (Feb 14, 2026):

#3268 added dedicated credentials for streaming, so the surveillance/normal user password does not need to be in plain text anymore.

So we can not hash it, and in turn we should apply a stronger algorithm: Argon2ID, yescrypt, bcrypt minimum, I'd say, depending on what is available in standard Python 3 libraries.

For backwards-compatibility/migration we could either check the hash for a SHA1 marker, or simply compare SHA1 as a fallback, if the stronger algorithm did not match, similar to how we compare the unhashed input as fallback as well. And optionally we could print a warning if SHA1 worked, suggestion to update the password (re-enter + save), to migrate to the stronger algorithm.

When changing/saving a password, the strong algorithm should be always used, of course.

@MichaIng commented on GitHub (Feb 14, 2026): #3268 added dedicated credentials for streaming, so the surveillance/normal user password does not need to be in plain text anymore. So we can not hash it, and in turn we should apply a stronger algorithm: Argon2ID, yescrypt, bcrypt minimum, I'd say, depending on what is available in standard Python 3 libraries. For backwards-compatibility/migration we could either check the hash for a SHA1 marker, or simply compare SHA1 as a fallback, if the stronger algorithm did not match, similar to how we compare the unhashed input as fallback as well. And optionally we could print a warning if SHA1 worked, suggestion to update the password (re-enter + save), to migrate to the stronger algorithm. When changing/saving a password, the strong algorithm should be always used, of course.
Author
Owner

@SuMaPa commented on GitHub (Feb 15, 2026):

Hi
I haven't been able to get sha512 working - bcrypt won't make it any better. :)

@SuMaPa commented on GitHub (Feb 15, 2026): Hi I haven't been able to get sha512 working - bcrypt won't make it any better. :)
Author
Owner

@MichaIng commented on GitHub (Feb 15, 2026):

Of course bcrypt would make it some magnitudes of order better. This calculator suggests it takes about a million times longer to calculate/brute-force bcrypt vs SHA1: https://kutatua.com/password/time-to-crack-calculator
Of course things depend, but you get the idea. Point is that bcrypt is made for password explicitly hashing, SHA1 isn't.

But true is that bcrypt aged as well, and it doesn't require much RAM to calculate, so cheap parallelism is possible, which yescrypt and especially Argon2 address.

Seems this is a good one to use Argon2: https://pypi.org/project/argon2-cffi/
It will require the C libffi library and the Python cffi module as additional dependencies.

PyNaCl also has a password hashing module, but it seems overkill.

The best standard library alternative would be PBKDF2-HMAC-SHA256: https://docs.python.org/3/library/hashlib.html#hashlib.pbkdf2_hmac
But recently this depends on OpenSSL. Might be rare, but theoretically possible that it is not available in some edge cases 🤔.

@MichaIng commented on GitHub (Feb 15, 2026): Of course bcrypt would make it some magnitudes of order better. This calculator suggests it takes about a million times longer to calculate/brute-force bcrypt vs SHA1: https://kutatua.com/password/time-to-crack-calculator Of course things depend, but you get the idea. Point is that bcrypt is made for password explicitly hashing, SHA1 isn't. But true is that bcrypt aged as well, and it doesn't require much RAM to calculate, so cheap parallelism is possible, which yescrypt and especially Argon2 address. Seems this is a good one to use Argon2: https://pypi.org/project/argon2-cffi/ It will require the C libffi library and the Python cffi module as additional dependencies. PyNaCl also has a password hashing module, but it seems overkill. The best standard library alternative would be PBKDF2-HMAC-SHA256: https://docs.python.org/3/library/hashlib.html#hashlib.pbkdf2_hmac But recently this depends on OpenSSL. Might be rare, but theoretically possible that it is not available in some edge cases 🤔.
Author
Owner

@SuMaPa commented on GitHub (Feb 16, 2026):

hi
I see it the same way; it should be encrypted with bcrypt.
I just wanted to express my incompetence.
I wasn't able to upgrade to SHA512. Therefore, I don't need to worry about bcrypt.
Sorry

@SuMaPa commented on GitHub (Feb 16, 2026): hi I see it the same way; it should be encrypted with bcrypt. I just wanted to express my incompetence. I wasn't able to upgrade to SHA512. Therefore, I don't need to worry about bcrypt. Sorry
Author
Owner

@MichaIng commented on GitHub (Feb 16, 2026):

Ah right, I remember 😄. I am currently updating the frontend, getting rid of some obsolete cruft and 3rd party scripts. I should find some time after this is done.

In any case, bcrypt, yescript, Argon2 all would require some more work, since this would need to be done with external modules. Only PBKDF2-HMAC-SHA256 is part of hashlib, hence could be applied the very same way as SHA1 is done, for hashing on value change and on input, the latter keeping SHA1 as fallback to not lock out users on upgrade.

But again, PBKDF2-HMAC-SHA256 is not assured to be available, since it requires Python to be build with OpenSSL. So I guess this would be problematic to use. The Argon2 module at least looks simple. Maybe worth to byte the apple and pull this in as additional dependency for the security benefit.

@MichaIng commented on GitHub (Feb 16, 2026): Ah right, I remember 😄. I am currently updating the frontend, getting rid of some obsolete cruft and 3rd party scripts. I should find some time after this is done. In any case, bcrypt, yescript, Argon2 all would require some more work, since this would need to be done with external modules. Only PBKDF2-HMAC-SHA256 is part of hashlib, hence could be applied the very same way as SHA1 is done, for hashing on value change and on input, the latter keeping SHA1 as fallback to not lock out users on upgrade. _But again, PBKDF2-HMAC-SHA256 is not assured to be available, since it requires Python to be build with OpenSSL. So I guess this would be problematic to use. The Argon2 module at least looks simple. Maybe worth to byte the apple and pull this in as additional dependency for the security benefit._
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/motioneye#2656
No description provided.