Encrypting sensitive data

/by apr/

Protecting Customer's Data

Many jPOS-EE users and PEP members have a recurring problem: encrypting sensitive data in order to protect their customers and if possible, make auditors happy.

But there’s a recurring issue: unless you use an HSM, a smartcard to protect your keystore, or you have someone enter a pass-phrase at boot time, you have to live with the fear that someone could somehow access your keystore, get your master keys and decrypt your sensitive data.

The problem becomes more critical in situations where jPOS is installed at small merchants or medium-sized store chains, we can’t ask them to enter a passphrase at boot time, we don’t have smartcard readers there and we don’t have HSMs. We could do some security through obscurity but we can leave that to our competitors :)

Based on the work performed by Hani in the jPOS’s security module, and the DUKPT implementation we did with Dave from InfoSecBlurb in a related project, I came up with a nice solution that I’d like to present here in order to get your feedback.

Here is a simple example:

  SSM ssm = (SSM) NameRegistrar.get ("ssm");
  SecureKeyStore ks = new SimpleKeyFile ("cfg/keys.cfg");
  SecureDESKey bdk = (SecureDESKey) ks.getKey ("our-bdk");
  String encrypted = ssm.customEncrypt (bdk, "4111111111111111=0805".getBytes());
  String decrypted = new String (ssm.customDecrypt (bdk, encrypted), "ISO8859_1");

customEncrypt produces something like this:

B9B35297F48C792646CED3B367F40E6D,F302BA266FDAC3A6D4773B4C74553C49F584934BCA9224F8,5C9538880BED87FD

and customDecrypt gives you your original byte[] array back.

Here is what we do:

  • We generate three clear-text 128-bit keys
  • We use the security module’s console to enter those keys and produce a BDK encrypted under the local master keys (using standard key-ceremony procedures)
  • The BDK cryptogram is stored in the keys.cfg file
  • When we receive a message to encrypt, we generate two 64-bit random DES keys that we use as KSNs. We split them to produce a baseKeyID, deviceID and transactionCounter as required by KeySerialNumber
  • We use our BDK and these two KSNs to produce a pair of derived 64 bit keys
  • We combine these two 64 bit keys and do 3DES with them in order to encrypt our payload
  • We use both KSNs and derived keys plus the encrypted payload to produce an SHA hash, so we can perform sanity checks

So far, so good, it seems pretty nice, but it’s still breakable if you have access to the master keys and the BDK’s cryptograms, or, if you have access to the jar and you manage to call ssm.customDecrypt with the right parameters.

In order to mitigate that, I’m experimenting with several anti-debug features; besides obfuscating the jpos-ee jar (that’s something that the PEP version already has), we are trying to have a hash of the stacktrace of the caller in order to validate the caller id and we have some timing control too.

But anyway, here is the best thing, a very simple one: we don’t have to run this at every merchant, we can use this approach (or an HSM-based one) at a central location (such as an acquirer or transaction switch).

When an acquirer or transaction switch receives say a 0200 message with a some sensitive data (i.e. PAN, EXP, TRACK1, TRACK2), it could reply with a 0210 carrying a cryptogram of that data computed at the remote facility, where we don’t have access to the LMKs. We could do that with an extremely simple ISOFilter.

Imagine something like this:

Message sent from merchant to acquirer:


    <isomsg>
      <field id="0" value="0200"/>
      <field id="3" value="000000"/>
      <field id="4" value="000000012183"/>
      <field id="7" value="0309123613"/>
      <field id="11" value="010370"/>
      <field id="12" value="123613"/>
      <field id="13" value="0309"/>
      <field id="22" value="022"/>
      <field id="24" value="105"/>
      <field id="25" value="00"/>
      <field id="35" value="4111111111111111=0805"/>
      <field id="41" value="29110001"/>
      <field id="42" value="012345         "/>
      <field id="44" value="0000000000"/>
      <field id="48" value="0010000000000000"/>
      <field id="49" value="858"/>
      <field id="60" value="jPOS-1.5.1"/>
      <field id="62" value="7297873"/>
    </isomsg>

Message response from acquirer:

    <isomsg>
      <field id="0" value="0210"/>
      <field id="3" value="000000"/>
      <field id="4" value="000000012183"/>
      <field id="7" value="0309123613"/>
      <field id="11" value="010370"/>
      <field id="12" value="123613"/>
      <field id="13" value="0309"/>
      <field id="22" value="022"/>
      <field id="24" value="105"/>
      <field id="37" value="000000130682"/>
      <field id="38" value="130682"/>
      <field id="39" value="00"/>
      <field id="41" value="29110001"/>
      <field id="42" value="012345         "/>
      <field id="44" value="0000000   0000000"/>
      <field id="48" value="0010000000000000"/>
      <field id="49" value="858"/>
      <field id="62" value="7297873"/>
      <field id="63" value="B9B35297F48C792646CED3B367F40E6D,F302BA266FDAC3A6D4773B4C74553C49F584934BCA9224F8,5C9538880BED87FD"/>
    </isomsg>

The acquirer’s outgoing filter would build a custom cryptogram for us either using the aforementioned algorithm, an HSM on any other similar approach, and it would put it in a private field for us (in this case we used field 63, but could be any other free variable length private field) and an incoming filter at the acquirer would take care of decrypting it locally, unsetting that private field and setting the appropriate field 35 (same goes for other sensitive fields such as 2,14,45,52,55, etc.).

