Encrypted PIN change

Solaris provides a set of secure, encrypted endpoints for changing the PIN for your customers' cards. This page explains how to implement this process in your solution.


You must have implemented device binding in your solution, and your customer's device must have already generated a private and public key pair.

PIN requirements

  • The PIN must be exactly 4 numerical digits (i.e., 0123456789).
  • The digits may not be sequential in any order (e.g., 1234 or 4321).
  • A single digit may not repeat 3 or more times (e.g., 1111 or 1112).
  • PINs cannot be changed at ATMs, even if the ATM presents this option.

Implementation steps

  1. In your backend, retrieve the encryption key in JWK format with the GET Retrieve latest public key method and make it available to the customer's device.
  2. On the customer's device, collect the customer's desired PIN through a text input in your frontend and store it as string containing a JSON-formatted object: {"pin": "<NEW_PIN>"}.
  3. On the customer's device, parse the received encryption key JWK from the first step (you may want to use a suitable library of your choice, e.g., JOSESwift for iOS or Nimbus JOSE for Android).
  4. On the customer's device, encrypt the string containing the new PIN from step 2 into a JWE using the previously received encryption key and the following properties:
  5. Algorithm: RSA-OAEP-256
  6. Encryption method: A256CBC-HS512
  7. Key ID: kid property from the encryption key JWK
  8. Note: Note: You can use the following tool to examine the metadata of the JWE payload: https://dinochiesa.github.io/jwt/
  9. On the customer's device, generate the compact serialization of the JWE created in the previous step. This will be used as the encrypted_pin parameter.
  10. On the customer's device, sign the serialized JWE of the previous step using the customer's restricted key used for device signing. This is the signature parameter.
  11. Transfer the values generated in the previous two steps (serialized JWE and created signature) to your backend.
  12. Call the POST Encrypted PIN change endpoint from your backend.

Code examples

