PDF 解密解锁 pdf-decrypt:移除打开密码与打印/复制/编辑权限限制,输出无限制副本。仅限处理您本人拥有或有明确处理权限的文档。适合忘记 PDF 密码、PDF 不能打印复制编辑。
---
name: pdf-decrypt
description: PDF 解密解锁 pdf-decrypt:移除打开密码与打印/复制/编辑权限限制,输出无限制副本。适合忘记 PDF 密码、PDF 不能打印复制、PDF 限制编辑、PDF 密码丢失找回。仅限处理您本人拥有或有明确处理权限的文档。
---
本 skill 的真实能力依赖平台后端工具。在网页 / chat 端点下,这些工具可能不可用:
此时**请勿轻信**,请改用 **agent / CLI 路径**运行本 skill 以获取真实结果。任何路径下都**严禁谎称「已调用 / 已搜索完成」而实际未执行**。
移除 PDF 的打开密码和权限限制(打印/复制/编辑/水印限制),输出无限制副本。**仅限处理您本人拥有或有明确处理权限的文档**(如忘记密码的自己的文件、移除自己文件的复制限制)。禁止用于破解他人加密文件——执行前必须确认文档归属。
**场景 1:忘记 PDF 打开密码** —— 多年前的合同 PDF 密码忘了,自己又是文档归属人,用 `--password` 已知密码解锁(如果密码完全忘了,这个工具帮不了你)。
**场景 2:PDF 能打开但不能打印/复制** —— 公司下发的产品手册被加了"禁止复制"权限限制,没密码也要去掉,自己存档查阅。
**场景 3:老 PDF 受限不能编辑** —— 5 年前的方案 PDF 现在要改内容,受限编辑。去掉权限后转 Word 改更方便。
| 角色 | 典型用途 |
|------|----------|
| 法务 / 律师 | 自己存档的合同 PDF 解除复制限制 |
| 学生 / 教师 | 论文 PDF 解除打印限制(打印装订)|
| 财务 / 行政 | 公司资料 PDF 解除复制限制便于摘录 |
| 个人用户 | 老 PDF 密码忘了 / 限制太多想解除 |
| 设计 / 出版 | 拿到素材 PDF 解除编辑限制便于二次编辑 |
# 1. 先看 PDF 是否加密 / 限制类型
python scripts/pdf_ops.py info --input 加密.pdf
# 返回 {"ok": true, "is_encrypted": true, "has_open_password": true, "has_owner_password": true}
# 2. 已知打开密码 → 解密 + 去权限限制
python scripts/pdf_ops.py decrypt --input 加密.pdf --output 解锁.pdf --password 我的密码
# 返回 {"ok": true, "output": "解锁.pdf", "was_encrypted": true,
# "note": "已生成无打开密码、无权限限制的副本"}
# 3. 无打开密码、仅带权限限制 → 不传密码
python scripts/pdf_ops.py decrypt --input 受限.pdf --output 无限制.pdf
| 对比项 | 在线 PDF 解密工具 | pdf-decrypt |
|--------|------------------|-------------|
| 隐私 | 上传到第三方服务器(机密泄露风险)| 本地处理,不上传 |
| 收费 | 多数按次收费 + 限额 | 按次付费,¥0.05/次 |
| 速度 | 看网络 | 本地秒处理 |
| 限制类型 | 仅打开密码 | 打开密码 + 所有者密码 + 权限限制 |
| 合规 | 风险(工具站可能存你文档)| 可控(本地 + 确认归属) |
| 适用 | 简单密码 | 复杂场景(含权限剥离)|
用户要求"PDF 解密 / PDF 解锁 / 去除 PDF 密码 / 移除 PDF 限制 / PDF 打不开要密码 / PDF 不能打印复制"时触发。
用户要求"PDF 解密 / PDF 解锁 / 去除 PDF 密码 / 移除 PDF 限制 / PDF 打不开要密码"时触发。
先向用户确认:这份 PDF 是您本人创建的、或您有处理权限的吗?确认后才能继续。
pip install pypdf
python scripts/pdf_ops.py decrypt --input 加密.pdf --output 解锁.pdf [--password 密码]
成功返回 JSON:
{"ok": true, "output": "解锁.pdf", "was_encrypted": true,
"note": "已生成无打开密码、无权限限制的副本;请确认您对该文档拥有处理权限"}
| 情况 | 处理 |
|---|---|
| 密码错误 | 报错"密码错误,解密失败",请用户核对密码 |
| 文件未加密 | `was_encrypted: false`,提示文件本来就可打开 |
| 依赖缺失 | 提示 `pip install pypdf` 后重试 |
<!-- ===== 以下为内嵌脚本代码(agent 安装时按需落盘为 scripts/<name> 并 chmod +x) ===== -->
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""PDF 处理脚本:合并 / 拆分 / 文本提取 / 压缩 / 加水印 / 解密解锁。
用法示例:
python pdf_ops.py merge --inputs a.pdf b.pdf --output out.pdf
python pdf_ops.py split --input a.pdf --pages 1-3,5 --output-dir out/
python pdf_ops.py extract --input a.pdf --output a.txt
python pdf_ops.py compress --input a.pdf --output a_small.pdf
python pdf_ops.py watermark --input a.pdf --output w.pdf --text "机密"
python pdf_ops.py decrypt --input locked.pdf --output free.pdf --password xxx
python pdf_ops.py info --input a.pdf
依赖:pip install pypdf reportlab
合规:解密仅限处理用户本人拥有或有权限处理的文档,禁止破解他人文件。
"""
import argparse
import json
import sys
from pathlib import Path
try:
from pypdf import PdfReader, PdfWriter
except ImportError: # pragma: no cover
sys.stderr.write("缺少依赖 pypdf,请先执行: pip install pypdf\n")
sys.exit(2)
# ---------- 工具 ----------
def _open_reader(path: str, password: str | None = None) -> PdfReader:
reader = PdfReader(path)
if reader.is_encrypted:
if not password:
raise ValueError(
f"文件已加密,需提供 --password(仅限您本人有权处理的文档)"
)
if not reader.decrypt(password):
raise ValueError("密码错误,解密失败")
return reader
def _write(writer: PdfWriter, output: str) -> dict:
out = Path(output)
out.parent.mkdir(parents=True, exist_ok=True)
with out.open("wb") as f:
writer.write(f)
return {"ok": True, "output": str(out), "size_bytes": out.stat().st_size}
# ---------- 命令 ----------
def cmd_info(args) -> dict:
reader = _open_reader(args.input, args.password)
pages = len(reader.pages)
meta = {}
if reader.metadata:
for k in ("title", "author", "subject", "creator"):
v = getattr(reader.metadata, k, None)
if v:
meta[k] = str(v)
return {
"ok": True,
"file": args.input,
"pages": pages,
"encrypted": reader.is_encrypted,
"metadata": meta,
}
def cmd_merge(args) -> dict:
writer = PdfWriter()
total = 0
for path in args.inputs:
reader = _open_reader(path, args.password)
for page in reader.pages:
writer.add_page(page)
total += 1
writer.append(reader) # 保留书签等结构
writer.add_metadata({"/Creator": "office-toolkit pdf_ops"})
return _write(writer, args.output) | {"pages": total}
def _parse_page_ranges(spec: str, total: int) -> list[int]:
"""'1-3,5,8-9' -> [1,2,3,5,8,9](1 基)"""
pages: list[int] = []
for part in spec.split(","):
part = part.strip()
if not part:
continue
if "-" in part:
a, b = part.split("-", 1)
lo, hi = int(a), int(b)
else:
lo = hi = int(part)
if lo < 1 or hi > total or lo > hi:
raise ValueError(f"页码范围越界: {part}(共 {total} 页)")
pages.extend(range(lo, hi + 1))
return sorted(set(pages))
def cmd_split(args) -> dict:
reader = _open_reader(args.input, args.password)
total = len(reader.pages)
pages = _parse_page_ranges(args.pages, total)
out_dir = Path(args.output_dir)
out_dir.mkdir(parents=True, exist_ok=True)
written = []
for num in pages:
writer = PdfWriter()
writer.add_page(reader.pages[num - 1])
name = f"{Path(args.input).stem}_p{num:03d}.pdf"
out = out_dir / name
with out.open("wb") as f:
writer.write(f)
written.append(str(out))
return {"ok": True, "output_dir": str(out_dir), "files": written, "count": len(written)}
def cmd_extract(args) -> dict:
reader = _open_reader(args.input, args.password)
start = args.page_start or 1
end = args.page_end or len(reader.pages)
parts = []
chars = 0
for i in range(start - 1, min(end, len(reader.pages))):
text = reader.pages[i].extract_text() or ""
parts.append(f"---- 第 {i + 1} 页 ----\n{text}")
chars += len(text)
body = "\n\n".join(parts)
if args.output:
Path(args.output).write_text(body, encoding="utf-8")
return {"ok": True, "output": args.output, "pages_extracted": end - start + 1, "chars": chars, "preview": body[:300]}
def cmd_compress(args) -> dict:
"""压缩:去掉重复对象 + 重构流。图片级压缩需额外依赖(pikepdf),缺省时提示。"""
reader = _open_reader(args.input, args.password)
writer = PdfWriter()
writer.append(reader)
writer.compress_identical_objects(
remove_duplicates=True, remove_unreferenced=True
)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
with out.open("wb") as f:
writer.write(f)
src_size = Path(args.input).stat().st_size
dst_size = out.stat().st_size
return {
"ok": True,
"output": str(out),
"before_bytes": src_size,
"after_bytes": dst_size,
"saved_pct": round((1 - dst_size / src_size) * 100, 1) if src_size else 0,
"note": "文本型 PDF 压缩有限;含大量图片的 PDF 建议后续接 pikepdf 图片重压缩",
}
def cmd_watermark(args) -> dict:
from reportlab.pdfgen import canvas as rl_canvas
from reportlab.lib.colors import Color
reader = _open_reader(args.input, args.password)
page_w = float(reader.pages[0].mediabox.width)
page_h = float(reader.pages[0].mediabox.height)
# 生成透明水印页
wm_path = Path(args.output).with_suffix(".wm_tmp.pdf")
c = rl_canvas.Canvas(str(wm_path), pagesize=(page_w, page_h))
c.setFont("Helvetica", args.font_size)
c.setFillColor(Color(0.5, 0.5, 0.5, alpha=args.opacity))
c.saveState()
c.translate(page_w / 2, page_h / 2)
c.rotate(args.angle)
c.drawCentredString(0, 0, args.text)
c.restoreState()
c.showPage()
c.save()
wm_reader = PdfReader(str(wm_path))
wm_page = wm_reader.pages[0]
writer = PdfWriter()
for page in reader.pages:
page.merge_page(wm_page)
writer.add_page(page)
result = _write(writer, args.output)
wm_path.unlink(missing_ok=True)
return result | {"watermark_text": args.text}
def cmd_decrypt(args) -> dict:
"""解密解锁:去掉打开密码;无密码且仅带权限限制时,直接重建输出为无限制副本。
仅限用户本人拥有或有明确权限处理的文档。
"""
reader = _open_reader(args.input, args.password)
writer = PdfWriter()
writer.append(reader)
writer.encrypt(user_password="", owner_password="", use_128bit=True)
# 上面的 encrypt 会重设权限;若要完全无加密副本:
# 重新以明文写出(PdfWriter 默认不加密即可)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
writer_plain = PdfWriter()
for page in reader.pages:
writer_plain.add_page(page)
with out.open("wb") as f:
writer_plain.write(f)
return {
"ok": True,
"output": str(out),
"was_encrypted": reader.is_encrypted,
"note": "已生成无打开密码、无权限限制的副本;请确认您对该文档拥有处理权限",
}
# ---------- CLI ----------
def build_parser() -> argparse.ArgumentParser:
ap = argparse.ArgumentParser(description="PDF 处理工具")
sub = ap.add_subparsers(dest="command", required=True)
p = sub.add_parser("info", help="查看页数/元数据/加密状态")
p.add_argument("--input", required=True)
p.add_argument("--password", default=None)
p.set_defaults(func=cmd_info)
p = sub.add_parser("merge", help="合并多个 PDF")
p.add_argument("--inputs", nargs="+", required=True)
p.add_argument("--output", required=True)
p.add_argument("--password", default=None)
p.set_defaults(func=cmd_merge)
p = sub.add_parser("split", help="按页码拆分")
p.add_argument("--input", required=True)
p.add_argument("--pages", required=True, help="如 1-3,5,8-9")
p.add_argument("--output-dir", required=True)
p.add_argument("--password", default=None)
p.set_defaults(func=cmd_split)
p = sub.add_parser("extract", help="提取文本")
p.add_argument("--input", required=True)
p.add_argument("--output", default=None)
p.add_argument("--page-start", type=int, default=None)
p.add_argument("--page-end", type=int, default=None)
p.add_argument("--password", default=None)
p.set_defaults(func=cmd_extract)
p = sub.add_parser("compress", help="压缩(去重复对象)")
p.add_argument("--input", required=True)
p.add_argument("--output", required=True)
p.add_argument("--password", default=None)
p.set_defaults(func=cmd_compress)
p = sub.add_parser("watermark", help="加水印")
p.add_argument("--input", required=True)
p.add_argument("--output", required=True)
p.add_argument("--text", required=True)
p.add_argument("--font-size", type=int, default=48)
p.add_argument("--opacity", type=float, default=0.25)
p.add_argument("--angle", type=int, default=45)
p.add_argument("--password", default=None)
p.set_defaults(func=cmd_watermark)
p = sub.add_parser("decrypt", help="解密解锁(仅限本人有权处理的文档)")
p.add_argument("--input", required=True)
p.add_argument("--output", required=True)
p.add_argument("--password", default=None)
p.set_defaults(func=cmd_decrypt)
return ap
def main():
args = build_parser().parse_args()
try:
result = args.func(args)
except Exception as e:
print(json.dumps({"ok": False, "error": str(e)}, ensure_ascii=False))
sys.exit(1)
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()