Convert Java RSA Asymmetric encryption to Flutter Dart

Viewed 3245

I'm trying to port our android mobile app to Flutter. It was written in Java. However, there is this part where I need to encrypt the login credential and card details with RSA encryption before posting to the server which I've not been able to get right.

I've tried several flutter packages which doesn't work. According to the Java developer, there is a public key which is base64 encoded that needs to be used in encrypting the password.

Here is the Java code

public  static  String Encrypt(String plaintext, String publicKey ) throws Exception {
  try
  {

      if(StringUtils.isEmpty(plaintext)) return "";
      byte[] modulusBytes = Base64.decode(publicKey.getBytes("UTF-8"),Base64.DEFAULT);
      byte[] exponentBytes = Base64.decode("AQAB".getBytes("UTF-8"),Base64.DEFAULT);
      BigInteger modulus = new BigInteger(1, modulusBytes );
      BigInteger exponent = new BigInteger(1, exponentBytes);
      RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
      KeyFactory fact = KeyFactory.getInstance("RSA");
      PublicKey pubKey = fact.generatePublic(rsaPubKey);

      Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
      cipher.init(Cipher.ENCRYPT_MODE, pubKey);

      byte[] plainBytes = new String(plaintext).getBytes("UTF-8");
      byte[] cipherData = cipher.doFinal( plainBytes );


      String outputEncrypted = Base64.encodeToString(cipherData,Base64.NO_WRAP);

      return  outputEncrypted;

  }catch (Exception ex)
  {
      Log.i("Exception", ex.getMessage());
      throw  ex;

  }

}

I'll appreciate if I can get any help to convert this to dart so I can use it in the flutter code.

UPDATE

I tried @Richard Heap pointycastle encryption which seems to work fine but the server was unable to decrypt the string. Exception thrown

Interop+AppleCrypto+AppleCFErrorCryptographicException: The operation couldn’t be completed. (OSStatus error -2147415994 - CSSMERR_CSP_INVALID_DATA)
   at Interop.AppleCrypto.ExecuteTransform(SecKeyTransform transform)
   at Interop.AppleCrypto.RsaDecrypt(SafeSecKeyRefHandle privateKey, Byte[] data, RSAEncryptionPadding padding)
   at System.Security.Cryptography.RSAImplementation.RSASecurityTransforms.Decrypt(Byte[] data, RSAEncryptionPadding padding)
   at System.Security.Cryptography.RSACryptoServiceProvider.Decrypt(Byte[] rgb, Boolean fOAEP)


Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The parameter is incorrect
   at Internal.NativeCrypto.CapiHelper.DecryptKey(SafeKeyHandle safeKeyHandle, Byte[] encryptedData, Int32 encryptedDataLength, Boolean fOAEP, Byte[]& decryptedData)
   at System.Security.Cryptography.RSACryptoServiceProvider.Decrypt(Byte[] rgb, Boolean fOAEP

The decryption method on the server is written with C#

UPDATE 2

Finally got this to work after hours of googling I landed on this github issue on PointyCastle and the solution by duncanhoggan. Turned out I just needed to use PKCS1Encoding.

  var pubKey = RSAPublicKey(modulus, exponent);
  var cipher = PKCS1Encoding(RSAEngine());
  cipher.init(true, PublicKeyParameter<RSAPublicKey>(pubKey));
  Uint8List output = cipher.process(utf8.encode(text));
  var base64EncodedText = base64Encode(output);
  return base64EncodedText;

@Richard Heap, thanks for helping.

1 Answers

Try this:

import 'dart:math';
import 'dart:convert';
import 'dart:typed_data';

import 'package:convert/convert.dart';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/asymmetric/rsa.dart';

String encrypt(String plaintext, String publicKey) {
  var modulusBytes = base64.decode(publicKey);
  var modulus = BigInt.parse(hex.encode(modulusBytes), radix: 16);
  var exponent = BigInt.parse(hex.encode(base64.decode('AQAB')), radix: 16);
  var engine = RSAEngine()
    ..init(
      true,
      PublicKeyParameter<RSAPublicKey>(RSAPublicKey(modulus, exponent)),
    );

  //PKCS1.5 padding
  var k = modulusBytes.length;
  var plainBytes = utf8.encode(plaintext);
  var paddingLength = k - 3 - plainBytes.length;
  var eb = Uint8List(paddingLength + 3 + plainBytes.length);
  var r = Random.secure();
  eb.setRange(paddingLength + 3, eb.length, plainBytes);
  eb[0] = 0;
  eb[1] = 2;
  eb[paddingLength + 2] = 0;
  for (int i = 2; i < paddingLength + 2; i++) {
    eb[i] = r.nextInt(254) + 1;
  }

  print(plainBytes.length);
  print(eb);

  return base64.encode(
    engine.process(eb),
  );
}
Related