4. User Authentication
Note
To use Authentication in a supported web framework please see integrations.
Warning
This does not protect you against brute force attacks - make sure to enable rate limiting on your host.
Usernames are not encrypted.
User classes are not thread-safe. Please create a new object to use in each thread!
Krptn does not verify the security of the password (e.g: complexity), please do this yourself!
User names cannot be longer then 450 characters
Here is an example usage of creating a new user:
1from krypton.auth.users import userModel
2
3model = userModel.standardUser(None)
4model.saveNewUser("Test_UserName", "Test_Password")
Warning
Please be carefull when setting credentials. The reasons are the following:
If you lose your credentials, and have not enabled password reset, you will permanently loose access to your account and data. To enable password reset, please read this document or skip to the part about password resets.
The encryption of your data is derieved from your credentials. Therefore, weak password equates to easily cracked encryption.
All that said, don’t panic :-); just enable password resets and validate user passwords for length, complexity, etc…
To retreive the user and set user data as key-value pairs:
1model = userModel.standardUser(userName="Test_UserName")
2sessionKey = model.login(pwd="Test_Password") # See below what sessionKey is
3model.setData("test", "example") # test is the key and example is the value
4data = model.getData("test") # Gives b"example". Would raise ValueError on error.
5model.deleteData("test")
Note
Do make sure that the key in setData does not start with _
- those are reserved for Krypton internals.
You can also use model.encryptWithUserKey
with model.decryptWithUserKey
, or shareSet
with shareGet
, if you want other users to read it. Please study the Data Sharing section of this document.
Warning
In setData
only the stored values are encrypted. Keys are plaintext!! Avoid storing sensitive data in keys!
4.1. User Sessions
Session keys can be used to restore a session after the user object has been destroyed. For example, in a webserver, the session key could be stored in a cookie, so that the model can be retrieved on each request.
To restore a session:
1model = userModel.standardUser(userName="Test_UserName")
2model.restoreSession(sessionKey)
To set session expiry please see the configurations.
4.1.1. Sign out of all sessions
1model.revokeSessions()
4.2. Logs
To control the retention period of logs, please see the configurations.
Once the user is logged in, it is easy to recall the login logs:
1model.getLogs()
This returns a list, in the folowing format:
[[time: datetime, success: bool], ...]
It is a 2-dimensional list. The first item in the nested list, is always the DateTime object representing the time of the log. The second item in the nested list, is a Boolean representing the success status of the attempt.
As mentioned in ISO/IEC 27002, it is a good idea to display the past login attempts to the user. This way, the user can detect an attack.
4.3. Change Username
In case you want to change the user’s username, you can simply do this by calling the changeUserName
method.
1model.changeUserName("NewName")
4.4. MFA
To avoid getting locked out, you may want to read Password Reset section of this document.
Before using MFA, make sure that the required configuration values are set.
4.4.1. TOTP
To enable:
1secret, qr = model.enableMFA()
2# Secret is a shared secret and qr is a string, that when converted to QR code can be scanned by authenticator apps.
3# If QR Codes are not supported by the app, you can tell the user enter secret instead.
4# You MUST discard these once the user enabled MFA.
When logging in:
1model.login(pwd="pwd", mfaToken="123456")
If a wrong code is provided, Krptn will impose a 5 second delay to slow brute force attacks. However, please note that is not enough to fully protect you. Therefore, it is necessary to employ a proper rate limiting solution on your webserver.
To disable TOTP (user must be logged in):
1model.disableMFA()
Note
On a failed login attempt, we will impose a 5 second delay to slow down brute force attacks. This is not available for purely password based authentication, so please do impose rate limiting protection on your server.
4.4.2. FIDO Passwordless
See FIDO Docs.
4.5. Data Sharing
Using these methods, you can grant another user access to some of the user’s data.
While deploying these methods, all data remains encrypted using the user’s credentials. No data is ever plaintext in a database! We use Elliptic-curve Diffie–Hellman
to share a common encryption key between the users, and we encrypt the data with the common key. Each user has their own Elliptic Curve key, with the private key encrypted with the user’s credentials.
Warning
One thing to note: if the original user used to set/encrypt the data is deleted. All other users will loose access. It is important that the other users create their own copy if they want to retain it.
4.5.2. Encryption
When possible, it is preferred to use shareSet
and shareGet
but when required you can directly use only Krptn’s encryption capabilities. E.g: if you want to use another database to store this data.
1model = userModel.standardUser(None)
2model.saveNewUser("Test_UserName", "Test_Password")
3
4model2 = userModel.standardUser(None)
5model2.saveNewUser("Test_UserName2", "Test_Password")
6
7r = model.encryptWithUserKey("data")
8model.decryptWithUserKey(r) # Returns b"data"
9
10## Here is the tricky part:
11
12r = model.encryptWithUserKey("data", ["Test_UserName2"]) # Allow Test_UserName to decrypt the data
13model2.decryptWithUserKey(r[0][1], r[0][2], "Test_UserName") # Returns b"data"
In the case that an incorrect data, or key is provided, a ValueError
will be raised.
encryptWithUserKey
needs the following parameters: data
, otherUsers
(optional). data
is the plaintext to encrypt and otherUsers
is a list of usernames of users who can also decrypt the data.
encryptWithUserKey
returns a list of tuples in the following format: (username, data, salt)
. username
is the name of the user to who we need to provide data
and salt
.
When decrypting, call decryptUserKey
, on the user object corresponding to username
, passing data
as the first argument and salt
as the second argument. It will return the plaintext.
Therefore, by using this method, you can grant another user access to some of the user’s data, simply by allowing that user to decrypt the data.
4.6. Password Reset
To enable password reset you need to obtain recovery codes, that you can use to unlock the account.
1keys = model.enablePWDReset() # keys is a list of OTPs that can be used to unlock the user account
2model.logout() # This is not needed but you can reset the password of a locked out user.
3sessionKey = model.resetPWD(keys[0], "newPWD") # Note: you cannot use keys[0] again, use the next one in the list.
4# Note: when you call resetPWD the model will automatically login, you may want to logout
5model.logout()
If a wrong code is provided, Krptn will impose a 5 second delay to slow brute force attacks. However, please note that is not enough to fully protect you. Therefore, it is necessary to impose a proper rate limiting solution on your webserver.
You may notice in the previous code block the resetPWD
returns a sessionKey
. This session key is the same as returned from the model.login
method.
If the OTPs get compromised you can revoke them and generate new ones:
1model.disablePWDReset() # Revoke
2keys = model.enablePWDReset() # Generate. This also revokes all codes but we already did so previously.