Search code examples
c#fluttercryptographypublic-key-encryptionpointycastle

how to do rsa encryption using publicKey.xml file in flutter dart , i am getting issue FormatException: Could not parse 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>

above you can see the sample format of my publicKey.xml file , i need to convert this into pem format for rsa encryption , mainly need to encrypt the username & password.

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);
}

i am using pointcastle & xml library for encryption , when i run this code i am getting the issue FormatException: Could not parse BigInt


Solution

  • The bug is that in the function decodeBigIntFromBytes() the data in BigInt.parse() is incorrectly Base64 encoded. Correct is a hex encoding, corresponding to the radix 16 used by you:

    import 'package:convert/convert.dart';
    ...
    BigInt decodeBigIntFromBytes(Uint8List bytes) {
      return BigInt.parse(hex.encode(bytes), radix: 16);
    }
    

    With this change, the code works, both the import of the public key and the encryption itself.


    Additional, but minor: Instead of the expired text, innerText should be used, e.g:

    var modulusBase64 = document.findAllElements('Modulus').single.innerText;
    

    If you want to export/import the key in PEM format, the basic_utils package provides suitable functions, e.g. CryptoUtils.encodeRSAPublicKeyToPem() can be used to export the public key PEM encoded in X.509/SPKI format:

    import 'package:basic_utils/basic_utils.dart';
    ...
    print('X.509/SPKI, PEM: ${CryptoUtils.encodeRSAPublicKeyToPem(publicKey)}');
    

    Note that the posted key, as already noted in the comments, is indeed a private RSA key. A public key only contains Modulus and Exponent field (all other fields belong to the private key and must not be disclosed). However, since this is obviously a test key, disclosure is probably not critical.


    Complete code with valid test key:

    import 'dart:convert';
    import 'dart:typed_data';
    import 'package:convert/convert.dart';
    import 'package:pointycastle/export.dart';
    import 'package:xml/xml.dart' as xml;
    import 'package:basic_utils/basic_utils.dart';
    
    ...
    
    void encryptRSa() {
    
      // key import
      var publicKeyXML = '''<RSAKeyValue><Modulus>unF5aDa6HCfLMMI/MZLT5hDk304CU+ypFMFiBjowQdUMQKYHZ+fklB7GpLxCatxYJ/hZ7rjfHH3Klq20/Y1EbYDRopyTSfkrTzPzwsX4Ur/l25CtdQldhHCTMgwf/Ev/buBNobfzdZE+Dhdv5lQwKtjI43lDKvAi5kEet2TFwfJcJrBiRJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1xH9FLojQfyia89/EykiOO7/3UWwd+MATZ9HLjSx2/Lf3g2jr81eifEmYDlri/OZp4OhZu+0Bo1LXloCTe+vmIQ2YCX7EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4iGw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>''';
      var xmlDocument = xml.XmlDocument.parse(publicKeyXML);
      var modulus = getData(xmlDocument, 'Modulus');
      var exponent = getData(xmlDocument, 'Exponent');
      var publicKey = RSAPublicKey(modulus, exponent);
    
      // encryption
      var message = 'Hello, World!';
      var encryptor = OAEPEncoding(RSAEngine())..init(true, PublicKeyParameter<RSAPublicKey>(publicKey)); // Applies SHA-1 by default for both digests, i.e. the OAEP and MGF1 digest, and an empty label
      var encrypted = encryptor.process(Uint8List.fromList(utf8.encode(message)));
      print('EncryptedOne: ${base64.encode(encrypted)}');
    
      // key export
      print('X.509/SPKI, PEM: ${CryptoUtils.encodeRSAPublicKeyToPem(publicKey)}');
    }
    
    BigInt getData(xml.XmlDocument xmlDocument, String element){
      var dataB64 = xmlDocument.findAllElements(element).single.innerText;
      var dataBytes = Uint8List.fromList(base64.decode(dataB64));
      return BigInt.parse(hex.encode(dataBytes), radix: 16);
    }
    

    Edit: Regarding the question from the comment about importing the private key. The import of the private key is analogous. The elements Modulus, D (private exponent), P (prime 1) and Q (prime 2) are required. These values are used to instantiate RSAPrivateKey, which can then be applied for decryption.

    Complete code with valid private key (associated with the public key from the encryption code):

    ...
    // key import
    var privateKeyXML = '''<RSAKeyValue><Modulus>unF5aDa6HCfLMMI/MZLT5hDk304CU+ypFMFiBjowQdUMQKYHZ+fklB7GpLxCatxYJ/hZ7rjfHH3Klq20/Y1EbYDRopyTSfkrTzPzwsX4Ur/l25CtdQldhHCTMgwf/Ev/buBNobfzdZE+Dhdv5lQwKtjI43lDKvAi5kEet2TFwfJcJrBiRJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1xH9FLojQfyia89/EykiOO7/3UWwd+MATZ9HLjSx2/Lf3g2jr81eifEmYDlri/OZp4OhZu+0Bo1LXloCTe+vmIQ2YCX7EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4iGw==</Modulus><Exponent>AQAB</Exponent><P>3e+jND6OS6ofGYUN6G4RapHzuRAV8ux1C9eXMOdZFbcBehn/ydhzR48LIPTW9HiRE00um27lXfW5/POCaEUvfOp1UxTWeHZ4xICo40PBo383ZKW1MbES1oiMbjkEqSFGRnTItnLU07bKbzLA7I0UWHWCEAnv0g7HRxk973FAsm8=</P><Q>1w8+olZ2POBYeYgw1a0DkeJWKMQi/4pAgyYwustZo0dHlRXQT0OI9XQ0j1PZWoQS28tFcmoEAg6f5MUDpdM9swS0SOCPI1Lc/f/Slus3u1O3UCezk37pneSPezskDhvV2cClJEYH8m/zwDAUlEi4KLIt/H/jgtyDd6pbxxc78RU=</Q><DP>iE6VAxJknM4oeakBiL6JTdXEReY+RMu7e4F2518/lJmoe5CaTCL3cnzFTgFyQAYIvD0MIgSzNMkl6Ni6QEY1y1fIpTVIIAZLWAzZLXPA6yTIJbWsmo9xzXdiIJQ+a433NnClkYDne/xpSnB2kxJ263mIX0drFq1i8STsqDH7lVs=</DP><DQ>VqUJsxXqpTQt8Sjxo+UE3y21UM9U2me0/iHQ2DE9eA8rw+D6ADVRZLLgyi4aD+HOR0dqP2J/IuUJfn3xrkmhPhLTH9l5Ud38s0jya2NxHMPpwx17uB0Vuktvk1KMgDKuwgBfiHG+meqI5hF4+RUjPSIsbOKJoxt8zCWSvG+b8tE=</DQ><InverseQ>s9Fu1JsTak+C84codMY+vuApuaxZVs5xADysbzTVPfxb9Q97Ve3KcwSPPNDb05pV5DC9Q334PEVcnpi/CPqKHhZ2rXT2Ls6jV8OcxzM5A30MpyHZ40Aes1I4zIsMIGb77BvIcCxLZPRU7z6DMsAG+JmbkAUJBZ+R7gtmjmY5LXQ=</InverseQ><D>SlJj0ExIomKmmBhG8q8SM1s2sWG6gdQMjs6MEeluRT/1c2v79cq2Dum5y/+UBl8x8TUKPKSLpCLs+GXkiVKgHXrFlqoN+OYQArG2EUWzuODwczdYPhhupBXwR3oX4g41k/BsYfQfZBVzBFEJdWrIDLyAUFWNlfdGIj2BTiAoySfyqmamvmW8bsvc8coiGlZ28UC85/Xqx9wOzjeGoRkCH7PcTMlc9F7SxSthwX/k1VBXmNOHa+HzGOgO/W3k1LDqJbq2wKjZTW3iVEg2VodjxgBLMm0MueSGoI6IuaZSPMyFEM3gGvC2+cDBI2SL/amhiTUa/VDlTVw/IKbSuar9uQ==</D></RSAKeyValue>''';
    var xmlDocument = xml.XmlDocument.parse(privateKeyXML);
    var modulus = getData(xmlDocument, 'Modulus');
    var privateExponent = getData(xmlDocument, 'D');
    var p = getData(xmlDocument, 'P');
    var q = getData(xmlDocument, 'Q');
    var privateKey = RSAPrivateKey(modulus, privateExponent, p, q);
    
    var decryptor = OAEPEncoding(RSAEngine())..init(false, PrivateKeyParameter<RSAPrivateKey>(privateKey)); // Applies SHA-1 by default for both digests, i.e. the OAEP and MGF1 digest, and an empty label
    var decrypted = decryptor.process(base64.decode(encryptedBase64)); // encryptedBase64: Base64 encoded ciphertext
    
    print('Decrypted data: ${utf8.decode(decrypted)}');
    
    // key export
    print('PKCS#8, PEM: ${CryptoUtils.encodeRSAPrivateKeyToPem(privateKey)}');