A Simple Guide to Implement Hashing into your Knack APPs

Skills: JavaScript, JQuery, AJAX, HTML
Level: Medium

Hello folks!

If you ever need to store sensitive information in the database, we advise you to try the solution based on data encryption or hashing, which in summary converts a value into an encrypted string of numbers and characters.

Let's suppose we want to store people's IDs in our database, but we don't want to save the ID in the database in plain text for the world to see. Yet, we want the ID to be searchable from a Knack.

Today we’ll show you the exact steps you’ll need to know to incorporate the Hashing technique into your Knack applications.

To see a working example, follow these link

Remember to always follow this guide:

  1. Set up the Object and Field in Knack

To start, let’s set up an object in Knack with two fields, the field where the hash code will be saved, in our example the ID of the person, and the field with the name of the person that will be retrieved.

![](upload://ihEUBYIPKSTuJyPnr4sPrduVnAr.png)

 

Make sure to mark the “Identification number” field as mandatory (*); this will let the user know they must enter the information before saving it. 

 

  1. Add the Search and Insert forms to your page

![](upload://8UYOwNDe3PulIteBY7P8CbtKCIG.png)

 

![](upload://55Ah1sKvzD6syu5qz6z8oOkKxvh.png)

 

  1. Add the JavaScript code 


Invoke the MD5 Library

You’ll need to invoke a hashing library using JavaScript. The library used in this example is found here https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.13.0/js/md5.min.js

LazyLoad.js(['https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.13.0/js/md5.min.js'], function () {
// your code will go here
});


Insert:

Once you have the hashing library ready, you need to capture the knack-form-submit event of the form so the person ID is converted to an encrypted hashed value before it’s saved in the database.

// Insert with Hash

            $(document).on('knack-form-submit.view_345', async function (event, view, data) {

                await update(lib.OBJECTS_IDS.hashes, data.id, JSON.stringify(

                    {

                        field_382: md5(data.field_382)

                    }

                ));

            });

 

const update = function (objectId, id, data) {

                var dfd = $.Deferred();            

                $.ajax({

                    type: 'PUT',

                    headers: this.headers,

                    url:  this.knackURL + 'objects/' + objectId + '/records/' + id,

                    data: data,

                    triedCount: 0,

                    retryLimit: 3,

                    error: function(xhr, textStatus, errorThrown ) {

                        console.log("error: " + this.triedCount);

                        this.triedCount++;

                        if (this.triedCount < this.retryLimit && xhr.status >= 500) {

                            console.log(this);

                            $.ajax(this);

                        } else{

                            dfd.reject(xhr);

                        }

                    },

                    success: function (response) {

                        dfd.resolve(response);

                    }

                });            

                return dfd.promise();

            };


Search:

Likewise, you’ll need to capture the ‘click’ event in the Search button so that value to search is hashed before the code looks for the match in the database.

 

 // Search by Hash

            $(document).on('knack-view-render.view_339', function (e, v) {

                $('#view_339 button').on('click', async function (e) {

                    e.preventDefault()

                    let val = $('#field_382').val()

                    let hash = md5(val)

                    let result = await find(lib.OBJECTS_IDS.hashes, [{

                        field: 'field_382',

                        operator: 'is',

                        value: hash

                    }])

                    if (result.records.length > 0) {

                        let userName = result.records[0].field_376

                        if ($('#result').length) {

                            $('#result').html(`<b>User:</b> ${userName}`)

                        } else {

                            $('#view_339 #kn-input-field_382').append(`<span id="result"><b>User:</b> ${userName}</span>`)

                        }

                    } else {

                        if ($('#result').length) {

                            $('#result').html('User not found')

                        } else {

                            $('#view_339 #kn-input-field_382').append(`<span id="result">User not found</span>`)

                        }

                    }

                });

            });

 

const find = function (objectId, filters, sortField, sortOrder, recordPerPage) {

                filters = filters || [];

                sortOrder = sortOrder || '';

                sortField = sortField || '';

                recordPerPage = recordPerPage || 'all';            

                var filterValEnc = encodeURIComponent(JSON.stringify(filters));

                var sortFEnc = encodeURIComponent(sortField);

                var sortOEnc = encodeURIComponent(sortOrder);            

                var dfd = $.Deferred();            

                $.ajax({

                    type: 'GET',

                    headers: this.headers,

                    url:  this.knackURL + 'objects/' + objectId + '/records?rows_per_page=' + recordPerPage +

                            '&filters=' + filterValEnc + "&sort_field=" + sortFEnc + "&sort_order=" +

                            sortOEnc,

                    triedCount: 0,

                    retryLimit: 3,

                    error: function(xhr, textStatus, errorThrown ) {

                        console.log("error: " + this.triedCount);

                        this.triedCount++;

                        if (this.triedCount < this.retryLimit && xhr.status >= 500) {

                            console.log(this);

                            $.ajax(this);

                        } else{

                            dfd.reject(xhr);

                        }

                    },

                    success: function (response) {

                        dfd.resolve(response);

                    }

                });            

                return dfd.promise();

            };

 

Did you find this insightful?

Please leave a comment below and let us know!

Make IT Happen!

Hey there,

There are a few issues with how this is implemented.

The first, is that you misleadingly use the term "encrypt" instead of "hash". MD5 is strictly a hashing algorithm and in no way encrypts your data—encryption implies that it is possible to recover the encrypted data directly. MD5 was primarily meant for password hashing, however it is no longer considered a secure password hashing algorithm, because it is just too fast. The only acceptable modern-day usecase for MD5 is as a checksum. Given how fast it is, it would be fairly trivial to use a rainbow table to recover the original IDs. If you wanted to securely hash your records, you would have to use an algorithm such as bcrypt, server-side, with a salt. Encryption is a totally different thing and would have to be done server-side to be of any use, too.

Next, the flaw in using this with Knack is that it will still always be possible to retrieve all the records, no matter what. So, hashing the ID does nothing to protect the security of the records you're searching. Knack API endpoints, regardless of whether it's a table, list, or search view are always capable of returning all the records they are searching, except for any server-side rules you set in the "Data Source" tab.

That brings me to the biggest issue which is that you are using API keys to query records in your code. API keys should absolutely never be exposed client-side, as they provide anyone with the same level of access you have to your app within the builder.

For instance, I know that in addition to Friedrich Riemann and Isaac Newton, you have "Hans", "Dean", "Testing", and a record with no name and an unhashed ID value in your Hash object.

Feel free to reach out to me at david@hmnd.io if you need any assistance with implementing this in a more secure manner, or with anything else! :)

1 Like