==================
Encryption details
==================

This document - provided by `SysEleven <https://syseleven.de>`_ - describes the server-side encryption scheme implemented by Nextcloud's default encryption module. This includes:

- the encryption and signature of files with a master key.
- the encryption and signature of files with a public sharing key.
- the encryption and signature of files with a recovery key.
- the encryption and signature of files with a user key.

These conventions apply throughout this document:

- Given file paths in this document are relative to the Nextcloud data directory that can be retrieved as ``datadirectory`` from the ``config.php``.
- Placeholders are denoted as ``$variable``. The variable has to be replaced with the appropriate information.
- Static strings are denoted as ``"some string"``.
- The concatenation of strings is denoted as ``$variable."some string"``.

.. note:: This document describes the server-side encryption scheme as implemented by Nextcloud 16. Previous versions of Nextcloud implemented slightly different schemes which Nextcloud still supports for backwards compatibility. Files that have been encrypted by a recent version of Nextcloud should follow the structure documented below. However, files that have been encrypted by previous versions of Nextcloud may have slightly different structures.

Key type: master key
--------------------

While the master key encryption had to be enabled explicitly by calling ``./occ encryption:enable-master-key`` in older versions of Nextcloud it is now the default encryption mode in newer versions including Nextcloud 16. With master key encryption enabled there is one central key that is used to secure the files handled by Nextcloud. The master key is protected by a password that can be generated by the server administrator. The advantage of the master key encryption is that the encryption is transparent to the users but has the disadvantage that the server administrator is able to decrypt user files without knowing any user password.

Key type: public sharing key
----------------------------

The public sharing key is used to secure files that have been publicly shared. The public sharing key is protected by a password that can be generated by the server administrator. The advantage of the public sharing key is that it is independent of the selected encryption mode so that Nextcloud is able to provide publicly shared files to outside parties.

Key type: recovery key
----------------------

The recovery key is used to provide a restore mechanism in cases where the user key encryption is enabled, where the administrator has enabled the recovery key feature and the user has opted into using the recovery key feature. The recovery key can then be used to restore files when users have lost their passwords. The recovery key is protected by a recovery password that the server administrator should store securely. The advantage of the recovery key is that files can be recovered but has the disadvantage that the server administrator is able to decrypt user files without knowing any user password.

Key type: user key
------------------

While the user key encryption has been enabled by default in older versions of Nextcloud it now has to be enabled explicitly in newer versions including Nextcloud 16 by calling ``./occ encryption:disable-master-key``. With user key encryption enabled all users have their own user keys that are used to secure the files handled by Nextcloud. The user keys are protected by the user passwords. The advantage is that the server administrator is not able to decrypt user files without knowing any user password - unless the file is publicly shared or a recovery key is defined - but has the disadvantage that files are permanently lost if the users forget their user passwords - unless the files are (publicly) shared or a recovery key is defined.

.. _file_type_public_key_file_label:

File type: public key file
--------------------------

Public key files contain RSA public keys that are used to encrypt/seal the share key files.

File format
^^^^^^^^^^^

Public key files are stored in PEM format.

File locations
^^^^^^^^^^^^^^

The locations of public key files depend on their key type:

- master public key: ``"files_encryption/OC_DEFAULT_MODULE/master_".$random.".publicKey"``
- public sharing public key: ``"files_encryption/OC_DEFAULT_MODULE/pubShare_".$random.".publicKey"``
- recovery public key: ``"files_encryption/OC_DEFAULT_MODULE/recoveryKey_".$random.".publicKey"``
- user public key: ``$username."/files_encryption/OC_DEFAULT_MODULE/".$username.".publicKey"``

.. _file_type_private_key_file_label:

File type: private key file
---------------------------

Private key files contain RSA private keys that are used to decrypt/unseal the share key files. The RSA private key is encrypted and signed with a password and stored in a format that is specific to the Nextcloud encryption module.

File format
^^^^^^^^^^^

The RSA private key that is represented in PEM format is encrypted and Base64 encoded (denoted as ``$encryption``). For the encryption an initialization vector of 16 bytes is selected (denoted as ``$iv``). Furthermore a hexadecimally encoded message authentication code of 64 bytes is calculated (denoted as ``$signature``). The resulting file contains::

 "HBEGIN:cipher:AES-256-CTR:keyFormat:hash:HEND".
 $encrypted."00iv00".$iv."00sig00".$signature."xxx"

