Password security in Nextion HMI

Professionalize your development

Who hasn’t experienced this – you are coding a HMI project and there are parts, for example a settings page, which you want to protect with a password. And then, tons of thoughts and considerations arise: Where and how do I store the password for comparison with the user entry? How do I protect a specific page from unauthorized access? And so on…

Neither the Nextion’s integrated MCU nor the Nextion programming language give us the required computing power to do true and secure encryption. But we aren’t left alone with that. There exist a few simple technologies which allow us to add some layers of security to our HMI project. You may use one or both of them, depending on your project’s security specifications or your personal level of paranoia 😉

Never store passwords as clear text – hashing

With Nextion Basic or Discovery screens, there seems to be no alternative than to hard code the password in a Text Variable component or similar, so that it can be compared to the user’s password entry at runtime. Thus, either by direct attack over serial with get pwd.txt or by looking at the .tft file with a binary editor, your secret becomes quickly public. The Enhanced and Intelligent series have an EEPROM which allows theoretically storing and even permanently modifying the password, but again, using the repo command over serial allows to read it out easily. Good practice is not to store the password itself but a hash value, calculated from it. At runtime, another hash is calculated from the user’s entry and compared to the stored value, using the same algorithm. If both strings were identical, their hash values are equal and we can switch the traffic light to green.

The advantage of “hashing” a string is that knowing the hash value and even the algorithm, you can’t find out the original string. Afterwards, since the range of the calculated values is limited, there is always a small probability that another word which you try entering has exactly the same hash value as your original password and might thus give you a “false positive”. That’s why usually, hashing algorithms like MD5 or SHA are used, where the probability of a false match is 1:2^512 or even 1:2^2048, but these algorithms are ways too demanding for a small embedded MCU.

So, let’s look at our onboard tools with the Nextion. Going through the Nextion Instruction Set documentation, we discover instructions like crcrest and crcputs and the crcval system variable, which allow us to calculate a 16bit hash for any string. 16bit means that there is a risk of 1:65536 to get a false match, but we can improve the security in an easy way: Let’s assume that 2 strings “abcde” and “pqrstuv” have the same hash. That means that if our original password was “abcde”, the entry of “pqrstuv” would also unlock everything. Now, let’s prepend their length to both strings and we get “5abcde” and “7pqrstuv”. Here, the hashes do not longer match – we have successfully reduced the risk of false positives!

Build an application firewall

The standard proceeding leads to a security breach: Imagine a project with a startup page, page0, which allows (among others) to click on a “settings” icon which will switch to the password page, page1, where a password is required. If you enter the correct password, page2, the settings page is finally shown. But what if someone sends simply page 2 over serial? Oooops, that went around all the password protection!

How can that be solved? By having page2 or any other password protected page checking in the PagePreinitialize event if the password has been correctly entered. If yes, show the page and kill the authentication info when leaving, so that a new page call requests again the password entry. If not, go back to page1, the password page. 3 global variables are required for that. We declare them in program.s as follows:

int ihash=65201 // manually calculated crc16 of the original pwd (example)
int chash=0 // crc16 of user's password entry attempt, initialized with whatever value, different from ihash
int cpage // page id of the page to load after successful pwd entry

On page 0, when clicking “Settings” (or whatever), the following code would be executed:

cpage=2
page 2

Since page2 is secured, its Preinitialize code is as follows and interrupts the page loading and goes to the password page if the hash values don’t match:

if(chash!=ihash)
{
  page 1 //the password page
}

But if the hash values match, page2 is loaded and accessible. It’s just important to reset the chash variable on leaving, so that at the next attempt, the password has to be entered again. So, we put the following into the Page Exit event:

chash=chash+17+ihash*23%65536 // set chash to a value which is for sure different from previous chash and ihash

And that’s it for security.

The password page is simple: It has a text component t0 with its .pw attribute set to “Password” and its .key attribute set to the full query keyboard. And there has to be a “validate” button which executes the validation code. Out of that, we need two local variables, a numeric one, strl, and a text one, strt, to prepend the text with its length as explained above. The TouchPress code of the validation button holds everything:

strlen t0.txt, strl.val // calculate the length of the pwd entry
covx strl.val,strt.txt,0,0 // convert into text 
strt.txt+=t0.txt // append the original pwd entry
crcrest 1,0xFFFF // initialize the crc16 computing
crcputs strt.txt,0 // load the string
chash=crcval // get the hash value
page cpage // go to the target page where the chash verification against ihash will happen

And that’s it. With these few lines of code snippets, you have greatly improved the security of your code project. It’s not perfect cryptography, but it’s better than any unsecured approach. There is only one obvious security hole: You should rename the chash and ihash variables everywhere and not reveal their new names, so that nobody can send chash=ihash over serial and thus annihilate all our precious efforts. You must not fear reverse engineering since the variable names themselves are hashed in the compiled .tft file and can’t thus not be read out with a binary editor.

In two or three weeks, we’ll continue this with a practical example project. For next Sunday, another topic is already in the make, stay tuned!

Thank you for reading!

Happy Nextioning!