aianyu commited on
Commit
b737f2b
·
verified ·
1 Parent(s): 48ccb12

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +191 -122
app.py CHANGED
@@ -3,51 +3,88 @@ from pywallet import wallet
3
  import time
4
  import itertools
5
  import string
 
6
 
7
  # --- 配置 ---
8
- # 密码字符集(仅用于基本暴力破解,非常不推荐用于实际钱包)
9
- DEFAULT_CHARSET = string.ascii_lowercase + string.digits # a-z, 0-9
10
- MAX_BRUTEFORCE_LEN = 4 # 限制暴力破解长度,否则会永远运行
11
 
12
- # --- 辅助函数 ---
13
  def try_decrypt_wallet(wallet_data_bytes, password_attempt):
14
- """
15
- 尝试用给定的密码解密钱包。
16
- 返回 True 如果成功, False 如果失败。
17
- """
18
  try:
19
- # pywallet 需要一个文件名或类文件对象,但我们可以从字节加载
20
- # 为了安全和简单,我们避免将钱包数据写入临时文件
21
- # 但 pywallet.Wallet.from_binary() 似乎是存在的
22
  w = wallet.Wallet.from_binary(wallet_data_bytes)
23
-
24
  if w.is_encrypted():
25
- if w.decrypt(password_attempt):
26
- # 解密成功!
27
- return True
28
- else:
29
- # 密码错误
30
- return False
31
  else:
32
- # 钱包未加密
33
- st.info("钱包文件未加密。")
34
- return True # 可以认为是一种“成功”
35
- except Exception as e:
36
- # st.error(f"解密时发生错误: {e}") # 调试时可以取消注释
37
- # 常见的错误可能是密码错误导致解密失败,或者钱包文件损坏
38
  return False
39
 
40
  def generate_passwords_bruteforce(charset, max_len):
41
- """
42
- (非常不推荐) 基本的暴力破解密码生成器
43
- """
44
  for length in range(1, max_len + 1):
45
  for item in itertools.product(charset, repeat=length):
46
  yield "".join(item)
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  # --- Streamlit UI ---
49
- st.set_page_config(page_title="Wallet.dat Cracker", layout="wide")
50
- st.title("Bitcoin Wallet.dat 密码破解尝试工具 (教育目的)")
51
 
52
  st.warning(
53
  """
@@ -59,6 +96,15 @@ st.warning(
59
  """
60
  )
61
 
 
 
 
 
 
 
 
 
 
62
  # --- 文件上传 ---
63
  uploaded_wallet_file = st.file_uploader("1. 上传你的 wallet.dat 文件", type=["dat"])
64
 
@@ -69,108 +115,131 @@ if uploaded_wallet_file:
69
  # --- 破解方法选择 ---
70
  crack_method = st.radio(
71
  "2. 选择破解方法:",
72
- ('使用密码列表', '尝试单个密码', '基本暴力破解 (非常不推荐,极慢)')
73
  )
74
 