File locations
^^^^^^^^^^^^^^

The locations of private key files depend on their key type:

- master private key: ``"files_encryption/OC_DEFAULT_MODULE/master_".$random.".privateKey"``
- public sharing private key: ``"files_encryption/OC_DEFAULT_MODULE/pubShare_".$random.".privateKey"``
- recovery private key: ``"files_encryption/OC_DEFAULT_MODULE/recoveryKey_".$random.".privateKey"``
- user private key: ``$username."/files_encryption/OC_DEFAULT_MODULE/".$username.".privateKey"``

.. _file_type_share_key_file_label:

File type: share key file
-------------------------

Share key files contain so-called envelope keys that are needed to decrypt the file key files. The envelope keys are created by ``openssl_seal()`` during the encryption and are needed for ``openssl_open()`` during the decryption. The envelope keys are encrypted with the public keys of the recipients that are allowed to read the actual files.

File format
^^^^^^^^^^^

The envelope keys are stored in binary format.

File locations
^^^^^^^^^^^^^^

The locations of share key files depend on the type of the encrypted file:

- regular file: ``$username."/files_encryption/keys/files/".$filename."/OC_DEFAULT_MODULE/".$recipient.".shareKey"``
- version file: *version files use the same location for the share key file as their regular file*
- trashed file: ``$username."/files_encryption/keys/files_trashbin/files/".$filename.".d".$timestamp."/OC_DEFAULT_MODULE/".$recipient.".shareKey"``
- trashed version file: *trashed version files use the same location for the share key file as their trashed file*

.. _file_type_file_key_file_label:

File type: file key file
------------------------

File key files contain symmetric keys used to encrypt the actual files. The file keys consist of 32 random bytes and are encrypted/sealed with the envelope keys stored in the share key files.

File format
^^^^^^^^^^^

The file keys are stored in binary format.

File locations
^^^^^^^^^^^^^^

The locations of the file key files depend on the type of the encrypted file:

- regular file: ``$username."/files_encryption/keys/files/".$filename."/OC_DEFAULT_MODULE/fileKey"``
- version file: *version files use the same location for the file key file as their regular file*
- trashed file: ``$username."/files_encryption/keys/files_trashbin/files/".$filename.".d".$delete_timestamp."/OC_DEFAULT_MODULE/fileKey"``
- trashed version file: *trashed version files use the same location for the file key file as their trashed file*

.. _file_type_file_label:

File type: file
---------------

Files contain the actual file content. The file content is encrypted and signed with a password and stored in a format that is specific to the Nextcloud encryption module.

File format
^^^^^^^^^^^

The file content is split into blocks of 6072 bytes. Each block is encrypted and Base64 encoded (denoted as ``$encryption[0..$n]``). For the encryption an initialization vector of 16 bytes is selected for each block (denoted as ``$iv[0..$n]``). Furthermore a hexadecimally encoded message authentication code of 64 bytes is calculated of each block (denoted as ``$signature[0..$n]``). An encrypted block has a total size of 8192 bytes (8096 bytes for ``$encrypted[]``, 6 bytes for ``"00iv00"``, 16 bytes for ``$iv[]``, 7 bytes for ``"00sig00"``, 64 bytes for ``$signature[]`` and 3 bytes for ``"xxx"``). Only the last encrypted block may be shorter. The header of the encrypted file is padded with 8147 bytes of ``"-"`` (denoted as ``$padding``) to a total of 8192 bytes. The resulting file contains::

 "HBEGIN:cipher:AES-256-CTR:keyFormat:hash:HEND".$padding.
 $encrypted[0]."00iv00".$iv[0]."00sig00".$signature[0]."xxx".
 $encrypted[1]."00iv00".$iv[1]."00sig00".$signature[1]."xxx".
 $encrypted[2]."00iv00".$iv[2]."00sig00".$signature[2]."xxx".
 [...]
 $encrypted[$n]."00iv00".$iv[$n]."00sig00".$signature[$n]."xxx"

File locations
^^^^^^^^^^^^^^

The locations of the files depend on the type of the encrypted file:

- regular file: ``$username."/files/".$filename``
- version file: ``$username."/files_versions/".$filename.".v".$version_timestamp``
- trashed file: ``$username."/files_trashbin/files/".$filename.".d".$delete_timestamp``
- trashed version file: ``$username."/files_trashbin/versions/".$filename.".v".$version_timestamp.".d".$delete_timestamp``

Key generation: generate the key pair
-------------------------------------

The key pair has to be generated with the ``openssl_pkey_new()`` function. Then the private key and public key are extracted from the the key resource with the ``openssl_pkey_export()`` function.

Key generation: store the public key
------------------------------------

The public key is written to the ``$username.".publicKey"`` file as documented in :ref:`file_type_public_key_file_label`.

Key generation: store the private key
-------------------------------------

Derive the encryption key
^^^^^^^^^^^^^^^^^^^^^^^^^

The salt for the encryption key is derived by creating a raw SHA256 hash of ``$uid.$instanceId.$instanceSecret`` with the ``hash()`` function. ``$instanceId`` can be retrieved as ``instanceid`` from the ``config.php``. ``$instanceSecret`` can be retrieved as ``secret`` from the ``config.php``.

The encryption key is then derived by creating a raw SHA256-PBKDF2 hash of the password with the salt, 100.000 rounds and (by default) with a target size of 32 bytes (as required for AES-256-CTR) with the ``hash_hmac()`` function (denoted as ``$passphrase``).

The used password depends on the key type:

- master private key: use ``secret`` from the ``config.php``
- public sharing private key: use an empty password
- recovery private key: use the recovery password
- user private key: use the user password

Encrypt the private key
^^^^^^^^^^^^^^^^^^^^^^^

The initialization vector is generated as a random string of 16 bytes with the ``random_bytes()`` function (denoted as ``$iv``). The private key is (by default) AES-256-CTR encrypted with the ``$iv`` and the ``$passphrase`` with the ``openssl_encrypt()`` function and returned as Base64 encoded without zero-padding (denoted as ``$encrypted``).

Sign the private key
^^^^^^^^^^^^^^^^^^^^

The message authentication key is derived by creating a raw SHA512 hash of ``$passphrase.$version.$position."a"`` with the ``hash()`` function.

- ``$version`` is always ``"0"``.
- ``$position`` is always ``"0"``.

The signature is then derived by creating a hexadecimally encoded SHA256-HMAC of ``$encrypted`` and the message authentication key with the ``hash_hmac()`` function (denoted as ``$signature``).

Store the private key
^^^^^^^^^^^^^^^^^^^^^

The private key is written to the ``$username.".privateKey"`` file with the derived ``$encrypted``, ``$iv`` and ``$signature`` as documented in :ref:`file_type_private_key_file_label`.

Encryption: generate the file key
---------------------------------

Generate the file key
^^^^^^^^^^^^^^^^^^^^^

The file key is generated as a random string of 32 bytes with the ``random_bytes()`` function (denoted as ``$filekey``).

Read the public key
^^^^^^^^^^^^^^^^^^^

The public keys of the recipients are read from the ``$username.".publicKey"`` files as documented in :ref:`file_type_public_key_file_label`.

Encrypt/seal the file key
^^^^^^^^^^^^^^^^^^^^^^^^^

The file key is encrypted/sealed with the ``openssl_seal()`` function with the public keys. This returns the encrypted file key and the encrypted envelope keys for the recipients.

Store the file key
^^^^^^^^^^^^^^^^^^

The encrypted file key is stored in the ``"fileKey"`` file as documented in :ref:`file_type_file_key_file_label`.

Store the envelope keys
^^^^^^^^^^^^^^^^^^^^^^^

The encrypted envelope keys for the recipients are stored in the ``$username.".shareKey"`` files as documented in :ref:`file_type_share_key_file_label`.

Encryption: encrypt the file
----------------------------

Split the file
^^^^^^^^^^^^^^

The file is split into 6072 bytes sized blocks. Only the last encrypted block may be shorter. Each block is referenced by its zero-based index within the file (denoted as ``$position``).

Encrypt the blocks
^^^^^^^^^^^^^^^^^^

