If you are encrypting some secrets (database passwords, access tokens, etc).
When it comes to key rotation, you'll need to store those encrypted values twice, for a short period of time (under the old and new keys).
Could the encrypted secrets be stored in two files named after the hash of their keys? maybe sha256?
Or could those file names (if known) be used in a malicious way? perhaps making it easier to determine the key values?
An alternative would be to use the keys to encrypt a known plain-text value, and use that for the file names.
I'm currently considering using libsodium, and "IETF ChaCha20-Poly1305" (aead_chacha20poly1305_ietf), to encrypt the secrets.
Why I'm not using 'secrets.new' and 'secrets.old'...
This will be used for some websites, where the script running will simply be given a key to decrypt the secrets (via an environment variable), I'd like to avoid having to also say if it's the new or old key.
Secondly, lets assume the website also has this security vulnerability:
<?php
$path = '/path/to/file/' . $_GET['file'];
readfile($path);
?>
It might not be that obvious, but the attacker won't know what the file will be called if it's based on the key (you see a similar thing with the Firefox profile directory).
As a very rough implementation:
<?php
//--------------------------------------------------
// Functions
function secrets_set($key, $secrets) {
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES);
$encrypted = sodium_crypto_aead_chacha20poly1305_ietf_encrypt(
json_encode($secrets),
$nonce,
$nonce,
$key
);
$name = hash('sha256', $key);
$path = SECRETS_FOLDER . '/' . $name . '-' . bin2hex($nonce);
file_put_contents($path, $encrypted);
}
function secrets_get($key) {
$name = hash('sha256', $key);
$prefix = SECRETS_FOLDER . '/' . $name . '-';
$path = NULL;
$nonce = NULL;
$secrets = NULL;
foreach (glob($prefix . '*') as $match) {
if (($pos = strrpos($match, '-')) !== false) {
$path = $match;
$nonce = hex2bin(substr($match, ($pos + 1)));
}
}
if ($path) {
$secrets = file_get_contents($path);
$secrets = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(
$secrets,
$nonce,
$nonce,
$key
);
$secrets = json_decode($secrets, true);
}
return $secrets;
}
//--------------------------------------------------
// Store
$key1 = sodium_crypto_aead_chacha20poly1305_ietf_keygen();
secrets_set($key1, [
'database' => 'u8syTtAvNWJbhwMNdFdRsfxJY',
'api_key1' => 'y9f82wZdkahEPsyjXQAmy6nPh',
'api_key2' => 'YtMp4pHR9EbHbGJXgRiniQixU',
]);
//--------------------------------------------------
// Get
print_r(secrets_get($key1));
//--------------------------------------------------
// Re-key
$key2 = sodium_crypto_aead_chacha20poly1305_ietf_keygen();
secrets_set($key2, secrets_get($key1));
print_r(secrets_get($key1));
print_r(secrets_get($key2));
// When $key1 is no longer in use, delete the old file.
?>