View PIN [Non-PCI DSS]

This document illustrates the sequence of API calls required to view card PIN for financial institutions that are non-compliant and can't store ZPK (Zone PIN Key).

Those flows apply to any card type or product.

Option 1:

Option 1 is about using Network mobile SDK, in which Network SDK will send the request directly to Network middleware

View PIN [Non-PCI DSS]

View PIN [Non-PCI DSS]

Option 1

User
User
Issuer App
Issuer App
Issuer App
NI SDK
Issuer Middleware
Issuer Middleware
Network
Network
Login Get list of cards GET /cards/search Return response HTTP 200 Return response Show user list of cards Select card, View PIN Call SDK View PIN Generate key pair POST sdk/v2/security/view_pin Send public key Return response HTTP 200 Decrypt PIN using private key Return response Show verification result

View PIN Flow

This guide explains the process of viewing a PIN for a card using a Java-based implementation. The flow involves generating a JWT token, calls the View PIN endpoint, receives a PIN block encrypted under certificate, then decrypts it locally using 3DES/ECB/NoPadding and extracts the PIN. Below, we break down each step in detail with code examples and explanations.

Prerequisites

  • Java Environment: Ensure you have a JDK installed (e.g., JDK 11 or higher).
  • Dependencies: The code relies on libraries such as Jackson (com.fasterxml.jackson.databind), Bouncy Castle (org.bouncycastle), SLF4J for logging, and the Java HTTP Client (java.net.http).
  • Constants: A Constants class is assumed to contain values like securedSvcUrl, financialId, channelId, clientId, clientSecret, etc.
  • Network Access: The code makes HTTPS requests to external APIs (e.g., https://apiuat.za.network.global/v1/tokenkc/generate).

Overview of the Viewing PIN Flow

The flow consists of the following steps:

  1. Generate a JWT Token: Authenticate with the server to obtain a token for subsequent requests.
  2. Generate an RSA key pair locally
  3. Request View PIN: Send the public key in the View PIN request, PIN response encrypted under it
  4. Decrypt & Extract PIN: locally decrypt and derive PIN from PIN block (ISO-0).

Here’s the main entry point of the flow:

public void execPinViewFlow() {
    String jwtToken = TokenUtils.generateToken(); //Step 1
    KeyPair keyPair = RsaUtil.generateKeyPair(); //Step 2
    String publicKeyB64 = RsaUtil.publicKeyToBase64(keyPair.getPublic()); // send this
  	PrivateKey privateKey = keyPair.getPrivate();
  	ViewPinResponse resp = viewPin(cardId, publicKeyB64, jwtToken);//Step 3
    byte[] clearBytes = RsaUtil.decryptBase64(resp.getEncryptedPinData(), privateKey);//Step 4
		String pin = new String(clearBytes, StandardCharsets.UTF_8);
}

Step-by-Step Explanation

Step 1: Generate a JWT Token

The TokenUtils.generateToken() method authenticates with an external API to obtain a JWT token, which is used for authorization in subsequent requests.

public static String generateToken() throws URISyntaxException {
    HttpClient client = HttpClient.newHttpClient();
    Map<String, String> parameters = new HashMap<>();
    parameters.put("client_id", Constants.clientId);
    parameters.put("client_secret", Constants.clientSecret);
    parameters.put("grant_type", "client_credentials");

    String form = parameters.entrySet()
        .stream()
        .map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
        .collect(Collectors.joining("&"));

    URI uri = new URI("https", Constants.basePath, "/v1/tokenkc/generate", null, null);

    HttpRequest request = HttpRequest.newBuilder()
        .uri(uri)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .header("NI-apiuat_za_network_global", "qWyQt3D44Upner1T")
        .POST(HttpRequest.BodyPublishers.ofString(form))
        .build();

    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 200) {
        throw new RuntimeException("Unexpected status code: " + response.statusCode());
    }
    return JsonPath.parse(response.body()).read("$.access_token");
}
  • Purpose: Obtains an OAuth 2.0 access_token using client credentials.
  • Endpoint: https://{Constants.basePath}/v1/tokenkc/generate.
  • Headers: Includes a custom Akamai header for routing/security.
  • Body: URL-encoded form data with client_id, client_secret, and grant_type.
  • Output: A JWT token (e.g., eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...).

Step 2: Generate Private/Public Key + Encode Public Key

The generateKeyPair generates key pair public and private so that we can use it to encrypt/decrypt pin.

public static KeyPair generateKeyPair() throws Exception {
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
    kpg.initialize(2048);
    return kpg.generateKeyPair();
}
  • Purpose: Create a one-time asymmetric key pair for secure “View PIN” response encryption.
  • Output: KeyPair { PublicKey, PrivateKey }

Step 3: View PIN

The viewPin method fetches the PIN encrypted under public key sent in request.

  1. Generate a PIN Block: Combine the PIN with the PAN using the ISO-0 PIN block format.
  2. Encrypt the PIN Block: Use the public key from the PIN certificate.

Generate PIN Block

private ViewPinResponse viewPin(String pan, String publicKeyB64, String jwtToken) throws Exception {
    ObjectMapper objectMapper = new ObjectMapper();
    HttpClient client = HttpClient.newHttpClient();

    ViewPinRequest req = new ViewPinRequest("PAN", pan, publicKeyB64);
    String body = objectMapper.writeValueAsString(req);

    URI uri = new URI("https", Constants.securedSvcUrl, "/sdk/v2/security/view_pin", null, null); // replace if needed

    HttpRequest request = HttpRequest.newBuilder()
            .uri(uri)
            .header("Content-Type", "application/json")
            .header("Authorization", "Bearer " + jwtToken)
            .header("Financial-Id", Constants.financialId)
            .header("Channel-Id", Constants.channelId)
            .header("Unique-Reference-Code", String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)))
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 200) {
        throw new RuntimeException("View PIN returned unexpected status code: " + response.statusCode());
    }

    return objectMapper.readValue(response.body(), ViewPinResponse.class);
}

  • Purpose: Fetches the encrypted PIN to a specific card.
  • Endpoint: https://{Constants.securedSvcUrl}/sdk/v2/security/view_pin.
  • Body: JSON payload with publicKey, and cardId.
  • Headers: Includes JWT token and other identifiers.
  • Output: encryptedPinData.

Step 4: Decrypt with Private Key

The decryptBase64 method accepts the encrypted PIN and decrypt it using private key.

public static byte[] decryptBase64(String encryptedB64, PrivateKey privateKey) throws Exception {
    byte[] encryptedBytes = Base64.getDecoder().decode(encryptedB64);

    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); 
    // If your API uses PKCS1 v1.5 instead, change to: "RSA/ECB/PKCS1Padding"

    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    return cipher.doFinal(encryptedBytes);
}
  • Purpose: Decrypt the server response locally (PIN never travels in clear).
  • Input: Encrypted PIN received from API encryptedB64, and privateKey.
  • Output: Decrypted bytes (either PIN directly, or PIN block hex.

Running the Code

To execute the flow, use the main method:

public static void main(String[] args) throws Exception {
    ViewPinDemo viewPinDemo = new ViewPinDemo();
    viewPinDemo.execPinViewFlow();
}
  • Execution: Instantiates ViewPinDemo and runs execPinViewFlow().

Troubleshooting

  • HTTP Errors: Check the response status codes and logs for details.
  • Certificate Issues: Ensure the PIN certificate is valid and properly Base64-encoded.
  • Dependencies: Verify all required libraries are included in your project.
  • Wrong PIN output: Ensure PAN used for extraction matches the same PAN used by the server (especially if Step 2 is involved).