For each block the initialization vector is generated as a random string of 16 bytes with the ``random_bytes()`` function (denoted as ``$iv[$position]``). The block is (by default) AES-256-CTR encrypted with the ``$iv[$position]`` and the ``$filekey`` with the ``openssl_encrypt()`` function and returned as Base64 encoded without zero-padding (denoted as ``$encrypted[$position]``).

Sign the blocks
^^^^^^^^^^^^^^^

The message authentication key is derived by creating a raw SHA512 hash of ``$filekey.$version.$position."a"`` with the ``hash()`` function.

- ``$version`` is the ``encrypted`` value that can be retrieved from the ``oc_filecache`` table in the database and must not be zero. Take into account that a file in the ``oc_filecache`` table is identified by its ``path`` value as well as its ``storage`` value which references the ``numeric_id`` field in the ``oc_storages`` table. Including ``$version`` into the message authentication key prevents blocks from being swapped between different versions of the same file.
- ``$position`` is the index of the current block starting at ``"0"`` and is appended with ``"end"`` for the last block of the file. Including ``$position`` into the message authentication key prevents blocks from being swapped within the same file. Furthermore, adding ``"end"`` to the message authentication key of the last block prevents file truncation attacks.

The signature is then derived by creating a hexadecimally encoded SHA256-HMAC of ``$encrypted[$position]`` and the message authentication key with the ``hash_hmac()`` function (denoted as ``$signature[$position]``).

Store the file
^^^^^^^^^^^^^^

The encrypted file is written to the file with the derived ``$encrypted[0..$n]``, ``$iv[0..$n]`` and ``$signature[0..$n]`` as documented in :ref:`file_type_file_label`.

Decryption: read the private key
--------------------------------

Read the private key file
^^^^^^^^^^^^^^^^^^^^^^^^^

The private key is read from the ``$username.".privateKey"`` file and the values ``$encrypted``, ``$iv`` and ``$signature`` are parsed as documented in :ref:`file_type_private_key_file_label`.

Derive the decryption key
^^^^^^^^^^^^^^^^^^^^^^^^^

The salt for the decryption key is derived by creating a raw SHA256 hash of ``$uid.$instanceId.$instanceSecret`` with the ``hash()`` function. ``$instanceId`` can be retrieved as ``instanceid`` from the ``config.php``. ``$instanceSecret`` can be retrieved as ``secret`` from the ``config.php``.

The decryption key is then derived by creating a raw SHA256-PBKDF2 hash of the password with the salt, 100.000 rounds and (by default) with a target size of 32 bytes (as required for AES-256-CTR) with the ``hash_hmac()`` function (denoted as ``$passphrase``).

The used password depends on the key type:

- master private key: use ``secret`` from the ``config.php``
- public sharing private key: use an empty password
- recovery private key: use the recovery password
- user private key: use the user password

Check the signature
^^^^^^^^^^^^^^^^^^^

The message authentication key is derived by creating a raw SHA512 hash of ``$passphrase.$version.$position."a"`` with the ``hash()`` function.

- ``$version`` is always ``"0"``.
- ``$position`` is always ``"0"``.

The signature is then derived by creating a hexadecimally encoded SHA256-HMAC of ``$encrypted`` and the message authentication key with the ``hash_hmac()`` function. Only proceed when the derived signature is equal to `$signature` which is checked with the ``hash_equals()`` function.

Decrypt the private key
^^^^^^^^^^^^^^^^^^^^^^^

The private key is (by default) AES-256-CTR decrypted with the ``$iv`` and the ``$passphrase`` with the ``openssl_decrypt()`` function.

Decryption: read the file key
-----------------------------

Read the file key
^^^^^^^^^^^^^^^^^

The encrypted file key is read from the ``"fileKey"`` file as documented in :ref:`file_type_file_key_file_label`.

Read the envelope key
^^^^^^^^^^^^^^^^^^^^^

The encrypted envelope key for the recipient is read from the ``$username.".shareKey"`` file as documented in :ref:`file_type_share_key_file_label`.

Decrypt/unseal the file key
^^^^^^^^^^^^^^^^^^^^^^^^^^^

The encrypted file key is decrypted/unsealed with the ``openssl_open()`` function with the private key and encrypted envelope key for the recipient (denoted as ``$filekey``).

Decryption: decrypt the file
----------------------------

