Skip to main content

Encrypting sensitive data

· 5 min read
Alejandro Revilla

/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 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:

Message response from acquirer:

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...