Java实现RFC2865 Radius协议的密码加密
最近项目中要做Radius协议的客户端,一看Radius几百页的协议,我头皮都发麻。放狗搜索一番,发现了一个实现好的Java的Radius客户端,哈哈,很好很强大:
http://sourceforge.net/projects/jradius-client/
摸索了一番,挖出了里面关于用户密码加密的一段代码,相信对很多想学习下Java MD5加密的同学会很有帮助。
首先来了解一番背景跟算法要求:
User-Password
描述
该属性表示用户用来认证的用户密码,或者是用户在收到接入挑战报文后用户的输入。它只出现在接入请求报文中。
在传输的过程中,密码是隐藏起来的,首先,将密码用null(\0)填充到16的整数倍个字节长,然后对在请求认证字后面加上共享密钥的字节流进行MD5加密生成hash值,将该hash值和密码的第一个16字节进行异或,然后将结果放入User-Password属性的第一个16字节中。
如果密码长度超过16个字符,则对第一个异或值后面加上共享密钥的字节流进行MD5加密生成hash值。该hash值和密码的第二个16个字节进行异或,然后将异或值放入User-Password属性的第二个16字节中。
如果必要,重复这个操作,每一个异或值后面加上共享密钥产生下一个hash值,然后和密码下一段的16字节进行异或。密码最长不能超过128个字符。
这个方法引用自Kaufman, Perlman和Speciner合写的《网络安全》一书,在第109到第110页。该方法的一个更加精确的解释如下:
共享密钥为S,伪随机数Request Authenticator为RA,将密码拆分成多个16个字节的块p1,p2,等等。最后一块用null(\0)填充满16个字节,密码块为c(1),c(2),等等。中间值为b1,b2,等等。
b1 = MD5(S + RA) c(1) = p1 xor b1
b2 = MD5(S + c(1)) c(2) = p2 xor b2
. .
. .
. .
bi = MD5(S + c(i-1)) c(i) = pi xor bi
User-Password属性字符串为c(1)+c(2)+…c(i),+表示连接。
一旦接收到报文,逆向处理就能得到密码明文。
User-Password属性的格式如下所示。各个域是按照自左向右的顺序传输的。
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Type | Length | String ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
类型
2 代表用户密码
长度
至少18字节,但不能超过130个字节
字符串
本字符串占位16到128(包含)个字节。
Java RADIUS Client的密码生成实现
//生成请求的Authenticator
byte [] requestAuthenticator = this.makeRFC2865RequestAuthenticator();
//Authenticator的生成
private byte[] makeRFC2865RequestAuthenticator() {
byte [] requestAuthenticator = new byte[16];
Random r = new Random();
for (int i = 0; i < 16; i++)
{
requestAuthenticator[i] = (byte) r.nextInt();
}
this.md5MessageDigest.reset();
this.md5MessageDigest.update(this.sharedSecret.getBytes());//sharedSecret指的是服务器端与客户端的共享密钥
this.md5MessageDigest.update(requestAuthenticator);
return this.md5MessageDigest.digest();//返回生成的hash值
}
//生成加密后的密钥,userPass指未加密的密码
byte [] encryptedPass = this.encodePapPassword(userPass, requestAuthenticator);
//用来加密的算法
private byte[] encodePapPassword(final byte[] userPass,
final byte[] requestAuthenticator) {
// encrypt the password.
byte[] userPassBytes = null;
//密码必须大于16小于或者等于128bytes,如果不是16的整数倍个字节长,不足的位补0。如果大于128位,则截断成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) {
// 如果是16的整数倍长
encryptedPass = new byte[userPassBytes.length];
} else {
// 数组长度变成16位的整数倍长
encryptedPass = new byte[((userPassBytes.length / 16) * 16) + 16];
}
} else {
// the encrypted password must be between 16 and 128 bytes
encryptedPass = new byte[128];
}
// 填充0
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
}
this.md5MessageDigest.reset();
// 添加共享密钥
this.md5MessageDigest.update(this.sharedSecret.getBytes());
// 添加请求Authenticator
this.md5MessageDigest.update(requestAuthenticator);
// //获得md5 hash( b1 = MD5(S + RA) ).
byte bn[] = this.md5MessageDigest.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) {
this.md5MessageDigest.reset();
// add the shared secret
this.md5MessageDigest.update(this.sharedSecret.getBytes());
// add the previous(encrypted) 16 bytes of the user password
this.md5MessageDigest.update(encryptedPass, i - 16, 16);
// get the md5 hash( bn = MD5(S + c(i-1)) ).
bn = this.md5MessageDigest.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;
}
呃,我在进行radius底层开发…..