This commit is contained in:
wanghongenpin
2025-05-17 13:18:06 +08:00
parent fa3c63675c
commit 2d621e6e79
16 changed files with 322 additions and 19 deletions

View File

@@ -319,6 +319,9 @@
"nowTimestamp": "Now timestamp",
"hosts": "Hosts",
"toAddress": "To Address",
"encrypt": "Encrypt",
"decrypt": "Decrypt",
"cipher": "Cipher",
"appUpdateCheckVersion": "Check for Updates",
"appUpdateNotAvailableMsg": "Already Using The Latest Version",

View File

@@ -318,6 +318,9 @@
"nowTimestamp": "当前时间戳(秒)",
"hosts": "Hosts 映射",
"toAddress": "映射地址",
"encrypt": "加密",
"decrypt": "解密",
"cipher": "加解密",
"appUpdateCheckVersion": "检查更新",
"appUpdateNotAvailableMsg": "已是最新版本",

View File

@@ -28,22 +28,23 @@ import 'package:proxypin/network/components/manager/rewrite_rule.dart';
import 'package:proxypin/network/components/manager/script_manager.dart';
import 'package:proxypin/network/http/http.dart';
import 'package:proxypin/network/util/logger.dart';
import 'package:proxypin/ui/component/toolbox/cert_hash.dart';
import 'package:proxypin/ui/component/device.dart';
import 'package:proxypin/ui/component/toolbox/encoder.dart';
import 'package:proxypin/ui/component/toolbox/js_run.dart';
import 'package:proxypin/ui/component/toolbox/qr_code_page.dart';
import 'package:proxypin/ui/component/toolbox/regexp.dart';
import 'package:proxypin/ui/component/utils.dart';
import 'package:proxypin/ui/content/body.dart';
import 'package:proxypin/ui/desktop/request/request_editor.dart';
import 'package:proxypin/ui/desktop/toolbar/setting/request_rewrite.dart';
import 'package:proxypin/ui/desktop/toolbar/setting/script.dart';
import 'package:proxypin/ui/toolbox/aes_page.dart';
import 'package:proxypin/utils/platform.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:window_manager/window_manager.dart';
import 'toolbox/timestamp.dart';
import '../toolbox/cert_hash.dart';
import '../toolbox/encoder.dart';
import '../toolbox/js_run.dart';
import '../toolbox/qr_code_page.dart';
import '../toolbox/regexp.dart';
import '../toolbox/timestamp.dart';
bool isMultiWindow = false;
@@ -100,6 +101,10 @@ Widget multiWindow(int windowId, Map<dynamic, dynamic> argument) {
return TimestampPage(windowId: windowId);
}
if (argument['name'] == 'AesPage') {
return AesPage();
}
//脚本日志
if (argument['name'] == 'ScriptConsoleWidget') {
return ScriptConsoleWidget(windowId: windowId);

View File

@@ -28,7 +28,6 @@ import 'package:proxypin/network/components/manager/rewrite_rule.dart';
import 'package:proxypin/network/http/content_type.dart';
import 'package:proxypin/network/http/http.dart';
import 'package:proxypin/network/util/logger.dart';
import 'package:proxypin/ui/component/toolbox/encoder.dart';
import 'package:proxypin/ui/component/json/json_viewer.dart';
import 'package:proxypin/ui/component/json/theme.dart';
import 'package:proxypin/ui/component/multi_window.dart';
@@ -41,6 +40,7 @@ import 'package:proxypin/utils/platform.dart';
import 'package:window_manager/window_manager.dart';
import '../component/json/json_text.dart';
import '../toolbox/encoder.dart';
///请求响应的body部分
///@Author wanghongen

View File

@@ -24,7 +24,6 @@ import 'package:proxypin/network/channel/channel_context.dart';
import 'package:proxypin/network/http/http.dart';
import 'package:proxypin/network/http/websocket.dart';
import 'package:proxypin/ui/component/memory_cleanup.dart';
import 'package:proxypin/ui/component/toolbox/toolbox.dart';
import 'package:proxypin/ui/component/widgets.dart';
import 'package:proxypin/ui/configuration.dart';
import 'package:proxypin/ui/content/panel.dart';
@@ -37,6 +36,7 @@ import 'package:proxypin/utils/listenable_list.dart';
import '../app_update/app_update_repository.dart';
import '../component/split_view.dart';
import '../toolbox/toolbox.dart';
/// @author wanghongen
/// 2023/10/8

View File

@@ -23,7 +23,7 @@ import 'package:proxypin/network/components/manager/request_block_manager.dart';
import 'package:proxypin/network/components/manager/request_rewrite_manager.dart';
import 'package:proxypin/network/http/http.dart';
import 'package:proxypin/storage/histories.dart';
import 'package:proxypin/ui/component/toolbox/toolbox.dart';
import 'package:proxypin/ui/toolbox/toolbox.dart';
import 'package:proxypin/ui/component/utils.dart';
import 'package:proxypin/ui/configuration.dart';
import 'package:proxypin/ui/mobile/setting/preference.dart';

View File

@@ -33,7 +33,7 @@ import 'package:proxypin/network/http/http.dart';
import 'package:proxypin/network/http/websocket.dart';
import 'package:proxypin/network/http/http_client.dart';
import 'package:proxypin/ui/component/memory_cleanup.dart';
import 'package:proxypin/ui/component/toolbox/toolbox.dart';
import 'package:proxypin/ui/toolbox/toolbox.dart';
import 'package:proxypin/ui/configuration.dart';
import 'package:proxypin/ui/content/panel.dart';
import 'package:proxypin/ui/launch/launch.dart';

View File

@@ -0,0 +1,222 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_toastr/flutter_toastr.dart';
import 'package:proxypin/network/util/logger.dart';
import '../component/buttons.dart';
import '../component/text_field.dart';
import '../../utils/aes.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class AesPage extends StatefulWidget {
const AesPage({super.key});
@override
State<AesPage> createState() => _AesWidgetState();
}
class _AesWidgetState extends State<AesPage> {
final TextEditingController inputController = TextEditingController();
final TextEditingController outputController = TextEditingController();
final TextEditingController keyController = TextEditingController();
final TextEditingController ivController = TextEditingController();
String selectedMode = 'ECB';
String selectedPadding = 'PKCS7';
int selectedKeyLength = 128;
final List<String> modes = ['ECB', 'CBC'];
final List<String> paddingModes = ['PKCS7', 'ZeroPadding'];
final List<int> keyLengths = [128, 192, 256];
void encryptText() {
try {
final input = Uint8List.fromList(utf8.encode(inputController.text));
final encrypted = AesUtils.encrypt(input,
key: keyController.text,
mode: selectedMode,
iv: ivController.text,
keyLength: selectedKeyLength,
padding: selectedPadding);
outputController.text = base64.encode(encrypted);
} catch (e) {
logger.e("Encryption error: $e");
FlutterToastr.show("Encryption failed", context, duration: 3, backgroundColor: Colors.red);
}
}
void decryptText() {
try {
final input = base64.decode(inputController.text);
final decrypted = AesUtils.decrypt(input,
key: keyController.text,
mode: selectedMode,
iv: ivController.text,
keyLength: selectedKeyLength,
padding: selectedPadding);
outputController.text = utf8.decode(decrypted);
} catch (e) {
outputController.text = "";
logger.e("Decryption error: $e");
FlutterToastr.show("Decryption failed", context, duration: 3, backgroundColor: Colors.red);
}
}
AppLocalizations get localizations => AppLocalizations.of(context)!;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("AES", style: TextStyle(fontSize: 16)), centerTitle: true),
body: Padding(
padding: const EdgeInsets.all(15),
child: ListView(children: [
const SizedBox(height: 5),
SizedBox(
height: 150,
child: TextField(
controller: inputController,
maxLines: 8,
onTapOutside: (event) => FocusManager.instance.primaryFocus?.unfocus(),
decoration: decoration(context, label: localizations.inputContent))),
const SizedBox(height: 15),
Wrap(spacing: 18, runSpacing: 5, crossAxisAlignment: WrapCrossAlignment.center, children: [
SizedBox(
width: 120,
child: Row(children: [
Text("Mode"),
const SizedBox(width: 15),
DropdownButton<String>(
value: selectedMode,
items: modes.map((mode) {
return DropdownMenuItem(value: mode, child: Text(mode));
}).toList(),
onChanged: (value) {
setState(() {
selectedMode = value!;
});
},
),
])),
SizedBox(
width: 195,
child: Row(children: [
Text("Padding"),
const SizedBox(width: 15),
DropdownButton<String>(
value: selectedPadding,
items: paddingModes.map((mode) {
return DropdownMenuItem(value: mode, child: Text(mode));
}).toList(),
onChanged: (value) {
setState(() {
selectedPadding = value!;
});
},
),
])),
SizedBox(
width: 190,
child: Row(children: [
Text("Key Length"),
const SizedBox(width: 15),
DropdownButton<int>(
value: selectedKeyLength,
items: keyLengths.map((length) {
return DropdownMenuItem(value: length, child: Text("$length bits"));
}).toList(),
onChanged: (value) {
setState(() {
selectedKeyLength = value!;
});
},
),
]))
]),
const SizedBox(height: 15),
Wrap(
spacing: 18.0, // 主轴方向子组件的间距
runSpacing: 10.0, // 交叉轴方向子组件的间距
crossAxisAlignment: WrapCrossAlignment.center,
children: [
SizedBox(
width: 230,
child: Row(children: [
const SizedBox(width: 25, child: Text("Key")),
const SizedBox(width: 15),
SizedBox(
width: 180,
height: 45,
child: TextField(
controller: keyController,
maxLength: 32,
onTapOutside: (event) => FocusManager.instance.primaryFocus?.unfocus(),
style: TextStyle(fontSize: 14),
decoration: InputDecoration(
border: const OutlineInputBorder(),
counterText: "",
contentPadding: const EdgeInsets.symmetric(horizontal: 6)))),
])),
SizedBox(
width: 260,
child: Row(children: [
const SizedBox(width: 25, child: Text("IV")),
const SizedBox(width: 15),
SizedBox(
width: 180,
height: 45,
child: TextField(
controller: ivController,
maxLength: 32,
onTapOutside: (event) => FocusManager.instance.primaryFocus?.unfocus(),
style: TextStyle(fontSize: 14),
decoration: InputDecoration(
border: const OutlineInputBorder(),
counterText: "",
contentPadding: const EdgeInsets.symmetric(horizontal: 6)))),
])),
]),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton(
style: ButtonStyle(
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)))),
onPressed: encryptText,
child: Text(localizations.encrypt)),
const SizedBox(width: 60),
FilledButton(
style: ButtonStyle(
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)))),
onPressed: decryptText,
child: Text(localizations.decrypt)),
],
),
const SizedBox(height: 5),
Text(localizations.output),
const SizedBox(height: 5),
TextFormField(
controller: outputController,
readOnly: true,
minLines: 5,
maxLines: 10,
decoration: const InputDecoration(border: OutlineInputBorder()),
),
const SizedBox(height: 10),
FilledButton.icon(
style: Buttons.buttonStyle,
onPressed: () {
Clipboard.setData(ClipboardData(text: outputController.text));
FlutterToastr.show(localizations.copied, context);
},
icon: const Icon(Icons.copy),
label: Text(localizations.copy),
),
]),
),
);
}
}

