Flutter Dart使用publicKey.xml实现RSA加密遇FormatException错误
解决RSA XML公钥转PEM及加密时的BigInt解析异常
问题背景
需要将以下
<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; }
关键说明
- 修复BigInt解析:
decodeBigIntFromBytes函数现在通过逐字节移位运算,将字节数组正确转换为BigInt,符合RSA密钥参数的解析规则。 - PEM格式生成:新增
generatePemFromRSAPublicKey函数,按照PKCS#1标准组装公钥结构,编码为符合规范的PEM格式公钥。 - 加密逻辑验证:保留原有的OAEP填充RSA加密逻辑,可直接用于用户名密码的加密。
内容的提问来源于stack exchange,提问作者Nns_ninteyFIve
相关产品推荐
相关产品推荐

