Overview

Use defaultPIN when an endpoint asks you to send a PIN during account setup or a PIN change flow. You must never send the PIN in plaintext. Instead, you encode the PIN into a PIN block, encrypt that PIN block with the Cardcore RSA public key, and submit the encrypted result with the matching key fingerprint. Cardcore decrypts the submitted value on the backend. After decryption, it expects to see a Format 1 PIN block. If the decrypted value is not a valid Format 1 PIN block, the request is rejected.

Object shape

{
  "defaultPIN": {
    "encryptedData": "A1B2C3D4...",
    "publicKeyFingerprint": "A884D774..."
  }
}
defaultPIN.encryptedData
string
required
The RSA-encrypted PIN block.Send this as a hex string.For the current 2048-bit RSA key, the encrypted output is 256 bytes, which becomes a 512-character hex string.Uppercase is recommended for consistency, but the important part is that the value must be valid hex.This value comes from encrypting an 8-byte PIN block with an RSA 2048-bit public key by using PKCS#1 v1.5 padding.This field is non-deterministic.The same PIN will produce a different ciphertext each time because PKCS#1 v1.5 uses random padding.
defaultPIN.publicKeyFingerprint
string
required
The fingerprint of the DER-encoded RSA public key used for encryption.The server uses this value to identify the correct private key for decryption.Fetch this fingerprint from the encryption key endpoint before you submit the request.

Why the PIN is encrypted this way

The PIN is treated as sensitive cardholder data. Cardcore does not accept plaintext PIN values in API requests. The PIN is first converted into a fixed-length Format 1 PIN block so the server can read it after decryption. That PIN block is then encrypted with RSA PKCS#1 v1.5 by using the public key published for your environment. This design gives you these protections:
  • The raw PIN never travels over the API as readable text.
  • Only the server that holds the matching private key can decrypt the payload.
  • Random padding prevents the same PIN from producing the same ciphertext on every request.
  • The fingerprint lets the server choose the exact private key that matches the public key you used.

Client integration flow

Follow these steps in your client before you send defaultPIN. The Cardcore institution dashboard follows this same flow before it creates a card, changes a PIN, or resets a PIN.

1. Fetch the encryption key

Call the encryption key endpoint before you encrypt the PIN. If you are integrating with Cardcore, use Get RSA key. Store these values from the response:
  • data.publicKey
  • data.publicKeyFingerprint
The publicKey value is a DER public key encoded as a hex string. The publicKeyFingerprint value must be sent back with the encrypted PIN so the backend knows which key to use.

2. Validate the PIN

Before you encrypt, confirm that the PIN:
  • contains only numeric digits
  • has a length from 4 to 9 digits
Reject invalid input on the client before you attempt encryption. The backend currently reads the PIN length as one decimal digit in the Format 1 block. Because of that, use 4 to 9 digit PINs unless your Cardcore environment explicitly confirms support for longer PINs. The Cardcore institution dashboard currently collects 4-digit PINs.

3. Build the PIN block

Create the PIN block with this format:
"1" + PIN length + PIN digits + hex padding to 16 characters total
The final PIN block must be exactly 16 hex characters. That is 8 bytes after it is converted from hex. The first character must be 1. The second character is the PIN length. The next characters are the PIN digits. The remaining characters are padding. Use F padding unless your integration has been told to use random hex padding. Examples:
PINPIN block
1234141234FFFFFFFFFF
12345616123456FFFFFFFF
12345678919123456789FFFFF

4. Encrypt the PIN block

Convert the PIN block from hex into bytes before encryption. Do not encrypt the PIN block as normal text. For example, 141234FFFFFFFFFF must become the bytes represented by that hex string. Encrypt those bytes with the RSA public key by using:
  • RSA
  • PKCS#1 v1.5 padding
  • a 2048-bit key
In node-forge, the encryption mode is RSAES-PKCS1-V1_5.

5. Hex-encode the ciphertext

Take the encrypted bytes and hex-encode them in uppercase. That uppercase hex string becomes defaultPIN.encryptedData. For a 2048-bit RSA key, the encrypted output is always 256 bytes, which becomes 512 uppercase hex characters.

6. Attach the fingerprint

Copy the fingerprint from the key-fetch response into defaultPIN.publicKeyFingerprint. Do not derive a different value locally unless your Cardcore integration explicitly tells you to.

Encryption examples

These examples follow the same approach used by the Cardcore institution dashboard.
import forge from 'node-forge';

function encryptPin(pin: string, rsaPublicKeyDerHex: string): string {
  if (!/^\d{4,9}$/.test(pin)) {
    throw new Error('PIN must be numeric and 4 to 9 digits long.');
  }

  const pinBlockStart = `1${pin.length}${pin}`;
  const padding = 'F'.repeat(16 - pinBlockStart.length);
  const pinBlock = pinBlockStart + padding;

  const messageBytes = forge.util.hexToBytes(pinBlock);
  const derBytes = forge.util.hexToBytes(rsaPublicKeyDerHex);
  const asn1 = forge.asn1.fromDer(forge.util.createBuffer(derBytes));
  const publicKey = forge.pki.publicKeyFromAsn1(asn1);

  const encryptedBytes = publicKey.encrypt(messageBytes, 'RSAES-PKCS1-V1_5');

  return forge.util.bytesToHex(encryptedBytes).toUpperCase();
}
After encryption, build the request object with the fingerprint from the RSA key response.
const rsa = await getRsaKey();

const encryptedData = encryptPin(pin, rsa.data.publicKey);

const protectedPin = {
  encryptedData,
  publicKeyFingerprint: rsa.data.publicKeyFingerprint,
};
For a PIN change, encrypt the old PIN and new PIN separately using the same RSA key response.

Use the encrypted PIN in requests

For card creation, send the encrypted PIN as defaultPIN.
{
  "defaultPIN": {
    "encryptedData": "A1B2C3D4E5F60708A9B0C1D2E3F40516...",
    "publicKeyFingerprint": "A884D7749D0E31C1B4E65F8A7B2C1190"
  }
}
For PIN change, encrypt the old PIN and new PIN separately.
{
  "oldPIN": {
    "encryptedData": "A1B2C3D4E5F60708A9B0C1D2E3F40516...",
    "publicKeyFingerprint": "A884D7749D0E31C1B4E65F8A7B2C1190"
  },
  "newPIN": {
    "encryptedData": "B1C2D3E4F5061728A9B0C1D2E3F40516...",
    "publicKeyFingerprint": "A884D7749D0E31C1B4E65F8A7B2C1190"
  }
}
For PIN reset, send only newPIN.
{
  "newPIN": {
    "encryptedData": "B1C2D3E4F5061728A9B0C1D2E3F40516...",
    "publicKeyFingerprint": "A884D7749D0E31C1B4E65F8A7B2C1190"
  }
}

Testing note

Do not compare encryptedData by exact value in tests. RSA PKCS#1 v1.5 encryption is non-deterministic, so the ciphertext changes across runs even when the input PIN is the same. Instead, validate:
  • that the value is uppercase hex
  • that the value length is exactly 512
  • that the fingerprint matches the key you fetched
  • that the server accepts the encrypted payload