如何从Python更改Linux用户密码

4

我在使用Python修改Linux用户密码时遇到了问题。我尝试了很多方法,但是都无法解决问题,以下是我已经尝试过的样例:

sudo_password是sudo的密码,sudo_command是我想要系统运行的命令,user是从列表中获取的要更改密码的用户,并且newpass是我想要分配给“user”的密码。

    user = list.get(ANCHOR)
    sudo_command = 'passwd'
    f = open("passwordusu.tmp", "w")
    f.write("%s\n%s" % (newpass, newpass))
    f.close()
    A=os.system('echo -e %s|sudo -S %s < %s %s' % (sudo_password, sudo_command,'passwordusu.tmp', user))
    print A
    windowpass.destroy()

'A'是os.system执行的返回值,在这种情况下为256。我也尝试过。

    A=os.system('echo  %s|sudo -S %s < %s %s' % (sudo_password, sudo_command,'passwordusu.tmp', user))

但它返回相同的错误代码。我尝试了几种使用“passwd”命令的其他方法,但没有成功。使用“chpasswd”命令,我尝试了这个:
    user = list.get(ANCHOR)
    sudo_command = 'chpasswd'
    f = open("passwordusu.tmp", "w")
    f.write("%s:%s" % (user, newpass))
    f.close()
    A=os.system('echo %s|sudo -S %s < %s %s' % (sudo_password, sudo_command,'passwordusu.tmp', user))
    print A
    windowpass.destroy()

还包括:

    A=os.system('echo %s|sudo -S %s:%s|%s' % (sudo_password, user, newpass, sudo_command))
    @;which returns 32512
    A=os.system("echo %s | sudo -S %s < \"%s\"" % (sudo_password, sudo_command,  "passwordusu.tmp"))
    @;which returns 256

我也尝试使用'mkpasswd'和'usermod',就像这样:

    user = list.get(ANCHOR)
    sudo_command = 'mkpasswd -m sha-512'
    os.system("echo %s | sudo -S %s %s > passwd.tmp" % (sudo_password,sudo_command, newpass))
    sudo_command="usermod -p"
    f = open('passwd.tmp', 'r')
    for line in f.readlines():
        newpassencryp=line
    f.close()
    A=os.system("echo %s | sudo -S %s %s %s" % (sudo_password, sudo_command, newpassencryp, user))
    @;which returns 32512

但是,如果你前往https://www.mkpasswd.net,对'newpass'进行哈希并替换为'newpassencryp',它会返回0,理论上意味着它已经正确执行,但到目前为止它并没有改变密码。
我在互联网和stackoverflow上搜索了这个问题或类似的问题,并尝试了所提出的解决方案,但是仍然没有成功。
我非常感谢任何帮助,当然,如果您需要更多信息,我会乐意提供!
提前致谢。

2
问题在于 passwdsudo 不会从标准输入读取密码,而是从 /dev/tty 读取。你需要使用伪终端来欺骗它们;pexpect 在这方面做得很好。 - Fred Foo
那么,如果不安装任何模块,我想要做到的目标没有其他方法吗?我已经对pexpect有所了解,它似乎可以完成这项工作,但我希望以类似上述方式进行操作。 - Vaulor
6个回答

4
用户在执行不需要密码的 passwd 命令时,必须具有sudo权限。
>>> from subprocess import Popen
>>> proc = Popen(['/usr/bin/sudo', '/usr/bin/passwd', 'test', '--stdin'])
>>> proc.communicate('newpassword')

这是哪个系统?--我的passwd程序中不存在stdin。 - kevr

3

尝试在管道中使用'--stdin'选项来执行passwd命令。引用手册上的内容:

    --stdin
      此选项用于指示passwd应从标准输入中读取新密码,可以是管道。

如果您的Linux有usermod命令,则可以作为root(或通过sudo)使用'-p'选项显式设置(加密的)密码。


非常感谢,我刚刚尝试了两种方法,但似乎对我不起作用。 - Vaulor
对于 usermod 方法的一个想法——确保用户密码被引用或不包含 shell 特殊字符(例如 $),否则它们将在系统命令中被解释,得到的值可能与您期望的不同。 - Neil Winton
@NeilWinton:如果您使用Popen(默认情况下不会调用任何shell)启动进程,则可以在密码中使用$(以及其他shell元字符),示例 - jfs
是的,这是正确的。但在上面的示例代码中,使用的是os.system(),它将调用(实际上)“sh -c 'command-string'”,并且元字符扩展将随之发生。 - Neil Winton

2

今天我遇到了同样的问题,我写了一个简单的包装器来调用subprocess命令并向stdin输入新密码。这段代码并不十分完美,并且只能在以root身份运行时才能正常工作,因为它不会提示旧密码。

import subprocess
from time import sleep

PASSWD_CMD='/usr/bin/passwd'

