Implementing Authentication
While JANOS strives to create a secure environment we generally fall short in that arena when it comes to applications. An application can listen for connections and process its own custom protocol. That is not so complicated to do but it is another big step to insure some level of security. We hardly ever get that done.
If the custom protocol first requires a username and password, you can use the method that JANOS provides User.validate()
to validate the login. The issue here is that the username and password are transferred in the clear unless the protocol requires a secure SSL/TLS connection.
When an application implements the client side of a connection there is the User.digestMD5()
method that can be used in combination with a NONCE to transfer credentials securely. Unfortunately we don’t have method available to validate digest encoded credentials on the server side.
By the way, I think it is better to transfer the username and password in the clear than to not implement authentication at all. Note also that the vast majority of our applications run on a physically secure network.
Still we can certainly ramp this up a level.
Also, applications run with Administrator privileges and merely authenticate the supplied username and password. The supplied account then does not limit the application. So you can define a guest level account solely for authenticating access. If those credentials are then compromised perhaps by being transferred in the clear they are really not a security issue otherwise. In other words, you shouldn’t use an administrator account to log into these application protocols. In addition, an application protocol shouldn’t implement capabilities that compromise the security of the JNIOR by allowing configuration changes.
We’ve been using a “nonce” string to encrypt credentials for transfer over clear text channels. The approach was first employed as an option in the JNIOR Protocol. It works like this.
Nonce String
The “nonce string” is any string of random (usually printable) characters. It is generated by the server and supplied to the client either upon request or as part of an announcement on connection. The nonce can only be used once to authenticate a set of credentials. It should only be valid for a brief period of time, usually 1 or 2 minutes.
The Hash
The client uses an MD5 message digest function to obtain a hash from a combination of username, password and nonce. Our procedure combines the username followed by the nonce followed by the password each separated by a colon ‘:’. Therefore:
hash = MD5( username + ":" + nonce + ":" + password )
The hash produced here is a 16 byte binary array. It is converted to a 32 character hexadecimal (case-insensitive) representation before it is used.
Encoded Credentials
The credentials are then supplied with the username in plain text as follows:
encoded_credentials = username + ":" + hash_hexadecimal
The encoded credentials string is supplied to the server for authentication. The server takes the username from the string and looks up the password for the account. It then uses the nonce it supplied to calculate the digest as defined above. If the calculated digest matches that sent by the client the login is valid.
Since it is practicably impossible to reverse the hash to determine the password for the account this limits risk when transmitted in the clear. It does not matter if the attacker knows the nonce. It is imperative that the nonce be single use and if possible only valid for the one socket connection. This is to prevent a replay attack where the encoded credentials are repeated by the attacker to gain access.
An issue with the above is that applications do not have access to user account passwords in clear text in order to calculate the digest. JANOS needs to provide some assistance here. A validation method is needed.
So it can be done. The following takes encoded credentials as if they were from a client to which we had supplied the random nonce. We then process the authentication without access to the password for the user.
package jtest;
import com.integpg.system.ArrayUtils;
import com.integpg.system.User;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws Exception {
// supplied encoded credentials and original nonce
String creds = "jnior:4f163e3fdaee54babdc0a8aaad7df1c1";
String nonce = "jhfjh23k4k3489ysf989(*(98a98a9835h2k3";
// parse credentials
StringTokenizer tokenizer = new StringTokenizer(creds, ":");
String username = tokenizer.nextToken();
String digest = tokenizer.nextToken();
// obtain binary digest
byte[] hash = new byte[16];
for (int n = 0; n < 16; n++)
hash[n] = (byte)Integer.parseInt(digest.substring(2*n, 2*(n+1)), 16);
// obtain digest using digestMD5()
int userid = User.getUserID(username);
byte[] hash2 = User.digestMD5(userid, username + ":" + nonce + ":", "");
// compare hashes
if (ArrayUtils.arrayComp(hash, 0, hash2, 0, hash.length))
System.out.println("Login successful!");
}
}
bruce_dev /> jtest Login successful! bruce_dev />
I am sure there are other ways to parse the credentials and to convert the hexadecimal string to a byte[]. JANOS does not implement the String.split()
method. You can use Regex.
As an alternative to processing the hexadecimal string you could convert hash2
into the hex string and compare. I am not sure which is faster.
Here’s the split done using Regex.
// parse credentials
String[] parts = Pattern.compile(":").split(creds);
String username = parts[0];
String digest = parts[1];
I had thought that I had implemented the String.split()
method but no. I am probably thinking of the split()
function in the PHP scripting.
And here’s another way to convert the hexadecimal digest string into the byte array.
// obtain binary digest
byte[] hash = new byte[16];
for (int n = 0; n < 32; n++)
hash[n/2] = (byte)(16 * hash[n/2] + Character.digit(digest.charAt(n), 16));