View File

@@ -9,7 +9,8 @@ import 'package:proxypin/ui/component/buttons.dart';
import 'package:proxypin/utils/lang.dart';
import 'package:proxypin/utils/platform.dart';
import '../text_field.dart';
import '../component/text_field.dart';
/// Timestamp page
/// @author Hongen Wang

View File

@@ -5,17 +5,19 @@ import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:proxypin/network/bin/server.dart';
import 'package:proxypin/ui/component/toolbox/cert_hash.dart';
import 'package:proxypin/ui/component/toolbox/encoder.dart';
import 'package:proxypin/ui/component/toolbox/js_run.dart';
import 'package:proxypin/ui/component/multi_window.dart';
import 'package:proxypin/ui/component/toolbox/qr_code_page.dart';
import 'package:proxypin/ui/component/toolbox/regexp.dart';
import 'package:proxypin/ui/component/toolbox/timestamp.dart';
import 'package:proxypin/ui/mobile/request/request_editor.dart';
import 'package:proxypin/ui/toolbox/qr_code_page.dart';
import 'package:proxypin/ui/toolbox/regexp.dart';
import 'package:proxypin/ui/toolbox/timestamp.dart';
import 'package:proxypin/utils/platform.dart';
import 'package:window_manager/window_manager.dart';
import 'aes_page.dart';
import 'cert_hash.dart';
import 'encoder.dart';
import 'js_run.dart';
class Toolbox extends StatefulWidget {
final ProxyServer? proxyServer;
@@ -79,7 +81,7 @@ class _ToolboxState extends State<Toolbox> {
]),
const Divider(thickness: 0.3),
Text(localizations.encode, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
Row(
Wrap(
children: [
InkWell(
onTap: () => encodeWindow(EncoderType.url, context),
@@ -106,11 +108,31 @@ class _ToolboxState extends State<Toolbox> {
onTap: () => encodeWindow(EncoderType.md5, context),
child: Container(
padding: const EdgeInsets.all(10),
child: const Column(children: [Icon(Icons.enhanced_encryption), SizedBox(height: 3), Text('MD5')]),
child: const Column(children: [
Icon(IconData(0x23, fontFamily: 'MaterialIcons')), // #
SizedBox(height: 3),
Text('MD5')
]),
)),
],
),
const Divider(thickness: 0.3),
Text("加解密", style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
Wrap(children: [
InkWell(
onTap: () {
if (Platforms.isMobile()) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const AesPage()));
return;
}
MultiWindow.openWindow("AES", "AesPage", size: const Size(700, 660));
},
child: Container(
padding: const EdgeInsets.all(10),
child: const Column(children: [Icon(Icons.enhanced_encryption), SizedBox(height: 3), Text('AES')]),
)),
]),
const Divider(thickness: 0.3),
Text(localizations.other, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
Wrap(
children: [

47
lib/utils/aes.dart Normal file
View File

@@ -0,0 +1,47 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
class AesUtils {
static Uint8List encrypt(Uint8List input,
{required String key, required int keyLength, required String mode, required String padding, String? iv}) {
return process(input, true, key: key, keyLength: keyLength, mode: mode, padding: padding, iv: iv);
}
static Uint8List decrypt(Uint8List input,
{required String key, required int keyLength, required String mode, required String padding, String? iv}) {
var data = process(input, false, key: key, keyLength: keyLength, mode: mode, padding: padding, iv: iv);
// 移除填充零字节
if (padding == 'ZeroPadding') {
int lastNonZeroIndex = data.lastIndexWhere((byte) => byte != 0);
data = data.sublist(0, lastNonZeroIndex + 1);
}
return data;
}
static Uint8List process(Uint8List input, bool isEncrypt,
{required String key, required int keyLength, required String mode, required String padding, String? iv}) {
int keySize = keyLength ~/ 8;
final aesKey = Uint8List.fromList(utf8.encode(key.padRight(keySize, '0')));
final aesIv = mode == 'CBC' ? Uint8List.fromList(utf8.encode(iv!.padRight(keySize, '0'))) : null;
BlockCipher cipher = BlockCipher(mode == 'CBC' ? 'AES/CBC' : 'AES/ECB');
CipherParameters params =
aesIv == null ? KeyParameter(aesKey) : ParametersWithIV<KeyParameter>(KeyParameter(aesKey), aesIv);
if (padding == 'PKCS7') {
cipher = PaddedBlockCipherImpl(PKCS7Padding(), cipher);
params = PaddedBlockCipherParameters<CipherParameters, CipherParameters>(params, null);
}
// 检查输入长度是否为块大小的整数倍
if (input.length % cipher.blockSize != 0 && padding == 'ZeroPadding') {
input = Uint8List.fromList(input + Uint8List(cipher.blockSize - (input.length % cipher.blockSize)));
}
cipher.init(isEncrypt, params);
return cipher.process(input);
}
}