@FieldDefaults(level = AccessLevel.PRIVATE)
class PinKey {
    String kid;
    String kty;
    String use;
    String alg;
    String n;
    String e;
@FieldDefaults(level = AccessLevel.PRIVATE)
class UpdatePinRequest {
    String encrypted_data;
    String id;
    String signature;
    String device_id;
private void solarisbank_pin_change_integration()  {
        //    STEP 1
        PinKey pin_key = client.get("/v1/cards/1234567mcrd/pin_keys/latest");
        //    STEP 2
        String payload_to_encrypt = "{\"pin\":\"1928\"}";
        //    STEP 3
        RSAPublicKey solaris_public_key = JWK.parse(new ObjectMapper().writeValueAsString(pin_key))
        //    STEP 4
        JWEHeader jweHeader = new JWEHeader
                .Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256CBC_HS512)
        JWEObject jweObject = new JWEObject(jweHeader, new Payload(payload_to_encrypt));
        jweObject.encrypt(new RSAEncrypter(solaris_public_key));
        String encrypted_data = jweObject.serialize();
        //    STEP 5
        String signature = extract_signature(encrypted_data);
        //    STEP 6
        client.post("/v1/cards/1234567mcrd/pin_update_requests", new UpdatePinRequest(encrypted_data, pin_key.getKid(), signature, device_id));
data class PinKey(val kid: String, val kty: String, val use: String, val alg: String, val n: String, val e: String)
data class UpdatePinRequest(val encrypted_pin: String, val key_id: String, val signature: String, val device_id: String)
internal fun solarisbank_pin_change_integration() {
    //    STEP 1
    val pin_key: PinKey = client.get("/v1/cards/1234567mcrd/pin_keys/latest")
    //    STEP 2
    val payload_to_encrypt = "{\"pin\":\"1928\"}"
    //    STEP 3
    val solaris_public_key = JWK.parse(jacksonObjectMapper().writeValueAsString(pin_key))
    //    STEP 4
    val jweHeader: JWEHeader = JWEHeader
        .Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256CBC_HS512)
    val jweObject = JWEObject(
    val encrypted_data = jweObject.serialize()
    //    STEP 5
    val signature = extract_signature(encrypted_data)
    //    STEP 6
    client.post("/v1/cards/1234567mcrd/pin_update_requests, UpdatePinRequest(encrypted_data,pin_key.kid,signature,device_id))
  # STEP 1
  pin_key = client.get("/v1/cards/1234567mcrd/pin_keys/latest").data
  # STEP 2
  payload_to_encrypt = {
    "pin": "1928",
  # STEP 3
  solaris_public_key = JOSE::JWK.from_map(pin_key.to_h.transform_keys(&:to_s))
  # STEP 4
  encrypted_data = JOSE::JWE.block_encrypt(
                          "alg" => "RSA-OAEP-256",
                          "enc" => "A256CBC-HS512",
                          "kid" => pin_key.kid
  # STEP 5
  signature = my_device_binding_library_signing_function(encrypted_data)
  # STEP 6
  update_pin_request = {
    "encrypted_pin": encrypted_data,
    "key_id": pin_key.kid,
    "signature": signature,
    "device_id": device_id,
  client.post("/v1/cards/1234567mcrd/pin_update_requests", update_pin_request)
def my_device_binding_library_signing_function(encrypted_payload)
  sha_hash = Digest::SHA256.digest(encrypted_payload)
// https://github.com/airsidemobile/JOSESwift v2.4.0
import JOSESwift
// Use our toolkit for device binding and signing (https://github.solarisbank.de/JulianDreissig/solarisbank-toolkit-ios)
import SolarisbankToolkit
// STEP 1: Retrieve JWK from endpoint
let pinKeyJSON = """
    "kid": "84b5eb09-72b4-4bb4-b105-4cfd11573136",
    "kty": "RSA",
    "use": "enc",
    "alg": "RS256",
    "n": "0REZBtc6LmFoWgGe5esVU6QmtmSSnzQFNwnaUeMwVf-8OMa3uxZh1z4upxR80SbHhiPcAKcpkU-2GSE9MS7Fr6VG25tO7JsN8kPEZ59RzEiSn_8sd57AHaIPJnBUHfT5a7qgsgsoJNW6XISGaNfA4MiLskbCnQxDMaOEK9E7yYqC-do4arrqPy61l7gyWkG2IyZFWp48wiibmeBlHqBkihstD0mnXKbx--kjNx0xQ2s5gmvhO402-F4Vap1Yc3Ub1enG0H8u8sIIPG8JHIDO3GgX40WZAI3uRURi7346eWWl0RJ7Ai6Fy7sDFXsn6YiS0o9RegRWFufwMJ8TbIlm5w",
    "e": "AQAB"
// STEP 2: Create encryption payload
let payloadToEncrypt = "{\"pin\":\"1928\"}"
// STEP 3: Parse JWK
let jwk = try! RSAPublicKey(data: pinKeyJSON.data(using: .utf8)!)
let publicKey: SecKey = try! jwk.converted(to: SecKey.self)
// STEP 4: Encrypt and create JWE
var jweHeader = JWEHeader(keyManagementAlgorithm: .RSAOAEP256, contentEncryptionAlgorithm: .A256CBCHS512)
let kid = jwk["kid"]!
jweHeader.kid = kid
let encrypter = Encrypter(keyManagementAlgorithm: jweHeader.keyManagementAlgorithm!, contentEncryptionAlgorithm: jweHeader.contentEncryptionAlgorithm!, encryptionKey: publicKey)
let jwe = try! JWE(header: jweHeader, payload: Payload(payloadToEncrypt.data(using: .utf8)!), encrypter: encrypter!)
let encryptedData = jwe.compactSerializedString
// STEP 5: Create signature with device binding key (assuming device keypair to be available)
let deviceId = "…" // stored device ID
let keyPair = DeviceBinding.KeyPair.defaultKeyPair!
keyPair.sign(message: payloadToEncrypt) { signature in
  // STEP 6: Prepare payload and send
  let pinUpdateRequestPayload = """
      "encrypted_pin": \(encryptedData),
      "key_id": \(kid),
      "signature": \(signature!),
      "device_id": \(deviceId)
  // Now execute POST request with generated payload
  // ...