遗忘海岸

江湖程序员 -Feiph(LM战士)

导航

实现WIFI MAC认证与漫游

 

前言

  单位里有10来个网件的AP(WNAP210),需要对接入端(主要是手机)进行MAC认证,原来采用AP本地MAC认证,但是人员经常变动(离职),另外人员的岗位(流水线)也经常调整,这样就需在变动后,将员工手机的MAC地址添加到对应AP的数据库里,AP一多就变的相当麻烦,后来发现网件的AP支持Radius认证,于是就着手进行,先是使用FreeRadius的windows版,整了半天死活搞不定将用户存储在mysql中,而且运行时一个命令行窗口不能关掉,用着相当别扭,后来采用WinRadius,但是经常出现ODBC重连对话框(图1),

图1

另外使用winRadius时,远程桌面登陆服务器后,还不能点注销,否则直接关闭winRadius.在痛苦的使用WinRadius小半年后打算直接自己写个。

 

1.Radius认证的一些内容(网件AP为客户端)

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
    }
}

 

2.网件AP的MAC认证配置

  登录每个AP,将wifi名称设置成一样,并设置同样的加密方式与wifi密码

  设置Radius Server如图2,注意Shared Secret,这个跟下面SConfig.txt文件中配置要一致

  

  图2

  设置MAC认证为远程数据库,参考图3

 

 图3

 

 3.安装与配置NGRadius服务

 需要.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小时运行,一切正常。

5.代码与可执行包

 代码在:https://github.com/doomguards/NGRadiusServer

 可执行包:点击下载

 

posted on 2016-04-06 13:53  遗忘海岸  阅读(3449)  评论(0编辑  收藏  举报