You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Dart使用publicKey.xml实现RSA加密遇FormatException错误

解决RSA XML公钥转PEM及加密时的BigInt解析异常

问题背景

需要将以下格式的XML公钥转换为PEM格式,用于RSA加密用户名和密码:

<RSAKeyValue><Modulus>D4ZblrI8NsZgnZOQbHJuEv7Lg0pCOuhwmGIS10rwlJ9Z/hGbes=</Modulus><Exponent>ABCD</Exponent><P>86TWhp553PrrlQ/xdOpGDbGy8/h3/5vg+P==</P><Q>D33zQmGtBnoO8OaQhCwbwqai1w==</Q><DP>i0RwPcLPh+WQKt5L75SR1PQ==</DP><DQ>3vayrPOJ1WMFxPzCpqMaUDWwWsBJ/c0PRwRhvrSw4/TR+6BlEkU/5b17/==</DQ><InverseQ>CbuGb8ZNvjCkSHqvSTJvFpFfqxxQjH09ABxBZx3K7SNvw3+6+LfKeRzoz7==</InverseQ><D>p6etDZ1ghROHdHWAauTUe6zn/RQzm7HE5aCWVOgMqcg9VkFMMZ3H+R1rec=</D></RSAKeyValue>

使用pointycastle和xml库编写代码时,抛出FormatException: Could not parse BigInt异常,原代码如下:

import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
import 'package:xml/xml.dart' as xml;

void encryptRSa() {
  String publicKeyXML = '''
   <RSAKeyValue><Modulus>D4ZblrI8NsZgnZOQbHJuEv7Lg0pCOuhwmGIS10rwlJ9Z/hGbes=</Modulus><Exponent>ABCD</Exponent><P>86TWhp553PrrlQ/xdOpGDbGy8/h3/5vg+P==</P><Q>D33zQmGtBnoO8OaQhCwbwqai1w==</Q><DP>i0RwPcLPh+WQKt5L75SR1PQ==</DP><DQ>3vayrPOJ1WMFxPzCpqMaUDWwWsBJ/c0PRwRhvrSw4/TR+6BlEkU/5b17/==</DQ><InverseQ>CbuGb8ZNvjCkSHqvSTJvFpFfqxxQjH09ABxBZx3K7SNvw3+6+LfKeRzoz7==</InverseQ><D>p6etDZ1ghROHdHWAauTUe6zn/RQzm7HE5aCWVOgMqcg9VkFMMZ3H+R1rec=</D></RSAKeyValue>
  ''';

  var document = xml.XmlDocument.parse(publicKeyXML);
  var modulusBase64 = document.findAllElements('Modulus').single.text;
  var exponentBase64 = document.findAllElements('Exponent').single.text;

  var modulusBytes = Uint8List.fromList(base64.decode(modulusBase64));
  var exponentBytes = Uint8List.fromList(base64.decode(exponentBase64));

  var modulus = decodeBigIntFromBytes(modulusBytes);
  var exponent = decodeBigIntFromBytes(exponentBytes);

  var publicKey = RSAPublicKey(modulus, exponent);

  var encryptor = OAEPEncoding(RSAEngine())
    ..init(true, PublicKeyParameter<RSAPublicKey>(publicKey));

  String message = 'Hello, World!';

  var encrypted = encryptor.process(Uint8List.fromList(utf8.encode(message)));

  print('EncryptedOne: ${base64.encode(encrypted)}');
}

BigInt decodeBigIntFromBytes(Uint8List bytes) {
  return BigInt.parse(base64.encode(bytes), radix: 16);
}

错误原因

decodeBigIntFromBytes函数的逻辑完全错误:

  • RSA密钥的Modulus、Exponent等参数是大整数的字节数组表示,需要直接将字节数组转换为BigInt。
  • 原函数先对字节数组做base64编码,再尝试以16进制解析为BigInt,这相当于把错误的字符串当作十六进制数解析,必然抛出格式异常。

修复后的代码

以下是修复异常并支持生成PEM格式公钥的完整代码:

import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
import 'package:xml/xml.dart' as xml;

