實現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

 可執行包:點擊下載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM