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.

Prerequisites

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

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 device signing method—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

JavaKotlinRubySwift
Copy
Copied
@Getter
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
class PinKey {
    String kid;
    String kty;
    String use;
    String alg;
    String n;
    String e;
}
 
@Getter
@AllArgsConstructor
@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))
                .toRSAKey()
                .toRSAPublicKey();
 
        //    STEP 4
        JWEHeader jweHeader = new JWEHeader
                .Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256CBC_HS512)
                .build();
 
        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));
    }
Copy
Copied
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))
        .toRSAKey()
        .toRSAPublicKey()
 
    //    STEP 4
    val jweHeader: JWEHeader = JWEHeader
        .Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256CBC_HS512)
        .build()
 
    val jweObject = JWEObject(
        jweHeader,
        Payload(payload_to_encrypt)
    )
    jweObject.encrypt(RSAEncrypter(solaris_public_key))
 
    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))
}
Copy
Copied
  # 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(
                      solaris_public_key,
                      payload_to_encrypt.to_json,
                      {
                          "alg" => "RSA-OAEP-256",
                          "enc" => "A256CBC-HS512",
                          "kid" => pin_key.kid
                      }
                  ).compact
  # 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)
end
 
def my_device_binding_library_signing_function(encrypted_payload)
  sha_hash = Digest::SHA256.digest(encrypted_payload)
  OpenSSL::PKey::EC.new($cards_device_signing_key).dsa_sign_asn1(sha_hash).unpack1("H*")
end
Copy
Copied
// 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
  // ...
}