In this post I explain how one can check whether a system account name and password combination is correct. Although the ways to do that on other UNIX-like operating systems are mostly similar, the following explanation is specific to Linux.

So how does user authentication on Linux actually work?

Here is an excerpt from the getspnam(3) man page that explains where password hashes used to be stored and where they are stored nowadays:

Long ago it was considered safe to have encrypted passwords openly visible in the password file. When computers got faster and people got more security-conscious, this was no longer acceptable. Julianne Frances Haugh implemented the shadow password suite that keeps the encrypted passwords in the shadow password database (e.g., the local shadow password file /etc/shadow, NIS, and LDAP), readable only by root.

The shadow(5) man page provides more details on what kind of password information is stored, and refers to the crypt(3) man page for details on how the encrypted password string is interpreted.

Putting the information from the man pages together, we can write a small C program that checks an account name and password combination provided as command line arguments:

#include <stdlib.h>
#include <string.h>

#include <crypt.h>

#include <shadow.h>

int check_password(const char *plain_password, const char *crypt_password)
{
	return strcmp(crypt(plain_password, crypt_password), crypt_password) == 0;
}

int main(int argc, char* argv[])
{
	struct spwd* spwd;

	if (argc != 3) {
  		// invalid number of arguments
		return EXIT_FAILURE;
	}

	spwd = getspnam(argv[1]);

	if (spwd == NULL) {
		// we're either not running as root
		// or the user doesn't exist
		return EXIT_FAILURE;
	}

	if (!check_password(argv[2], spwd->sp_pwdp)) {
		// the provided password is incorrect
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

Warning: The above code is prone to a side channel attack, more specifically to a timing attack. One can trivially find out whether an account with a particular name exists or not, without having to try to guess the password.

Note that you will have to link against the crypt library (i.e., use the -lcrypt flag). For instance, use the following command to compile a check-password executable: gcc check-password.c -o check-password -lcrypt. Then you can check whether it works like this, for example:

sudo ./check-password username password || echo "Login incorrect"

Finally, keep in mind that the program needs to be executed with root privileges in order to work properly.