Overview
You'll be setting up the public portion of a Public-Private RSA keypair in your Instnt javascript agent workflow configuration. You alone, being in possession of the private portion of the keypair, can decrypt this data. These keys are signed via RSASSA-PKCS1-v1_5 SHA256 and encrypted by RSA-OAEP for key wrapping and AES GCM for payload encryption. Both of these processes require a minimum key length of 2048 bytes.
Generate Keys
First, generate a 4096-bit RSA key pair private.pem that encrypts a passphrase using the AES256 cipher:
% openssl genrsa -aes256 -out private.pem 4096
Generating RSA private key, 4096 bit long modulus
.............................................................
..............................................................++
..........................................................................................++
e is 65537 (0x10001)
Enter pass phrase for private.pem:
Verifying - Enter pass phrase for private.pem:
Public Key
The public key required when creating a wortkflow must be extracted from the encrypted bundle created above. This can be extracted into a Privacy Enhanced Mail (PEM) format.
PEM is a Base64 encoded DER certificate. PEM certificates are frequently used for web servers as they can easily be translated into readable data using a simple text editor.
Generally when a PEM encoded file is opened in a text editor, it contains very distinct headers and footers as shown below:
% openssl rsa -in private.pem -outform PEM -pubout -out public.pem
Enter pass phrase for private.pem:
writing RSA key
% cat public.pem
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAySvoxZG9dKFjV/7Jfg27
cZKdUr7sfeeziwnZXMGLkvsRZldxNdWPMkgr/UvgQuov5TlTXG8wddN9YJGDsFQt
F9xzEgC9uAZ8LZkP4nyRm41NyHSwaX+8ANy1X0Xngsqmi3EqF6mCfj733lGq24uH
GwfZ3NGtzFeXthTSTHvOuFpnVX2ci2l4XEEU0R/qowWJ76aJAgEpquMEazzY6Vff
Sxyw9rBDS5YNbL/i6JlTNUElVY2A+CkYZ/dILe+iod/lXyiB6Y0nKfqpExK9t3RJ
gxXcxOcquABIuMMAtKPe/jQElHTX5xkCPdkPwmabcTj+eOUY4EdCf0T9nfB7M6Fg
TDvFAvm0xYo/T4pJ22kO1I6x5dT+u12ZNueDQfLVlKFi4381/9f/vDCn0hi7a1EL
CxiQqGnK6XBz06oB+zfYdButSArn14K81cXr/ETAxgN9SgjjWSecaFfktcI4oQdD
7FaCQwZX6C8gMUMQX/j2Xn+n9pPXBvyCfgAa4fgBRzU4SAIH4StVrz+lv6b8zXI0
taDKGjwDNrcjJKzBGYs3RvhzSuDwKqI037/k1+nx/J53SZ1uqRx+OrqfSK2FXqVn
xzE6iuRo8cCFDVDVVCku6ZO/n9qAH6InGtRu1S4gRM/0txLa8whZKzv+/vdD1QhA
5M8HKZ7bKCLkRxTlZasv6rsCAwEAAQ==
-----END PUBLIC KEY-----

