我想从 Linux 命令行检查给定的明文密码是否与 /etc/shadow 上的加密密码相同
(我需要它来验证网络用户的身份。我正在运行嵌入式 Linux。)
我可以访问 /etc/shadow 文件本身。
答案1
您可以使用 awk 轻松提取加密的密码。然后,您需要提取前缀$algorithm$salt$
(假设该系统不使用传统的 DES,传统的 DES 已被强烈弃用,因为现在它可能会被暴力破解)。
correct=$(</etc/shadow awk -v user=bob -F : 'user == $1 {print $2}')
prefix=${correct%"${correct#\$*\$*\$}"}
对于密码检查,底层 C 函数是crypt
,但没有标准的 shell 命令来访问它。
在命令行上,您可以使用 Perl 单行代码来调用crypt
密码。
supplied=$(echo "$password" |
perl -e '$_ = <STDIN>; chomp; print crypt($_, $ARGV[0])' "$prefix")
if [ "$supplied" = "$correct" ]; then …
由于这无法在纯 shell 工具中完成,因此如果您有可用的 Perl,那么您也可以在 Perl 中完成这一切。 (或者 Python、Ruby,...任何可以调用该函数的可用crypt
工具。)警告,未经测试的代码。
#!/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);
}
在没有 Perl 的嵌入式系统上,我会使用一个小型的专用 C 程序。警告,直接在浏览器中输入,我什至没有尝试编译。这是为了说明必要的步骤,而不是作为一个可靠的实现!
/* 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);
}
另一种方法是使用现有程序,例如su
或login
。事实上,如果可以的话,最好安排 Web 应用程序通过su -c somecommand username
.这里的困难在于将密码提供给su
;这需要一个终端。模拟终端的常用工具是预计,但这对于嵌入式系统来说是一个很大的依赖。另外,虽然su
在 BusyBox 中,但它经常被省略,因为它的许多用途都需要将 BusyBox 二进制文件设置为 setuid root。不过,如果您能做到的话,从安全角度来看,这是最可靠的方法。
答案2
看看man 5 shadow
和man 3 crypt
。从后者中,您可以了解到密码散列/etc/shadow
具有以下形式:
$id$salt$encrypted
其中id
定义了加密类型,进一步阅读,可以是以下之一
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)
根据哈希类型,您需要使用适当的函数/工具“手动”生成和验证密码。如果系统中有mkpasswd
程序,就可以使用正如这里所建议的。 (你采取盐来自影子文件,如果这不明显的话。)例如,使用md5
密码:
mkpasswd -5 <the_salt> <the_password>
将生成应与条目匹配的字符串/etc/shadow
。
答案3
有一个Stack Overflow 上提出了类似的问题。CluelessCoder提供了一个使用expect的脚本,您的嵌入式系统上可能有也可能没有。
#!/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
答案4
我回复的人的 C 代码有错误。永远不明白为什么人们在没有首先检查代码是否有效的情况下在这里发布代码,因为总是存在错误。并不是说我不需要检查我的代码。我第一次尝试制作的没有(语法)错误的最大文件有 1000 行,这是 30 年经验的结果。
我已经在 KDE Neon 下对此进行了测试,它需要以 root 身份运行,因为您需要提升权限才能读取 /etc/shadow,或者调用它的用户需要属于“shadow”组(即 /etc/shadow 的组)属于)。它以用户名作为参数。
编译: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);
}
}
您可以删除 printf 函数并删除对 DBG() 的调用;但在您可以验证程序是否正常工作之前,两者都是有用的。我必须将它们添加进去,看看它是如何以及在哪里失败的。每个错误退出也应该是不同的数字,但这只是我的肛门保留。