Coded something up in Couch in an interesting way? Have a snippet or shortcode to share? Post it here for the community to benefit.
7 posts Page 1 of 1
When dealing with sensitive customer data, such as Stripe API keys, OpenAI API keys, etc, we need to be careful and only store encrypted data in the database. This tutorial will outline how we encrypt and decrypt messages within the CouchCMS workflow. 8-)

Brief Overview

Encryption is a process that converts readable data (plaintext) into an unreadable format (ciphertext), using a specific key. When it comes to web applications or services, encryption ensures that data transferred between systems or stored in databases remains safe and secure.

encryption.v1.html.zip
(1.01 KiB) Downloaded 127 times
Simply include the ecryption.html file before you call the functions in your code.

IMP: Follow best practices here. Avoid committing the file where the key is held to version control, and consider placing that file above the webroot in case your server is mal-configured (you can never be too cautious). Thanks for the tip @KK.

Requires PHP 7.2+
Read more about PHP's Sodium extension here: https://php.watch/articles/modern-php-e ... ion-sodium

Now, let's see some basic usage of these functions.

Key Generation:
Code: Select all
<!-- create a key and manually store it in couch/config as K_ENCRYPTION_KEY -->
<cms:call 'encrypt-keygen' />
<cms:show keygen-response />

Encryption
Code: Select all
<!-- we can safely store this to the database -->
<cms:call 'encrypt' string='Sensitive Data' />
<cms:show encrypt-response />

Decryption
Code: Select all
<!-- we can check given strings against the decrypted response -->
<cms:call 'decrypt' string='encrypted-string-from-earlier' />
<cms:show decrypt-response />


Now that we've got a sneak peek of how to use these functions, let's dive into the technical details.

- - - - -

Digging Deeper

Our simple toolkit consists of three primary functions built using Couch's native <cms:func> tag - encrypt-keygen, encrypt, and decrypt. Note: you can read more about the <cms:func> tag as introduced here: viewtopic.php?f=8&t=11368&start=10#p30174 and documented here: https://simonwpt.github.io/CouchDocs/ta ... /func.html

1. encrypt-keygen

The encrypt-keygen function generates a new encryption key, a vital element in the encryption and decryption process that you should store somewhere safe, like couch/config.php.

Code: Select all
<cms:func 'encrypt-keygen' into='keygen-response' scope='global'>
    <cms:php>
        global $CTX;
        $key = sodium_crypto_secretbox_keygen();
        $hex_key = sodium_bin2hex($key);
        $CTX->set("<cms:show into />", $hex_key, "<cms:show scope />");
    </cms:php>
</cms:func>

This function generates a new key using the PHP Sodium library's 'sodium_crypto_secretbox_keygen' function and then transforms this binary key into a hex string with 'sodium_bin2hex'. It's important to manually take this generated key and place it in the couch/config.php file under the K_ENCRYPTION_KEY constant.

2. encrypt

The encrypt function takes in a string and an encryption key, performing the actual encryption.

Code: Select all
<cms:func 'encrypt' string='' into='encrypt-response' scope='global'>
    <cms:php>
        global $CTX;
        $message = "<cms:show string />";
        $key = sodium_hex2bin(K_ENCRYPTION_KEY);
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $ciphertext = sodium_crypto_secretbox($message, $nonce, $key);
        $encrypted = base64_encode($nonce . $ciphertext);
        $CTX->set("<cms:show into />", $encrypted, "<cms:show scope />");
    </cms:php>
</cms:func>

This function retrieves the key, generates a nonce (Number Used Once), and uses these alongside the 'sodium_crypto_secretbox' function to encrypt the message.

3. decrypt

Finally, the 'decrypt' function takes an encrypted string and the key, returning the original plaintext data.