Decrypting and Verifying the JWE Token
Once a potential customer fills out the workflow fields listed on your website and submits their information, your business will receive onboarding decisions and assertion details in a cryptographically secure JWT container format which they must decrypt and validate. The JWT specification has support in multiple programming languages including but not limited to: Python, Node.js, and Java.
The JWE token is first decrypted using your business' private PKI key. In the second step the enclosed JWS token signature is verified as having being issued by Instnt. This verification uses the public portion of the Instnt signing key which is shared with you:
-
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvZmtK56bWzsQiCnXysb9
qCRgIW0NSVtZUmlOyVQSYndsM01fExznujGn4I9mHVjuecnObQckklzCeq3VbfIr
SmcCUyNegoiTzBUjJ9XuIXjnDUTeoCzTtDuOLdgespYQ/mAnO+EDa2tKiZiSUkNZ
tZ40XyHgd5w9L58FhzSX64LjduWYpiDXLAUQtWFOixkrWjAV4/WSqZdEfKf3kkrz
P7cIfuJwkg8EmcQwJ8uMbUFyvgeoPghdhcpOZV8uIf91rrhWaJMxQqXb7RafoFIM
dlvwE2GAgyMHxHcGKlPq5AJucaiuYzSzp8rSyTj5+Z841jmqpxk6WFuV6T9yk5a7
8KPZz8mwjkEvZZQN/0sbPEH9skH+VK+DrTsIOQkUmu/dCcS78M///TnfCWNGvsxh
7N6xz9PAVjXuegj2tqLUuhnGiZKWovQP+teSnEeZT17T+Fg1Yt4uhHNNCTlxESX6
BNH1Iq1WR99SScVjCrNQ0G9VewcGH1Ym7qFrLzihYAMmMF4orGO9slrD2kZN/FPs
vd4Xe3qQuzC7McFiFCb8xMvsaVi+u2Ogy2Q775hXWpwcrcKgm9Iky7vA2k/qbRLe
CQoHHgWOZiypX1ZfBYV98dI3kMfsh6TYOTmJXw3P4wij7DsKcKhCPLDnAIKqBkyz
Q1TldZKTWJJxtqDC525oCVECAwEAAQ==
-----END PUBLIC KEY----- -
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyxnDuCjaGcK0QLWx5m4d
cNfalWJp2shGHV32NReGxqw1JM+toXDK6cqvmUomZ9DG6bVembw2hDKPTajsQwJO
nrM6D0KfNiAxi2Mm01nQ7a9+39/l0uSxBMHLVk9e9w3GHiCz/E7cR1b8myrnghoW
lMKUmMhTOCDANglf3LtIv4Tj13V8Si/Rj+AoBU/st8g6e6OnpZbqjOfSb9VS+3PI
TrM93drjzW+ZE2L/tyF1pM6f+OavBDUtKwAqguXB7RU2JMp8hNw7ehSoqx1vFmqL
q6gKwURfsjIwh2dVNMm+3Icr5ZalH188/0iwPlu5zC9bknTG1K8gPVHs8LlFRAxJ
asIlTOrGSk4v5Qaf4wskqCBlA5aFvoQzDfJhMgnN0x9eHocY6MmztEMViOJl9XyO
+aEdAfaVqrHstBiT/r6FBe5/mCR4I+fl+GdO6AxCOIJ0ZKUTkbWPVy6jjOvWFm7z
INfqZas9IQ7rvQv0a/hvs03pewToEwVycbgdlfMJ2B62jR+EDFYpzJnOwvuTLcZE
SX2tlf1bGPiuoJD+IJKvAb9Mdf90ZTMuldiEdLnPcjC13ITxYrPfuENfYLs6fVMl
lxn1qUVnyngqzUSKu6c6D1WVkSB2modtyUNbAMEEyf3UY7gaPL5TciRktltK2Zi7
twXuhzM/UCTR2XXNaFmdMyECAwEAAQ==
-----END PUBLIC KEY-----
In order to resume the onboarding workflow after the Instnt decision callback, you must retrieve the user assertion from the Instnt Assertion API using the instntid UUID reference in the Decision Callback payload. Instnt provides a reference with this redirect for you to move on with the next step in your business' onboarding process.
The instntjwt token must be decrypted first to access the instntid which is used as an input to the GetAssertion function as described in Analyzing the Assertion Response.
The following decryption and verification examples are written within the frameworks of Python and Node.js:
-
from authlib.jose import JsonWebEncryption
from authlib.jose import JsonWebSignature
from authlib.jose import JWE_ALGORITHMS
# JWE token
JWE = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.Tst5foFWOBfhGiKSBse3R3...'
# Read customer private key
with open('private.pem', 'r') as f:
private_key = f.read()
# Decrypt JWE
jwe = JsonWebEncryption(algorithms=JWE_ALGORITHMS)
jwe_decrypted = jwe.deserialize_compact(JWE, private_key)
# Read Instnt public key
with open('instnt_kms_public.pem', 'r') as file:
instnt_kms_public_pem = file.read()
# Validate JWS
jws = JsonWebSignature(algorithms=['RS256'])
assertion = jws.deserialize_compact(jwe_decrypted['payload'], instnt_kms_public_pem)
json.loads(assertion['payload'].decode('utf-8'))
# {'iss': 'https://instnt.org', 'aud': 'https://acmebank.org', ..., 'instntid': '90205512-586d-4637-86cd-b63b582e480c'} -
var jose = require('node-jose');
var fs = require('fs');
// JWE token
var JWE = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.Tst5foFWOBfhGiKSBse3R3...'
// Read customer private key
var private_pem = fs.readFileSync('private.pem', 'utf8');
var private_key;
jose.JWK.asKey(private_pem, 'pem').
then(function(result) {
private_key = result;
});
// Decrypt JWE
var jwe_decrypted;
jose.JWE.createDecrypt(private_key).
decrypt(jwe).
then(function(result) {
jwe_decrypted = result;
});
// Read Instnt public key
var instnt_kms_public_pem = fs.readFileSync('instnt_kms_public.pem', 'utf8');
var instnt_public_key;
jose.JWK.asKey(instnt_kms_public_pem, 'pem').
then(function(result) {
instnt_public_key = result;
});
// Validate JWS
var assertion;
jose.JWS.createVerify(instnt_public_key).
verify(jws).
then(function(result) {
assertion = result;
});
assertion.payload.toString()
// {'iss': 'https://instnt.org', 'aud': 'https://acmebank.org', ..., 'instntid': '90205512-586d-4637-86cd-b63b582e480c'} -
import com.nimbusds.jose.crypto.RSADecrypter;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.SignedJWT;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public class JWTDecryptVerify
{
public static void main(String[] args) throws Exception
{
String jwe = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ....";
// Customer private encryption key
String private_pem = new String(Files.readAllBytes(Paths.get("private.pem")),
StandardCharsets.UTF_8);
JWK private_key = JWK.parseFromPEMEncodedObjects(private_pem);
RSADecrypter decrypter = new RSADecrypter((RSAKey) private_key);
// Decrypt
EncryptedJWT jweObject = EncryptedJWT.parse(jwe);
jweObject.decrypt(decrypter);
// Instnt public signing key
String instnt_kms_public_pem = new String(Files.readAllBytes(Paths.get("instnt_kms_public.pem")),
StandardCharsets.UTF_8);
JWK instnt_kms_public_key = JWK.parseFromPEMEncodedObjects(instnt_kms_public_pem);
JWSVerifier verifier = new RSASSAVerifier((RSAKey) instnt_kms_public_key);
// Verify
SignedJWT signedJWT = jweObject.getPayload().toSignedJWT();
if (signedJWT.verify(verifier)) {
System.out.println(signedJWT.getJWTClaimsSet());
} else {
System.err.println("Not verified!");
}
}
}
This same token format and unwrapping process is used for both the assertion reference on the callback as well as the assertion itself as described in Analyzing the Assertion Response.