Recommended method for encrypting passwords before storing them in a database

Are there recommended encryption algorithms/libraries for encrypting password data gathered in Ignition before storing it in a database? Jython is missing a lot of newer Python encryption libraries such as Argon2id and bcrypt, so I was wondering if there are any common community solutions/standards that are typically implemented when trying to encrypt and store sensitive data such as system credentials, API passwords, etc.

Do you need to store for later retrieval and conversion back to plaintext, or do you need to store a hash of the password, which is what Argon2 and bcrypt are for?

1 Like

I’m looking to store system credentials such as API passwords, so I presume I’d need a secure method of 2-way encryption such as with AES-256.

I apologize for the mistake, I now realize that hashing is probably not the solution I’m looking for.

:fearful:

I would recommend waiting for a future 8.3 release where we allow you to offload credential storage to a third party vault and access it from scripting.

5 Likes

That sounds very convenient and useful for my current predicament, and I don’t doubt that that’s a solution I would likely implement if it was currently available, but are there any present working solutions to this kind of problem in lieu of future updates that could make them obsolete?

In general, your answer in Ignition should not be "try to find a Python lib that does X" but "try to find a Java lib that does X". For which there are a variety of Java encryption libs available, and some would be available to you implicitly on the classpath because we use them internally.

Be aware that, basically by definition within the scripting environment the best you can do is obfuscate secrets. You cannot store a key somewhere to access that cannot also be read/exploited by someone else with malicious access.

4 Likes

Okay, thank you for all of your help!

I'll be working on a similar problem in the next few weeks. One of my ideas was to store half of the master password key in the project and the other half in a system file. The idea being that the full key couldn't be extracted from a gateway backup file.

Is there a grand fallacy in this approach?

You'd need to store the half that wasn't part of the gwbk somewhere else in case you lose the server.

But why bother at all with putting half in the project? Why not just put the whole secret on the file system?

2 Likes

I haven't got a good answer to that question! :zany_face:

2 Likes

I have one example using SHA256 using Java Libraries, so we have the Secret and Salt to encrypt and decrypt

from javax.crypto import Cipher
from javax.crypto import SecretKey
from javax.crypto import SecretKeyFactory
from javax.crypto.spec import IvParameterSpec
from javax.crypto.spec import PBEKeySpec
from javax.crypto.spec import SecretKeySpec
from java.nio.charset import StandardCharsets
from java.security.spec import KeySpec
from java.util import Base64

import java.lang.Exception
logger = system.util.getLogger('encryptionLogger')

#SECRET_KEY = 'one_key_to_rule_them_all'
#SALT = 'salt_of_the_earth'

def encrypt(strToEncrypt, SECRET_KEY, SALT):
encryptedText = ''
try:
iv = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
ivspec = IvParameterSpec(iv)

	factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
	spec = PBEKeySpec(list(SECRET_KEY), SALT.encode("UTF-8"), 65536, 256)
	tmp = factory.generateSecret(spec)
	secretKey = SecretKeySpec(tmp.getEncoded(), "AES")	
	cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
	cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
	cipherText = cipher.doFinal(strToEncrypt.encode("UTF-8"))
	encoder = Base64.getEncoder()
	encryptedText = encoder.encodeToString(cipherText)
	
except Exception, e:
	logger.warnf("Encrypt Exception: %s",e)
	
return encryptedText

def decrypt(encryptedText, SECRET_KEY, SALT):
decryptedText = ''

try:
	iv = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
	ivspec = IvParameterSpec(iv)
	
	factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
	spec = PBEKeySpec(list(SECRET_KEY), SALT.encode("UTF-8"), 65536, 256)
	tmp = factory.generateSecret(spec)
	secretKey = SecretKeySpec(tmp.getEncoded(), "AES")	
	cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
	cipher.init(Cipher.DECRYPT_MODE,secretKey,ivspec)
	decoder = Base64.getDecoder()
	cipherText = decoder.decode(encryptedText.encode('UTF-8'))
	decryptedText = ''.join(map(chr,cipher.doFinal(cipherText)))
except Exception, e:
	logger.warnf('Decrypt Exception: %s',e)

return decryptedText

Thanks for the posts:
AES Encryption using Scripting - Ignition - Inductive Automation Forum
AES 256 Encryption Using Scripting? - Ignition - Inductive Automation Forum

Your error handling is broken. except Exception, e: with only catch jython errors, not any java errors. You should use from java.lang import Throwable to make that name available, then include another clause to catch it. (You need two except clauses to reliably catch both java and jython exceptions--they do not share a parent class.)