At the client application, you could store some clear-text information (such as BIN, last four digits of the PAN) for reporting purposes and store that opaque cryptogram produced by your acquirer/processor; that would be your ticket to request funds for that transaction if for some reason you need to perform a batch upload operation.

Although an scheme like this wouldn’t solve problems like this, it would certainly help merchants protect its customers’ data with a minor effort from the acquirers.

The encrypted information could be an inner ISOMsg with just some sensitve field. The document Secure Payment Framework presents some related concepts.

I’d love to listen to potential flaws in this. I’d also like you to exchange some anti-debug ideas and links. So far, I’m serializing a Throwable’s stacktrace, producing and validating a set of hashes with valid caller id info, an approach that is extremely sensible to JVM versions…

3 Responses to “Encrypting sensitive data”

  1. Andy Orrock Says:

    jPOS readers and users will be interested to note that a good applicability for Alejandro’s proposed approach is for adherence to Visa’s CISP program.

    For auditing purposes and network compliance, applications connected to the Visa network (either directly or through a payment gateway like FDR, Fifth Third, NPC, etc.) must adhere to Visa’s Cardholder Information Security Program (“CISP”), which dictates acceptable practices in regards to the protection of cardholder data for official information, refer to http://usa.visa.com/business/accepting_visa/ops_risk_management/cisp.html. The specific requirements at issue here are those related to cardholder data – these are discussed in bullets 3.3 and 3.4 of Visa’s Payment Card Industry (“PCI”) PCI Security Audit (see embedded PDF as hyperlink in Visa page referenced above). Those requirements read:

    3.3 Mask account numbers when displayed (the first six and last four digits are the maximum number of digits to be displayed).

    Note that this does not apply to those employees and other parties with a specific need to see full credit card numbers.

    3.4 Render sensitive cardholder data unreadable anywhere it is stored, (including data on portable media, in logs, and data received from or stored by wireless networks) by using any of the following approaches:

    • One-way hashes (hashed indexes) such as SHA-1

    • Truncation

    • Index tokens and PADs, with the PADs being securely stored

    • Strong cryptography, such as Triple-DES 128-bit or AES 256-bit with associated key management processes and procedures.

  2. apr Says:

    While reviewing use cases for the proposed scheme, I found that it has flaw when dealing with stand-in situations, where you need to SAF a message. You don’t have access to the acquirer’s encryption service at that time.

    I thought we could use an hybrid approach, local encryption until you transmit the transaction and get the acquirer-generated cryptogram. But this is actually not required if we use PKI.

    So the proposed scheme seems good to me for local encryption where you need to protect customer information. When you can use a remote encryption service, PKI is the way to go. You just need to encrypt using your acquirer’s public key.

  3. aaorrock Says:

    An additional observation about this approach…in terms of logging cardholder information on your transaction log, you’ve got two situations where you’re going to want to use that data (I’m generalizing here):

    1. Sometime within the next 24 hour cycle, you’ll want to decrypt that data in order to put the card number into detail records on an extract/settlement file. So, Alejandro’s DUKPT-like 128-bit methodology (as described above) works perfectly here. It surely meets PCI/CISP standards and the routines are reversible. This does result in two additional responsiblities:

    * The file containing the LMKs and BDKs should be locked down in a secure directory using appropriate key management processes.

    * The settlement file - at rest - will contain the clear card numbers. It’s beyond the scope of this blog to offer a solution to that, but as a consultant I will tell you that our customers have elected to solve that issue by encrypting the file using PGP prior to transmission to the settlement partner.

    2. At any point subsequent to transaction completion, there’s a chance that a user will want to interrogate the transaction log to address a question like “Hey, what transpired with card XXXX at store 0720 on May 28th and May 29th?” (most probably this request was generated by a cardholder concern). So, you’d want to query the transaction log by card number. But - we’ve got an issue: the result of the field outputed in the methodology described here varies *by transaction* (the essence of DUKPT). So, to find all occurrences of usage by card XXXX, you’d have to decrpyt *all* the cards on the file in order to address the question. Obviously, this isn’t tenable. So, there’s a need to encrypt the card a second time for this other purpose. Here, though, we can make use a less-consumptive and less process-intensive methodology: an SHA-1 one-way hash gives us what we need because the result for any card Y will always be the same. It isn’t reversible, but doesn’t need to be: subsequent queries on card XXXX can create the SHA-1 hash and then search for all occurrences of the same value, thus addressing the requirements of ad-hoc, user-driven query needs.

    The upshot of all this is an implementation in which the transaction log contains three fields:

    a. A field that contains the literal string that matches what you can show according to PCI/CISP standards, i.e., the six-digit BIN + four trailing digits of a card. In other words, we have a field in a varchar(19) entry on a SQL DB that would contain ‘567890_______1234′ (that’s not implying use of mask here - we’re literally storing the underbars).

    b. A second field contains the output of the SHA-1 hash, used for queries. Note in all cases that we’re encrypting ONLY the PAN. You can never store the entire track, not even if encrypted.

    c. A third field (call it ’secureData’) will contain the result of the DUKPT routine (as well as some ancillary components and sanity checks) as described by Alejandro.