fix:调整新的中继协议中的上报签名逻辑
This commit is contained in:
parent
a92ae906ef
commit
7a4fbe19d8
@ -1,19 +1,24 @@
|
||||
import 'package:crc32_checksum/crc32_checksum.dart';
|
||||
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
|
||||
import 'package:star_lock/talk/startChart/constant/payload_type_constant.dart';
|
||||
import 'package:star_lock/talk/startChart/constant/protocol_flag_constant.dart';
|
||||
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
||||
|
||||
class MessageCommand {
|
||||
|
||||
/// 客户端去中继上线命令
|
||||
static List<int> goOnlineRelay() {
|
||||
static List<int> goOnlineRelay({
|
||||
required String FromPeerId,
|
||||
required String ToPeerId,
|
||||
}) {
|
||||
String serializedBytesString = ScpMessage(
|
||||
ProtocolFlag: ProtocolFlagConstant.scp01,
|
||||
MessageType: MessageTypeConstant.Req,
|
||||
MessageId: 1,
|
||||
SpTotal: 0,
|
||||
SpIndex: 0,
|
||||
FromPeerId: 'ToPeerId',
|
||||
ToPeerId: 'ToPeerId',
|
||||
FromPeerId: FromPeerId,
|
||||
ToPeerId: ToPeerId,
|
||||
Payload: 'hello',
|
||||
PayloadCRC: 55230,
|
||||
PayloadLength: 5,
|
||||
@ -46,15 +51,18 @@ class MessageCommand {
|
||||
}
|
||||
|
||||
// 心跳消息
|
||||
static List<int> heartbeatMessage() {
|
||||
static List<int> heartbeatMessage({
|
||||
required String FromPeerId,
|
||||
required String ToPeerId,
|
||||
}) {
|
||||
ScpMessage message = ScpMessage(
|
||||
ProtocolFlag: ProtocolFlagConstant.scp01,
|
||||
MessageType: MessageTypeConstant.Req,
|
||||
MessageId: 1,
|
||||
SpTotal: 0,
|
||||
SpIndex: 0,
|
||||
FromPeerId: 'FromPeerId',
|
||||
ToPeerId: 'ToPeerId',
|
||||
FromPeerId: FromPeerId,
|
||||
ToPeerId: ToPeerId,
|
||||
Payload: 'hello',
|
||||
PayloadCRC: 55230,
|
||||
PayloadLength: 5,
|
||||
@ -73,4 +81,9 @@ class MessageCommand {
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int calculationCrc(payload){
|
||||
var checkSumResult = Crc32.calculate(payload);
|
||||
return checkSumResult;
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,85 +144,135 @@ class ScpMessage {
|
||||
return bytesToHexString;
|
||||
}
|
||||
|
||||
// 反序列化方法
|
||||
static ScpMessage deserialize(List<int> bytes) {
|
||||
final message = ScpMessage();
|
||||
|
||||
int offset = 0;
|
||||
|
||||
// ProtocolFlag (4 bytes)
|
||||
if (bytes.length - offset >= 4) {
|
||||
message.ProtocolFlag = utf8.decode(bytes.sublist(offset, offset + 4));
|
||||
offset += 4;
|
||||
} else {
|
||||
throw FormatException("Invalid ProtocolFlag length");
|
||||
}
|
||||
|
||||
// MessageType (1 byte)
|
||||
if (bytes.length - offset >= 1) {
|
||||
message.MessageType = bytes[offset];
|
||||
offset += 1;
|
||||
} else {
|
||||
throw FormatException("Invalid MessageType length");
|
||||
}
|
||||
|
||||
// MessageId (2 bytes, little-endian)
|
||||
if (bytes.length - offset >= 2) {
|
||||
message.MessageId = (bytes[offset + 1] << 8) | bytes[offset];
|
||||
offset += 2;
|
||||
} else {
|
||||
throw FormatException("Invalid MessageId length");
|
||||
}
|
||||
|
||||
// SpTotal (1 byte)
|
||||
if (bytes.length - offset >= 1) {
|
||||
message.SpTotal = bytes[offset];
|
||||
offset += 1;
|
||||
} else {
|
||||
throw FormatException("Invalid SpTotal length");
|
||||
}
|
||||
|
||||
// SpIndex (1 byte)
|
||||
if (bytes.length - offset >= 1) {
|
||||
message.SpIndex = bytes[offset];
|
||||
offset += 1;
|
||||
} else {
|
||||
throw FormatException("Invalid SpIndex length");
|
||||
}
|
||||
|
||||
// FromPeerId (字符串,长度固定为44字节)
|
||||
if (bytes.length - offset >= 44) {
|
||||
message.FromPeerId = utf8.decode(bytes.sublist(offset, offset + 44));
|
||||
message.FromPeerId = utf8.decode(bytes.sublist(offset, offset + 44)).trimRight();
|
||||
offset += 44;
|
||||
} else {
|
||||
throw FormatException("Invalid FromPeerId length");
|
||||
}
|
||||
|
||||
// ToPeerId (字符串,长度固定为44字节)
|
||||
if (bytes.length - offset >= 44) {
|
||||
message.ToPeerId = utf8.decode(bytes.sublist(offset, offset + 44));
|
||||
message.ToPeerId = utf8.decode(bytes.sublist(offset, offset + 44)).trimRight();
|
||||
offset += 44;
|
||||
} else {
|
||||
throw FormatException("Invalid ToPeerId length");
|
||||
}
|
||||
|
||||
// PayloadType (2 bytes, little-endian)
|
||||
if (bytes.length - offset >= 2) {
|
||||
message.PayloadType = (bytes[offset + 1] << 8) | bytes[offset];
|
||||
offset += 2;
|
||||
} else {
|
||||
throw FormatException("Invalid PayloadType length");
|
||||
}
|
||||
|
||||
// PayloadCRC (2 bytes, little-endian)
|
||||
if (bytes.length - offset >= 2) {
|
||||
message.PayloadCRC = (bytes[offset + 1] << 8) | bytes[offset];
|
||||
offset += 2;
|
||||
} else {
|
||||
throw FormatException("Invalid PayloadCRC length");
|
||||
}
|
||||
|
||||
// PayloadLength (4 bytes, big-endian)
|
||||
// PayloadLength (4 bytes, little-endian)
|
||||
if (bytes.length - offset >= 4) {
|
||||
// 打印PayloadLength对应的4个字节
|
||||
print('PayloadLength bytes: ${bytes.sublist(offset, offset + 4)}');
|
||||
|
||||
message.PayloadLength = (bytes[offset] |
|
||||
(bytes[offset + 1] << 8) |
|
||||
(bytes[offset + 2] << 16) |
|
||||
(bytes[offset + 3] << 24));
|
||||
(bytes[offset + 1] << 8) |
|
||||
(bytes[offset + 2] << 16) |
|
||||
(bytes[offset + 3] << 24)); // 修正为 little-endian
|
||||
offset += 4;
|
||||
} else {
|
||||
throw FormatException("Invalid PayloadLength length");
|
||||
}
|
||||
|
||||
// Payload (字符串,转换为字节)
|
||||
if (message.PayloadLength != null &&
|
||||
bytes.length - offset >= message.PayloadLength!) {
|
||||
message.Payload =
|
||||
utf8.decode(bytes.sublist(offset, offset + message.PayloadLength!));
|
||||
if (message.PayloadLength != null && bytes.length - offset >= message.PayloadLength!) {
|
||||
message.Payload = utf8.decode(bytes.sublist(offset, offset + message.PayloadLength!));
|
||||
offset += message.PayloadLength!;
|
||||
} else {
|
||||
throw FormatException("Invalid Payload or PayloadLength");
|
||||
}
|
||||
|
||||
// 验证PayloadCRC
|
||||
// if (message.Payload != null) {
|
||||
// var crcBytes = List<int>.from(utf8.encode(message.Payload!));
|
||||
// var calculatedCrc = _calculateCrc16(crcBytes);
|
||||
// if (calculatedCrc != message.PayloadCRC) {
|
||||
// throw FormatException("PayloadCRC verification failed. Expected: ${message.PayloadCRC}, Actual: $calculatedCrc");
|
||||
// }
|
||||
// }
|
||||
|
||||
return message;
|
||||
}
|
||||
// CRC-16 计算函数(示例实现,可能需要根据具体协议调整)
|
||||
static int _calculateCrc16(List<int> data) {
|
||||
const poly = 0x8005;
|
||||
int crc = 0xFFFF;
|
||||
|
||||
for (final b in data) {
|
||||
crc ^= b << 8;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((crc & 0x8000) != 0) {
|
||||
crc = (crc << 1) ^ poly;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
crc &= 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
static String bytesToHex(List<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
|
||||
|
||||
@ -2,11 +2,17 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:fast_rsa/fast_rsa.dart' as fastRsa;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pointycastle/asn1/asn1_parser.dart';
|
||||
import 'package:pointycastle/asn1/primitives/asn1_integer.dart';
|
||||
import 'package:pointycastle/asn1/primitives/asn1_sequence.dart';
|
||||
import 'package:pointycastle/asymmetric/api.dart';
|
||||
import 'package:pointycastle/digests/sha256.dart';
|
||||
import 'package:pointycastle/export.dart' as pc;
|
||||
import 'package:star_lock/app_settings/app_settings.dart';
|
||||
import 'package:star_lock/flavors.dart';
|
||||
import 'package:star_lock/network/start_chart_api.dart';
|
||||
@ -55,11 +61,11 @@ class StartChartManage {
|
||||
String ToPeerId = ''; // 对端ID
|
||||
String FromPeerId = ''; // 我的ID
|
||||
|
||||
bool _isLoginSuccessfulToStartChart = false; // 是否在星图登录成功
|
||||
final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH';
|
||||
|
||||
// 星图服务初始化
|
||||
Future<void> init() async {
|
||||
// 客户端注册
|
||||
// 节点注册
|
||||
await _clientRegister();
|
||||
|
||||
// 中继查询
|
||||
@ -70,12 +76,6 @@ class StartChartManage {
|
||||
|
||||
// 初始化udp服务
|
||||
await _onlineRelayService();
|
||||
|
||||
// 发送心跳消息
|
||||
_sendHeartbeatMessage();
|
||||
|
||||
// 发送送上线消息
|
||||
await _sendOnlineMessage();
|
||||
}
|
||||
|
||||
/// 客户端注册
|
||||
@ -85,6 +85,8 @@ class StartChartManage {
|
||||
await _requestStarChartRegisterNode();
|
||||
await _saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode);
|
||||
_log(text: '获取到星图注册节点信息:$requestStarChartRegisterNode');
|
||||
FromPeerId = requestStarChartRegisterNode.peer!.id ?? '';
|
||||
ToPeerId = echoPeerId;
|
||||
}
|
||||
|
||||
// 中继查询
|
||||
@ -98,12 +100,11 @@ class StartChartManage {
|
||||
|
||||
if (relayInfoEntity.relay_list?.length != 0) {
|
||||
final data = relayInfoEntity.relay_list?[0];
|
||||
FromPeerId = data?.peerID ?? '';
|
||||
final parseUdpUrl = _parseUdpUrl(data?.listenAddr ?? '');
|
||||
remoteHost = parseUdpUrl['host'] ?? '';
|
||||
remotePort = parseUdpUrl['port'] ?? '';
|
||||
_log(text: '中继信息----》${data}');
|
||||
}
|
||||
_log(text: '中继信息----》${relayInfoEntity}');
|
||||
}
|
||||
|
||||
void closeUdpSocket() {
|
||||
@ -138,6 +139,7 @@ class StartChartManage {
|
||||
_log(text: '收到消息---> 长度:${dg?.data?.length}, 数据:${dg?.data}');
|
||||
if (dg?.data != null) {
|
||||
final deserialize = ScpMessage.deserialize(dg!.data);
|
||||
_log(text: '=============${bytesToHex(dg!.data)}');
|
||||
_log(text: 'Udp收到结构体数据---》$deserialize');
|
||||
}
|
||||
} catch (e) {
|
||||
@ -156,15 +158,19 @@ class StartChartManage {
|
||||
reportInformationData: data,
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
// TODO 登录成功之后的逻辑
|
||||
_log(text: '星图登录成功');
|
||||
// 发送送上线消息
|
||||
await _sendOnlineMessage();
|
||||
// 发送心跳消息
|
||||
_sendHeartbeatMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// 发送上线消息
|
||||
Future<void> _sendOnlineMessage() async {
|
||||
// 组装上线消息
|
||||
final message = MessageCommand.goOnlineRelay();
|
||||
final message = MessageCommand.goOnlineRelay(
|
||||
FromPeerId: FromPeerId, ToPeerId: ToPeerId);
|
||||
await _sendMessage(message: message);
|
||||
}
|
||||
|
||||
@ -178,7 +184,10 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
// 发送心跳包消息
|
||||
void _sendHeartbeatMessage() {
|
||||
void _sendHeartbeatMessage() async {
|
||||
// 从缓存中获取中继信息
|
||||
final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo();
|
||||
final String relayPeerId = relayInfoEntity?.relay_list?[0].peerID ?? '';
|
||||
if (_heartBeatTimerRunning) {
|
||||
_log(text: '心跳已经开始了. 请勿重复发送心跳包消息');
|
||||
return;
|
||||
@ -188,7 +197,10 @@ class StartChartManage {
|
||||
seconds: heartbeatIntervalTime,
|
||||
),
|
||||
(Timer timer) async {
|
||||
final List<int> message = MessageCommand.heartbeatMessage();
|
||||
final List<int> message = MessageCommand.heartbeatMessage(
|
||||
FromPeerId: FromPeerId,
|
||||
ToPeerId: relayPeerId,
|
||||
);
|
||||
await _sendMessage(message: message);
|
||||
},
|
||||
);
|
||||
@ -248,15 +260,16 @@ class StartChartManage {
|
||||
|
||||
// 构造上报信息数据参数
|
||||
Future<ReportInformationData> _makeReportInformationData() async {
|
||||
// 获取当前时间戳
|
||||
int currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
// 从缓存中获取中继信息
|
||||
final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo();
|
||||
|
||||
// 获取公钥
|
||||
final publicKey = await getPublicKey();
|
||||
// 获取私钥
|
||||
final privateKey = await getPrivateKey();
|
||||
// 生成签名
|
||||
final sign = await _generateSign(
|
||||
currentTimestamp: currentTimestamp,
|
||||
currentTimestamp: relayInfoEntity!.time ?? 0,
|
||||
privateKeyHex: privateKey,
|
||||
);
|
||||
|
||||
@ -264,8 +277,7 @@ class StartChartManage {
|
||||
final List<ListenAddrData> listenAddrDataList =
|
||||
await _makeListenAddrDataList();
|
||||
|
||||
// 从缓存中获取中继信息
|
||||
final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo();
|
||||
//
|
||||
final RelayServiceData relayServiceData = RelayServiceData(
|
||||
name: relayInfoEntity?.relay_list?[0].name ?? '',
|
||||
listen_addr: relayInfoEntity?.relay_list?[0].listenAddr ?? '',
|
||||
@ -278,7 +290,7 @@ class StartChartManage {
|
||||
public_key: publicKey,
|
||||
listen_addr: listenAddrDataList,
|
||||
relay_service: relayServiceData,
|
||||
time: currentTimestamp,
|
||||
time: relayInfoEntity.time ?? 0,
|
||||
sign: sign,
|
||||
);
|
||||
|
||||
@ -289,7 +301,7 @@ class StartChartManage {
|
||||
Future<List<ListenAddrData>> _makeListenAddrDataList() async {
|
||||
final List<ListenAddrData> listenAddrDataList = [];
|
||||
final List<String> localIp = await _getAllIpAddresses();
|
||||
// 从缓存中获取中继信息
|
||||
// 从缓存中获取中继信息,取出返回的客户端ip地址
|
||||
final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo();
|
||||
if (relayInfoEntity != null && relayInfoEntity.client_addr != null) {
|
||||
listenAddrDataList.add(
|
||||
@ -313,7 +325,7 @@ class StartChartManage {
|
||||
return listenAddrDataList ?? [];
|
||||
}
|
||||
|
||||
/// 获取本机所有ip
|
||||
/// 获取本机所有 IP 地址
|
||||
Future<List<String>> _getAllIpAddresses() async {
|
||||
final List<String> ipAddresses = [];
|
||||
try {
|
||||
@ -324,9 +336,21 @@ class StartChartManage {
|
||||
|
||||
for (final interface in interfaces) {
|
||||
for (final address in interface.addresses) {
|
||||
if (address.address.isNotEmpty &&
|
||||
!IpConstant.reportExcludeIp.contains(address.address)) {
|
||||
ipAddresses.add(address.address);
|
||||
// 获取原始 IP 地址
|
||||
String ipAddress = address.address;
|
||||
|
||||
// 解码 URL 编码的字符串
|
||||
ipAddress = Uri.decodeFull(ipAddress);
|
||||
|
||||
// 移除 IPv6 地址中的接口标识符(如果有)
|
||||
if (ipAddress.contains('%')) {
|
||||
ipAddress = ipAddress.split('%').first;
|
||||
}
|
||||
|
||||
// 确保 IP 地址不为空且不在排除列表中
|
||||
if (ipAddress.isNotEmpty &&
|
||||
!IpConstant.reportExcludeIp.contains(ipAddress)) {
|
||||
ipAddresses.add(ipAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -376,30 +400,104 @@ class StartChartManage {
|
||||
}) async {
|
||||
String resultSign = '';
|
||||
try {
|
||||
// 1. 获取当前时间戳并编码为小端字节序
|
||||
Uint8List signData = Uint8List(4);
|
||||
ByteData.view(signData.buffer)
|
||||
.setUint32(0, currentTimestamp, Endian.little);
|
||||
// 1. 将 32 位时间戳以小端字节序编码为二进制数据
|
||||
Uint8List signData = encodeTimestampToLittleEndianBytes(currentTimestamp);
|
||||
|
||||
// 2. 对时间戳数据计算 SHA-256 哈希值
|
||||
Digest hash = sha256.convert(signData);
|
||||
// 2.将十六进制字符串转换为字节数组
|
||||
List<int> privateKeyBytes = hexToBytes(privateKeyHex);
|
||||
|
||||
// 3. 使用 RSA 私钥进行签名 (需要提供 RsaPrivateKey)
|
||||
pc.RSAPrivateKey privateKey =
|
||||
loadPrivateKey(privateKeyHex); // 需要加载你的 RSA 私钥
|
||||
Uint8List signature = rsaSign(privateKey, hash.bytes);
|
||||
|
||||
// 4. 将签名结果转换为十六进制字符串
|
||||
String hexSignature = signature
|
||||
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
resultSign = hexSignature;
|
||||
// 3.将私钥转换为 PEM 格式
|
||||
final pemPrivateKey =
|
||||
convertToPemPrivateKey(privateKeyBytes, isPKCS8: true);
|
||||
// 4.签名
|
||||
var result = await fastRsa.RSA
|
||||
.signPKCS1v15Bytes(signData, fastRsa.Hash.SHA256, pemPrivateKey);
|
||||
resultSign = hex.encode(result);
|
||||
} catch (e) {
|
||||
_log(text: '❌--->上报信息生成签名时出现错误: $e');
|
||||
e.printError();
|
||||
}
|
||||
return resultSign ?? '';
|
||||
}
|
||||
|
||||
// 将 32 位时间戳以小端字节序编码为二进制数据
|
||||
Uint8List encodeTimestampToLittleEndianBytes(int timestamp) {
|
||||
// 创建一个 4 字节的 ByteData 对象
|
||||
ByteData byteData = ByteData(4);
|
||||
|
||||
// 将 32 位时间戳写入 ByteData,使用小端字节序
|
||||
byteData.setUint32(0, timestamp, Endian.little);
|
||||
|
||||
// 将 ByteData 转换为 Uint8List
|
||||
Uint8List bytes = byteData.buffer.asUint8List();
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// 将字节数组转换为 PEM 格式的公钥
|
||||
String convertToPemPublicKey(List<int> publicKeyBytes,
|
||||
{bool isPKCS8 = true}) {
|
||||
// 将字节数组转换为Base64编码的字符串
|
||||
String base64PublicKey = base64Encode(publicKeyBytes);
|
||||
// 添加PEM格式的头尾标签
|
||||
String pemHeader;
|
||||
String pemFooter;
|
||||
if (isPKCS8) {
|
||||
// 添加PEM格式的头尾标签
|
||||
pemHeader = "-----BEGIN PUBLIC KEY-----";
|
||||
pemFooter = "-----END PUBLIC KEY-----";
|
||||
} else {
|
||||
// 添加PEM格式的头尾标签
|
||||
pemHeader = "-----BEGIN RSA PUBLIC KEY-----";
|
||||
pemFooter = "-----END RSA PUBLIC KEY-----";
|
||||
}
|
||||
|
||||
// 将Base64字符串分行为每行64个字符
|
||||
const lineLength = 64;
|
||||
List<String> lines = [];
|
||||
for (int i = 0; i < base64PublicKey.length; i += lineLength) {
|
||||
int end = (i + lineLength < base64PublicKey.length)
|
||||
? i + lineLength
|
||||
: base64PublicKey.length;
|
||||
lines.add(base64PublicKey.substring(i, end));
|
||||
}
|
||||
|
||||
// 组合成完整的PEM格式字符串
|
||||
return "$pemHeader\n${lines.join('\n')}\n$pemFooter";
|
||||
}
|
||||
|
||||
String convertToPemPrivateKey(List<int> privateKeyBytes,
|
||||
{bool isPKCS8 = true}) {
|
||||
// 将字节数组转换为Base64编码的字符串
|
||||
String base64PrivateKey = base64Encode(privateKeyBytes);
|
||||
|
||||
// 添加PEM格式的头尾标签
|
||||
String pemHeader;
|
||||
String pemFooter;
|
||||
|
||||
if (isPKCS8) {
|
||||
pemHeader = "-----BEGIN PRIVATE KEY-----";
|
||||
pemFooter = "-----END PRIVATE KEY-----";
|
||||
} else {
|
||||
pemHeader = "-----BEGIN RSA PRIVATE KEY-----";
|
||||
pemFooter = "-----END RSA PRIVATE KEY-----";
|
||||
}
|
||||
|
||||
// 将Base64字符串分行为每行64个字符
|
||||
const lineLength = 64;
|
||||
List<String> lines = []; // 用于存储每一行
|
||||
|
||||
for (int i = 0; i < base64PrivateKey.length; i += lineLength) {
|
||||
int end = (i + lineLength < base64PrivateKey.length)
|
||||
? i + lineLength
|
||||
: base64PrivateKey.length;
|
||||
lines.add(base64PrivateKey.substring(i, end));
|
||||
}
|
||||
|
||||
// 组合成完整的PEM格式字符串
|
||||
return "$pemHeader\n${lines.join('\n')}\n$pemFooter";
|
||||
}
|
||||
|
||||
/// 自定义 PEM 格式的 RSA 私钥解析器
|
||||
pc.RSAPrivateKey loadPrivateKey(String privateKeyHex) {
|
||||
// 将十六进制字符串转换为字节数组
|
||||
|
||||
@ -250,7 +250,7 @@ dependencies:
|
||||
crc32_checksum: ^0.0.2
|
||||
cryptography: ^2.7.0
|
||||
asn1lib: ^1.0.0
|
||||
|
||||
fast_rsa: ^3.6.6
|
||||
|
||||
dependency_overrides:
|
||||
#强制设置google_maps_flutter_ios 为 2.5.2
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user