在Radius服務(wù)中使用MS-CHAP-V1協(xié)議進(jìn)行通訊
Posted on 2009-07-04 11:42 青果 閱讀(4033) 評(píng)論(3) 編輯 收藏 所屬分類(lèi): 技術(shù)點(diǎn)滴最近一個(gè)項(xiàng)目“無(wú)線網(wǎng)接入動(dòng)態(tài)密碼驗(yàn)證” ,使用思科的ACS作為Radius客戶(hù)端,自己實(shí)現(xiàn)Radius服務(wù)端進(jìn)行密碼驗(yàn)證,步驟如下:(握手過(guò)程Radius已經(jīng)封裝,無(wú)須管它)
1, 客戶(hù)端:發(fā)送報(bào)文,接受返回報(bào)文并解析,然后進(jìn)行相應(yīng)的處理(客戶(hù)端由思科的ACS處理);
2, 服務(wù)端:接受報(bào)文,解析報(bào)文并驗(yàn)證(密碼之類(lèi)),然后響應(yīng)相應(yīng)的結(jié)果(需要java實(shí)現(xiàn))
使用MS-CHAP-V1協(xié)議通訊過(guò)程簡(jiǎn)介:
1,客戶(hù)端和服務(wù)端需要一個(gè)共享密鑰,這個(gè)可以自己約定并隨意設(shè)置,只要保持兩邊一致就行;
2,客戶(hù)端傳過(guò)來(lái)的報(bào)文包括用戶(hù)名和加密密碼,密碼是通過(guò)挑戰(zhàn)數(shù)并利用DES算法和MD4算法進(jìn)行加密的;
3,服務(wù)端收到報(bào)文后,需要解析報(bào)文,并取出用戶(hù)名和加密密碼;由于用戶(hù)傳過(guò)來(lái)的加密密碼是通過(guò)不可逆算法加密的,所以,如果利用保存在服務(wù)器或動(dòng)態(tài)生成的明文密碼來(lái)進(jìn)行驗(yàn)證的話,需要使用與客戶(hù)端同樣的步驟同樣的算法來(lái)對(duì)明文密碼進(jìn)行加密,然后才能與傳遞過(guò)來(lái)的加密密碼進(jìn)行匹配驗(yàn)證(重點(diǎn)就在這里了);
4,如果驗(yàn)證失敗,只需要寫(xiě)回RadiusPacket.ACCESS_REJECT 狀態(tài)字即可,否則,需要寫(xiě)回RadiusPacket.ACCESS_ACCEPT,并返回通過(guò)算法計(jì)算生成的報(bào)文(這一塊也比較煩瑣)
我使用tinyradius 開(kāi)源框架做為服務(wù)端框架,由于tinyradius 只處理了pap、chap協(xié)議進(jìn)行了處理,沒(méi)有特別針對(duì)微軟的ms-chap-v1和ms-chap-v2進(jìn)行處理,所以我稍微改裝了一下tinyradius,修改了org.tinyradius.packet.AccessRequest.java,在其中的153行插入了RadiusAttribute msChapSpecial = getAttribute(VENDOR_ID,VENDOR_TYPE); 這樣,我就取出了ms-chap-v1的報(bào)文屬性, 其中VENDOR_ID=311代表微軟的協(xié)議,VENDOR_TYPE = 25 是ms-chap-v1協(xié)議類(lèi)型。當(dāng)然,在163行也加如了else if(msChapSpecial != null){}的處理,要不它會(huì)報(bào) "Access-Request: User-Password or CHAP-Password/CHAP-Challenge missing" 錯(cuò)誤,整個(gè)tinyradius修改就這么簡(jiǎn)單,下面我會(huì)附上修改了的AccessRequest.java;
接下來(lái)分三步介紹處理過(guò)程:
1,接收?qǐng)?bào)文
服務(wù)端接收客戶(hù)端傳過(guò)來(lái)的報(bào)文格式如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Vendor-Type | Vendor-Length | Ident | Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| LM-Response
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LM-Response (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LM-Response (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LM-Response (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LM-Response (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LM-Response(cont) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NT-Response
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
NT-Response (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
NT-Response (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
NT-Response (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
NT-Response (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
NT-Response (cont) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
由以上報(bào)文分析可得到:
Vendor-Type (一個(gè)8位字節(jié))
Vendor協(xié)議類(lèi)型
1 for MS-CHAP-Response.
Vendor-Length (一個(gè)8位字節(jié))
報(bào)文長(zhǎng)度
52
Ident (一個(gè)8位字節(jié))
唯一標(biāo)志位
Identical to the PPP CHAP Identifier.
Flags (一個(gè)8位字節(jié))
如果flags為0x01,則要優(yōu)先使用NT-Response來(lái)進(jìn)行驗(yàn)證,如果flags為0,則NT-Response必須被忽略掉
The Flags field is one octet in length. If the Flags field is one
(0x01), the NT-Response field is to be used in preference to the
LM-Response field for authentication. The LM-Response field MAY
still be used (if non-empty), but the NT-Response SHOULD be tried
first. If it is zero, the NT-Response field MUST be ignored and
the LM-Response field used.
LM-Response
(LM-Response)域是長(zhǎng)度為24位并由密碼和挑戰(zhàn)數(shù)通過(guò)函數(shù)LmChallengeResponse()加密后的,如果這個(gè)域是空的,那它肯定是被0填充的
The LM-Response field is 24 octets in length and holds an encoded
function LmChallengeResponse() of the password and the received challenge. If this field is empty, it SHOULD be zero-filled.
2,加密本地明文密碼,并驗(yàn)證密碼的正確性
以上是對(duì)接收?qǐng)?bào)文的分析,具體使用tinnyRadius接收代碼如下:
byte[] authenticatorChallenge = accessRequest.getAttribute(311, 11).getAttributeData();
byte[] vendorSpecial = accessRequest.getAttribute(211,1).getAttributeData();
int flag = vendorSpecial.length < 2 ? 0 : vendorSpecial[1];
authenticatorChallenge 是驗(yàn)證挑戰(zhàn)數(shù),vendorSpecial包含了以上報(bào)文中從indent開(kāi)始到最后的數(shù)據(jù)
然后從vendorSpecial中取出
byte[] lmPassword = RadiusServerHelper .getSubbytes(vendorSpecial, 2, 24);
byte[] ntPassword = RadiusServerHelper.getSubbytes(vendorSpecial, 26,24);
byte[] username = accessRequest.getUserName();
byte[] localPassword = “”;//本地密碼,從數(shù)據(jù)庫(kù)中或者文件或者其他地方取出或生成,用來(lái)驗(yàn)證客戶(hù)端傳過(guò)來(lái)的密碼是否正確。
好了,所有的數(shù)據(jù)準(zhǔn)備好了,接下來(lái)就是認(rèn)證了,認(rèn)證需要將本地密碼使用相應(yīng)的算法加密后與客戶(hù)端傳過(guò)來(lái)的密碼進(jìn)行比較。
具體算法如下:
byte[] encryPassword = new byte[24];
if (flag == 0) {
encryPassword = MSCHAP.LmChallengeResponse(authenticatorChallenge,
localPassword);
//如果flag=0,則使用lmPasswor
if (RadiusServerHelper.byteEquels(encryPassword, lmPassword)) {
System.out.println("--success--");
return encryPassword;
}
//否則,如果falg=1則先使用ntPassword,如果沒(méi)通過(guò),再使用lmPassword
} else if (flag == 1) {
encryPassword = MSCHAP.NtChallengeResponse(authenticatorChallenge,
localPassword);
if (RadiusServerHelper.byteEquels(encryPassword, ntPassword)) {
System.out.println("--success--");
return encryPassword;
}else{
encryPassword = MSCHAP.LmChallengeResponse(
authenticatorChallenge, localPassword);
if(RadiusServerHelper.byteEquels(encryPassword,lmPassword)){
System.out.println("--success--");
return encryPassword;
}
}
}
使用函數(shù)如下:
public static byte[] LmChallengeResponse(byte[] Challenge, byte[] Password) {
byte[] PasswordHash = LmPasswordHash(Password);
return ChallengeResponse(Challenge, PasswordHash);
}
public static byte[] NtChallengeResponse(byte[] Challenge, byte[] Password) {
byte[] PasswordHash = NtPasswordHash(Password);
return ChallengeResponse(Challenge, PasswordHash);
}
//使用DES算法對(duì)本地密碼加密
private static byte[] LmPasswordHash(byte[] Password) {
String pString = (new String(Password)).toUpperCase();
byte[] PasswordHash = new byte[16];
byte[] pByte = new byte[14];
for (int i = 0; i < 14; i++)
pByte[i] = 0;
Password = pString.getBytes();
for (int i = 0; i < 14 && i < Password.length; i++)
pByte[i] = Password[i];
DesHash(pByte, 0, PasswordHash, 0);
DesHash(pByte, 7, PasswordHash, 8);
return PasswordHash;
}
//使用MD4算法對(duì)本地密碼進(jìn)行加密
private static byte[] NtPasswordHash(byte[] Password) {
byte PasswordHash[] = new byte[16];
byte uniPassword[] = unicode(Password);
IMessageDigest md = HashFactory.getInstance("MD4");
md.update(uniPassword, 0, uniPassword.length);
System.arraycopy(md.digest(), 0, PasswordHash, 0, 16);
return PasswordHash;
}
其中DES算法可以通過(guò)“gnu-crypto-2.0.1.jar” 包中的
IBlockCipher cipher = CipherFactory.getInstance("DES");進(jìn)行實(shí)現(xiàn)
具體算法見(jiàn) http://www.ietf.org/rfc/rfc2433.txt PAGE 10
3,返回報(bào)文
如果驗(yàn)證失敗,只需要返回 RadiusPacket.ACCESS_ACCEPT 即可,如果驗(yàn)證成功,則需要返回使用算法計(jì)算出來(lái)的響應(yīng)報(bào)文
返回的Access-Accept報(bào)文里需要包含MS-CHAP-MPPE-Keys屬性
The MS-CHAP-MPPE-Keys Attribute contains two session keys for use
by the Microsoft Point-to-Point Encryption Protocol (MPPE). This
Attribute is only included in Access-Accept packets.
A summary of the MS-CHAP-MPPE-Keys Attribute format is given below.
The fields are transmitted left to right.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Vendor-Type | Vendor-Length | Keys
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Keys (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Keys (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Keys (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Keys (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Keys (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Keys (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Keys (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Keys (cont) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Vendor-Type
12 for MS-CHAP-MPPE-Keys.
Vendor-Length
34
Keys
The Keys field consists of two logical sub-fields: the LM-Key and
the NT-Key. The LM-Key is eight octets in length and contains the
first eight bytes of the output of the function LmPasswordHash(P,
This hash is constructed as follows: let the plain-text password
be represented by P.
The NT-Key sub-field is sixteen octets in length and contains the
first sixteen octets of the hashed Windows NT password. The
format of the plaintext Keys field is illustrated in the following
diagram:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| LM-Key
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LM-Key (cont) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NT-Key
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
NT-Key (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
NT-Key (cont)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
NT-Key (cont) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Padding
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Padding (cont) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The Keys field MUST be encrypted by the RADIUS server using the
same method defined for the User-Password Attribute [3]. Padding
is required because the method referenced above requires the field
to be encrypted to be a multiple of sixteen octets in length.
Implementation Note
This attribute should only be returned in response to an
Access-Request packet containing MS-CHAP attributes.
返回報(bào)文需要將明文密碼進(jìn)行LmPasswordHash 加密成LM-key,將明文密碼使用2次MD4加密成NT-Key,然后將LM-key + NT-key + 8位padding組成32位key,
再將key 通過(guò)客戶(hù)端傳來(lái)的16位加密挑戰(zhàn)數(shù)(SA)進(jìn)行加密(即接收整體報(bào)文的前16位),這個(gè)加密算法為:
1, 將key 分成兩個(gè)16位P1,P2
2, 將共享密鑰(S)與SA進(jìn)行MD5加密得到B1
3, 將B1與P1進(jìn)行異或操作得到C1
4, 將C1與S進(jìn)行MD5加密得到B2
5, 將B2與P2進(jìn)行異或操作得到C2
6, 將C1+C2得到返回報(bào)文
具體參見(jiàn)http://www.ietf.org/rfc/rfc2548.txt
返回報(bào)文具體代碼如下:
byte[] lmPass = MSCHAP.LmPasswordHash(generatePassword);
byte[] ntPass = MSCHAP.NtPasswordHash(generatePassword);
ntPass = MSCHAP.HashNtPasswordHash(ntPass);
byte[] keys = MSCHAP.getKeysForMSCHAPv1(lmPass, ntPass);
byte[] p1 = new byte[16];
byte[] p2 = new byte[16];
byte[] b1 = null;
byte[] b2 = null;
byte[] secret = getSecret()==null?null:getSecret().getBytes();
byte[] challenge accessRequest.getAuthenticator();
System.arraycopy(keys, 0, p1, 0, 16);
System.arraycopy(keys, 16, p2, 0, 16);
b1 = MSCHAP.HashKeys(secret, challenge);
byte[] c1 = MSCHAP.Xor(b1, p1);
b2 = MSCHAP.HashKeys(secret, c1);
byte[] c2 = MSCHAP.Xor(b2, p2);
byte[] response = new byte[32];
System.arraycopy(c1, 0, response, 0, 16);
System.arraycopy(c2, 0, response, 16, 16);
RadiusAttribute attribute = new RadiusAttribute( MS_CHAP_MPPE_KEYS_TYPE, response);
attribute.setVendorId(VENDOR_SPECIAL_ID);
RadiusAttribute attribute_Encryption_Policy = new RadiusAttribute(
MS_MPPE_ENCRYPTION_POLICY_TYPE, MSCHAP
.toByteArray("00000001"));
attribute_Encryption_Policy.setVendorId(VENDOR_SPECIAL_ID);
RadiusAttribute attribute_Encryption_Types = new RadiusAttribute(
MS_MPPE_ENCRYPTION_TYPES_TYPE, MSCHAP
.toByteArray("00000006"));
attribute_Encryption_Types.setVendorId(VENDOR_SPECIAL_ID);
int ident = accessRequest.getPacketIdentifier();
packet = new RadiusPacket(type, accessRequest.getPacketIdentifier());
copyProxyState(accessRequest, packet);
其中,加密算法和異或算法可以按照算法步驟自己實(shí)現(xiàn),有些算法比如說(shuō)DES可以直接從jar包中引入對(duì)象實(shí)現(xiàn)。
相關(guān)附件:gnu-crypto-2.0.1.jar
---------------------------------
假到真時(shí)真亦假,真到假時(shí)假亦真
---------------------------------