2

In PHP, is there any way to make a variable "stick" across multiple invocations of my script? Specifically, I need to compute hash_hmac('sha-256', $data, $key) where data will probably be different on each invocation of my script, but key needs to remain the same for all invocations on a specific server (Apache2) instance. Actually what I need is that key is initialized to a pseudo-random value once per server (Apache2) instance. Please note that I do not want the key to be hard-coded or loaded from a config file. The key should be generated pseudo-randomly when the server (Apache2) instance is starting up, or when it is needed (read) for the first time. Also the key should "stick" in the memory for as long as the specific server instance running, but, as soon as the server instance is shutting down, it should be discarded with no way to re-construct it!

I would be fine if $_SERVER contained any value that is "unique" per server instance, with very high probability, so that I could just take that seed and pump it through a KDF in order to derive my unique key. But it seems there is no such entry in $_SERVER, or am I missing something?

Also, I can not use uniqid(), because it will give a different unique value on each call (script invocation), not a value that is unique per server (Apache2) instance.

Furthermore, I can not use Cookies or session_start(), because key is not supposed to be per-user, and it must not be visible to the users! I understand that contents of $_SESSION are not visible to the user(s), but I do not want to track any data per user when that is not necessary – and when it would only serve as a workaround for the inability to retain a single "global" value.

I could probably use an "in-memory" DB with something like SQLite, but this really feels like taking a sledgehammer to crack a nut. This "problem" of requiring a variable (value) to be initialized once and then "stick" across multiple invocations of a script seems like something that would arise naturally in many applications! So is there no simple straight-forward solution for this? 🤔


EDIT

For clarification: In this particular setup, there is no security risk in using the same MAC key for all users (of a particular server instance), because we never show that key to the user(s). We want to ensure that the user returns the "authentic" message to the same server instance where it was originally obtained from. If a user modifies its given message, we detect this on return, because the MAC doesn't match anymore. The same holds true, if the message was returned to the "wrong" server, because different servers shall have different MAC keys and so the MAC won't match. Using a separate key per user doesn't improve things in this scenario and just is not necessary, but it would add new complication (i.e. session management) that I really want to avoid...

15
  • Put it in a file/database/cookie Commented Feb 25, 2024 at 13:51
  • Would shmop work? First read attempt would need to initialize the key. php.net/manual/en/book.shmop.php Commented Feb 25, 2024 at 14:43
  • To your last paragraph, yes, the problem is very common, and we’ve got databases, file systems, memory stores, “secret vaults’’, etc. to support it. What is not common, however, is the “unique for the process’s lifecycle” aspect. Commented Feb 25, 2024 at 14:53
  • @ChrisHaas "Would shmop work?" It would survive an Apache restart. Only a restart of the whole machine would reset it. And there could be a race condition if the first two requests are executed at the same time. Commented Feb 25, 2024 at 15:02
  • 1
    Late to the party, but is there any drawback of just using an environment variable for the *UNIX user running PHP and initialize it on server startup? It'll survive Apache being stopped, but if initialized on startup it will just be overriden. Commented Mar 7, 2024 at 1:44

3 Answers 3

1

You can use PHPs built-in APCu cache to store the random value persistently within each running server instance until restart:

$instancekey = apcu_fetch('instancekey');
if (!$instancekey) {
    $instancekey = uniqid(); // or use whatever RNG you like
    apcu_store('instancekey', $instancekey, 0);
}
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks! This seems to be the most promising (simple) solution, thus far, that does not required an additional PHP extension. However there are two problems that come to my mind: The initialization is not "atomic", i.e. we could end up with two invocations of the script ending up with different keys (in the eraly phase). Also, to the best of my understanding, APCu does not guarantee that the entry remains in the cache for as long as the server is running, even if we did not set an explicit TTL. Entries that haven't been accessed recently may "soft" expire and be evicted...
Update: I think using apcu_entry() could be used to ensure "atomic" initialization. But the problem of expiring cache entries still remains. From the APCu docs: "An entry is soft expired if no per-entry TTL is set [...] and the access time of the entry is older than the global TTL. Soft expired entries are accessible by lookup operation, but may be removed from the cache at any time."
@Schraubstock1990 well, at least I've tried. To be honest, I strongly suggest you don't do what you asked for and store the HMAC key in the user _$_SESSION instead. Session variables are not readable by the user, they are stored locally on the server: only session id is provided to the user, not content. The session still exists after a service restart and could even be stored in a way that it the value exists after reboot. This way, a user will not notice your server restart between to requests. With your own solution, the second requests fails when you reset the server in between.
@Schraubstock1990 Why is key not supposed to be per user? Why share the same HMAC key between users for a long period of time? (until restart) I see a huge security issue here. Don't do that. Use a unique key per session instead of per server. I see no reason not to.
There is no security risk in using the same MAC key for all users, because we never show that key to the user(s). We want to ensure that the user returns the "authentic" message to the same server instance where he got it from. If a user modifies its given message, we detect this on return, because the MAC doesn't match anymore. Same if the message was returned to the "wrong" server, because different server has other MAC key and so the MAC won't match. Using a separate key per user doesn't improve improve things here, but adds new troubles (session management).
0

