configcrypt 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ """
2
+ KeyVault - 文件级加密工具
3
+
4
+ 提供CLI、GUI和Python Library三种使用方式的跨平台文件加密解决方案。
5
+ """
6
+
7
+ __version__ = "0.1.0"
8
+ __author__ = "KeyVault Team"
9
+
10
+ # 导出主要API
11
+ from configcrypt.core.vault import Vault, VaultAPI
12
+ from configcrypt.core.exceptions import (
13
+ KeyVaultError,
14
+ EncryptionError,
15
+ DecryptionError,
16
+ InvalidTokenError,
17
+ KeychainError,
18
+ ParseError,
19
+ EditorNotFoundError,
20
+ PasswordValidationError,
21
+ )
22
+
23
+ __all__ = [
24
+ "Vault",
25
+ "VaultAPI",
26
+ "KeyVaultError",
27
+ "EncryptionError",
28
+ "DecryptionError",
29
+ "InvalidTokenError",
30
+ "KeychainError",
31
+ "ParseError",
32
+ "EditorNotFoundError",
33
+ "PasswordValidationError",
34
+ ]
@@ -0,0 +1,9 @@
1
+ """
2
+ KeyVault CLI模块
3
+
4
+ 提供命令行界面功能,包括文件加密、解密、密码管理等命令。
5
+ """
6
+
7
+ from configcrypt.cli.commands import cli, main
8
+
9
+ __all__ = ["cli", "main"]
@@ -0,0 +1,388 @@
1
+ """
2
+ KeyVault CLI命令实现
3
+
4
+ 使用click实现命令行界面
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+ import click
10
+
11
+ from configcrypt.core.vault import Vault
12
+ from configcrypt.core.keychain_store import KeychainStore
13
+ from configcrypt.core.editor_launcher import EditorLauncher
14
+ from configcrypt.core.exceptions import (
15
+ EncryptionError,
16
+ DecryptionError,
17
+ InvalidTokenError,
18
+ KeychainError,
19
+ EditorNotFoundError
20
+ )
21
+
22
+
23
+ def _calculate_password_strength(password: str) -> str:
24
+ """
25
+ 计算密码强度
26
+
27
+ 强度等级:
28
+ - 弱: 长度<8 或 只包含字母/数字
29
+ - 中: 长度>=8 且 包含字母+数字
30
+ - 强: 长度>=12 且 包含字母+数字+特殊字符
31
+
32
+ Args:
33
+ password: 密码字符串
34
+
35
+ Returns:
36
+ "弱"、"中"或"强"
37
+ """
38
+ if len(password) < 8:
39
+ return "弱"
40
+
41
+ has_letter = any(c.isalpha() for c in password)
42
+ has_digit = any(c.isdigit() for c in password)
43
+ has_special = any(not c.isalnum() for c in password)
44
+
45
+ if len(password) >= 12 and has_letter and has_digit and has_special:
46
+ return "强"
47
+ elif len(password) >= 8 and has_letter and has_digit:
48
+ return "中"
49
+ else:
50
+ return "弱"
51
+
52
+
53
+ @click.group()
54
+ def cli():
55
+ """KeyVault - 文件级加密工具
56
+
57
+ 安全地加密和解密敏感配置文件
58
+ """
59
+ pass
60
+
61
+
62
+ @cli.command()
63
+ def init():
64
+ """初始化KeyVault,设置主密码"""
65
+ click.echo("🔐 KeyVault 初始化")
66
+ click.echo("=" * 50)
67
+
68
+ keychain = KeychainStore()
69
+
70
+ # 检查Keychain是否可用
71
+ if not keychain.is_available():
72
+ click.echo("❌ 错误: 系统Keychain不可用")
73
+ click.echo("请确保您的系统支持密码存储功能")
74
+ sys.exit(1)
75
+
76
+ # 检查是否已有密码
77
+ existing_password = keychain.get_password()
78
+ if existing_password:
79
+ click.echo("⚠️ 警告: 已存在主密码")
80
+ if not click.confirm("是否要重新设置主密码?", default=False):
81
+ click.echo("操作已取消")
82
+ sys.exit(0)
83
+
84
+ # 输入新密码
85
+ while True:
86
+ password = click.prompt(
87
+ "请输入主密码",
88
+ hide_input=True,
89
+ confirmation_prompt=True
90
+ )
91
+
92
+ # 验证密码长度
93
+ if len(password) < 8:
94
+ click.echo("❌ 密码长度至少为8个字符,请重新输入")
95
+ continue
96
+
97
+ # 显示密码强度
98
+ strength = _calculate_password_strength(password)
99
+ click.echo(f"密码强度: {strength}")
100
+
101
+ # 如果密码较弱,询问是否继续
102
+ if strength == "弱":
103
+ if not click.confirm("密码强度较弱,是否继续?", default=False):
104
+ continue
105
+
106
+ break
107
+
108
+ # 保存密码到Keychain
109
+ try:
110
+ keychain.save_password(password)
111
+ click.echo("✅ 主密码已成功保存到系统Keychain")
112
+ click.echo("\n现在您可以使用以下命令:")
113
+ click.echo(" kv encrypt <file> - 加密文件")
114
+ click.echo(" kv decrypt <file> - 解密文件")
115
+ except KeychainError as e:
116
+ click.echo(f"❌ 保存密码失败: {e}")
117
+ sys.exit(1)
118
+
119
+
120
+ @cli.command()
121
+ @click.argument('file', type=click.Path(exists=True, path_type=Path))
122
+ @click.option(
123
+ '--out',
124
+ type=click.Path(path_type=Path),
125
+ help='输出文件路径(默认: 原文件名.enc)'
126
+ )
127
+ @click.option(
128
+ '--keep',
129
+ is_flag=True,
130
+ help='保留源文件(默认会删除)'
131
+ )
132
+ def encrypt(file, out, keep):
133
+ """加密文件
134
+
135
+ FILE: 要加密的文件路径
136
+ """
137
+ keychain = KeychainStore()
138
+ vault = Vault(keychain_store=keychain)
139
+
140
+ try:
141
+ # 检查输出文件是否已存在
142
+ output_path = out if out else Path(str(file) + '.enc')
143
+ if output_path.exists():
144
+ if not click.confirm(f"输出文件 {output_path} 已存在,是否覆盖?", default=False):
145
+ click.echo("操作已取消")
146
+ sys.exit(0)
147
+ # 删除已存在的文件以便加密
148
+ output_path.unlink()
149
+
150
+ # 加密文件
151
+ delete_source = not keep
152
+ encrypted_path = vault.encrypt_file(
153
+ file,
154
+ output_path=out,
155
+ delete_source=delete_source
156
+ )
157
+
158
+ # 显示成功消息
159
+ click.echo(f"✅ 文件已成功加密")
160
+ click.echo(f"📁 输出文件: {encrypted_path}")
161
+
162
+ if delete_source:
163
+ click.echo(f"🗑️ 源文件已删除: {file}")
164
+ else:
165
+ click.echo(f"📄 源文件保留: {file}")
166
+
167
+ except FileNotFoundError as e:
168
+ click.echo(f"❌ 错误: {e}")
169
+ sys.exit(1)
170
+ except EncryptionError as e:
171
+ if "未找到保存的密码" in str(e):
172
+ click.echo("❌ 错误: 未找到主密码")
173
+ click.echo("请先运行 'kv init' 初始化并设置主密码")
174
+ elif "输出文件已存在" in str(e):
175
+ click.echo(f"❌ 错误: {e}")
176
+ click.echo("提示: 使用 --out 选项指定不同的输出路径")
177
+ else:
178
+ click.echo(f"❌ 加密失败: {e}")
179
+ sys.exit(1)
180
+ except PermissionError as e:
181
+ click.echo(f"❌ 权限错误: {e}")
182
+ sys.exit(1)
183
+ except Exception as e:
184
+ click.echo(f"❌ 未知错误: {e}")
185
+ sys.exit(1)
186
+
187
+
188
+ @cli.command()
189
+ @click.argument('file', type=click.Path(exists=True, path_type=Path))
190
+ @click.option(
191
+ '--out',
192
+ type=click.Path(path_type=Path),
193
+ help='输出文件路径(默认: 移除.enc扩展名)'
194
+ )
195
+ @click.option(
196
+ '--open',
197
+ 'open_editor',
198
+ is_flag=True,
199
+ help='解密后立即用编辑器打开文件'
200
+ )
201
+ @click.option(
202
+ '--editor',
203
+ type=str,
204
+ help='指定编辑器命令(配合--open使用)'
205
+ )
206
+ def decrypt(file, out, open_editor, editor):
207
+ """解密文件
208
+
209
+ FILE: 要解密的文件路径
210
+ """
211
+ keychain = KeychainStore()
212
+ vault = Vault(keychain_store=keychain)
213
+
214
+ try:
215
+ # 检查输出文件是否已存在
216
+ if out and Path(out).exists():
217
+ if not click.confirm(f"输出文件 {out} 已存在,是否覆盖?", default=False):
218
+ click.echo("操作已取消")
219
+ sys.exit(0)
220
+ # 删除已存在的文件以便解密
221
+ Path(out).unlink()
222
+
223
+ # 解密文件
224
+ decrypted_path = vault.decrypt_file(
225
+ file,
226
+ output_path=out
227
+ )
228
+
229
+ # 显示成功消息
230
+ click.echo(f"✅ 文件已成功解密")
231
+ click.echo(f"📁 输出文件: {decrypted_path}")
232
+
233
+ # 如果指定了--open选项,打开编辑器
234
+ if open_editor:
235
+ try:
236
+ launcher = EditorLauncher()
237
+ launcher.open_file(decrypted_path, editor_command=editor)
238
+ click.echo(f"📝 已用编辑器打开文件")
239
+ except EditorNotFoundError as e:
240
+ click.echo(f"⚠️ 警告: {e}")
241
+ click.echo("提示: 使用 --editor 选项指定编辑器命令")
242
+ click.echo("示例: kv decrypt file.enc --open --editor nano")
243
+
244
+ except FileNotFoundError as e:
245
+ click.echo(f"❌ 错误: {e}")
246
+ sys.exit(1)
247
+ except InvalidTokenError:
248
+ click.echo("❌ 解密失败: 密码错误或文件已损坏/被篡改")
249
+ click.echo("提示: 请确认:")
250
+ click.echo(" 1. 主密码是否正确(运行 'kv init' 重新设置)")
251
+ click.echo(" 2. 文件是否完整且未被修改")
252
+ sys.exit(1)
253
+ except DecryptionError as e:
254
+ if "未找到保存的密码" in str(e):
255
+ click.echo("❌ 错误: 未找到主密码")
256
+ click.echo("请先运行 'kv init' 初始化并设置主密码")
257
+ elif "输出文件已存在" in str(e):
258
+ click.echo(f"❌ 错误: {e}")
259
+ click.echo("提示: 使用 --out 选项指定不同的输出路径")
260
+ else:
261
+ click.echo(f"❌ 解密失败: {e}")
262
+ sys.exit(1)
263
+ except PermissionError as e:
264
+ click.echo(f"❌ 权限错误: {e}")
265
+ sys.exit(1)
266
+ except Exception as e:
267
+ click.echo(f"❌ 未知错误: {e}")
268
+ sys.exit(1)
269
+
270
+
271
+ @cli.command()
272
+ def status():
273
+ """显示KeyVault状态信息"""
274
+ click.echo("🔐 KeyVault 状态")
275
+ click.echo("=" * 50)
276
+
277
+ keychain = KeychainStore()
278
+
279
+ # 检查Keychain可用性
280
+ if not keychain.is_available():
281
+ click.echo("❌ Keychain状态: 不可用")
282
+ click.echo("⚠️ 警告: 系统Keychain不可用,无法使用KeyVault")
283
+ sys.exit(1)
284
+ else:
285
+ click.echo("✅ Keychain状态: 可用")
286
+
287
+ # 检查主密码是否已设置
288
+ password = keychain.get_password()
289
+ if password:
290
+ click.echo("✅ 主密码: 已设置")
291
+ click.echo(f" 密码强度: {_calculate_password_strength(password)}")
292
+ else:
293
+ click.echo("❌ 主密码: 未设置")
294
+ click.echo("提示: 运行 'kv init' 设置主密码")
295
+
296
+ click.echo("\n可用命令:")
297
+ click.echo(" kv init - 初始化/重置主密码")
298
+ click.echo(" kv encrypt <file> - 加密文件")
299
+ click.echo(" kv decrypt <file> - 解密文件")
300
+ click.echo(" kv reset-password - 修改主密码")
301
+ click.echo(" kv status - 显示状态信息")
302
+
303
+
304
+ @cli.command('reset-password')
305
+ def reset_password():
306
+ """修改主密码
307
+
308
+ 验证旧密码后设置新密码
309
+ """
310
+ click.echo("🔐 修改主密码")
311
+ click.echo("=" * 50)
312
+
313
+ keychain = KeychainStore()
314
+ vault = Vault(keychain_store=keychain)
315
+
316
+ # 检查Keychain是否可用
317
+ if not keychain.is_available():
318
+ click.echo("❌ 错误: 系统Keychain不可用")
319
+ sys.exit(1)
320
+
321
+ # 检查是否已有密码
322
+ old_password = keychain.get_password()
323
+ if not old_password:
324
+ click.echo("❌ 错误: 未找到主密码")
325
+ click.echo("请先运行 'kv init' 初始化并设置主密码")
326
+ sys.exit(1)
327
+
328
+ # 验证旧密码
329
+ click.echo("请先验证当前主密码")
330
+ input_old_password = click.prompt("当前主密码", hide_input=True)
331
+
332
+ if input_old_password != old_password:
333
+ click.echo("❌ 错误: 当前密码不正确")
334
+ sys.exit(1)
335
+
336
+ click.echo("✅ 密码验证成功")
337
+ click.echo()
338
+
339
+ # 输入新密码
340
+ click.echo("请设置新的主密码")
341
+ while True:
342
+ new_password = click.prompt(
343
+ "新主密码",
344
+ hide_input=True,
345
+ confirmation_prompt=True
346
+ )
347
+
348
+ # 验证密码长度
349
+ if len(new_password) < 8:
350
+ click.echo("❌ 密码长度至少为8个字符,请重新输入")
351
+ continue
352
+
353
+ # 不允许使用相同的密码
354
+ if new_password == old_password:
355
+ click.echo("❌ 新密码不能与旧密码相同,请重新输入")
356
+ continue
357
+
358
+ # 显示密码强度
359
+ strength = _calculate_password_strength(new_password)
360
+ click.echo(f"密码强度: {strength}")
361
+
362
+ # 如果密码较弱,询问是否继续
363
+ if strength == "弱":
364
+ if not click.confirm("密码强度较弱,是否继续?", default=False):
365
+ continue
366
+
367
+ break
368
+
369
+ # 保存新密码
370
+ try:
371
+ keychain.save_password(new_password)
372
+ click.echo("✅ 主密码已成功更新")
373
+ click.echo("\n⚠️ 重要提示:")
374
+ click.echo(" - 所有已加密的文件仍使用旧密码加密")
375
+ click.echo(" - 新密码仅用于加密新文件")
376
+ click.echo(" - 若要使用新密码,需重新加密旧文件")
377
+ except KeychainError as e:
378
+ click.echo(f"❌ 保存密码失败: {e}")
379
+ sys.exit(1)
380
+
381
+
382
+ def main():
383
+ """CLI入口点"""
384
+ cli()
385
+
386
+
387
+ if __name__ == '__main__':
388
+ main()
@@ -0,0 +1,36 @@
1
+ """
2
+ KeyVault Core模块
3
+
4
+ 包含核心加密引擎、密钥管理、格式解析等核心功能。
5
+ """
6
+
7
+ from configcrypt.core.vault import Vault, VaultAPI
8
+ from configcrypt.core.keychain_store import KeychainStore
9
+ from configcrypt.core.format_parser import FormatParser
10
+ from configcrypt.core.editor_launcher import EditorLauncher
11
+ from configcrypt.core.exceptions import (
12
+ KeyVaultError,
13
+ EncryptionError,
14
+ DecryptionError,
15
+ InvalidTokenError,
16
+ KeychainError,
17
+ ParseError,
18
+ EditorNotFoundError,
19
+ PasswordValidationError,
20
+ )
21
+
22
+ __all__ = [
23
+ "Vault",
24
+ "VaultAPI",
25
+ "KeychainStore",
26
+ "FormatParser",
27
+ "EditorLauncher",
28
+ "KeyVaultError",
29
+ "EncryptionError",
30
+ "DecryptionError",
31
+ "InvalidTokenError",
32
+ "KeychainError",
33
+ "ParseError",
34
+ "EditorNotFoundError",
35
+ "PasswordValidationError",
36
+ ]
@@ -0,0 +1,116 @@
1
+ """
2
+ EditorLauncher 编辑器启动模块
3
+
4
+ 跨平台编辑器启动器,支持Windows、macOS和Linux系统
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import shutil
10
+ import subprocess
11
+ from pathlib import Path
12
+ from typing import Optional, List
13
+
14
+ from .exceptions import EditorNotFoundError
15
+
16
+
17
+ class EditorLauncher:
18
+ """跨平台编辑器启动器"""
19
+
20
+ # 各平台默认编辑器列表(优先级从高到低)
21
+ DEFAULT_EDITORS = {
22
+ 'win32': ['code', 'notepad++', 'notepad', 'start'], # start 使用 Windows 默认程序
23
+ 'darwin': ['code', 'subl', 'open'],
24
+ 'linux': ['code', 'gedit', 'nano', 'vim']
25
+ }
26
+
27
+ def open_file(
28
+ self,
29
+ file_path: Path,
30
+ editor_command: Optional[str] = None
31
+ ) -> None:
32
+ """
33
+ 打开文件编辑
34
+
35
+ 优先级:
36
+ 1. editor_command参数
37
+ 2. $EDITOR环境变量
38
+ 3. 平台默认编辑器列表
39
+
40
+ Args:
41
+ file_path: 要打开的文件路径
42
+ editor_command: 指定的编辑器命令
43
+
44
+ Raises:
45
+ EditorNotFoundError: 无可用编辑器
46
+ """
47
+ # 优先使用指定的编辑器
48
+ if editor_command:
49
+ if self._try_open(editor_command, file_path):
50
+ return
51
+ raise EditorNotFoundError(f"指定的编辑器不可用: {editor_command}")
52
+
53
+ # 尝试 $EDITOR 环境变量
54
+ editor_env = os.environ.get('EDITOR')
55
+ if editor_env and self._try_open(editor_env, file_path):
56
+ return
57
+
58
+ # 尝试平台默认编辑器
59
+ platform = sys.platform
60
+ editor_list = self.DEFAULT_EDITORS.get(platform, [])
61
+ for editor in editor_list:
62
+ if self._try_open(editor, file_path):
63
+ return
64
+
65
+ raise EditorNotFoundError("未找到可用的编辑器")
66
+
67
+ def _try_open(self, editor: str, file_path: Path) -> bool:
68
+ """
69
+ 尝试用指定编辑器打开文件
70
+
71
+ Args:
72
+ editor: 编辑器命令(可能包含参数)
73
+ file_path: 要打开的文件路径
74
+
75
+ Returns:
76
+ 成功返回True,失败返回False
77
+ """
78
+ # 特殊处理:Windows 的 notepad 和 macOS 的 open 命令
79
+ editor_parts = editor.split()
80
+ editor_name = editor_parts[0]
81
+
82
+ # Windows 特殊处理
83
+ if sys.platform == 'win32':
84
+ if editor_name == 'notepad':
85
+ # notepad 是 Windows 内置命令,不需要检查 which
86
+ try:
87
+ subprocess.Popen(['notepad.exe', str(file_path)])
88
+ return True
89
+ except Exception:
90
+ return False
91
+ elif editor_name == 'start':
92
+ # 使用 Windows 默认程序打开
93
+ try:
94
+ os.startfile(str(file_path))
95
+ return True
96
+ except Exception:
97
+ return False
98
+
99
+ # macOS 特殊处理
100
+ if sys.platform == 'darwin' and editor_name == 'open':
101
+ try:
102
+ subprocess.Popen([*editor_parts, str(file_path)])
103
+ return True
104
+ except Exception:
105
+ return False
106
+
107
+ # 检查编辑器是否存在(普通编辑器)
108
+ if not shutil.which(editor_name):
109
+ return False
110
+
111
+ try:
112
+ # 使用subprocess.Popen非阻塞启动
113
+ subprocess.Popen([*editor_parts, str(file_path)])
114
+ return True
115
+ except Exception:
116
+ return False
@@ -0,0 +1,45 @@
1
+ """
2
+ KeyVault 异常定义模块
3
+
4
+ 定义所有KeyVault相关的异常类型。
5
+ """
6
+
7
+
8
+ class KeyVaultError(Exception):
9
+ """所有KeyVault异常的基类"""
10
+ pass
11
+
12
+
13
+ class EncryptionError(KeyVaultError):
14
+ """加密操作失败"""
15
+ pass
16
+
17
+
18
+ class DecryptionError(KeyVaultError):
19
+ """解密操作失败(密码错误或通用解密错误)"""
20
+ pass
21
+
22
+
23
+ class InvalidTokenError(DecryptionError):
24
+ """文件完整性校验失败(文件被篡改)"""
25
+ pass
26
+
27
+
28
+ class KeychainError(KeyVaultError):
29
+ """Keychain操作失败"""
30
+ pass
31
+
32
+
33
+ class ParseError(KeyVaultError):
34
+ """配置文件格式解析失败"""
35
+ pass
36
+
37
+
38
+ class EditorNotFoundError(KeyVaultError):
39
+ """未找到可用的编辑器"""
40
+ pass
41
+
42
+
43
+ class PasswordValidationError(KeyVaultError):
44
+ """密码不符合安全要求"""
45
+ pass