41

I want to check, from the linux command line, if a given cleartext password is the same of a crypted password on a /etc/shadow

(I need this to authenticate web users. I'm running an embedded linux.)

I have access to the /etc/shadow file itself.

2
  • Log in as the user with the password?
    – Kusalananda
    Commented Sep 29, 2011 at 14:39
  • The test must be done automatically, I can't manually type the password from the web server Commented Sep 29, 2011 at 14:43

9 Answers 9

31

You can easily extract the encrypted password with awk. You then need to extract the prefix $algorithm$salt$ (assuming that this system isn't using the traditional DES, which is strongly deprecated because it can be brute-forced these days).

correct=$(</etc/shadow awk -v user=bob -F : 'user == $1 {print $2}')
prefix=${correct%"${correct#\$*\$*\$}"}

For password checking, the underlying C function is crypt, but there's no standard shell command to access it.

On the command line, you can use a Perl one-liner to invoke crypt on the password.

supplied=$(echo "$password" |
           perl -e '$_ = <STDIN>; chomp; print crypt($_, $ARGV[0])' "$prefix")
if [ "$supplied" = "$correct" ]; then …

Since this can't be done in pure shell tools, if you have Perl available, you might as well do it all in Perl. (Or Python, Ruby, … whatever you have available that can call the crypt function.) Warning, untested code.

#!/usr/bin/env perl
use warnings;
use strict;
my @pwent = getpwnam($ARGV[0]);
if (!@pwent) {die "Invalid username: $ARGV[0]\n";}
my $supplied = <STDIN>;
chomp($supplied);
if (crypt($supplied, $pwent[1]) eq $pwent[1]) {
    exit(0);
} else {
    print STDERR "Invalid password for $ARGV[0]\n";
    exit(1);
}

On an embedded system without Perl, I'd use a small, dedicated C program. Warning, typed directly into the browser, I haven't even tried to compile. This is meant to illustrate the necessary steps, not as a robust implementation!

/* Usage: echo password | check_password username */
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <shadow.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
    char password[100];
    struct spwd shadow_entry;
    char *p, *correct, *supplied, *salt;
    if (argc < 2) return 2;
    /* Read the password from stdin */
    p = fgets(password, sizeof(password), stdin);
    if (p == NULL) return 2;
    *p = 0;
    /* Read the correct hash from the shadow entry */
    shadow_entry = getspnam(username);
    if (shadow_entry == NULL) return 1;
    correct = shadow_entry->sp_pwdp;
    /* Extract the salt. Remember to free the memory. */
    salt = strdup(correct);
    if (salt == NULL) return 2;
    p = strchr(salt + 1, '$');
    if (p == NULL) return 2;
    p = strchr(p + 1, '$');
    if (p == NULL) return 2;
    p[1] = 0;
    /*Encrypt the supplied password with the salt and compare the results*/
    supplied = crypt(password, salt);
    if (supplied == NULL) return 2;
    return !!strcmp(supplied, correct);
}

A different approach is to use an existing program such as su or login. In fact, if you can, it would be ideal to arrange for the web application to perform whatever it needs via su -c somecommand username. The difficulty here is to feed the password to su; this requires a terminal. The usual tool to emulate a terminal is expect, but it's a big dependency for an embedded system. Also, while su is in BusyBox, it's often omitted because many of its uses require the BusyBox binary to be setuid root. Still, if you can do it, this is the most robust approach from a security point of view.

2
  • 2
    I like the su approach.
    – Benjohn
    Commented Aug 1, 2017 at 17:11
  • A bash function based on your answer.
    – x-yuri
    Commented Jul 19, 2023 at 23:23
14

Have a look at man 5 shadow and man 3 crypt. From the latter, you can learn that password hashes in /etc/shadow have the following form:

 $id$salt$encrypted

where id defines the type of encryption and, reading further, can be one of

          ID  | Method
          ---------------------------------------------------------
          1   | MD5
          2a  | Blowfish (not in mainline glibc; added in some
              | Linux distributions)
          5   | SHA-256 (since glibc 2.7)
          6   | SHA-512 (since glibc 2.7)

Depending on the type of hash, you need to use the appropriate function/tool for generating and verifying the password "by hand". If the system contains mkpasswd program, you can use it as suggested here. (You take the salt from the shadow file, if that wasn't obvious.) For example, with md5 passwords :

 mkpasswd -5 <the_salt> <the_password>

will generate the string that should match /etc/shadow entry.

1
  • 3
    On my Debian wheezy I had a completely different syntax for the command mkpasswd, which I had to install using apt-get install whois. The command line for the shadow line <user>:$6$<salt>$<pwd>: was mkpasswd -msha-512 <password> <salt> Commented Jul 9, 2014 at 9:34
2

There was a similar question asked on Stack Overflow. cluelessCoder provided a script using expect, which you may or may not have on your embedded system.

#!/bin/bash
#
# login.sh $USERNAME $PASSWORD

#this script doesn't work if it is run as root, since then we don't have to specify a pw for 'su'
if [ $(id -u) -eq 0 ]; then
        echo "This script can't be run as root." 1>&2
        exit 1
fi

if [ ! $# -eq 2 ]; then
        echo "Wrong Number of Arguments (expected 2, got $#)" 1>&2
        exit 1
fi

USERNAME=$1
PASSWORD=$2

#since we use expect inside a bash-script, we have to escape tcl-$.
expect << EOF
spawn su $USERNAME -c "exit" 
expect "Password:"
send "$PASSWORD\r"
#expect eof

set wait_result  [wait]

# check if it is an OS error or a return code from our command
#   index 2 should be -1 for OS erro, 0 for command return code
if {[lindex \$wait_result 2] == 0} {
        exit [lindex \$wait_result 3]
} 
else {
        exit 1 
}
EOF
1

Bear in mind that, assuming the system is properly configured, the program will need to be run as root.

A better solution than reading the shadow file directly and writing your own code around crypt would be to just use the pam bindings.

The squid tarball used to come with a simple CLI tool for verifying usernames/passwords using stdio - so simple to adapt to using arguments - although the version I hacked previously was hardly a pin-up poster for structured programming. A quick google and it looks like the more recent versions have been cleaned up significantly but still a few 'goto's in there.

1

The C code of the person I'm responding to has bugs. Never understand why people publish code here without checking it works first, because there's ALWAYS bugs. Not like I don't have to check my code. Largest file I've ever made without a (syntax) bug on the first try was 1000 lines, and that's in 30 years of experience.

I've tested this under KDE Neon, it needs to be run as root because you need elevated privileges to read /etc/shadow or the user calling it needs to belong to the "shadow" group (that's the group that /etc/shadow belongs to). It takes the username as an argument.

Compile with: gcc <source_file.c> -lcrypt

#define _GNU_SOURCE  

#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <shadow.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#define DBG()                             \
do {                                      \
  char buf[100];                          \
  sprintf (buf, "error: %d\n", __LINE__); \
  perror (buf);                           \
} while (0)

void chomp (char *str)
{
  while (*str != '\0' && *str != '\n')
  {
    str++;
  }
  *str = '\0';
}

int main(int argc, char *argv[])
{
  char password[100];
  struct spwd *shadow_entry;
  char *p, *correct, *supplied, *salt;
  if (argc < 2)
  {
    DBG ();
    return 2;
  }
  /* Read the password from stdin */
  p = fgets(password, sizeof(password), stdin);
  if (p == NULL)
  {
    DBG ();
    return 2;
  }
  //*p = 0; - this was a pretty obvious error
  chomp (p); // this is what was intended above
  printf ("password = %s\n", p);
  /* Read the correct hash from the shadow entry */
  shadow_entry = getspnam( argv[1] );
  if (shadow_entry == NULL)
  {
    DBG ();
    return 1;
  }
  correct = shadow_entry->sp_pwdp;
  /* Extract the salt. Remember to free the memory. */
  salt = strdup(correct);
  if (salt == NULL)
  {
    DBG ();
    return 2;
  }
  p = strchr(salt + 1, '$');
  if (p == NULL)
  {
    DBG ();
    return 2;
  }
  p = strchr(p + 1, '$');
  if (p == NULL)
  {
    DBG ();
    return 2;
  }
  p[1] = 0;
  /*Encrypt the supplied password with the salt and compare the results*/
  supplied = crypt(password, salt);
  if (supplied == NULL)
  {
    DBG ();
    return 2;
  }
  if (strcmp(supplied, correct) == 0)
  {
    printf ("pass\n %s\n %s\n", supplied, correct);
    return (0);
  }
  else
  {
    printf ("fail\n %s\n %s\n", supplied, correct);
    return (1);
  }
}

You can remove the printf functions and remove calls to DBG(); but both are useful until you can verify that the program is working properly. I had to add them in to see how and where it was failing. Every error exit should be a different number as well, but that's just me being anal retentive.

1
  • Works good. Glad you mentioned it has to run as root -- most answers omit that. Also, you may want to edit your answer and reword your last sentence about being "careful" -- it flagged your answer as possible spam! Commented Mar 30, 2021 at 18:22
1

I don't see a "quick and dirty" answer, so I'll post my solution:

One line

openssl recent enough

userline=$(sudo awk -v u="$user" -F: 'u==$1 {print $2}' /etc/shadow); IFS='$'; a=($userline); [[ "$(printf "%s" "${pass}"|openssl passwd -"${a[1]}" -salt "${a[2]}" -stdin)" = "${userline}" ]]

with perl

user="user1";pass="eeeeee";userline="$(awk -v u="$user" -F: 'u==$1 {print $2}' /etc/shadow)"; a="$(printf "%s" "$userline"|grep -Eo '^\$.*\$.*\$')"; [[ "$(perl -e "print crypt('${pass}', '${a}')")" = "${userline}" ]]

In both case, it's a one-liner that returns 0 if the supplied password is correct.

You need three things:

  1. Set the variable "$user"
  2. Ensure that the user exists in the /etc/shadow (e.g., if ! grep -q $user /etc/shadow; then return 1; fi)
  3. Set the variable "$pass"

Some explanations

  1. First get the shadow line of the user
  2. Split it on $
  3. Use the openssl command to generate the string from the supplied password
  4. Check if the generated string matches the stored one
2
  • (1)  printf "${pass}" will blow up if $pass contains % or \.  It’s better to say printf "%s" "$pass".  (P.S.  As I just illustrated, you almost never need to use {} when referencing variables.  ${variable_name} doesn’t mean what you think it does …) (2)  echo "$userline" is even worse.  echo is probably safe to use for simple, alphanumeric constant strings, like echo 'Processing is done.';  … (Cont’d) Commented Aug 6, 2022 at 21:32
  • 1
    (Cont’d) …  for anything including control characters (e.g., \n or \033) or variables (e.g., "$userline"), you should probably stick to printf.  (3) You say u=$user in your first command and u="$user" in the second, and then you regress to -q $user in the explanations.  You should always quote shell variables unless you have a good reason not to, and you’re sure you know what you’re doing. Commented Aug 6, 2022 at 21:32
-1

Use the passwd command from a terminal.

If the password entered is correct the passwd command will prompt for a new password:

Enter new UNIX password

Ctrl+D a few times to stop this, should output:

passwd: Authentication token manipulation error
passwd: password unchanged

If the password entered is incorrect the passwd command will output:

passwd: Authentication token manipulation error
passwd: password unchanged
1
  • 2
    Although this might work, I'd argue this is very unwise. What is someone manages to put newline characters in the input? A failure to properly sanitize user input could lead to arbitrary passwords being set and changed. Perhaps this hack might work in a highly controlled environment, but I wouldn't risk using it anywhere else.
    – Matt
    Commented Mar 20, 2021 at 14:49
-1

I wrote a script similar to @Boops oneliner. You can find it here - feel free to use.

1
  • 1
    As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Sep 20, 2021 at 16:49
-2

In BASH you may use:

function is-password-correct(){

    username=$1
    password=$2
    su ${username} -c 'echo' <<< ${password} 
    return $?
}

is-password-correct root passw0rd
[[ $? == 0 ]] && echo "password is correct" || echo "password is NOT correct"

3
  • Does this work for you? I get “su: must be run from a terminal”. Please describe your operating environment. … … … … … … … … … … … … … … … … … … … … … Please do not respond in comments; edit your answer to make it clearer and more complete. (Or, if you can’t identify an environment where this actually works, feel free to delete it.) Commented Jul 13, 2022 at 11:32
  • Also, (2) It probably doesn’t really matter in this case (if the above function is invoked by an infrastructure that does extensive sanity checks on the input), but it’s good to get into the habit of always quoting shell variables. Using {} isn’t nearly as important. ${variable_name} doesn’t mean what you think it does … (3a) You hardly ever need to reference $? directly; just run the program / script / function and test its status, and (3b) in my opinion, && and || are overused. They are easy to misuse. … (Cont’d) Commented Aug 6, 2022 at 19:19
  • (Cont’d) …  Why not just say if is-password-correct root passw0rd; then echo "password is correct"; else echo "password is NOT correct"; fi? Commented Aug 6, 2022 at 19:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .