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.
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
or4321
). - A single digit may not repeat 3 or more times (e.g.,
1111
or1112
). - PINs cannot be changed at ATMs, even if the ATM presents this option.
Implementation steps
- 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.
- 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>"}
. - 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).
- 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:
- Algorithm:
RSA-OAEP-256
- Encryption method:
A256CBC-HS512
- Key ID:
kid
property from the encryption key JWK - Note: Note: You can use the following tool to examine the metadata of the JWE payload: https://dinochiesa.github.io/jwt/
- 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. - 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. - Transfer the values generated in the previous two steps (serialized JWE and created signature) to your backend.
-
Call one of the following endpoints from your backend:
- POST Change PIN with Change Request: Changes the card's PIN without a Change Request. Note that this endpoint includes Device Monitoring.
- POST Change PIN: Changes the card's PIN using a Change Request.
Code examples
JavaKotlinRubySwift
@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));
}
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))
}
# 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
// https://github.com/airsidemobile/JOSESwift v2.4.0
import JOSESwift
// Use our toolkit for device binding and signing
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
// ...
}