75
- # --- 密码列表方法 ---
76
- if crack_method == '使用密码列表':
77
- uploaded_password_list = st.file_uploader(
78
- "上传密码列表文件 ( .txt, 每行一个密码)", type=["txt"]
79
- )
80
- if uploaded_password_list:
81
- passwords_to_try = [
82
- line.strip() for line in uploaded_password_list.read().decode().splitlines() if line.strip()
83
- ]
84
- st.info(f"密码列表加载了 {len(passwords_to_try)} 个密码。")
85
-
86
- if st.button("开始使用列表破解", key="list_crack"):
87
- if not passwords_to_try:
88
- st.error("密码列表为空。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  else:
90
- st.info("开始破解尝试...")
91
- progress_bar = st.progress(0)
92
- status_text = st.empty()
93
- found_password = None
94
- start_time = time.time()
95
-
96
- for i, pwd in enumerate(passwords_to_try):
97
- status_text.text(f"尝试密码 ({i+1}/{len(passwords_to_try)}): {pwd}")
98
- if try_decrypt_wallet(wallet_bytes, pwd):
99
- found_password = pwd
100
- break
101
- progress_bar.progress((i + 1) / len(passwords_to_try))
102
- # 在 Streamlit 中添加少量延迟以允许 UI 更新,尤其是在快速循环中
103
- # time.sleep(0.001)
104
-
105
-
106
- end_time = time.time()
107
- status_text.text("破解尝试完成。")
108
- elapsed_time = end_time - start_time
109
-
110
- if found_password:
111
- st.success(f"🎉 密码找到: **{found_password}** 🎉")
112
- st.balloons()
113
- else:
114
- st.error("未能在密码列表中找到正确的密码。")
115
- st.write(f"总耗时: {elapsed_time:.2f} 秒。")
116
- st.write(f"平均速度: {len(passwords_to_try) / elapsed_time if elapsed_time > 0 else float('inf'):.2f} 密码/秒。")
117
-
118
-
119
- # --- 单个密码方法 ---
120
- elif crack_method == '尝试单个密码':
121
- single_password = st.text_input("输入要尝试的密码:", type="password")
122
- if st.button("尝试此密码", key="single_crack"):
123
- if not single_password:
124
- st.warning("请输入密码。")
125
  else:
126
- st.info(f"正在尝试密码: {single_password}")
127
- if try_decrypt_wallet(wallet_bytes, single_password):
128
- st.success(f"🎉 密码正确: **{single_password}** 🎉")
129
- st.balloons()
130
- else:
131
- st.error("密码错误。")
132
-
133
- # --- 基本暴力破解方法 ---
134
- elif crack_method == '基本暴力破解 (非常不推荐,极慢)':
135
- charset_input = st.text_input("字符集:", DEFAULT_CHARSET)
136
- max_len_input = st.number_input("最大密码长度 (警告: 长度大于4会非常慢):", min_value=1, max_value=8, value=MAX_BRUTEFORCE_LEN)
137
-
138
- st.warning(f"使用字符集 '{charset_input}' 和最大长度 {max_len_input} 进行暴力破解,"
139
- f"可能需要尝试 {sum(len(charset_input)**i for i in range(1, max_len_input + 1))} 个密码。")
140
-
141
- if st.button("开始暴力破解 (请耐心等待)", key="brute_crack"):
142
- st.info("开始暴力破解尝试...")
143
- # 无法预估总数,因此不使用标准进度条
144
- status_text = st.empty()
145
- found_password = None
146
- start_time = time.time()
147
- count = 0
148
- estimated_total = sum(len(charset_input)**i for i in range(1, max_len_input + 1))
149
- progress_bar = st.progress(0)
150
-
151
 
152
- for pwd in generate_passwords_bruteforce(charset_input, max_len_input):
153
- count += 1
154
- status_text.text(f"尝试密码 #{count}: {pwd}")
155
- if try_decrypt_wallet(wallet_bytes, pwd):
156
- found_password = pwd
157
- break
158
- if count % 100 == 0: # 每100次更新一次进度条,避免过于频繁
159
- progress_bar.progress(min(1.0, count / estimated_total if estimated_total > 0 else 0))
160
- # time.sleep(0.001) # 允许UI更新
161
 
 
 
 
 
 
 
 
 
 
162
 
163
- end_time = time.time()
164
- status_text.text("暴力破解尝试完成。")
165
- elapsed_time = end_time - start_time
166
 
167
- if found_password:
168
- st.success(f"🎉 密码找到: **{found_password}** 🎉")
169
- st.balloons()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  else:
171
- st.error(f"在指定的字符集和长度内未能找到密码 (尝试了 {count} 个)。")
172
- st.write(f"总耗时: {elapsed_time:.2f} 秒。")
173
- st.write(f"平均速度: {count / elapsed_time if elapsed_time > 0 else float('inf'):.2f} 密码/秒。")
 
 
 
 
 
 
 
174
 
175
  else:
176
  st.info("请先上传 wallet.dat 文件。")
 
3
  import time
4
  import itertools
5
  import string
6
+ import threading
7
 
8
  # --- 配置 ---
9
+ DEFAULT_CHARSET = string.ascii_lowercase + string.digits
10
+ MAX_BRUTEFORCE_LEN = 4
 
11
 
12
+ # --- 辅助函数 (保持不变) ---
13
  def try_decrypt_wallet(wallet_data_bytes, password_attempt):
 
 
 
 
14
  try:
 
 
 
15
  w = wallet.Wallet.from_binary(wallet_data_bytes)
 
16
  if w.is_encrypted():
17
+ return w.decrypt(password_attempt)
 
 
 
 
 
18
  else:
19
+ st.session_state.info_message = "钱包文件未加密。"
20
+ return True
21
+ except Exception:
 
 
 
22
  return False
23
 
24
  def generate_passwords_bruteforce(charset, max_len):
 
 
 
25
  for length in range(1, max_len + 1):
26
  for item in itertools.product(charset, repeat=length):
27
  yield "".join(item)
28
 
29
+ # --- 后台工作函数 ---
30
+ def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, method_name):
31
+ """
32
+ 在后台线程中运行破解过程。
33
+ 更新 st.session_state 来反映进度和结果。
34
+ """
35
+ st.session_state[f'task_{task_id}_status'] = 'running'
36
+ st.session_state[f'task_{task_id}_found_password'] = None
37
+ st.session_state[f'task_{task_id}_error_message'] = None
38
+ st.session_state[f'task_{task_id}_attempts'] = 0
39
+ st.session_state[f'task_{task_id}_time_taken'] = 0
40
+
41
+ start_time = time.time()
42
+ found = False
43
+ processed_passwords = 0
44
+
45
+ # 特殊处理暴力破解,因为它是一个生成器,无法预知总数
46
+ is_bruteforce = (method_name == "bruteforce")
47
+ total_passwords_for_progress = 0
48
+ if not is_bruteforce and isinstance(passwords_iterable, list):
49
+ total_passwords_for_progress = len(passwords_iterable)
50
+
51
+ for pwd in passwords_iterable:
52
+ # 检查是否应该停止 (如果用户启动了新任务)
53
+ if st.session_state.get('current_task_id') != task_id:
54
+ st.session_state[f'task_{task_id}_error_message'] = "任务被另一个操作中止。"
55
+ st.session_state[f'task_{task_id}_status'] = 'failed'
56
+ return
57
+
58
+ processed_passwords += 1
59
+ st.session_state[f'task_{task_id}_attempts'] = processed_passwords
60
+
61
+ # 为了避免过于频繁地更新session_state (可能导致性能问题)
62
+ # 可以在这里添加一些逻辑,例如每隔N次尝试或每隔X秒更新一次用于显示的尝试计数
63
+ # 不过对于最终结果的设置,这个不是大问题
64
+
65
+ if try_decrypt_wallet(wallet_bytes_data, pwd):
66
+ st.session_state[f'task_{task_id}_found_password'] = pwd
67
+ st.session_state[f'task_{task_id}_status'] = 'success'
68
+ found = True
69
+ break
70
+ # time.sleep(0.0001) # 极小的延时,以允许GIL切换,但对CPU密集型任务效果不大
71
+
72
+ end_time = time.time()
73
+ st.session_state[f'task_{task_id}_time_taken'] = end_time - start_time
74
+
75
+ if not found and st.session_state[f'task_{task_id}_status'] == 'running': # 确保不是因为中止而退出
76
+ st.session_state[f'task_{task_id}_error_message'] = "未找到密码。"
77
+ st.session_state[f'task_{task_id}_status'] = 'failed'
78
+
79
+ # 主动触发一次 Streamlit 刷新,让UI能立刻感知到状态变化
80
+ # 这个在线程中调用需要小心,但对于更新session_state然后让Streamlit自己刷新是安全的
81
+ # st.experimental_rerun() # 旧版,如果需要可以尝试
82
+ # Streamlit 通常会自动检测 session_state 的变化并重新运行脚本
83
+
84
+
85
  # --- Streamlit UI ---
