1

Let's say I have a website where people keep a personal diary (diary content type). I have configured permissions so that each user can only read, write, and view his/her own diary.

Users can create and submit several different content types on the site; only the diary content type needs to be private.

I would like to encrypt diaries per user, ideally by tying the encryption to the user's name and password.

I surveyed the encryption modules available for Drupal, and here are the only ones with 300+ installs:

  • Encrypt: makes use of Real AES module and the Key module
  • Encryption: simpler to configure than Encrypt, single module
  • Dbee, which encrypts just the email addresses in the database--nifty

In addition, there is the Pubkey Encrypt module, which is an abandoned Drupal 8 module that allows encryption of data per user role.

So, to get encryption per user, I guess I would have to write this myself.

How can I encrypt content per user for a specific content type?

If this is really hard to do, I would also like to hear an answer that simply explains why this problem is harder than I think it is.

3
  • 1
    There is actually a talk on this topic on the Drupal developer days: drupalcamp.be/en/drupal-dev-days-2022/session/… (livestream should be available in a few days) Commented Apr 6, 2022 at 16:58
  • I'm missing a bit that piece of requirement why access/permissions are not sufficient here.
    – leymannx
    Commented Sep 25, 2022 at 19:13
  • @leymannx I also set up access permissions. However, encryption of important data at rest is an additional safeguard that I would like to offer my users. Commented Sep 26, 2022 at 0:47

2 Answers 2

2

The problem here isn't the encryption itself, it's easy enough to symmetrically encrypt some data before saving it to the database, it's the decryption.

In order to decrypt the data you need the key, which means you need to keep the user's password somewhere in plain text, or ask them for their password every time they want to see their "diary". Or you could decrypt it on login and leave it decrypted for the session somehow.

The other issue here is that it's not true end to end encryption. At some point in the process you will know the user's key unless you do the encryption on their computer.

0

So ultimately I decided on implementing a less-secure but "better than nothing" approach.

I evaluated the Field Encrypt module but it can only use a single key to encrypt all fields, and I wanted per-user keys to encrypt each user's fields.

So, I stored each user's encryption key in a field, and then encrypted that field with Field Encrypt. (Because the encryption keys for users are stored in the db, this is not a high level of security, but encrypted db entries are better than "completely in the open", which is the Drupal default.)

The following is only example code to give the reader an idea of how to solve this.

Based on code in the dbee module, I generated a key per user:

  /**
   * Generate a Real AES encryption key.
   *
   * Adapted from the dbee module.
   *
   * @param int $bytes
   *   An integer, the number of bytes for the key. Default to 16 (=128bits).
   *
   * @return string
   *   The encryption key in hexadecimal.
   */
  private function generateEncryptionKey(int $bytes = 16): string {
    return bin2hex(random_bytes($bytes));
  }

Then, based on the Real AES module, I added encrypt and decrypt methods to my user bundle:

  /**
   * Returns the user's encryption key.
   *
   * @return \Defuse\Crypto\Key
   *   The user's encryption key.
   */
  public function getEncryptionKey(): Key {
    $user_key = 'abc'; // Add code to get from appropriate field.
    // Defuse PHP-Encryption requires a key object instead of a string.
    $checksummed_key = Encoding::saveBytesToChecksummedAsciiSafeString(Key::KEY_CURRENT_VERSION, $user_key);
    return Key::loadFromAsciiSafeString($checksummed_key);
  }

  public function encryptContent(string $text_to_encrypt): string {
    $key = $this->getEncryptionKey();
    return Crypto::encrypt($text_to_encrypt, $key);
  }

Then, in hook_entity_presave(), I call the encryptContent() function.

For decryption, I use hook_entity_view() to decrypt only when appropriate.

Your Answer

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

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