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.

- A
**private key file**holds secret prime numbers - An
**ASN key reader**handles the binary file format - The
**message to sign**consists of a JSON Web Token - Then a
**SHA512 hash**reduces the message to a big integer - 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:

-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCpIwKH7fYUgtCtOCtnvIlAXcFvzLPkLWWHNZoT+hOIzT WR+PrOQiQrat7wsVD+161g3CUUcdpTR2HMSThWxEjLldsZq0BA3TqEecXm V6rZzy0ZTJb2tgzjoX+eNiLvIHpgAaq5x2g2J9mB/b/9PFWspvQgMPAXl6 rpAM5ZNIEwuQIDAQABAoGBAKLMYP5HbMo3U/a3Dwhtr8p1s+ARr8FcdNId JO4/khfmNb8IYRixDzF/T5FriyOQo4CMxWAVamkoVxkUDRdvHQTO17hWMK ysLIn46y3bHxxRb6g5tpSwK61L25Hdiwi9GjqGrn7Z5rMFIqOqaCIX9gUQ ICmW31Y7XyekZ9RPA+8VAkEA39A2eqystpFTtkvfrAgG7uOL4IrckA5kuC 4xPHeehvlI8bDZU4kmLFNnduNOVtfKSxzfT4l+Qvl8IRxUycRodwJBAMF1 2s9NvILcJ1i2Ah2oTf91gkv82XoNOr0DsdLMqRkNSODKWiqxwZTVq8OeiW wF58FzdBmNJegVfoE2eO0zrE8CQH7B0KkHtMWtZwjeze4DmdGgQ+9HFgXs cPSzDKWfZcQx2TMxItSh32HJVtbJg+vBSUvjLUJBr6XE4J1sC0U+nJ8CQE /4eunk1X82qGEoY7mEwDFQjvsAW5nzbAuEQnbEOUZs0mpx21H4xu/SX71u hJoN2t6B7kU9rqTAddnN/bD4AksCQBuhIVld31iTzgyrc4R9e+KnLuW0rP /01SWYPYE0oeYCViZ/r19XsHQicPFLjKtKoOLNhVlHGsrW0yCnpcb2ahs= -----END RSA PRIVATE KEY-----

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:

3082025C02010002818100A9230287EDF61482D0AD382B67BC89405DC16FCCB3E42D6587359A 13FA1388CD3591F8FACE42242B6ADEF0B150FED7AD60DC251471DA534761CC493856C448CB95 DB19AB4040DD3A8479C5E657AAD9CF2D194C96F6B60CE3A17F9E3622EF207A6001AAB9C76836 27D981FDBFFD3C55ACA6F42030F01797AAE900CE59348130B9020301000102818100A2CC60FE 476CCA3753F6B70F086DAFCA75B3E011AFC15C74D21D24EE3F9217E635BF086118B10F317F4F 916B8B2390A3808CC560156A69285719140D176F1D04CED7B85630ACAC2C89F8EB2DDB1F1C51 6FA839B694B02BAD4BDB91DD8B08BD1A3A86AE7ED9E6B30522A3AA682217F60510202996DF56 3B5F27A467D44F03EF15024100DFD0367AACACB69153B64BDFAC0806EEE38BE08ADC900E64B8 2E313C779E86F948F1B0D95389262C536776E34E56D7CA4B1CDF4F897E42F97C211C54C9C468 77024100C175DACF4DBC82DC2758B6021DA84DFF75824BFCD97A0D3ABD03B1D2CCA9190D48E0 CA5A2AB1C194D5ABC39E896C05E7C17374198D25E8157E813678ED33AC4F02407EC1D0A907B4 C5AD6708DECDEE0399D1A043EF471605EC70F4B30CA59F65C431D9333122D4A1DF61C956D6C9 83EBC1494BE32D4241AFA5C4E09D6C0B453E9C9F02404FF87AE9E4D57F36A8612863B984C031 508EFB005B99F36C0B844276C439466CD26A71DB51F8C6EFD25FBD6E849A0DDADE81EE453DAE A4C075D9CDFDB0F8024B02401BA121595DDF5893CE0CAB73847D7BE2A72EE5B4ACFFF4D52598 3D8134A1E60256267FAF5F57B0742270F14B8CAB4AA0E2CD8559471ACAD6D320A7A5C6F66A1B

## 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 lengthis 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 lengthis 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 = C

^{D}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 = C^{Dp} mod Pm2 = C ^{Dq} mod Qh = Q _{inv} (m1 – m2) mod Pm = 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.

## Acknowledgements

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:

- RSA key utility by Kenju Urushima https://github.com/kjur/jsrsasign
- ASN.1 parser by Lapo Luchini https://github.com/lapo-luchini/asn1js
- BIG INTEGER math by Tom Hu http://www-cs-students.stanford.edu/~tjw
- SHA512 message digest by Michael Tritton https://github.com/trittimo/SHA512

## Comments