Percona Server for MySQLImagine you need to design an online system for storing documents on a per-user basis where nobody, including database administrators, would be able to change the content of those documents without being noticed by document owners.

In Percona Server for MySQL 8.0.28-20, we added a new component called Encryption UDFs – an open-source alternative to MySQL Enterprise Encryption that allows users to access a number of low-level OpenSSL encryption primitives directly from MySQL. This includes calculating digests (with a great variety of hash functions), asymmetric key generation (RSA, DSA), asymmetric encryption/decryption for RSA, and calculating/verifying digital signatures (RSA, DSA) as well as primitives for working with Diffie-Hellman (DH) key exchange algorithm.

Prerequisites

In contrast to MySQL Enterprise Encryption, to make the functions available, users of Percona Server for MySQL do not have to register them manually with

for each individual function. All they have to do is invoke

Schema definition

Now, let us define a simple data schema in which we will store all the info required for our online document storage service.

It will have two tables: user and document.

Here,
id – unique user identifier (numerical, for internal usage).
login – unique user identifier (lexical, for public usage).
key_type – type of the asymmetric key generated by the user (currently, either RSA or DSA).
public_key_pem – public component of the asymmetric key generated by the user in PEM format (“–––––BEGIN PUBLIC KEY–––––”).

Here,
id – unique document identifier (numerical, internal).
user_ref – the ID of the user who owns the document.
name – a name under which the document is stored for the user_ref user.
content – binary object that holds document content.
digest_type – the name of the hash function used to calculate the digital signature on content.
signature – digital signature calculated by signing the digest value (calculated with digest_type hash function on content) with a private key that belongs to user_ref.

User registration

So, the first step in registering a new user would be to generate an asymmetric key pair. Although in this example we will be using the RSA algorithm, our system is flexible enough and allows us to use DSA keys as well.

Important notice: for our use case, private keys must always be generated on the client machine and never leave local secure storage. Only public keys are allowed to be transferred via the network.

To generate an RSA private key, the user can run the standard openssl utility.

The content of the generated 4096-bit RSA private key in PEM format will be written to the private_key.pem file.
Next, we need to extract the public component from the generated key.

The extracted 4096-bit RSA public key in PEM format will be written to the public_key.pem file.

Here, <public_key_pem_content> is the content of the public_key.pem file (a public key in PEM format with the “–––––BEGIN PUBLIC KEY–––––” header).

Just for the simplicity of this blog post (Once again, never use this approach in a real system in production), RSA / DSA keys can be generated by the Percona Server:

Now, when we have a public key, the user registration is straightforward.

Again, in production, @public_key must be set to the content of the public_key.pem generated locally (with openssl utility, for instance) rather than generated on the server.

Uploading documents

When a user wants to upload a new file to our online document storage, the first step would be to calculate its digital signature.

For instance, if we are going to upload a local file called secure_data.doc and we want to use, say, SHA256 as a hash function to calculate digest before signing with a previously generated 4096-bit RSA private key, execute the following

The signature in binary format will be written to the secure_data.binsig.

In order to simplify copying the content of this file to SQL statements, let us also convert this signature to HEX format. We will be using the xxd utility to perform this operation (please notice that on some Linux distributions this utility is a part of the vim-common package).

The signature in HEX format will be written to the secure_data.hexsig file. After that, the user is supposed to call the upload_document() stored procedure.

Here,
alice – name of the document owner.
secure_data.doc – a name under which the document will be stored.
<file_content> – the content of the local secure_data.doc passed as binary data.
SHA256 – the name of the hash function used to calculate the file digest.
<file_signature_hex> – the file signature in HEX format (the content of the secure_data.hexsig file).
On the server upload_document() stored routine should do the following.
First, it needs to find @user_ref, @key_type, and @public_key_pem in the user table for the provided owner’s login (alice).

Second, it needs to calculate message digest @digest using the provided hash function name (SHA256) for the provided file data (file_content).

Then, the server code needs to verify the file signature provided by the user (file_signature) with the public key associated with the file owner (alice).

After that, only if verification_result is equal to 1, we confirm the identity of the document owner and insert a new record into the document table.

Here is how upload_document() may look like

Downloading documents and verifying their integrity

In order to download a file from our online document storage, the first step will be getting its content along with digital signature metadata.

Here,
alice – name of the document owner.
secure_data.doc – the name of the file we want to download.
@downloaded_content – the content of the downloaded file will be put in this variable.
@downloaded_digest_type – the name of the hash function used to calculate file digest for this file will be put in this variable.
@downloaded_signature – the digital signature of the downloaded file will be put in this variable.
On the server, the download_document() stored routine should do the following.
First, it needs to find @user_ref, @key_type, and @public_key_pem in the user table for the provided owner’s login (alice).

Second, it needs to get the file content and digital signature metadata.

After that, we calculate the digest of the file_content using the file_digest_type hash function.

And finally, we verify the integrity of the document:

Only if verification_result is equal to 1, do we confirm the integrity of the document and return successful status.
Here is how download_document() may look like

Although we included a digital signature verification code into the download_document() routine, it does not guarantee that the end-user (caller of the download_document() routine) will get the unmodified document. This code was added only as an additional step to detect integrity violations at earlier stages. The real digital signature verification must be performed on the client-side, not inside Percona Server.

Basically, after calling download_document(), we need to save the content of the @downloaded_content output variable to a local file (say, downloaded.doc). In addition, the content of the @downloaded_signature in HEX format (HEX(@downloaded_signature)) must be saved into a local file as well (say, downloaded.hexsig).
After that, we can convert the signature in HEX format into binary form.

A digital signature in binary form will be written to the downloaded.binsig file. Now, all we have to do is verify the digital signature:

And only if we see the desired Verified OK status line, we can be sure that the document we just downloaded has not been modified.

Conclusion

To begin with, I would like to highlight that a real production-ready online document storage system is far more complicated than the one we just described. Probably because it does not use the OpenSSL command-line utility to perform cryptographic operations on the client-side. 🙂 Moreover, it takes into consideration a number of other security aspects that are outside the scope of this blog post.

Nevertheless, I still hope that the example with digital signatures shown here helped to convince you that asymmetric cryptography is not rocket science and with the help of Encryption UDFs component for Percona Server for MySQL can be indeed easy and straightforward. Check out the full documentation.

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments