5

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.

ibnhamza
  • 861
  • 1
  • 15
  • 29

1 Answers1

2

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),
  );
}
Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • Thanks Richard. Heard of PointyCastle but haven't really tried using it. Was thinking dart was gonna have some built in encryption suit. Would try out your solution. – ibnhamza Jun 08 '19 at 13:50
  • I didn't have any issue encryption but the server was unable to decrypt my encrypted string. I debugged and it was throwing an exception at the point of decrypting. I've updated my question with the exception. – ibnhamza Jun 08 '19 at 16:47
  • Dart, by design, has very little built in. This kind of add-on library is added in packages, like pointy castle. Updated the question with PKCSv1.5 padding implementation. – Richard Heap Jun 10 '19 at 01:05
  • What is hex value? – Pravin Divraniya Aug 13 '21 at 11:58
  • 1
    @PravinDivraniya `hex` is a const value from the `convert` package (note the extra package import) for a hexadecimal codec. – Richard Heap Aug 14 '21 at 22:07
  • do we have EncodedKeySpec in flutter – Hemavathi Jan 04 '22 at 12:05
  • @RichardHeap i m trying to convert java code to flutter please refer https://stackoverflow.com/questions/70576691/crypto-x509encodedkeyspec-equivalent-code-in-flutter any help is appreciated – Hemavathi Jan 04 '22 at 12:06