86
+ st.set_page_config(page_title="Wallet.dat Cracker (Background)", layout="wide")
87
+ st.title("Bitcoin Wallet.dat 密码破解尝试 (后台运行)")
88
 
89
  st.warning(
90
  """
 
96
  """
97
  )
98
 
99
+ # 初始化 session_state 变量
100
+ if 'current_task_id' not in st.session_state:
101
+ st.session_state.current_task_id = None
102
+ if 'task_counter' not in st.session_state: # 用于生成唯一的 task_id
103
+ st.session_state.task_counter = 0
104
+ if 'info_message' not in st.session_state:
105
+ st.session_state.info_message = None
106
+
107
+
108
  # --- 文件上传 ---
109
  uploaded_wallet_file = st.file_uploader("1. 上传你的 wallet.dat 文件", type=["dat"])
110
 
 
115
  # --- 破解方法选择 ---
116
  crack_method = st.radio(
117
  "2. 选择破解方法:",
118
+ ('使用密码列表', '尝试单个密码', '基本暴力破解 (极慢,后台运行)')
119
  )
120
 
121
+ current_task_id = st.session_state.get('current_task_id')
122
+ task_status = None
123
+ if current_task_id:
124
+ task_status = st.session_state.get(f'task_{current_task_id}_status')
125
+
126
+ # --- 显示任务状态和结果 ---
127
+ if task_status == 'running':
128
+ attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
129
+ with st.spinner(f"正在后台破解... 已尝试 {attempts} 个密码。请勿关闭此页面。"):
130
+ # 由于 Streamlit 的特性,spinner 会在脚本重新运行时自动更新。
131
+ # 我们需要一种方式让脚本在线程运行时定期重新运行以更新spinner的文本。
132
+ # 一个简单的方法是使用 st.empty() 和 time.sleep() 在主线程中轮询,
133
+ # 但这又会阻塞主线程。
134
+ # 更好的方式是依赖 Streamlit 对 session_state 变化的自动刷新。
135
+ # 为了让 spinner 中的数字更新,后台线程可能需要更频繁地更新一个 session_state 变量
136
+ # 但为了简单,我们这里只显示一个通用的 "正在后台破解..."
137
+ # 并且依赖于任务完成后 session_state 的变化来刷新整个页面。
138
+ # 或者,我们可以在这里放一个占位符,后台线程完成后,其设置的session_state会导致页面刷新
139
+ st.empty() # 只是为了保持 spinner 可见
140
+ time.sleep(1) # 让streamlit有机会重跑脚本并显示spinner
141
+ st.rerun() # 强制重跑以更新spinner中的计数(如果做了计数更新)
142
+
143
+
144
+ elif task_status == 'success':
145
+ found_pwd = st.session_state.get(f'task_{current_task_id}_found_password')
146
+ time_taken = st.session_state.get(f'task_{current_task_id}_time_taken', 0)
147
+ attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
148
+ st.success(f"🎉 密码找到: **{found_pwd}** 🎉")
149
+ st.balloons()
150
+ st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
151
+ if st.button("清除结果并重试", key="clear_success"):
152
+ st.session_state.current_task_id = None # 清除任务标记
153
+ st.rerun()
154
+
155
+
156
+ elif task_status == 'failed':
157
+ error_msg = st.session_state.get(f'task_{current_task_id}_error_message')
158
+ time_taken = st.session_state.get(f'task_{current_task_id}_time_taken', 0)
159
+ attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
160
+ st.error(f"破解失败: {error_msg}")
161
+ st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
162
+ if st.button("清除结果并重试", key="clear_failed"):
163
+ st.session_state.current_task_id = None # 清除任务标记
164
+ st.rerun()
165
+
166
+ if st.session_state.get('info_message'):
167
+ st.info(st.session_state.info_message)
168
+ st.session_state.info_message = None # 显示后清除
169
+
170
+ # --- 启动按钮 (仅当没有任务运行时显示) ---
171
+ if task_status not in ['running', 'success', 'failed'] or not task_status :
172
+ passwords_to_try_list = []
173
+ brute_force_params = {}
174
+ can_start = False
175
+ start_button_label = "开始破解"
176
+ method_name_for_task = ""
177
+
178
+ if crack_method == '使用密码列表':
179
+ uploaded_password_list = st.file_uploader(
180
+ "上传密码列表文件 ( .txt, 每行一个密码)", type=["txt"], key="pwdlist_uploader"
181
+ )
182
+ if uploaded_password_list:
183
+ passwords_to_try_list = [
184
+ line.strip() for line in uploaded_password_list.read().decode().splitlines() if line.strip()
185
+ ]
186
+ if passwords_to_try_list:
187
+ st.info(f"密码列表加载了 {len(passwords_to_try_list)} 个密码。")
188
+ can_start = True
189
+ start_button_label = "开始使用列表破解"
190
+ method_name_for_task = "list"
191
  else:
