What I have currently in /etc/shadow file is password in a format like $6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/ (a sha512 encrypted password). What I need to do is validate the password (user provided password and the current hashed password). I'm implementing this on Go.
How I'm trying to achieve this is, get the user provided password, hash it with the same salt as what is in the /etc/shadow and check if they are similar or different. How can I have generate the same hash value and validate the password?
Below is the rough code what I'm doing (updated with Amadan's comment on encoding)
// this is what is stored on /etc/shadow - a hashed string of "test"
myPwd := "$6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/"
// getting the salt
salt := strings.Split(myPwd, "$")[2]
encoding := base64.NewEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz").WithPadding(base64.NoPadding)
decodedSalt, err := encoding.DecodeString(salt)
// user provided password
myNewPwd := "test"
newHashedPwd, err := hashPwd512(string(myNewPwd), string(decodedSalt))
// comparision
if (newHashedPwd == myPwd) {
// password is same, validate it
}
// What I'm expecting from this method is that for the same password stored in the /etc/shadow,
// and the same salt, it should return (like the one in /etc/shadow file)
// AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/
func hashPwd512(pwd string, salt string) (string, error) {
hash := sha512.New()
hash.Write([]byte(salt))
hash.Write([]byte(pwd))
encoding := base64.NewEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz").WithPadding(base64.NoPadding)
hashedPwd := encoding.EncodeToString(hash.Sum(nil))
return hashedPwd, nil
}
Note: The password is set/changed through passwd or chpasswd.
SHA512 is a fast hash. This is bad for passwords, because brute force becomes very easy when each attempt costs almost nothing. To slow it down, what is in
/etc/shadowis not just a simple SHA512 hash, but rather key stretching is applied where the hash algorithm is run thousands of times. The specific key stretching algorithm seems to be this one. Thus,crypto/sha512is only doing about 1/5000th of what is needed (in the default case).Fortunately, there's people who already did the hard work; you can see their implementation here.
Since you only need validation, the
Verifyshortcut suffices (it doesGeneratebehind the scenes for you):Output:
NOTE: I believe I was incorrect in the comments. The password hash itself is encoded by such an encoding, but the salt seems to be the actual salt (it's just that when it is generated randomly, the characters from that encoding are used).
The library also supports other hashing functions, but you do need an import for each one to register it. You can also see that I did not bother isolating the salt from
saltedPass; that's also something you don't need to worry about.But if you do want to isolate the salt, for some reason, then note also that counting the
$from the start is not a safe idea, as it will fail to process entries like$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gcorrectly, for example. Usestrings.LastIndex(saltedPass, "$") + 1as a cutting point, instead.