aianyu commited on
Commit
856d71b
·
verified ·
1 Parent(s): b737f2b

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -59
app.py CHANGED
@@ -16,7 +16,11 @@ def try_decrypt_wallet(wallet_data_bytes, password_attempt):
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
@@ -28,10 +32,6 @@ def generate_passwords_bruteforce(charset, max_len):
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
@@ -42,14 +42,7 @@ def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, meth
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'
@@ -58,30 +51,20 @@ def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, meth
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 密码破解尝试 (后台运行)")
@@ -96,23 +79,20 @@ st.warning(
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
 
111
  if uploaded_wallet_file:
112
  wallet_bytes = uploaded_wallet_file.getvalue()
113
  st.success(f"已上传 {uploaded_wallet_file.name} ({len(wallet_bytes)} bytes)")
114
 
115
- # --- 破解方法选择 ---
116
  crack_method = st.radio(
117
  "2. 选择破解方法:",
118
  ('使用密码列表', '尝试单个密码', '基本暴力破解 (极慢,后台运行)')
@@ -123,22 +103,16 @@ if uploaded_wallet_file:
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':
@@ -149,8 +123,11 @@ if uploaded_wallet_file:
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':
@@ -160,15 +137,19 @@ if uploaded_wallet_file:
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
@@ -193,7 +174,6 @@ if uploaded_wallet_file:
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:
@@ -204,26 +184,29 @@ if uploaded_wallet_file:
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(
@@ -232,14 +215,16 @@ if uploaded_wallet_file:
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 文件。")
 
16
  if w.is_encrypted():
17
  return w.decrypt(password_attempt)
18
  else:
19
+ # 使用 session_state 存储需要在主线程显示的消息
20
+ if 'info_message' not in st.session_state:
21
+ st.session_state.info_message = "钱包文件未加密。"
22
+ elif st.session_state.info_message is None: # 避免覆盖已有的消息直到它被显示
23
+ st.session_state.info_message = "钱包文件未加密。"
24
  return True
25
  except Exception:
26
  return False
 
32
 
33
  # --- 后台工作函数 ---
34
  def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, method_name):
 
 
 
 
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
 
42
  found = False
43
  processed_passwords = 0
44
 
 
 
 
 
 
 
45
  for pwd in passwords_iterable:
 
46
  if st.session_state.get('current_task_id') != task_id:
47
  st.session_state[f'task_{task_id}_error_message'] = "任务被另一个操作中止。"
48
  st.session_state[f'task_{task_id}_status'] = 'failed'
 
51
  processed_passwords += 1
52
  st.session_state[f'task_{task_id}_attempts'] = processed_passwords
53
 
 
 
 
 
54
  if try_decrypt_wallet(wallet_bytes_data, pwd):
55
  st.session_state[f'task_{task_id}_found_password'] = pwd
56
  st.session_state[f'task_{task_id}_status'] = 'success'
57
  found = True
58
  break
59
+ # time.sleep(0.00001) # 极小的延时,CPU密集型任务效果有限
60
 
61
  end_time = time.time()
62
  st.session_state[f'task_{task_id}_time_taken'] = end_time - start_time
63
 
64
+ if not found and st.session_state[f'task_{task_id}_status'] == 'running':
65
  st.session_state[f'task_{task_id}_error_message'] = "未找到密码。"
66
  st.session_state[f'task_{task_id}_status'] = 'failed'
67
 
 
 
 
 
 
 
68
  # --- Streamlit UI ---
69
  st.set_page_config(page_title="Wallet.dat Cracker (Background)", layout="wide")
70
  st.title("Bitcoin Wallet.dat 密码破解尝试 (后台运行)")
 
79
  """
80
  )
81
 
 
82
  if 'current_task_id' not in st.session_state:
83
  st.session_state.current_task_id = None
84
+ if 'task_counter' not in st.session_state:
85
  st.session_state.task_counter = 0
86
  if 'info_message' not in st.session_state:
87
  st.session_state.info_message = None
88
 
89
 
 
90
  uploaded_wallet_file = st.file_uploader("1. 上传你的 wallet.dat 文件", type=["dat"])
91
 
92
  if uploaded_wallet_file:
93
  wallet_bytes = uploaded_wallet_file.getvalue()
94
  st.success(f"已上传 {uploaded_wallet_file.name} ({len(wallet_bytes)} bytes)")
95
 
 
96
  crack_method = st.radio(
97
  "2. 选择破解方法:",
98
  ('使用密码列表', '尝试单个密码', '基本暴力破解 (极慢,后台运行)')
 
103
  if current_task_id:
104
  task_status = st.session_state.get(f'task_{current_task_id}_status')
105
 
 
106
  if task_status == 'running':
107
  attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
108
  with st.spinner(f"正在后台破解... 已尝试 {attempts} 个密码。请勿关闭此页面。"):
109
+ st.empty()
110
+ time.sleep(1)
111
+ try:
112
+ st.experimental_rerun()
113
+ except AttributeError:
114
+ st.warning("您的 Streamlit 版本较旧,spinner 状态可能不会实时更新。请考虑升级 Streamlit 或手动刷新页面。")
115
+ pass
 
 
 
 
 
116
 
117
 
118
  elif task_status == 'success':
 
123
  st.balloons()
124
  st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
125
  if st.button("清除结果并重试", key="clear_success"):
126
+ st.session_state.current_task_id = None
127
+ try:
128
+ st.experimental_rerun()
129
+ except AttributeError:
130
+ st.warning("您的 Streamlit 版本较旧。请考虑升级 Streamlit 或手动刷新页面。")
131
 
132
 
133
  elif task_status == 'failed':
 
137
  st.error(f"破解失败: {error_msg}")
138
  st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
139
  if st.button("清除结果并重试", key="clear_failed"):
140
+ st.session_state.current_task_id = None
141
+ try:
142
+ st.experimental_rerun()
143
+ except AttributeError:
144
+ st.warning("您的 Streamlit 版本较旧。请考虑升级 Streamlit 或手动刷新页面。")
145
 
146
+ # 显示来自 try_decrypt_wallet 的一次性消息
147
  if st.session_state.get('info_message'):
148
  st.info(st.session_state.info_message)
149
  st.session_state.info_message = None # 显示后清除
150
 
151
+
152
+ if not task_status or task_status not in ['running', 'success', 'failed']:
153
  passwords_to_try_list = []
154
  brute_force_params = {}
155
  can_start = False
 
174
  else:
175
  st.info("请上传密码列表文件。")
176
 
 
177
  elif crack_method == '尝试单个密码':
178
  single_password = st.text_input("输入要尝试的密码:", type="password", key="single_pwd_input")
179
  if single_password:
 
184
  else:
185
  st.info("请输入要尝试的密码。")
186
 
 
187
  elif crack_method == '基本暴力破解 (极慢,后台运行)':
188
  charset_input = st.text_input("字符集:", DEFAULT_CHARSET, key="charset_input")
189
  max_len_input = st.number_input(
190
  "最大密码长度 (警告: 长度大于4会非常慢):",
191
  min_value=1, max_value=8, value=MAX_BRUTEFORCE_LEN, key="maxlen_input"
192
  )
193
+ if charset_input: # 确保字符集不为空
194
+ estimated_total = sum(len(charset_input)**i for i in range(1, max_len_input + 1))
195
+ st.warning(f"使用字符集 '{charset_input}' 和最大长度 {max_len_input} 进行暴力破解,"
196
+ f"可能需要尝试 {estimated_total} 个密码。这会��常耗时。")
197
+ brute_force_params = {'charset': charset_input, 'max_len': max_len_input}
198
+ can_start = True
199
+ start_button_label = "开始暴力破解 (后台)"
200
+ method_name_for_task = "bruteforce"
201
+ else:
202
+ st.error("字符集不能为空。")
203
 
204
 
205
  if can_start and st.button(start_button_label, key=f"start_button_{method_name_for_task}"):
206
  st.session_state.task_counter += 1
207
  new_task_id = st.session_state.task_counter
208
  st.session_state.current_task_id = new_task_id
209
+ st.session_state.info_message = None # 清除任何旧的钱包未加密消息
210
 
211
  if method_name_for_task == "bruteforce":
212
  passwords_iterable = generate_passwords_bruteforce(
 
215
  else:
216
  passwords_iterable = passwords_to_try_list
217
 
 
218
  thread = threading.Thread(
219
  target=background_cracker_task,
220
  args=(wallet_bytes, passwords_iterable, new_task_id, method_name_for_task)
221
  )
222
+ thread.daemon = True
223
  thread.start()
224
+ try:
225
+ st.experimental_rerun()
226
+ except AttributeError:
227
+ st.warning("您的 Streamlit 版本较旧。请考虑升级 Streamlit 或手动刷新页面。")
228
 
229
  else:
230
  st.info("请先上传 wallet.dat 文件。")