192
+ st.warning("上传的密码列表为空或无效。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  else:
194
+ st.info("请上传密码列表文件。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
 
 
 
 
 
 
 
 
 
196
 
197
+ elif crack_method == '尝试单个密码':
198
+ single_password = st.text_input("输入要尝试的密码:", type="password", key="single_pwd_input")
199
+ if single_password:
200
+ passwords_to_try_list = [single_password]
201
+ can_start = True
202
+ start_button_label = "尝试此密码"
203
+ method_name_for_task = "single"
204
+ else:
205
+ st.info("请输入要尝试的密码。")
206
 
 
 
 
207
 
208
+ elif crack_method == '基本暴力破解 (极慢,后台运行)':
209
+ charset_input = st.text_input("字符集:", DEFAULT_CHARSET, key="charset_input")
210
+ max_len_input = st.number_input(
211
+ "最大密码长度 (警告: 长度大于4会非常慢):",
212
+ min_value=1, max_value=8, value=MAX_BRUTEFORCE_LEN, key="maxlen_input"
213
+ )
214
+ estimated_total = sum(len(charset_input)**i for i in range(1, max_len_input + 1))
215
+ st.warning(f"使用字符集 '{charset_input}' 和最大长度 {max_len_input} 进行暴力破解,"
216
+ f"可能需要尝试 {estimated_total} 个密码。这会非常耗时。")
217
+ brute_force_params = {'charset': charset_input, 'max_len': max_len_input}
218
+ can_start = True
219
+ start_button_label = "开始暴力破解 (后台)"
220
+ method_name_for_task = "bruteforce"
221
+
222
+
223
+ if can_start and st.button(start_button_label, key=f"start_button_{method_name_for_task}"):
224
+ st.session_state.task_counter += 1
225
+ new_task_id = st.session_state.task_counter
226
+ st.session_state.current_task_id = new_task_id
227
+
228
+ if method_name_for_task == "bruteforce":
229
+ passwords_iterable = generate_passwords_bruteforce(
230
+ brute_force_params['charset'], brute_force_params['max_len']
231
+ )
232
  else:
233
+ passwords_iterable = passwords_to_try_list
234
+
235
+ # 启动后台线程
236
+ thread = threading.Thread(
237
+ target=background_cracker_task,
238
+ args=(wallet_bytes, passwords_iterable, new_task_id, method_name_for_task)
239
+ )
240
+ thread.daemon = True # 允许主程序退出时线程也退出
241
+ thread.start()
242
+ st.rerun() # 立即重新运行脚本以显示 spinner
243
 
244
  else:
245
  st.info("请先上传 wallet.dat 文件。")