5

I am developing a service that will involve the direct debit of customers' accounts on a routine (weekly, monthly, etc.) basis, and so I'll need to store their information (BSB/routing number and account number) in my database. I am very concerned about security, and so my first thought was to not store this information, but after reading through many other questions about this topic, I have come up with a solution that I think will work - but I would like to run it past you guys to make sure I haven't left open any security holes.

When a user registers to the service, it will create a public/private key pair with the private key having a passphrase that the user provides (their account password). The password is hashed with bcrypt, and the public and private keys are stored alongside the password hash in the user table.

I will also establish a single admin public/private key pair which will be used when we need to process a direct debit. The public key will be known to the application and database, but the private key will be kept off of the server, so that the data can only be downloaded when encrypted, and then processed elsewhere. An alternative to downloading when encrypted would be to instead upload the private key to the application, decrypt the data for that request only, and then delete the uploaded private key - is this a security hole?

When a user registers for direct debit, the service will take the user's public key, and the admin's public key, and encrypt them using GPG's multiple recipients feature.

To the best of my knowledge this covers the following situations:

  • If the user wants to view the data, they will need to provide their password to decrypt the information.
  • If the user wants to add more data, they won't need to provide their password as I just use their public key.
  • If the admin wants to view the data, they will need to download the information and decrypt it, or else upload their private key (see above).
  • If the user changes their password, I will just need to update their private key. If they have forgotten their password, the only way I could decrypt their data would be through the admin private key, but as this is kept off-site the data should be deleted.

Will there be any concerns if the admin key pair ever becomes compromised (apart from the obvious) and I needed to generate a new admin key pair and then re-encrypt all of the stored data?

Are there any security holes in this design? I am admittedly no expert but I've tried to do my best with researching this, and would now love to get some input.

I have also looked in to services like Authorize.net's Customer Information Manager (CIM) but as far as I can see you need to be using their service to collect payments, when instead we have already established our own merchant to do this at a cheaper rate, and so we just need a secure information storage service.

The service will be using HTTPS, and running on Ubuntu 12.04, with PHP and MySQL, if that helps.

Any advice you can give will be greatly appreciated!

Thanks

4

The password is hashed with bcrypt, and the public and private keys are stored alongside the password hash in the user table.

Storing the private key means a compromise of the database would allow attacker to decrypt the bank information. You might as well store the bank information in plain text.

Why exactly would the bank information need to be encrypted by the user's key anyways? Is the user going to request that information "please tell me the bank account I told you about earlier"?

  • The customer might ask for that information, yes. If they wish to check on their details then this is a valid situation that could happen and in my opinion they should be able to check without having to enter them again, it's not the same as a password. As the private key has a passphrase on it that only the customer knows, doesn't this stop an attacker from using the private key to decrypt the data? – mitchdav Jul 1 '15 at 1:57
  • 1
    That assumes the user's passphrases are secure which they aren't. If your server is compromised it is a numbers game some % of your users will be using weak passwords and you service will lead to loss of bank information. You may say "oh that is the user's fault" but I doubt the victims (or their lawyers) will see it that way. Fair or not you will be the fall guy. Stored a redacted copy of the bank account number XXXXXXXX1234 for verifcation. Edits are just replacements. A compromise of the server would not leak details if your key's passphrase is strong (and it should be strong/random). – Gerald Davis Jul 1 '15 at 2:04
  • Absolutely, you're right - I'll just keep a redacted copy of the details and show that instead, and then keep the data encrypted with the admin key pair. I'll keep the question up for a while longer to see if anybody else has any other suggestions, but that looks like the right approach for now, thanks! – mitchdav Jul 1 '15 at 3:26
2

I recommend that you strongly consider paying a third party for the security, because it will probably be the most fiscally responsible solution. If you handle the payment card data yourself, you will be responsible for PCI audits, which can be very expensive. And after the upcoming October 2015 liability shift (assuming you are in the U.S.A.), the weakest link in the payment chain will be responsible for covering the expense of fraud. If your systems are breached, you would no doubt be hung out to dry by the banks, your payment processors, and the victims. Would you have the money to cover those kinds of losses?

  • 1
    Hey John, we aren't looking to store payment card data, but instead just BSB/routing numbers and account numbers of bank accounts. We are based in Australia, but I don't want any technicalities of our laws to prevent the service being as secure as possible, I want to do this right. Can you suggest any services to store the data? – mitchdav Jul 1 '15 at 2:02
0

Yes, use these functions to encrypt & decrypt the data from the DB.

  static function encrypt($s) {
    $keyHex = getenv('APP_KEY');
    if (!$keyHex) {
      Yii::error ("Could not retrieve environment variable APP_KEY");
      return null;
    }
    $key = pack('H*', $keyHex); 
    $iv = mcrypt_create_iv(256/8); // 256 bit / 8 bit/byte = 32
    $cipherText = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $s, 'cbc', $iv);
    $cipherText64 = base64_encode($iv.$cipherText);
    return $cipherText64;
  }

  static function decrypt($s) {
    $keyHex = getenv('APP_KEY');
    if (!$keyHex) {
      Yii::error ("Could not retrieve environment variable APP_KEY");
      return null;
    }
    $key = pack('H*', $keyHex);
    $cipher = base64_decode($s); 
    $iv = substr($cipher, 0, 256/8);
    $cipher = substr($cipher, 256/8); // strip off IV from front
    $text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $cipher, "cbc", $iv);
    $text = rtrim($text, "\0"); // mcrypt leaves null padding on the text
    return $text;
  }

They currently get the key from an environment variable, so you will have to modify them to get the key from the user. I assume you will want to have the user type in the password each & every time they access their bank details. You don't have to use public/private encryption. That is only good for small values, but bank account numbers are probably small enough. The key should be a 64 character hex string, or a 32 byte key, or a 256 bit key. It has to be securely random though. If you let the user supply a password and turn it into a key with a password-based-key-derivation-function (PBKDF), then someone could brute force attack the database.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.