Asymmetric Key Generation in Flutter

Gonçalo Palma
Mar 10 · 7 min read
Photo by Giulio Magnifico on Unsplash

When developing applications focused on security and encryption, we may need to use Asymmetric Keys in order to sign and verify data before sending it to our servers. Though the encryption algorithms may be complex, we can use the help of libraries such as Pointy Castle to integrate it in our Flutter applications.

PointyCastle is a port of Java’s Bouncy Castle and it provides us the implementation for the most commonly used cryptography algorithms. With this library we will create the main methods to build an app example, that is able to generate a new asymmetric key pair and sign a message with a private key.

RSA Key Generation Example App

In order to achieve this result, we will have to go through the following steps:

  1. Generating a RSA KeyPair using PointyCastle’s RSAKeyGenerator
  2. Using Isolate for optimizing the generation of a KeyPair
  3. Encoding Asymmetric Keys to Plain Text
  4. Signing a message with a Private Key

We will also look into the curious case of generating keys in a physical iOS device in Debug Mode.

Generating a RSA KeyPair using PointyCastle’s RSAKeyGenerator

RSAKeyGenerator has a convenient method called generateKeyPair() that returns a AsymmetricKeyPair<PublicKey, PrivateKey> variable which we can use to sign and verify our messages.

However, if we try to use the following code:

We get the error

Looking closely into the RSAKeyGenerator we see that the getter bitStrength is called on the variable RSAKeyGeneratorParameters _params. We can also verify that RSAKeyGenerator takes no arguments in the constructor, so the only way to set the value of _params if by using the method void init(CipherParameters params.

And this is where we jump into the rabbit hole of initializations.

We will use the class ParametersWithRandom which extends from CipherParameters to initialize the RSAKeyGenerator.

And here we have two more objects that we need to instantiate: UnderlyingParameters and SecureRandom. For RSA Key generation, we can use RSAKeyGeneratorParameters in which we provide the bit strength, public exponent and certainty to use in the keys. On the other hand, we will need an algorithm that implements SecureRandom to satisfy the constructor. For our purposes, we will use the FortunaRandom which we will initialize shortly.

Before getting our hands into the code, we will recap what we need to do:

  1. Initialize FortunaRandom and RSAKeyGeneratorParameters instances to:
  2. Create a ParametersWithRandom object that is used in:
  3. The initmethod of RSAKeyGenerator that we need to:
  4. Create the Asymmetric KeyPair using the generateKeyPair() method.

In order to create a FortunaRandom instance, we need to give it a randomized seed of parameters which will be generated using a Random.secure(). To make our code testable, we will wrap our code in a method:

The RSAKeyGeneratorParameters will need 3 parameters for its initialization, as we have discussed before:

  • Public Exponent
  • Certainty
  • Bit Strength

For the first two parameters, we can take a look at the following Stack Overflow answer that states that the most widely used values for Public Exponent and Certainty are 65537 and 5, respectively. The Bit Strength will depend on our actual needs for the project, since a higher value will lower the performance of the code but will increase the security of our keys. However, the digicert website has the following recommendation:

And with this, we can finally create our AsymmetricKeyPair<PublicKey, PrivateKey>:

Optimizing Code Execution with Isolates

The generation of Asymmetric Keys poses a problem. Creating a key pair is an expensive operation, and in Dart we only have one “thread” of execution:

Asynchronous Programming: Futures | Dart

One way we can optimize our code is by using Isolates (which you can read more in this article by Didier Bolens), which can be compared to a thread with the major difference of not being able to share memory. For convenience, Flutter has its own method to create Isolates called compute. In order to use it, we must first declare a top level function to be used as a callback and the necessary set of arguments for it. Thankfully, we can use the getRSAKeyPair function with the getSecureRandom as an argument.

With this, we can finally create a pair of Asymmetric Keys using PointyCastle, but now the question is: how can we save these keys in plain text?

Encoding Asymmetric Keys to Plain Text

PEM or Privacy-Enhanced Mail, is a file format that we can use to store and send cryptography keys. Each key will have a header and a footer that indicates the type of key, as we can see in the following example.

The body is composed of a Base64 String that we encode using the DER format using ASN.1. This format will tell us, for a specific encoding, what information should we retrieve from a key in order to create a structure that can be encoded to Base64. This format will depend on the type of standard that we are using for the encoding, and for this article we are using PKCS-1

Using as a reference this article by Paul Bakker, we see that the DER structure of a Public Key using PKCS1 is:

In practicality, to encode a key, we will need to create an ASN.1 structure with the modulus and publicExponent Integer. In order to create the sequence, we will first need to add to our pubspec the asn1lib package. This library gives us the tools to create a ASN1Sequence that we can populate with ASN1Integers that we can then encode.

If we look at the RSAPublicKeyand PublicKey classes, we can see that it exposes the following properties:

With this, we can start coding our DER structure:

Using the encodedBytes of the ASN1Sequence we get the Uint8list to encode with base64.encode

Finally, we must add the header and footer

This is the resulting function:

Next, we can follow the standard for the PrivateKey

The RSAPrivateKey and PrivateKey object provides the following properties:

The remaining properties, exponent1, exponent2 and coefficient can be calculated following the specification:

This result in the following code:

If we want to encode our keys to PKCS8, we can follow proteye’s gist: How to encode/decode RSA private/public keys to PEM format in Dart with asn1lib and pointycastle · GitHub. This gist also show us how we can decode keys from the PEM format to RSAPrivateKey and RSAPublicKey. In the RsaKeyHelper provided in the example for this article, I suggest an edit to this decode so that we can distinguish between PKCS1 and PKCS8 encoding.

Signing a message with a Private Key

Now that we have created a Private Key, we might want to sign a message with it. Fortunately for this we can use the RSASigner class provided by PointyCastle. To initialise it, we will need to state what type of digest we are going to use from the following list:

Using SHA-256, we will first declare a signer variable

Then, we need to initialize it using the RSAPrivateKey that we created before. Since we want to sign the message, the first parameter of the init method is set to true

With this signer, we can use the generateSignature that returns a Uint8List . This method will require a Uint8List of the plain text we need to sign.

Now, if we want to send this information to a backend server, we might want to encode it with base64:

With this, we have completed our sign method:

Using the RSAPublicKey, we could then verify this signature using the same signer with a PublicKeyParamer in its initialization and using the verifySignature method:

The curious case of generating keys in a physical iOS device in Debug Mode

When testing the example app on a iOS device, you will see that the generation of keys will take a long (and possible infinite) amount of time, even when we are using isolates.

If we read the debug description in the official Flutter Github page, we read the following:

I’d assume that some of the constraints of this mode will conflict with the algorithm that generates the new Asymetric KeyPair.

The solution for this problem is simple (though may be cumbersome): test the app in release mode.

Conclusion

And we’re finally done 🥂. We have come a long way, from creating a pair of Asymmetric Keys, to converting them to a PEM format so that they can be printed to then use the Private Key to sign the message. Though there might be easier ways to solve most of the problems depicted in this article, I hope that it may be used to show you that if you need a more custom tailored solution for your applications, you can get your hands dirty and dive into cryptography in Dart.

In the example repository you will find a fully working example of generating the keys, printing them and signing a message. As a suggestion, the next steps would involve creating a way to persist the keys and to verify signed messages while providing the PEM strings for the private and public keys.


Flutter Community

Articles and Stories from the Flutter Community

Gonçalo Palma

Written by

Flutter developer and enthusiast. Organizer @ Flutter Portugal and collaborator @ FlutterExp. https://gpalma.pt/

Flutter Community

Articles and Stories from the Flutter Community