However, it looks as though I need to keep the encryption_key & the initialization_vector for later use, if I want to be able to decrypt the data that's been encrypted at a later stage.
Please be careful to not confuse the two.
- You will store one key, total, for your system. Keys can be reused for multiple messages.
- Initialization vectors must be unique and unpredictable for each message.
That doesn't even get into the other problems (CBC mode isn't IND-CCA2 secure, padding oracle attacks against PKCS#7 padding, and a lot of implementation flaws that can occur in trying to remedy this by constructing a CBC+HMAC protocol).
How to Securely Solve the Problem
The best thing to do here is to make it someone else's problem and use a library that solves this problem for you. As security experts say, "Don't roll your own crypto."
CipherSweet is a PHP library (with a Node.js port) meant to facilitate searchable symmetric encryption. You don't have to use the searchable bits (it involves creating a blind index derived from the plaintext), the encryption is fine-tuned for security and easy to use.
See here for example
<?php
use ParagonIE\CipherSweet\CipherSweet;
use ParagonIE\CipherSweet\EncryptedField;
use ParagonIE\CipherSweet\EncryptedRow;
use ParagonIE\CipherSweet\KeyProvider\StringProvider;
// Set up CipherSweet; you can use other key providers (e.g. for Amazon KMS)
$provider = new StringProvider(
// Example key, chosen randomly, hex-encoded:
'4e1c44f87b4cdf21808762970b356891db180a9dd9850e7baf2a79ff3ab8a2fc'
);
$engine = new CipherSweet($provider);
// For just a single field
$fieldEncrypter = new EncryptedField($engine, 'my_table', 'my_field_name');
$ciphertext = $fieldEncrypter->encryptValue("some plaintext value");
// For multiple fields, this API is probably easier:
$rowEncrypter = (new EncryptedRow($engine, 'other_table'))
->addTextField('name')
->addIntegerField('hidden_id')
->addFloatField('latitude')
->addFloatField('longitude')
->addBooleanField('secret_boolean');
$safeToStore = $rowEncrypt->encryptRow([
'name' => 'John',
'hidden_id' => 123,
'latitude' => 12.34,
'longitude' => 56.789,
'secret_boolean' => true
]);
It's free, open source, permissively licensed, developed by a company that specializes in application security and cryptography, and is well suited to the problem at hand.
The cool thing about CipherSweet is, if you use it, you don't have to ponder over the intrinsic differences between byte arrays and strings (and the conversion logic betwixt the two). You don't have to know what an IV even is, let alone how to use them correctly. The cryptography engineering is already done for you.
If you need to generate a key, just copy the StringProvider
example above and replace the hex-encoded string with the output of bin2hex(random_bytes(32))
.