如何让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
相关产品推荐
相关产品推荐

