I am really lost in this subject, I find it hard to get any reliable documentation on the subject that is a bit friendly for beginners like me. So I apologize in advance if this question would sound nonsensical.

Anyway, I am implementing encryption in a project using php openssl_encrypt. So far this is what I have:

// encrypt

$dataToEncrypt = 'Hello World';

$cypherMethod = 'AES-256-GCM';
$key = random_bytes(32);
$iv = random_bytes(openssl_cipher_iv_length($cypherMethod));

$encryptedString = openssl_encrypt($dataToEncrypt, $cypherMethod, $key, $options=0, $iv, $tag);

And then I store $encryptedString, $key, $iv, and $tag

// decrypt

$decryptedString = openssl_decrypt($encryptedString, $cypherMethod, $key, $options=0, $iv, $tag);

My questions are:

  1. Is there anything wrong with my current implementation?

  2. Should $key be randomly generated or should I use a single secret key for general use? I am asking this because in some examples I see around the web they are using text strings for the $key. If it is not required for the $key to be random, does it have any improvements to security if it is random?

  3. Is the $tag the message authentication code (MAC)? In the PHP documentation it states:

The authentication tag passed by reference when using AEAD cipher mode (GCM or CCM).

If $tag is the MAC, then does that mean that PHP openssl_decrypt is also handling the authentication process?

Thanks for any help!

  • 1
    $\begingroup$ I'm voting to close this question as off-topic because this should be asked on codereview.SE instead. Please create a MCVE if you do. $\endgroup$
    – Maarten Bodewes
    Dec 30, 2017 at 6:44
  • $\begingroup$ 1. No 2. It should be initially randomized, yes; if it is ephemeral or static is up to you / your protocol 3. yes, it's the MAC and if you can read the documentation you know if it is verified or not (you lazy person, you). -1 for misspelling GCM and not looking at the API or our tutorial for the site. $\endgroup$
    – Maarten Bodewes
    Dec 30, 2017 at 6:48
  • $\begingroup$ @MaartenBodewes Thanks for the heads up about codereview.SE, will definitely remember it next time. Just read the description of tag in openssl_decrypt, answered my question 3.2 if I just read the doc (whoops?). And sorry for the misspelling (lack of sleep?), corrected it! $\endgroup$
    – Jo E.
    Dec 30, 2017 at 14:52

1 Answer 1

  1. This is a question for codereview.se or a PHP forum. As an aside: if you're building a new protocol for your own application, rather than trying to interoperate with an existing application, you will almost certainly be better served by libsodium than by OpenSSL's byzantine API. Even if you have to use older versions of PHP that don't have it as core, you can use sodium_compat from Paragon IE.

    Note that AES-GCM tends to be vulnerable to timing attacks on AES and on GCM in software implementations, and has small limits on the number of messages that can be exchanged with random nonces. So if you're not restricted to interoperation with an existing application, it may be safer to use libsodium's crypto_secretbox_xsalsa20poly1305, or if you have associated data as well, crypto_aead_xchacha20poly1305_ietf. I would suggest a nonce-misuse-resistant scheme, but they are not widely available yet and the world hasn't standardized on one yet.

  2. Your obligations for using AES-GCM are: (a) The key must be uniform random from the perspective of the attacker, and (b) the nonce (here confusingly called ‘IV’) must never be reused with the same key. In exchange, AES-GCM guarantees an active adversary can't learn anything about message content (length and timing are not concealed) or forge messages not previously seen, even if they could adaptively learn the encryption of chosen messages. If you break your side of this contract, then AES-GCM may provide no security to you.

    You can't just set the key down alongside the ciphertext, of course: then the attacker knows what the key is, and it is no longer uniform random from the attacker's perspective. In order for this to work, the peers have to know the key ahead of time. Maybe it's a shared secret obtained by hashing the output of an authenticated Diffie–Hellman key agreement, $H(g^{ab})$; maybe it's a pre-shared secret you pulled out of /dev/urandom on your laptop and copied over an authentic channel to an application server. If you have a password, rather than a uniform random string of 16 or 32 bytes, then you must use a password-based key derivation function such as scrypt or argon2 to derive a key from it.

    You will have to study the documentation to figure out how to supply a 16- or 32-byte key. To confirm your understanding, write an experiment of reproducing an AES-GCM test vector with your understanding of how to supply the bytes of a key exactly, and save that experiment as a unit test.

    The peers have to agree on the nonce to use for each message, too. In the best case, they exchange messages in order, or mostly in order; in that case, you can use message sequence number as a nonce. If this is a bidirectional channel, make sure to use one key for each direction, or odd nonces in one direction but even nonces in the other direction, or similar. If you use message sequence numbers, there is no meaningful limit on the number of messages you can use AES-GCM for. (The limit is $2^{96}$. Exercise: How many centuries are in $2^{96}$ picoseconds?)

    If you can't use a message sequence number as a nonce, you can pick the nonce uniformly at random from $2^{96}$ possibilities (AES-GCM is defined to accept larger nonces, but hashes them into only $2^{96}$ internal possiblities), but random nonces severely limit the number of messages you can exchange—you should avoid using them for more than a billion messages or so, to be on the safe side.

  3. I can't tell you about $tag because again it's a PHP programming question. But it is good that you are paying attention to this, because there are some APIs that inexplicably have a separate step for verifying the authentication tag, which is probably what $tag is. You must never touch message data before you verify the authentication tag. Unauthenticated message data is pure evil—don't touch it! In a sensible API, the AES-GCM decryption operation will throw an exception or return an error code if verification fails, before you can even touch the evil.

    I'd suggest you triple-check your API documentation—but that's not enough, because OpenSSL's documentation is notoriously bad. So write an experiment to flip a bit in a message before you try to decrypt it that confirms it fails, and save that experiment as a unit test.

  • 1
    $\begingroup$ From the openssl_decrypt function for PHP: "The authentication tag in AEAD cipher mode. If it is incorrect, the authentication fails and the function returns FALSE." $\endgroup$
    – Maarten Bodewes
    Dec 30, 2017 at 17:40
  • $\begingroup$ @MaartenBodewes That sounds like a reasonable API design (as long as the function (a) also returns true when the authentication tag is correct, and (b) does the comparison in constant time). But my experience with OpenSSL documentation is such that I don't trust it without an experiment to verify! $\endgroup$ Dec 30, 2017 at 20:42

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