标签:
单位里有10来个网件的AP(WNAP210),需要对接入端(主要是手机)进行MAC认证,原来采用AP本地MAC认证,但是人员经常变动(离职),另外人员的岗位(流水线)也经常调整,这样就需在变动后,将员工手机的MAC地址添加到对应AP的数据库里,AP一多就变的相当麻烦,后来发现网件的AP支持Radius认证,于是就着手进行,先是使用FreeRadius的windows版,整了半天死活搞不定将用户存储在mysql中,而且运行时一个命令行窗口不能关掉,用着相当别扭,后来采用WinRadius,但是经常出现ODBC重连对话框(图1),
图1
另外使用winRadius时,远程桌面登陆服务器后,还不能点注销,否则直接关闭winRadius.在痛苦的使用WinRadius小半年后打算直接自己写个。
AP接收到用户手机连接请求后,会发送一个Raidus认证数据包(通过UDP)给Radius服务器,Radius服务从数据包中提取用户名跟密码后到数据库里做比对,如果存在就发送“接受回应”否则发送“拒绝回应”。AP发送过来的用户名跟密码其实就是接入手机的mac地址(如:54271eacab03),具体格式可以参考http://www.freeradius.org 上的说明。
主要的几组代码,主要是密码加密,与签名生成
using System; using System.Collections.Generic; using System.Linq; using System.Text; using NGRadius.Core; using System.Security.Cryptography; namespace NGRadius.Core { public class RadiusPaket { private RadiusPaket() { Attributes = new List<RadiusAttribute>(); } public byte Code { get; set; } public byte Id { get; set; } public UInt16 Length { get; set; } public byte[] Authenticator{get;set;} public byte[] Paket { get; set; } public List<RadiusAttribute> Attributes { get; set; } public static RadiusPaket Parser(byte[] receiveData) { var paket = new RadiusPaket(); if (receiveData.Length < 20) throw new Exception("包长度小于20!"); byte code = receiveData[0]; byte id = receiveData[1]; UInt16 len = BitConverter.ToUInt16(new byte[] { receiveData[3], receiveData[2] }, 0); if (len != receiveData.Length) throw new Exception("包长度异常!"); var authenticator = new byte[16]; Array.Copy(receiveData, 4, authenticator, 0, 16); paket.Code = code; paket.Id = id; paket.Length = len; paket.Authenticator = authenticator; paket.Paket = new byte[receiveData.Length]; Array.Copy(receiveData, paket.Paket, paket.Length); //提取属性 var index = 20; while (index < len) { var attrType = receiveData[index]; var attrLen = receiveData[index + 1]; var attrValue = new byte[attrLen - 2]; Array.Copy(receiveData, index + 2, attrValue, 0, attrValue.Length); var attr = new RadiusAttribute(attrType, attrValue); index += attrLen; paket.Attributes.Add(attr); } return paket; } /// <summary> /// /// </summary> /// <param name="sharedSecret"></param> /// <param name="requestAuthernticator"></param> /// <param name="code">2:accept,3:reject</param> /// <param name="id"></param> /// <returns></returns> public static RadiusPaket Build(string sharedSecret,byte[] requestAuthernticator,byte code, byte id) { var attributes = new List<RadiusAttribute>(); var sessionTimeoutAttr = new RadiusAttribute(27, new byte[] { 00, 0x98, 0x96, 0x7F }); attributes.Add(sessionTimeoutAttr); var msgAuth= GenMessageAuthenticator(sharedSecret, requestAuthernticator, code, id, attributes); attributes.Add(msgAuth); //20个字节加,属性长度,加MessageAuthenticator 18字节 UInt16 len = (ushort)(20 + attributes.Sum(ent => ent.Paket.Length)); var lenBytes = BitConverter.GetBytes((ushort)len).Reverse(); #region 计算Response Authernticator, var authRaw = new List<byte>(); authRaw.Add(code); authRaw.Add(id); authRaw.AddRange(lenBytes); authRaw.AddRange(requestAuthernticator); foreach (var a in attributes) { authRaw.AddRange(a.Paket); } authRaw.AddRange(Encoding.Default.GetBytes(sharedSecret)); var authernticator =MD5.Create("MD5").ComputeHash(authRaw.ToArray()); #endregion var paketBytes = new List<byte>(); paketBytes.Add(code); paketBytes.Add(id); paketBytes.AddRange(lenBytes); paketBytes.AddRange(authernticator); foreach (var a in attributes) { paketBytes.AddRange(a.Paket); } var paket = new RadiusPaket(); paket.Attributes = attributes; paket.Authenticator = authernticator; paket.Code = code; paket.Id = id; paket.Length = len; paket.Paket = paketBytes.ToArray(); return paket; } public static string ToHexStr(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", ""); } public static String ToHexStr(byte b) { return BitConverter.ToString(new byte[] { b }).Replace("-", ""); } #region Util /// <summary> /// /// </summary> /// <param name="pwdAttrPaket">User-Password段,包括type跟length+x...</param> /// <param name="SharedSecret"></param> /// <param name="RequestAuthenticator"></param> /// <returns></returns> public static byte[] EncodePAPPwd(String pwdStr, string SharedSecret, byte[] RequestAuthenticator) { var pwdBytes = Encoding.Default.GetBytes(pwdStr); var dataLen = pwdBytes.Length / 16; var r = pwdBytes.Length % 16; if (r != 0) { dataLen++; } var pArr = new byte[dataLen * 16]; Array.Copy(pwdBytes, pArr, pwdBytes.Length); //补0字节处理 if (r != 0) { for (int i = pwdBytes.Length; i < pArr.Length; i++) { pArr[i] = 0; } } var bi = new byte[16]; var ciArr = new byte[pArr.Length]; var shareSecretBytes = Encoding.Default.GetBytes(SharedSecret); var tmp = new byte[shareSecretBytes.Length + 16]; Array.Copy(shareSecretBytes, tmp, shareSecretBytes.Length); Array.Copy(RequestAuthenticator, 0, tmp, shareSecretBytes.Length, 16); Array.Copy(MD5.Create("MD5").ComputeHash(tmp), bi, 16); for (int i = 0; i < dataLen; i++) { for (int bIndex = 0; bIndex < 16; bIndex++) { ciArr[i * 16 + bIndex] = (byte)(bi[bIndex] ^ pArr[i * 16 + bIndex]); } Array.Copy(ciArr, i * 16, tmp, shareSecretBytes.Length, 16); Array.Copy(MD5.Create("MD5").ComputeHash(tmp), bi, 16); } return ciArr; } /// <summary> /// /// </summary> /// <param name="pwdAttrPaket">User-Password段,包括type跟length+x...</param> /// <param name="SharedSecret"></param> /// <param name="RequestAuthenticator"></param> /// <returns></returns> public static byte[] DecodePAPPwd(byte[] pwdAttrPaket, string SharedSecret, byte[] RequestAuthenticator) { var chunksCount = (pwdAttrPaket.Length - 2) / 16; var biArr = new byte[pwdAttrPaket.Length - 2]; var shareSecretBytes = Encoding.Default.GetBytes(SharedSecret); var tmp = new byte[shareSecretBytes.Length + 16]; Array.Copy(shareSecretBytes, tmp, shareSecretBytes.Length); Array.Copy(RequestAuthenticator, 0, tmp, shareSecretBytes.Length, 16); Array.Copy(MD5.Create("MD5").ComputeHash(tmp), biArr, 16); for (int i = 1; i < chunksCount; i++) { Array.Copy(pwdAttrPaket, ((i - 1) * 16) + 2, tmp, shareSecretBytes.Length, 16); Array.Copy(MD5.Create("MD5").ComputeHash(tmp), 0, biArr, i * 16, 16); } for (int i = 0; i < biArr.Length; i++) { biArr[i] = (byte)(biArr[i] ^ pwdAttrPaket[2 + i]); } return biArr; } public static RadiusAttribute GenMessageAuthenticator(string sharedSecret, byte[] requestAuthenticator, byte code, byte id, List<RadiusAttribute> attributes) { if (attributes == null) attributes = new List<RadiusAttribute>(); var attr = new RadiusAttribute(80, new byte[16] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}); var msgAuthRaw = new List<byte>(); msgAuthRaw.Add(code); msgAuthRaw.Add(id); //20个字节加,属性长度,加MessageAuthenticator 18字节 UInt16 len = (ushort)(20 + attributes.Sum(ent => ent.Paket.Length) + 18); msgAuthRaw.AddRange(BitConverter.GetBytes((ushort)len).Reverse() ); msgAuthRaw.AddRange(requestAuthenticator); foreach (var a in attributes) { msgAuthRaw.AddRange(a.Paket); } msgAuthRaw.AddRange(attr.Paket); var hmacMD5 = HMACMD5.Create("HMACMD5"); hmacMD5.Key = Encoding.Default.GetBytes(sharedSecret); var hmacBytes = hmacMD5.ComputeHash(msgAuthRaw.ToArray()); //更新属性内容 for (int i = 0; i < 16; i++) { attr.Value[i] = hmacBytes[i]; attr.Paket[i + 2] = hmacBytes[i]; } return attr; } #endregion } }
登录每个AP,将wifi名称设置成一样,并设置同样的加密方式与wifi密码
设置Radius Server如图2,注意Shared Secret,这个跟下面SConfig.txt文件中配置要一致
图2
设置MAC认证为远程数据库,参考图3
图3
需要.net4.0框架,在win2003,win7,win2008上测试过正常
3.1.先远行脚本安装数据库
3.2.调整配置文件NGRadius.WinServer.exe.config 中的连接字符串
3.3.设置配置文件SConfig.txt, 主要是SharedSecret参数
3.4.运行NGRadius.Setup.exe 安装windows服务
图4
使用与测试:
将允许接入的手机MAC地址添加到表tbUsers中即可(需要小写,并去除":"符号)。
性能方面模拟30个客户端,各发起100个请求,等待返回,整个过程结束需要165毫秒,也就是说165毫秒至少可以处理3000个请求。
稳定性一周7*24小时运行,一切正常。
代码在:https://github.com/doomguards/NGRadiusServer
可执行包:点击下载
标签:
原文地址:http://www.cnblogs.com/wdfrog/p/5359010.html