Split the file
^^^^^^^^^^^^^^

The encrypted file is split into a 8192 bytes sized header and one or more 8192 bytes sized blocks. Only the last encrypted block may be shorter. Each block is referenced by its zero-based index within the file (denoted as ``$position``). The values ``$encrypted[0..$n]``, ``$iv[0..$n]`` and ``$signature[0..$n]`` are parsed as documented in :ref:`file_type_file_label`.

Check the block signatures
^^^^^^^^^^^^^^^^^^^^^^^^^^

The message authentication key is derived by creating a raw SHA512 hash of ``$filekey.$version.$position."a"`` with the ``hash()`` function.

- ``$version`` is the ``encrypted`` value that can be retrieved from the ``oc_filecache`` table in the database and must not be zero. Take into account that a file in the ``oc_filecache`` table is identified by its ``path`` value as well as its ``storage`` value which references the ``numeric_id`` field in the ``oc_storages`` table. Including ``$version`` into the message authentication key prevents blocks from being swapped between different versions of the same file.
- ``$position`` is the index of the current block starting at ``"0"`` and is appended with ``"end"`` for the last block of the file. Including ``$position`` into the message authentication key prevents blocks from being swapped within the same file. Furthermore, adding ``"end"`` to the message authentication key of the last block prevents file truncation attacks.

The signature is then derived by creating a hexadecimally encoded SHA256-HMAC of ``$encrypted[$position]`` and the message authentication key with the ``hash_hmac()`` function. Only proceed when the derived signature is equal to ``$signature[$position]`` which is checked with the ``hash_equals()`` function.

Decrypt the blocks
^^^^^^^^^^^^^^^^^^

Each block is (by default) AES-256-CTR decrypted with the ``$iv[$position]`` and the ``$filekey`` with the ``openssl_decrypt()`` function.

Sources
-------

- `nextcloud-tools repository on GitHub <https://github.com/syseleven/nextcloud-tools>`_
- `Nextcloud Encryption Configuration documentation <https://docs.nextcloud.com/server/stable/admin_manual/configuration_files/encryption_configuration.html>`_
- `Nextcloud Help response concering the usage of version information <https://help.nextcloud.com/t/allow-file-decryption-with-only-the-files-keys-and-passwords/436/12>`_
- `Overview of ownCloud Encryption Model <https://owncloud.com/wp-content/uploads/2015/07/Overview_of_ownCloud_Encryption_Model_2.2.pdf>`_
- `Sourcecode: Creation of the Message Authentication Code <https://github.com/nextcloud/server/blob/a374d8837d6de459500e619cf608e0721ea14574/apps/encryption/lib/Crypto/Crypt.php#L504>`_
- `Sourcecode: Derivation of the Encryption Key <https://github.com/nextcloud/server/blob/a374d8837d6de459500e619cf608e0721ea14574/apps/encryption/lib/Crypto/Crypt.php#L346>`_
- `Sourcecode: Encryption of the File <https://github.com/nextcloud/server/blob/a374d8837d6de459500e619cf608e0721ea14574/apps/encryption/lib/Crypto/Crypt.php#L234>`_
- `Sourcecode: Encryption/Sealing of the File Key <https://github.com/nextcloud/server/blob/a374d8837d6de459500e619cf608e0721ea14574/apps/encryption/lib/Crypto/Crypt.php#L686>`_
- `Sourcecode: Extraction of the Private and Public Key <https://github.com/nextcloud/server/blob/a374d8837d6de459500e619cf608e0721ea14574/apps/encryption/lib/Crypto/Crypt.php#L124>`_
- `Sourcecode: Generation of the File Key <https://github.com/nextcloud/server/blob/a374d8837d6de459500e619cf608e0721ea14574/apps/encryption/lib/Crypto/Crypt.php#L645>`_
- `Sourcecode: Generation of the Initialization Vector <https://github.com/nextcloud/server/blob/a374d8837d6de459500e619cf608e0721ea14574/apps/encryption/lib/Crypto/Crypt.php#L634>`_
- `Sourcecode: Generation of a Key Pair <https://github.com/nextcloud/server/blob/a374d8837d6de459500e619cf608e0721ea14574/apps/encryption/lib/Crypto/Crypt.php#L153>`_