I am curious to know why you want to do this? What is the use case? You are asking for an approach that makes your application extremely tightly coupled to a specific setup on a specific web server on a specific platform or at least OS. for example, you can use your root Apache2 process ID:

netstat -lntp | grep apache | awk -F '[/ ]*' '{print $7}'

To get a unique process ID that will be changed with each apache2 restart, but as mentioned, this is a thing that will make you stuck to a specific setup, the previous command will be invoked using exec. For example, the previous command will not work when you run your PHP & Apache2 behind docker, using a function like posix_getppid() may fix this issue, but the function is getting the parent ID, so it is not a thing for Apache2.

On the other hand, relying on a random number/string as keys will make you lose the saved encrypted / hashed data.

Alternatively, you can go with something like saving the key in a tmp file, Database, or Session and use cronjob or scheduled task to reload your key periodically.

2 Comments

Thanks for the idea, but I need a "key" that is unique with very high probability, such as a pseudo-random string of bytes generated by a CSRNG. Something like a process-ID or host IP-address is not sufficient. Generating a random "key" is easy, e.g. with openssl_random_pseudo_bytes(), but making it persistent across multiple script invocation (for the lifetime of a server instance) is the problem! Had to come up with my own solution, as using an "in-memory" database or even a local file would be an unpleasant solution.
I'm not using the "unique" key for long-term encryption/storage, don't worry!. I just need the key to compute a MAC over a piece of information that I will provide to the client. When the client later returns the information to the server, I need to verify that the information actually originated from this specific server instance. So the key really must be unique per server instance. But we don't need to do this verification over more than a few minutes or so, i.e. there is no need to persist the key in the long-term.
-2

Since a satisfying solution was not readily available, I spent the afternoon creating a simple PHP extension that does exactly what I need. Since it may be helpful for others, I'm posting it here.

It generates a unique ID, via getentropy() system call, in the module initialization function, converts it to Base64 string and exposes it to the PHP user script as a constant via REGISTER_STRING_CONSTANT(). That is a straight-forward and efficient way to ensure that the ID is generated exactly once per server instance, when the server is starting up. It is then kept in "static" memory. Accessing the ID as a PHP constant also is much simpler than other approaches 😀

It can be used like this:

<?php
echo 'Unique instance ID: "' . PHP_INSTANCE_UNIQID . '"' . PHP_EOL;
?>
<?php
$unique_key = hash_hkdf('sha256', PHP_INSTANCE_UNIQID_BINARY, 16, 'my-context');
?>

See also:
https://php-uniqueid.bitbucket.io/

I'm still a bit surprised that something like this doesn't exist in PHP already...

6 Comments

Note that it will only work with mod-php. In case of CGI, a new key will be generated for every CGI process.
@Olivier Is "classic" CGI still a thing? I would think its all FastCGI nowadays, where you have a single long-running process that communications with the "main" server process. So that should work fine too.
FastCGI uses a pool of worker processes. In your case, it means that every worker would have its own key.
@Olivier I think that is configuration thing? Anyway, I think I can live with that. After all, if we had several separate worker processes, then the APCu-based approach or any similar solution would face the same "problem", right? And, in my actual project, I don't use (Fast)CGI anyway.
Correct, in an FCGI setup, APCu memory is also not shared across instances.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.