Salesforce integrations can be tricky. One of our customers had to connect a system that required the use of JSON Web Tokens with an RSA-SHA512 signature.

One workaround was 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.

Update: the platform Crypto class now supports RSA-SHA512 signatures
You should use native methods as of the Summer '20 release - Stronger hashing algorithms in Apex

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 by email 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:

Hex Type Hex Length
30 Sequence 82025C﹡ 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:

Hex Type Hex Length Hex Value
02 Integer 01 1 byte 00 Version: V
02 Integer 8181﹡ 129 bytes 00A92302… RSA modulus: N
02 Integer 03 3 bytes 010001 RSA public exponent: E
02 Integer 8181﹡ 129 bytes 00A2CC60… RSA private exponent: D
02 Integer 41 65 bytes 00DFD036… Prime1: P
02 Integer 41 65 bytes 00C175DA… Prime2: Q
02 Integer 40 64 bytes 7EC1D0A9… Dp Exponent1: D mod (P-1)
02 Integer 40 64 bytes 4FF87AE9… Dq Exponent2: D mod (Q-1)
02 Integer 40 64 bytes 1BA12159… 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)

Eddie Woo - The RSA Encryption Algorithm

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: