You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Python脚本实现类似sudo的短时密码记忆功能?

实现脚本短时密码记忆的三种方案

方案A:纯Python本地缓存实现

通过加密存储密码到本地临时文件,并设置超时时间来实现。核心是确保缓存文件仅当前用户可读,避免密码泄露。

实现步骤

  • 生成当前用户专属的缓存文件路径,避免多用户冲突
  • 输入密码后,用对称加密(如cryptography库的Fernet)加密密码,同时记录过期时间
  • 每次启动脚本时,先检查缓存文件是否存在、权限合规且未过期,解密后获取密码
  • 超时或验证失败时,删除缓存文件并重新要求输入密码

代码示例

首先安装依赖:

pip install cryptography

核心代码片段:

import os
import time
import tempfile
from cryptography.fernet import Fernet
from hashlib import sha256

# 生成当前用户专属缓存文件
CACHE_FILE = os.path.join(tempfile.gettempdir(), f"myscript_pw_cache_{os.getuid()}")
# 设置5分钟超时(单位:秒)
TIMEOUT = 300

# 生成用户专属加密密钥(基于用户HOME目录哈希,避免硬编码)
def get_fernet_key():
    key_material = sha256(os.environ["HOME"].encode()).digest()
    return Fernet(key_material[:32]) if len(key_material) >=32 else Fernet.generate_key()

def get_cached_password():
    if not os.path.exists(CACHE_FILE):
        return None
    # 校验文件权限,必须仅当前用户可读
    if oct(os.stat(CACHE_FILE).st_mode)[-3:] != "600":
        os.remove(CACHE_FILE)
        return None
    with open(CACHE_FILE, "rb") as f:
        data = f.read().split(b"||")
        if len(data) != 2:
            return None
        expire_ts, encrypted_pw = data
        if float(expire_ts) < time.time():
            os.remove(CACHE_FILE)
            return None
        # 解密密码
        fernet = Fernet(get_fernet_key())
        try:
            return fernet.decrypt(encrypted_pw).decode()
        except:
            return None

def cache_password(password):
    fernet = Fernet(get_fernet_key())
    encrypted_pw = fernet.encrypt(password.encode())
    expire_ts = str(time.time() + TIMEOUT).encode()
    with open(CACHE_FILE, "wb") as f:
        f.write(expire_ts + b"||" + encrypted_pw)
    # 锁定文件权限
    os.chmod(CACHE_FILE, 0o600)

# 主逻辑
password = get_cached_password()
if not password:
    password = input("请输入密码:")
    cache_password(password)

# 后续执行SSH/SFTP等操作

方案B:调用系统密码管理器

利用keyring库集成系统原生密码管理服务(如macOS Keychain、Windows凭据管理器、Linux Secret Service),无需自己处理加密和权限,安全性更高。

实现步骤

  • 安装keyring库,它会自动适配系统密码管理器
  • 脚本启动时,尝试从密码管理器获取存储的密码及过期时间
  • 若密码不存在或已超时,重新输入并将密码+当前时间存入管理器

代码示例

首先安装依赖:

pip install keyring

核心代码片段:

import time
import os
import keyring

# 服务标识与用户标识
SERVICE_NAME = "myscript_ssh_services"
USER_ID = os.getlogin()
# 5分钟超时
TIMEOUT = 300

def get_cached_password():
    stored_data = keyring.get_password(SERVICE_NAME, USER_ID)
    if not stored_data:
        return None
    try:
        stored_ts, password = stored_data.split("||", 1)
        if float(stored_ts) + TIMEOUT < time.time():
            keyring.delete_password(SERVICE_NAME, USER_ID)
            return None
        return password
    except:
        keyring.delete_password(SERVICE_NAME, USER_ID)
        return None

def cache_password(password):
    stored_data = f"{time.time()}||{password}"
    keyring.set_password(SERVICE_NAME, USER_ID, stored_data)

# 主逻辑
password = get_cached_password()
if not password:
    password = input("请输入密码:")
    cache_password(password)

# 后续执行SSH/SFTP等操作

方案C:守护进程模式

启动一个后台守护进程专门存储密码,脚本实例通过本地套接字与守护进程通信获取密码,守护进程超时后自动销毁密码并退出,密码仅在内存中存在,安全性最高。

实现步骤

  • 脚本启动时,检查是否有运行的守护进程(通过本地套接字文件判断)
  • 若无守护进程,输入密码并启动守护进程,守护进程监听本地套接字
  • 其他脚本实例连接套接字,验证身份后获取密码
  • 守护进程启动超时线程,到期后清除密码、关闭套接字并退出

代码示例

import os
import sys
import time
import socket
import threading
import subprocess

# 当前用户专属套接字路径
SOCKET_PATH = f"/tmp/myscript_daemon_{os.getuid()}"
TIMEOUT = 300
password = None

def handle_client(conn):
    """处理脚本实例的密码请求"""
    global password
    if password:
        conn.send(password.encode())
    conn.close()

def daemon_loop(init_password):
    """守护进程主循环"""
    global password
    password = init_password
    # 清理旧套接字
    if os.path.exists(SOCKET_PATH):
        os.remove(SOCKET_PATH)
    # 创建本地套接字
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.bind(SOCKET_PATH)
    os.chmod(SOCKET_PATH, 0o600)
    sock.listen(1)

    # 超时退出线程
    def timeout_shutdown():
        time.sleep(TIMEOUT)
        global password
        password = None
        sock.close()
        if os.path.exists(SOCKET_PATH):
            os.remove(SOCKET_PATH)
    threading.Thread(target=timeout_shutdown, daemon=True).start()

    # 处理连接请求
    while password is not None:
        try:
            conn, _ = sock.accept()
            handle_client(conn)
        except:
            break

def get_password_from_daemon():
    """从守护进程获取密码"""
    if not os.path.exists(SOCKET_PATH):
        return None
    try:
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.connect(SOCKET_PATH)
        pw = sock.recv(1024).decode()
        sock.close()
        return pw if pw else None
    except:
        return None

if __name__ == "__main__":
    # 处理守护进程启动参数
    if len(sys.argv) == 3 and sys.argv[1] == "--daemon":
        daemon_loop(sys.argv[2])
    else:
        # 正常脚本逻辑
        password = get_password_from_daemon()
        if not password:
            password = input("请输入密码:")
            # 启动后台守护进程
            subprocess.Popen(
                [sys.executable, __file__, "--daemon", password],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL
            )
            time.sleep(0.5)  # 等待守护进程启动
        # 后续执行SSH/SFTP等操作

内容的提问来源于stack exchange,提问作者Kap4Lin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 00:24:17