【问题标题】:How to change a Linux user password from python如何从 python 更改 Linux 用户密码
【发布时间】:2026-02-20 09:50:02
【问题描述】:

我在从 python 更改 Linux 用户密码时遇到问题。我尝试了很多东西,但我无法解决问题,以下是我已经尝试过的示例:

sudo_password 是 sudo 的密码,sudo_command 是我希望系统运行的命令, user 是从 List 中获取的,是我要为其更改密码的用户,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,理论上意味着它已经正确,但到目前为止它没有更改密码。

我在互联网和 * 上搜索过这个问题或类似问题,并尝试了暴露的解决方案,但同样没有成功。

我真的很感激任何帮助,当然,如果您需要更多信息,我很乐意提供!

提前致谢。

【问题讨论】:

  • 问题是passwdsudo不是从标准输入读取密码,而是从/dev/tty读取密码。您必须使用伪终端来欺骗他们; pexpect 擅长这样做。
  • 那么,没有任何其他方法可以在不安装任何模块的情况下做我想做的事吗?我一直在阅读有关 pexpect 的内容,它似乎确实可以完成工作,但我想以与上述类似的方式进行。

标签: python linux passwords python-2.7


【解决方案1】:

尝试在管道中的 passwd 命令中使用“--stdin”选项。引用手册页:

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

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

【讨论】:

  • 非常感谢,两种方法我都试过了,但似乎不适合我
  • 只是对 usermod 方法的一个想法——让用户的密码被引用或不包含 shell 特殊字符(例如 $),否则它们将在系统命令中被解释并且不会给出您期望的值。
  • @NeilWinton: $(和其他 shell 元字符)如果您使用 Popen(默认情况下不调用任何 shell)启动进程,则可以在密码中使用,@987654321 @.
  • 是的,确实如此,但在上面的示例代码中,正在使用的是 os.system(),它将(有效地)调用“sh -c 'command-string'”和元字符扩展结果会发生。
【解决方案2】:

运行此命令的用户必须具有 sudo 权限才能在没有密码的情况下运行 passwd 命令。

>>> from subprocess import Popen
>>> proc = Popen(['/usr/bin/sudo', '/usr/bin/passwd', 'test', '--stdin'])
>>> proc.communicate('newpassword')

【讨论】:

  • 这是用于什么系统的? --stdin 在我的 passwd 程序中不存在。
【解决方案3】:

我今天遇到了同样的问题,我在subprocess 周围编写了一个简单的包装器来调用passwd 命令并使用新密码输入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 passwd &lt;username&gt;,我建议您在/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 会挂起,直到我按 Enter 键。包含该代码可以解决此问题。
  • 另外,为什么我需要包含else 子句?在什么情况下,passwd 的结束时间会超过 1 秒?谢谢。
  • 更改密码可能需要超过 1 秒的时间有多种可能。想想一个非常繁忙的系统或使用 LDAP 服务器的身份验证。通过给“passwd” 1 秒的时间来完成,我确保 passwd 回答旧密码的情况在可接受的时间内终止。如响应中所述,此代码并非万无一失,而是由我编写的,因为我需要一个快速而肮脏的解决方案来解决此问题。如果您想要一个更优雅的解决方案,请将语言环境设置为“C”并实现一个类似于“expect”的解析器,并为 passwd 命令提供正确的响应。
【解决方案4】:

基于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是如何使用的?我读到您需要密码进行哈希处理,然后是“盐”,但是,它会影响验证登录时提供的密码吗?
  • crypt(plaintext) 返回类似$6$salt$encrypted 的内容,其中salt 是自动生成的。查看您的 /etc/passwd 或 /etc/shadow 文件
【解决方案5】:

对于那些不能选择 --stdin 的人:

import subprocess
cmd = "bash -c \"echo -e 'NewPassword\\nNewPassword' | passwd root\""
subprocess.check_call(cmd, shell=True)

【讨论】:

    【解决方案6】:

    不幸的是,如前所述,在命令行上传递密码并不是很安全。此外,passwd 的“--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 将保持完全沉默。因此,如果发生任何类型的错误,上面的代码将引发异常(无需仔细查看实际错误)。

    【讨论】: