/**
* JRadius - A RADIUS Server Java Adapter
* Copyright (C) 2004-2005 PicoPoint, B.V.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package net.jradius.util;
import java.io.ByteArrayOutputStream;
import java.security.MessageDigest;
import java.util.Random;
/**
* Radius Utilities
*
* @author David Bird
*/
public final class RadiusUtils
{
/*
* The make*Authenticator and encodePapPassword functions are
* borrowed from jradius-client, also a sourceforge project
* and under GPL.
*/
/**
* This method encodes the plaintext user password according to RFC 2865
* @param userPass java.lang.String the password to encrypt
* @param requestAuthenticator byte[] the requestAuthenicator to use in the encryption
* @return byte[] the byte array containing the encrypted password
*/
public static byte[] encodePapPassword(
byte[] userPass,
byte[] requestAuthenticator,
String sharedSecret)
{
MessageDigest md5 = MD5.getMD5();
// encrypt the password.
byte[] userPassBytes = null;
//the password must be a multiple of 16 bytes and less than or equal
//to 128 bytes. If it isn't a multiple of 16 bytes fill it out with zeroes
//to make it a multiple of 16 bytes. If it is greater than 128 bytes
//truncate it at 128
if (userPass.length > 128)
{
userPassBytes = new byte[128];
System.arraycopy(userPass,0,userPassBytes,0,128);
}
else
{
userPassBytes = userPass;
}
// declare the byte array to hold the final product
byte[] encryptedPass = null;
if (userPassBytes.length < 128)
{
if (userPassBytes.length % 16 == 0)
{
// It is already a multiple of 16 bytes
encryptedPass = new byte[userPassBytes.length];
}
else
{
// Make it a multiple of 16 bytes
encryptedPass = new byte[((userPassBytes.length / 16) * 16) + 16];
}
}
else
{
// the encrypted password must be between 16 and 128 bytes
encryptedPass = new byte[128];
}
// copy the userPass into the encrypted pass and then fill it out with zeroes
System.arraycopy(userPassBytes, 0, encryptedPass, 0, userPassBytes.length);
for(int i = userPassBytes.length; i < encryptedPass.length; i++)
{
encryptedPass[i] = 0; //fill it out with zeroes
}
// add the shared secret
md5.update(sharedSecret.getBytes(), 0, sharedSecret.length());
// add the Request Authenticator.
md5.update(requestAuthenticator, 0, requestAuthenticator.length);
// get the md5 hash( b1 = MD5(S + RA) ).
byte bn[] = md5.digest();
for (int i = 0; i < 16; i++)
{
// perform the XOR as specified by RFC 2865.
encryptedPass[i] = (byte)(bn[i] ^ encryptedPass[i]);
}
if (encryptedPass.length > 16)
{
for (int i = 16; i < encryptedPass.length; i += 16)
{
md5.reset();
// add the shared secret
md5.update(sharedSecret.getBytes(), 0, sharedSecret.length());
//add the previous(encrypted) 16 bytes of the user password
md5.update(encryptedPass, i - 16, 16);
// get the md5 hash( bn = MD5(S + c(i-1)) ).
bn = md5.digest();
for (int j = 0; j < 16; j++)
{
// perform the XOR as specified by RFC 2865.
encryptedPass[i+j] = (byte)(bn[j] ^ encryptedPass[i+j]);
}
}
}
return encryptedPass;
}
public static byte[] decodePapPassword(byte[] encryptedPass, byte[] authenticator, String sharedSecret)
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
MessageDigest md5 = MD5.getMD5();
int pwlen = encryptedPass.length;
if (pwlen > 128) pwlen = 128;
if (pwlen == 0) return out.toByteArray();
md5.update(sharedSecret.getBytes(), 0, sharedSecret.length());
md5.update(authenticator, 0, authenticator.length);
byte[] hash = md5.digest();
for (int n = 0; n < pwlen; n += 16)
{
if (n == 0)
{
md5.update(sharedSecret.getBytes(), 0, sharedSecret.length());
if (pwlen > 16)
{
md5.update(encryptedPass, 0, 16);
}
}
else
{
hash = md5.digest();
md5.update(sharedSecret.getBytes(), 0, sharedSecret.length());
if (pwlen > (n + 16))
{
md5.update(encryptedPass, n, 16);
}
}
for (int i = 0; i < 16 && (i + n) < pwlen; i++)
{
out.write(encryptedPass[i + n] ^ hash[i]);
}
}
return out.toByteArray();
}
/**
* This method builds a Request Authenticator for use in outgoing RADIUS
* Access-Request packets as specified in RFC 2865.
* @return byte[]
*/
public static byte[] makeRFC2865RequestAuthenticator(String sharedSecret)
{
MessageDigest md5 = MD5.getMD5();
byte [] requestAuthenticator = new byte[16];
Random r = new Random();
for (int i = 0; i < 16; i++)
{
requestAuthenticator[i] = (byte) r.nextInt();
}
md5.update(sharedSecret.getBytes(), 0, sharedSecret.length());
md5.update(requestAuthenticator, 0, requestAuthenticator.length);
return md5.digest();
}
/**
* This method builds a Response Authenticator for use in validating
* responses from the RADIUS Authentication process as specified in RFC 2865.
* The byte array returned should match exactly the response authenticator
* recieved in the response packet.
* @param code byte
* @param identifier byte
* @param length short
* @param requestAuthenticator byte[]
* @param responseAttributeBytes byte[]
* @return byte[]
*/
public static byte[] makeRFC2865ResponseAuthenticator(
String sharedSecret,
byte code,
byte identifier,
short length,
byte[] requestAuthenticator,
byte[] responseAttributeBytes,
int responseAttributeLength)
{
MessageDigest md5 = MD5.getMD5();
md5.update((byte)code);
md5.update((byte)identifier);
md5.update((byte)(length >> 8));
md5.update((byte)(length & 0xff));
md5.update(requestAuthenticator, 0, requestAuthenticator.length);
md5.update(responseAttributeBytes, 0, responseAttributeLength);
md5.update(sharedSecret.getBytes(), 0, sharedSecret.length());
return md5.digest();
}
/**
* This method builds a Request Authenticator for use in RADIUS Accounting
* packets as specified in RFC 2866.
* @param code byte
* @param identifier byte
* @param length short
* @param requestAttributes byte[]
* @return byte[]
*/
public static byte[] makeRFC2866RequestAuthenticator(
String sharedSecret,
byte code,
byte identifier,
int length,
byte[] requestAttributes,
int attributesOffset,
int attributesLength)
{
MessageDigest md5 = MD5.getMD5();
byte[] requestAuthenticator = new byte[16];
md5.reset();
md5.update((byte)code);
md5.update((byte)identifier);
md5.update((byte)(length >> 8));
md5.update((byte)(length & 0xff));
md5.update(requestAuthenticator, 0, requestAuthenticator.length);
md5.update(requestAttributes, attributesOffset, attributesLength);
md5.update(sharedSecret.getBytes(), 0, sharedSecret.length());
return md5.digest();
}
/**
* Converts a binary array to a human readable string
* @param in bytes to be hexed
* @return Returns a hex string
*/
public static String byteArrayToHexString(byte in[])
{
byte ch = 0x00;
int i = 0;
if (in == null || in.length <= 0) return null;
String pseudo[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
StringBuffer out = new StringBuffer(in.length * 2);
while (i < in.length)
{
ch = (byte) (in[i] & 0xF0);
ch = (byte) (ch >>> 4);
ch = (byte) (ch & 0x0F);
out.append(pseudo[(int) ch]);
ch = (byte) (in[i] & 0x0F);
out.append(pseudo[(int) ch]);
i++;
}
String rslt = new String(out);
return rslt;
}
}