def set_password(user, password):
    cmd = [PASSWD_CMD, user]
    p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
    p.stdin.write(u'%(p)s\n%(p)s\n' % { 'p': password })
    p.stdin.flush()
    # Give `passwd` cmd 1 second to finish and kill it otherwise.
    for x in range(0, 10):
        if p.poll() is not None:
            break
        sleep(0.1)
    else:
        p.terminate()
        sleep(1)
        p.kill()
        raise RuntimeError('Setting password failed. '
                '`passwd` process did not terminate.')
    if p.returncode != 0:
        raise RuntimeError('`passwd` failed: %d' % p.returncode)

如果您需要passwd的输出,您也可以将stdout=subprocess.PIPE传递给Popen调用并从中读取。在我的情况下,我只对操作是否成功感兴趣,所以我直接跳过了那部分。
安全注意事项:不要使用类似echo -n 'password\npassword\n | passwd username'这样的东西,因为这会使密码在进程列表中可见。
SUDO
由于您似乎想要使用sudo passwd ,我建议在/etc/sudoers中添加一行新的内容(请使用visudo!)
some_user ALL = NOPASSWD: /usr/bin/passwd

Sudo不会要求输入some_user的密码,脚本将按预期运行。

或者只需添加额外的一行p.stdin.write(u'%s\n' % SUDO_PASSWORD)代码。这样,sudo将首先接收用户密码,然后passwd接收新的用户密码。


你能解释一下为什么需要包含“give passwd cmd 1 second to finish”的代码吗?我注意到如果我不包括这段代码,Python会一直挂起直到我按回车键。包含这段代码可以解决这个问题。 - Matthew Moisen
另外,为什么我需要包括else子句?在什么情况下,passwd结束需要超过1秒钟的时间?谢谢。 - Matthew Moisen
更改密码需要超过1秒的原因有几种可能性。想象一下非常繁忙的系统或使用LDAP服务器进行身份验证。通过给“passwd” 1秒钟的时间来完成,我确保了当passwd回答旧密码的情况在可接受的时间内终止。正如响应中所述,此代码并不是万无一失的,并且是由我编写的,因为我需要一个快速而简单的解决方案来解决这个问题。如果您想要更优雅的解决方案,请将语言环境设置为“C”,并实现一个像“expect”一样工作并向passwd命令提供正确响应的解析器。 - bikeshedder

1

usermod基于版本:

#!/usr/bin/env python
from crypt      import crypt
from getpass    import getpass
from subprocess import Popen, PIPE

sudo_password_callback = lambda: sudo_password # getpass("[sudo] password: ")
username, username_newpassword = 'testaccount', '$2&J|5ty)*X?9+KqODA)7'

# passwd has no `--stdin` on my system, so `usermod` is used instead
# hash password for `usermod`
try:
    hashed = crypt(username_newpassword) # use the strongest available method
except TypeError: # Python < 3.3
    p = Popen(["mkpasswd", "-m", "sha-512", "-s"], stdin=PIPE, stdout=PIPE,
              universal_newlines=True)
    hashed = p.communicate(username_newpassword)[0][:-1] # chop '\n'
    assert p.wait() == 0
assert hashed == crypt(username_newpassword, hashed)

# change password
p = Popen(['sudo', '-S',  # read sudo password from the pipe
           # XXX: hashed is visible to other users
           'usermod',  '-p', hashed, username],
          stdin=PIPE, universal_newlines=True)
p.communicate(sudo_password_callback() + '\n')
assert p.wait() == 0

在try语句中,crypt如何使用?我读过需要密码进行哈希,然后是“盐”,但是,在验证登录时给出的密码时,它会产生影响吗? - Vaulor
crypt(plaintext) 返回类似于 $6$salt$encrypted 的内容,其中 salt 是自动生成的。请查看您的 /etc/passwd 或 /etc/shadow 文件。 - jfs

1
对于那些不能使用--stdin选项的人:
import subprocess
cmd = "bash -c \"echo -e 'NewPassword\\nNewPassword' | passwd root\""
subprocess.check_call(cmd, shell=True)

0

如前所述,通过命令行传递密码并不是非常安全的。此外,像 "--stdin" 这样的选项在每个 passwd 实现中都不可用。因此,这里提供了一个更安全的版本,使用 chpasswd

def setPassword(userName:str, password:str):
    p = subprocess.Popen([ "/usr/sbin/chpasswd" ], universal_newlines=True, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdout, stderr) = p.communicate(userName + ":" + password + "\n")
    assert p.wait() == 0
    if stdout or stderr:
        raise Exception("Error encountered changing the password!")

解释:

使用subprocess.Popen启动一个chpasswd实例。我们将用户名和密码传输到chpasswd的实例中。chpasswd将使用当前操作系统定义的设置更改密码。如果没有发生错误,chpasswd将保持完全沉默。因此,如果发生任何类型的错误(而不必仔细查看实际错误),上面的代码将引发异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接