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.
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.
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.
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.
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>
– Daniel Alder
Jul 09 '14 at 09:34
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
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.
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.
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!
– Mark Stewart
Mar 30 '21 at 18:22
I don't see a "quick and dirty" answer, so I'll post my solution:
openssl
recent enoughuserline=$(sudo awk -v u=$user -F: 'u==$1 {print $2}' /etc/shadow); IFS='$'; a=($userline); [[ "$(printf "${pass}"|openssl passwd -"${a[1]}" -salt "${a[2]}" -stdin)" = "${userline}" ]]
perl
user="user1";pass="eeeeee";userline="$(awk -v u="$user" -F: 'u==$1 {print $2}' /etc/shadow)"; a="$(echo "$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:
/etc/shadow
(e.g., if ! grep -q $user /etc/shadow; then return 1; fi
)$
openssl
command to generate the string from the supplied passwordprintf "${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)
– G-Man Says 'Reinstate Monica'
Aug 06 '22 at 21:32
\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.
– G-Man Says 'Reinstate Monica'
Aug 06 '22 at 21:32
I wrote a script similar to @Boops oneliner. You can find it here - feel free to use.
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
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"
{
…}
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)
– G-Man Says 'Reinstate Monica'
Aug 06 '22 at 19:19
if is-password-correct root passw0rd; then echo "password is correct"; else echo "password is NOT correct"; fi
?
– G-Man Says 'Reinstate Monica'
Aug 06 '22 at 19:19