Code: Select all
<cms:func 'decrypt' string='' into='decrypt-response' scope='global'>
    <cms:php>
        global $CTX;
        $key = sodium_hex2bin(K_ENCRYPTION_KEY);
        $nonceFromEncrypted = mb_substr(base64_decode("<cms:show string />"), 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
        $ciphertextFromEncrypted = mb_substr(base64_decode("<cms:show string />"), SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
        $decrypted = sodium_crypto_secretbox_open($ciphertextFromEncrypted, $nonceFromEncrypted, $key);
        if ($decrypted === false) {
            throw new Exception('The message was tampered with in transit');
        }
        $CTX->set("<cms:show into />", $decrypted, "<cms:show scope />");
    </cms:php>
</cms:func>


Wrapping Up

1. Use 'encrypt-keygen' function to create a valid key and manually store in our database
2. Use 'encrypt' function to encrypt and safely store encrypted strings in our database
3. Use 'decrypt' to decrypt encrypted strings from the database and check them against provided strings.

By integrating these cryptography functions into our CouchCMS workflows, we can protect sensitive data. Very handy and responsible! :)

If you have any thoughts or suggestions, I'd love to hear them!

Happy Couch-ing
:shock: :shock: :shock:

Did you simply trust yourself that you can make it right?

and place it in the couch/config.php file under the K_ENCRYPTION_KEY constant


They call it Improper cryptographic key management in https://crashtest-security.com/owasp-cr ... -failures/

CouchCMS, for example, keeps KEYS in database for a reason.

Did you talk to anyone about this? :?
@anton, I am no security expert but anyway here is my understanding on this subject -

'symmetrical' keys (the kind that Couch uses and stores in database) are best suited when the data is encrypted and decrypted by the same agent (Couch, in this case) e.g. Couch uses this for encrypting 'cloaked links' that are made public but when such a link is clicked it is decrypted by the same Couch instance to get back the original link.

In such a case storing the key in the database is fine because the encrypted data (e.g. the cloaked link) is never stored in the same database.

Had the encrypted data been stored in the database, storing the key in the same database would yield no security benefit - in case of a data breach (the database gets exposed), both the encrypted data and the encryption keys get exposed.

I think @mwlarkin1's solution is geared towards actually storing the encrypted data in database so keeping the key elsewhere (in a file) shouldn't be a bad idea as such.

Problem with keeping keys in a file is that the chances of exposing them are greater here e.g.
a. It is often seen that people commit such files to public git repos thus exposing their API keys
b. if the file is within the web root (as is the couch/config) file, then mal-configured servers (e.g. showing PHP files as plain-text files) could divulge them.

The recommended way here is to keep the file somewhere 'above' the web-root and then use PHP to read in that file.

Please let me know if you think I am missing something obvious in coming to this conclusion.
@KK, your posting is certainly welcome. I am no security expert, that is why twice I pointed out that the code/approach dealing with sensitive matters can not be trusted as a solution here, unless validated by someone else, other than the dev, preferrably a security expert. In your post I do not see any kind of approval either; so I still re-address my question to @matt if he had designed all this by himself without talking to anyone.

I'd prefer an expert's validation in the post saying that this solution is acceptably good.

The recommended way here is to keep the file somewhere 'above' the web-root and then use PHP to read in that file.

That's somewhat better than proposed solution with config file.

The example where Couch stores its keys in db is for illustration that there is reasoning behind the chosen solution. I have not seen in the original post any reasoning whatsoever, but a very vague description of what it can be used for. For instance, a quote that raised my eyebrows — we need to be careful and only store encrypted data in the database. Why? @matt did not provide any reasoning for this sensitive choice. I appreciate @KK stepping in to try fix the hole, but apparently you can't say the solution is good? Also, the choice of <cms:func> instead of a tag, plus storing open values in vairables (context) opens another door to read values, now from memory.
Hey @anton and @KK, thanks for chiming in! And thank you for the concern and taking security seriously. This keeps us all in check and helps us reflect and become better programmers; thank you both!!

Regarding writing it myself
Definitely not. :) I'm using Libsodium, which is a 'super opinionated' built-in PHP extension. From what I can tell, it's industry-standard, and, more importantly, because of its opinionated nature, it makes making bad choices during the actual encryption process hard to do (which, I believe, is where most folks go wrong when writing it themselves). Instead, I'm letting Libsodium make all of those decisions.

I referenced a short article on Libsodium in the initial post, but here are some other videos I recommend:
- a very easy and brief overview
- a very detailed overview (given by the author of phpmailer :) )

Regarding where to store the key
Anton, yes, the reason for not storing the encrypted values in the database is for the reason @KK pointed out. That is, if someone got hold of the database values, the encryption is essentially broken since the key is there too. Keeping the values in the database and the key in the config.php file should increase security here. Another measure I'm looking into would be to re-generate and expire keys (circulating old ones out). Have to be careful there though, and it should probably involve some manual interaction. I have to study this more.

In the meantime, the best practices mentioned by @KK, 'not storing the key in version control' and storing the file 'above the webroot', should be followed.

Key in memory in <cms:func>
This is insightful, thank you for pointing that out @anton! My reasoning was putting it there (as a default value, key="<cms:php>echo(K_ENCRYPTION_KEY);</cms:php>", was to have an easy default, but passing in the value during runtime is probably a better approach, as it wouldn't store the cryptographically relevant part in memory. I'll ahead and edit the original post.

The other parts within the <cms:func> are purely instructional, so I think that should be fine in memory.

- - -

Yes, I'm in talks with a couple security expert friends of mind, and they know of libsodium. In particular, I plan on looping back here with their thoughts on the points brought up about memory, database vs file storage, perhaps putting the database on its own server to enhance security. I'll follow up here.

- - -

By the way @anton, I believe you'd find the in depth talk particularly insightful, as I recall it having a potentially robust solution to your "challenge" a few months ago, where you wanted to ensure that "[some field] can be saved with something only once but never changes its value again during subsequent page saves" and truly authenticate that it didn't change. It's mentioned around mid-way through, I think, around the point where they're talking about detecting tampering of data during transit. In fact, I think it could be accomplished using the functions I provided above. Perhaps I'll chime back in on that if I fine something interesting there.
Oh, and by the way, the intention here is to store arbitrary sensitive user data in the database that our application can still make use of.

For instance, this data may be Stripe API or OpenAI API keys (which I mentioned in the initial post). Storing these as plaintext in the database would be poor practice. Hashing them (like we do with passwords) won't really work because our application needs to also make subsequent use of the stored values.

So, the symmetrical key approach seems best suited here, as we'd be encrypting and decrypting via the same application. Here, the primary reason for encryption is to serve as a fallback security measure in case the database values themselves get leaked via SQL injection.

The server itself being compromised would indeed be very bad. This would mean both the database and config.php file would be compromised, and I'm not familiar with ways of enhancing security there. But my security friends probably have some insight, so I'll share what I find.
@matt, I will look through the links, thanks. One clarification about my memory suggestion — do you see with me an unnesessary placement of newly generated key into context? Here I would suggest to simply echo the keygen following the <cms:call> (or <cms:call_ex> which additionally trims the output - a new core tag from Couch). It's about the func 'encrypt-keygen' which stores key into='keygen-response'. Losing the extra variable here removes context-stored keys, which is important as the context is available freely for any 3rd-party addon. 8-)

Have you considered tags? Tags are very easy to use and require less writing e.g. <cms:generate_key />; <cms:encrypt 'me' />; <cms:decrypt 'AaZz' />. I know you can do them, so it's interesting why you chose to have funcs + extra vars. Thank you for your work! :)
7 posts Page 1 of 1