I've been on a painful journey over the last couple of weeks. But I reached my destination. In the hope that I'm able to help others reach this point faster, and in a less stressful way, I'll share my results.
The Background Story
We're currently rebuilding our platform in Python and Django. The existing platform is based on .NET and related MS technologies. We're soon preparing to launch, and part of that is also migrating user data from the old to the new platform.
In the old system, user passwords are hashed and verified using the built-in PWDENCRYPT and PWDCOMPARE functions of Microsoft SQL Server.
As we want the transition to be as frictionless as possible for our users, we of course also want them to not have to reset their password after we launch. So I got the seemingly simple task to create a custom Django password hasher that is able to verify user entered passwords againts the hashes from PWDENCRYPT.
First I set out to find some documentation on what those MS SQL functions actually do. It seems to officially not be very well documented what happens behind the scenes (if at all), and according to the docs you're not really supposed to use it:
PWDENCRYPT is an older function and might not be supported in a future release of SQL Server. Use HASHBYTES instead. HASHBYTES provides more hashing algorithms.
Fortunately, a couple of Stack Overflow questions were able to guide me in the right direction, although not all the way, due to errors in their explanations and examples, which sent me on several detours.
After spending way too much time on it, I gave up. But then a colleague gave it a try and found the missing key: The passwords must be encoded using UTF-16LE. A detail I missed, but might be obvious to someone who is used to work in a Windows environment.