Astute readers have noticed that our blog posts have decreased in frequency this year. We put a heavy emphasis on quality, not so much on quantity. At the same time, we field a *lot* of questions on social media, where our answers (and, sometimes, the questions themselves) are difficult to locate, especially when people close or lock their accounts. With both of these thoughts in mind, I asked my Twitter followers [if they'd be interested in a Q&A-style blog series](https://twitter.com/CiPHPerCoder/status/1033756295503986689). I expected maybe a 55:45 split on yes/no responses, but the final tally was overwhelmingly "Yes". So with that in mind, I'd like to introduce the pilot for our new series, *Slice of PIE*.
<?php
/* Don't actually use this code! It's to demonstrate a concept. */
$public_key = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyaTgTt53ph3p
5GHgwoGWwz5hRfWXSQA08NCOwe0FEgALWos9GCjNFCd723nCHxBtN1qd
74MSh/uN88JPIbwxKheDp4kxo4YMN5trPaF0e9G6Bj1N02HnanxFLW+g
mLbgYO/SZYfWF/M8yLBcu5Y1Ot0ZxDDDXS9wIQTtBE0ne3YbxgZJAZTU
5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAApVRuUI2Sd6L1E2vl9bSBumZ5
IpNxkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6alUyhKC1+1w/FW6HWc
p/JG1kKC8DPIidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8owIDAQAB
-----END PUBLIC KEY-----';
// The letter "A" repeated 246 times:
$msg = str_repeat('A', 246);
// Encrypt
$ciphertext = '';
$encrypted = openssl_public_encrypt($msg, $ciphertext, $public_key); // Note: Insecure padding mode by default
var_dump(bin2hex($ciphertext));
The above script fails. It will print out `string(0) ""` to your console. Now change the `246` to an integer between 1 and 245, and you'll be greeted with a string of 512 hex characters.
Some libraries work around this limitation by breaking the input message into distinct blocks and encrypting them separately, similar to [ECB mode](https://blog.filippo.io/the-ecb-penguin). Not only is this *very slow*, it's also dangerous.
Since each block is encrypted separately, you can simply reorder ciphertext blocks and the changes will be reflected in the decrypted plaintext.
That's the danger of directly encrypting messages with RSA. There are other asymmetric cryptography algorithms that have their own security considerations, but generally, you want a hybrid cryptosystem.
**Hybrid encryption** involves encrypting each message with symmetric cryptography (e.g. AES-GCM), then encrypting the key asymmetric cryptography (rather than encrypting the message).
Since most symmetric encryption algorithms call for, at most, 256-bit keys (which is 32 bytes, far below the maximum 245 byte message that 2048-bit RSA allows), this tends to work out better for security *and* performance.
See, for example, [Zend\Crypt's Hybrid encryption documentation](https://github.com/zendframework/zend-crypt/blob/d67dd451489583966bd606b321fdf43ebddcb580/docs/book/hybrid.md).
#### What algorithms should developers use?
Avoid RSA, [unless you absolutely must support it](https://paragonie.com/blog/2018/04/protecting-rsa-based-protocols-against-adaptive-chosen-ciphertext-attacks).
Our general recommendation is to use [libsodium's crypto_box_seal API](https://paragonie.com/blog/2017/06/libsodium-quick-reference-quick-comparison-similar-functions-and-which-one-use#crypto-box-seal).
You don't even need to know what libsodium does here in order to use it safely. That's the entire point of libsodium.
But for the sake of completeness, the crypto_box_seal algorithm looks roughly like this:
1. Generate a random X25519 secret key and its associated public key.
2. Hash the public key from step 1 with the recipient's public key, using BLAKE2b, to get the nonce.
3. Perform an Elliptic Curve Diffie-Hellman key exchange between the random secret key and the recipient's public key.
4. Encrypt the message with XSalsa20-Poly1305 using the HSalsa20 hash of the ECDH output in step 3 as the message key, and the nonce from step 2.
5. Prepend the public key from step 1 to the ciphertext output from step 4.
If it helps, also look at [how `sodium_crypto_box_seal()` is implemented in sodium_compat](https://github.com/paragonie/sodium_compat/blob/aaaf71f3f7713248703c23cfbd1189ce1215f57f/src/Crypto.php#L438-L482).