Salesforce integrations can be tricky every so often. For example, one of our customers had to connect a system that required the use of JSON Web Tokens with an RSA-SHA512 signature.

As of writing, Crypto doesn’t support RSA-SHA512 signatures.
(Now supported thanks to Summer ’20 release! Stronger hashing algorithms in Apex)

One workaround is to implement the RSA cryptography in Apex. Let’s see how the Apex language enables us to express a wide variety of solutions, even at the bare metal if needed.

At its core, RSA (invented by Rivest / Shamir / Adleman) is a cryptosystem based on a mathematical trick and involves several moving parts. Apex source code on GitHub.

  1. A private key file holds secret prime numbers
  2. An ASN key reader handles the binary file format
  3. The message to sign consists of a JSON Web Token
  4. Then a SHA512 hash reduces the message to a big integer
  5. Finally the RSA signer executes the modPow signature math

To succeed, we must implement each logical part in Apex then make it performant. A similar look and feel to the platform Crypto class can be achieved starting with pseudocode like this:

Blob sign(Blob inputMessage, String privateKeyFile)
    ASN reader = new ASN(privateKeyFile);
    RSA signer = new RSA(reader.values());
    return signer.sign(SHA512(inputMessage));

Step 1) Private Key File

The signing process always starts with an RSA Private Key. For example, this RSA tool generates a binary representation of a private key wrapped in a header / footer. This is called a PEM file and it is base64 encoded to simplify transport using electronic mail or your clipboard:


To work with individual bytes, convert the data into hexadecimals:

String b64 = 'MIICXAIB...';
Blob binary = EncodingUtil.base64decode(b64);
String hex = EncodingUtil.convertToHex(binary);

Now with each byte seen as a hex pair, structure emerges:


Step 2) Abstract Syntax Notation – ASN.cls

The private key file is not just a random seed. It holds 9 pieces of content in Tag-Length-Value encoding per Abstract Syntax Notation One. Reading each hex pair (or byte) as a string, Apex will extract the critical prime numbers (P, Q, etc) described in the RSA specification.

  • Tag byte
  • Length byte
  • Contents bytes

The first tag tells us the data holds a list of values called a sequence:

30Sequence82025C﹡604 bytes

﹡When content length exceeds 128 bytes (hex 80) the length of the length is also given.
Here, hex 82 means a 2-byte length, then 025C means the sequence is 604 bytes long.

Let’s tabulate all the subsequent values contained inside the sequence:

02Integer011 byte00Version: V
02Integer8181﹡129 bytes00A92302…RSA modulus: N
02Integer033 bytes010001RSA public exponent: E
02Integer8181﹡129 bytes00A2CC60…RSA private exponent: D
02Integer4165 bytes00DFD036…Prime1: P
02Integer4165 bytes00C175DA…Prime2: Q
02Integer4064 bytes7EC1D0A9…Dp Exponent1: D mod (P-1)
02Integer4064 bytes4FF87AE9…Dq Exponent2: D mod (Q-1)
02Integer4064 bytes1BA12159…Coefficient: Qinv mod P

﹡When content length exceeds 128 bytes (hex 80) the length of the length is also given.
Here, hex 81 means a 1-byte length, then 81 means the integer is 129 bytes long. Not bits.

Step 3) JWT Message – Tutorial

JSON Web Tokens exist to prove the integrity of an API request: only the private key holder can sign tokens and issue valid requests. Each token consists of three parts: Header / Payload / Signature. The special values are covered in depth in the tutorial.

Combine the Header and Payload to prepare the message:

String header = '{"alg":"RS512","typ":"JWT"}';
String payload = '{"iat":1581009850,"exp":1581011650}';
String message = base64url(header) + '.' + base64url(payload);

Note the base64 URL variant strips trailing = padding, swaps + for -, and / for _ according to the JWT spec. This avoids issues if intermediate systems use tokens as filenames.

Step 4) Hash Function – SHA512.cls

Hashing the message ensures the input to the signature math is a predictable length. Else the signature math would become more and more expensive with each byte of data in the token.

Blob hashedMessage = Crypto.generateDigest('SHA-512', Blob.valueOf(message));

Step 5) Signature – RSA.cls

The original concept of modular exponentiation underlying RSA was described in 1977 and (assuming small prime numbers) can be executed with pen and paper by hand in 10 minutes:

Signature = CD mod P×Q (for message C, private exponent D, primes P and Q)

In real situations, this modular exponentiation gets computationally expensive and exceeds the Apex CPU limit without using a specific optimization: the Chinese Remainder Theorem.

It isn’t the end of the road. Most crypto libraries use the CRT optimization. In fact, its use is so common that ALL private keys hold 5 extra values, precomputed to help implement CRT. The implementation in Apex can be seen as a direct parallel of the algorithm from Wikipedia:

Chinese remainder theorem (Wikipedia)Apex implementation in RSA.cls
m1 = CDp mod P
m2 = CDq mod Q
h = Qinv (m1 – m2) mod P
m = m2 + HQ mod PQ
M1 = C.powMod(Dp, P);
M2 = C.powMod(Dq, Q);
H = Qinv.multiply(M1.subtract(M2)).mod(P);
M =  M2.add(H.multiply(Q).mod(P.multiply(Q)));

Big Integer math class – BigInt.cls

All the aforementioned math must work with big integers. The calculations must be exact and this is where the real challenge lies. Back in Step 2 you probably spotted the 129-byte value in the private key. That number has 300+ digits while the maximum length of any number in Apex is 19 digits. This isn’t a shortcoming of Apex – most languages have the same constraint. An extra class handles the big integers, representing them as lists of smaller integer primitives.


This solution stands of the shoulders of a number of people who provided reference implementations and ideas. We wish to express appreciation for their published work:

Related Posts

Last modified: 29th September 2020