Check user's password with a shell script

There's no fully portable way to check the user's password. This requires a privileged executable, so this isn't something you can whip up from scratch. PAM, which is used on most non-embedded Linux systems as well as many embedded systems and most other Unix variants, comes with a setuid binary but it doesn't have a direct shell interface, you need to go via the PAM stack.

You can use a PAM binding in Perl, Python or Ruby.

You can install one of several checkpassword implementations.

If the user is allowed to run sudo for anything, then sudo -kv will prompt for authentication (unless this has been disabled in the sudo configuration). But that doesn't work if there's no sudoers rule concerning the user.

You can run su. This works on most implementations. This is probably the best bet in your case.

if su -c true "$USER"; then
  echo "Correct password"
fi

You can validate that a given local password is correct for a given username using the shadow file.

On most modern distributions, the hashed passwords are stored in the shadow file /etc/shadow (which is only readable by root). As root, pull the line from the shadow file for the given user like so:

cat /etc/shadow | grep username

You will see something like this:

username:$1$TrOIigLp$PUHL00kS5UY3CMVaiC0/g0:15020:0:99999:7:::

After the username there is $1. This indicates that it is an MD5 hash. After that there is another $, then (in this case) TrOIigLp followed by another $. TrOIigLp is the salt. After that is the hashed password, which was hashed using the salt - in this case PUHL00kS5UY3CMVaiC0/g0.

Now, you can use openssl to hash the given password using the same salt, like so:

openssl passwd -1 -salt TrOIigLp

Enter the given password when prompted, the openssl command should compute the MD5 hash using the salt provided, and it should be exactly the same as the above from the shadow file. The -1 in the above command is for MD5 hashing.


Here's a relatively robust solution that works with both local and remote users and attempts to authenticate against the full PAM stack as the user in question, rather than simply comparing password hashes.

#!/usr/bin/env ruby

require 'rpam'

username = ARGV[0]
password = ARGV[1]

if Rpam.auth(username, password, service: 'system-auth')
  puts 'authentication successful'
  exit 0
else
  puts 'authentication failure'
  exit 1
end

To run it: Save it as ./authenticate_as.rb, chmod +x ./authenticate_as.rb, and ./authenticate_as.rb $username $password. Requires Ruby and the rpam gem, in case that wasn't obvious.