void encryptRSA() {
  String publicKeyXML = '''
   <RSAKeyValue><Modulus>D4ZblrI8NsZgnZOQbHJuEv7Lg0pCOuhwmGIS10rwlJ9Z/hGbes=</Modulus><Exponent>ABCD</Exponent><P>86TWhp553PrrlQ/xdOpGDbGy8/h3/5vg+P==</P><Q>D33zQmGtBnoO8OaQhCwbwqai1w==</Q><DP>i0RwPcLPh+WQKt5L75SR1PQ==</DP><DQ>3vayrPOJ1WMFxPzCpqMaUDWwWsBJ/c0PRwRhvrSw4/TR+6BlEkU/5b17/==</DQ><InverseQ>CbuGb8ZNvjCkSHqvSTJvFpFfqxxQjH09ABxBZx3K7SNvw3+6+LfKeRzoz7==</InverseQ><D>p6etDZ1ghROHdHWAauTUe6zn/RQzm7HE5aCWVOgMqcg9VkFMMZ3H+R1rec=</D></RSAKeyValue>
  ''';

  var document = xml.XmlDocument.parse(publicKeyXML);
  var modulusBase64 = document.findAllElements('Modulus').single.text;
  var exponentBase64 = document.findAllElements('Exponent').single.text;

  var modulusBytes = Uint8List.fromList(base64.decode(modulusBase64));
  var exponentBytes = Uint8List.fromList(base64.decode(exponentBase64));

  // 修复BigInt解析逻辑
  var modulus = decodeBigIntFromBytes(modulusBytes);
  var exponent = decodeBigIntFromBytes(exponentBytes);

  var publicKey = RSAPublicKey(modulus, exponent);

  // 生成PEM格式公钥(可选)
  var pemPublicKey = generatePemFromRSAPublicKey(publicKey);
  print('PEM格式公钥:\n$pemPublicKey');

  // 初始化加密器,执行加密
  var encryptor = OAEPEncoding(RSAEngine())
    ..init(true, PublicKeyParameter<RSAPublicKey>(publicKey));

  String message = 'username=test&password=123456';
  var encrypted = encryptor.process(Uint8List.fromList(utf8.encode(message)));

  print('加密结果(Base64): ${base64.encode(encrypted)}');
}

// 正确实现字节数组转BigInt
BigInt decodeBigIntFromBytes(Uint8List bytes) {
  BigInt result = BigInt.zero;
  for (int byte in bytes) {
    result = (result << 8) | BigInt.from(byte);
  }
  return result;
}

// 生成PEM格式RSA公钥
String generatePemFromRSAPublicKey(RSAPublicKey publicKey) {
  // 按照PKCS#1格式组装公钥结构
  var algorithmIdentifier = ASN1Sequence()
    ..add(ASN1ObjectIdentifier.fromIdentifier('1.2.840.113549.1.1.1')) // RSA加密算法OID
    ..add(ASN1Null());

  var modulusBytes = encodeBigIntToBytes(publicKey.modulus!);
  var exponentBytes = encodeBigIntToBytes(publicKey.exponent!);

  var rsaPublicKey = ASN1Sequence()
    ..add(ASN1Integer(publicKey.modulus))
    ..add(ASN1Integer(publicKey.exponent));

  var publicKeyInfo = ASN1Sequence()
    ..add(algorithmIdentifier)
    ..add(ASN1OctetString(octets: rsaPublicKey.encode()));

  // 编码为Base64并添加PEM头部尾部
  var base64Encoded = base64.encode(publicKeyInfo.encode());
  var pemLines = <String>[];
  pemLines.add('-----BEGIN PUBLIC KEY-----');
  // 分割为每行64个字符
  for (int i = 0; i < base64Encoded.length; i += 64) {
    var end = i + 64;
    if (end > base64Encoded.length) end = base64Encoded.length;
    pemLines.add(base64Encoded.substring(i, end));
  }
  pemLines.add('-----END PUBLIC KEY-----');
  return pemLines.join('\n');
}

// BigInt转字节数组(用于PEM生成)
Uint8List encodeBigIntToBytes(BigInt number) {
  int bytes = (number.bitLength + 7) ~/ 8;
  var result = Uint8List(bytes);
  for (int i = 0; i < bytes; i++) {
    result[bytes - 1 - i] = (number >> (8 * i)).toInt() & 0xff;
  }
  return result;
}

关键说明

  1. 修复BigInt解析:decodeBigIntFromBytes函数现在通过逐字节移位运算,将字节数组正确转换为BigInt,符合RSA密钥参数的解析规则。
  2. PEM格式生成:新增generatePemFromRSAPublicKey函数,按照PKCS#1标准组装公钥结构,编码为符合规范的PEM格式公钥。
  3. 加密逻辑验证:保留原有的OAEP填充RSA加密逻辑,可直接用于用户名密码的加密。

内容的提问来源于stack exchange,提问作者Nns_ninteyFIve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 22:42:05