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.
In order to achieve this result, we will have to go through the following steps:
- Generating a RSA KeyPair using PointyCastle’s
Isolatefor optimizing the generation of a KeyPair
- Encoding Asymmetric Keys to Plain Text
- 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 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
And here we have two more objects that we need to instantiate:
SecureRandom. For RSA Key generation, we can use
RSAKeyGeneratorParameters in which we provide the
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:
- Create a
ParametersWithRandomobject that is used in:
RSAKeyGeneratorthat we need to:
- Create the Asymmetric KeyPair using the
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:
RSAKeyGeneratorParameters will need 3 parameters for its initialization, as we have discussed before:
- Public Exponent
- 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
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
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:
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
In practicality, to encode a key, we will need to create an
ASN.1 structure with the
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
PublicKey classes, we can see that it exposes the following properties:
With this, we can start coding our
encodedBytes of the
ASN1Sequence we get the
Uint8list to encode with
Finally, we must add the header and footer
This is the resulting function:
Next, we can follow the standard for the
PrivateKey object provides the following properties:
The remaining properties,
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
RSAPublicKey. In the RsaKeyHelper provided in the example for this article, I suggest an edit to this decode so that we can distinguish between
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:
SHA-256, we will first declare a
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
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
With this, we have completed our sign method:
RSAPublicKey, we could then verify this signature using the same
signer with a
PublicKeyParamer in its initialization and using the
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
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.