实现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 < ) throw new Exception("包长度小于20!");
byte code = receiveData[];
byte id = receiveData[]; UInt16 len = BitConverter.ToUInt16(new byte[] { receiveData[], receiveData[] }, );
if (len != receiveData.Length) throw new Exception("包长度异常!"); var authenticator = new byte[];
Array.Copy(receiveData, , authenticator, , ); 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 = ; while (index < len)
{
var attrType = receiveData[index];
var attrLen = receiveData[index + ];
var attrValue = new byte[attrLen - ];
Array.Copy(receiveData, index + , attrValue, , 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(, new byte[] { , 0x98, 0x96, 0x7F });
attributes.Add(sessionTimeoutAttr); var msgAuth= GenMessageAuthenticator(sharedSecret, requestAuthernticator, code, id, attributes);
attributes.Add(msgAuth); //20个字节加,属性长度,加MessageAuthenticator 18字节
UInt16 len = (ushort)( + 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 / ;
var r = pwdBytes.Length % ;
if (r != )
{
dataLen++;
} var pArr = new byte[dataLen * ];
Array.Copy(pwdBytes, pArr, pwdBytes.Length); //补0字节处理
if (r != )
{
for (int i = pwdBytes.Length; i < pArr.Length; i++)
{
pArr[i] = ;
}
} var bi = new byte[];
var ciArr = new byte[pArr.Length]; var shareSecretBytes = Encoding.Default.GetBytes(SharedSecret); var tmp = new byte[shareSecretBytes.Length + ];
Array.Copy(shareSecretBytes, tmp, shareSecretBytes.Length);
Array.Copy(RequestAuthenticator, , tmp, shareSecretBytes.Length, );
Array.Copy(MD5.Create("MD5").ComputeHash(tmp), bi, ); for (int i = ; i < dataLen; i++)
{
for (int bIndex = ; bIndex < ; bIndex++)
{
ciArr[i * + bIndex] = (byte)(bi[bIndex] ^ pArr[i * + bIndex]);
} Array.Copy(ciArr, i * , tmp, shareSecretBytes.Length, );
Array.Copy(MD5.Create("MD5").ComputeHash(tmp), bi, ); }
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 - ) / ;
var biArr = new byte[pwdAttrPaket.Length - ]; var shareSecretBytes = Encoding.Default.GetBytes(SharedSecret);
var tmp = new byte[shareSecretBytes.Length + ];
Array.Copy(shareSecretBytes, tmp, shareSecretBytes.Length);
Array.Copy(RequestAuthenticator, , tmp, shareSecretBytes.Length, );
Array.Copy(MD5.Create("MD5").ComputeHash(tmp), biArr, ); for (int i = ; i < chunksCount; i++)
{ Array.Copy(pwdAttrPaket, ((i - ) * ) + , tmp, shareSecretBytes.Length, );
Array.Copy(MD5.Create("MD5").ComputeHash(tmp), , biArr, i * , );
} for (int i = ; i < biArr.Length; i++)
{
biArr[i] = (byte)(biArr[i] ^ pwdAttrPaket[ + 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(, new byte[] {,,,,,,,,,,,,,,,}); var msgAuthRaw = new List<byte>();
msgAuthRaw.Add(code);
msgAuthRaw.Add(id);
//20个字节加,属性长度,加MessageAuthenticator 18字节
UInt16 len = (ushort)( + attributes.Sum(ent => ent.Paket.Length) + );
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 = ; i < ; i++)
{
attr.Value[i] = hmacBytes[i];
attr.Paket[i + ] = 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
可执行包:点击下载
实现WIFI MAC认证与漫游的更多相关文章
- Redpine的Lite-Fi解决方案获Wi-Fi CERTIFIED认证
应用微电路公司(AMCC)和Redpine Signals日前共同宣布,已合作开发出新一代基于Power Architecture的嵌入式Wi-Fi连接性解决方案,目前双方已经在AMCC的PowerP ...
- Android 获取WIFI MAC地址的方法
1. 经常用法,调用Android的API:WifiManager <uses-permission android:name="android.permission.ACCESS_W ...
- 自学Aruba7.4-Aruba安全认证-MAC认证(web页面配置)
点击返回:自学Aruba之路 自学Aruba7.4-Aruba安全认证-MAC认证(web页面配置) 由于前三节已经讲述了3种如何web页面配置安全认证,MAC认证就不过多讲解重复的步骤. 步骤1 ...
- 如何从MTK机器的NVRAM中获取WIFI mac地址
在MTK的机器中,如果不用特定的工具烧写MAC地址,在开机后打开WIFI后会显示: "NVRAM WARNING: Err=0x10" 这就是没有烧写mac地址的原因,所以每次打开 ...
- 解决Android 6.0获取wifi Mac地址为02:00:00:00:00:00问题【转】
本文转载自:http://www.jb51.net/article/128398.htm 这篇文章主要介绍了Android 6.0获取wifi Mac地址为02:00:00:00:00:00的解决方法 ...
- [修改高通平台WIFI MAC 地址] & [adb over wifi]
[修改高通平台WIFI MAC 地址]fccmd --helpfccmd startfccmd getwifimacfccmd setwifimac 74:AC:5F:F5:D7:40 [adb ov ...
- Wifi 开放系统认证和共享密钥身份认证
记录开放系统认证和共享密钥认证的区别. 开放系统身份认证(open-systern authentication) 是802.11 要求必备的惟一方式. 由行动式工作站所发出的第一个帧被归类为auth ...
- 转: MAC认证码的说明
转: http://blog.sina.com.cn/s/blog_4940e1fc01012vk3.html MAC(Message Authentication Code) 消息认证码(带密钥的H ...
- Android获取wifi MAC,关闭的wifi不能获取
最近一直在尝试着在Android上通过ndk用C++获取本机MAC地址,但是用ioctl(SIOCGIFHWADDR)时,一直返回值小于0,即获取不到接口信息.刚开始我以为是自己漏掉了哪些访问权限,所 ...
随机推荐
- C++Primer笔记-----继承
==========================================================================day11 面向对象程序设计============ ...
- 迷你MVVM框架 avalonjs 学习教程13、模板引用
稍为复杂一点的网站都是多个前端工程师合作而成,因此分工是必需的.简单一点的分工就是一个人负责一个频道,某个页面是由一个人全部做的:但如果涉及到一个页面非常复杂,需要多个人同时动工呢?于是到模板的出场时 ...
- 进化的Spark, 从DataFrame说起
进化的Spark, 从DataFrame说起:http://www.tuicool.com/articles/IzeY7zM
- SQL 数据库 子查询及示例
子查询,又叫做嵌套查询. 将一个查询语句做为一个结果集供其他SQL语句使用,就像使用普通的表一样,被当作结果集的查询语句被称为子查询. 子查询有两种类型: 一种是只返回一个单值的子查询,这时它可以用在 ...
- 大型运输行业实战_day07_2_数据字典实现
1.数据字典表 CREATE TABLE `dic` ( `id` ) NOT NULL AUTO_INCREMENT, `table_name` ) DEFAULT NULL, `field_nam ...
- 如何设置maven的local repository目录
step1:默认会放在~/.m2/repository目录下 (“~”代表用户的目录,比如windows下一般都是C:\Documents and Settings\[你的用户名]\.由于“Docum ...
- Maven(一)——Maven入门
一.Maven的基本概念 Maven(翻译为"专家","内行")是跨平台的项目管理工具.主要服务于基于Java平台的项目构建,依赖管理和项目信息管理. 1.1. ...
- LocalDateTime json格式化
参考https://www.cnblogs.com/xiaozhang9/p/jackson.html?utm_source=itdadao&utm_medium=referral <d ...
- Spring简单获得实体类的实例, 使用ApplicationContext()方法的几点注意事项
今天接触了Spring的初步用法, 感觉跟实例化实体类没啥区别, 像这种简单的代码还不如直接实例化来的方便, 这样使用Spring的话总共需要三个文件 第一个当然是一个实体类了, 定义好属性, get ...
- Python issubclass() 函数
Python issubclass() 函数 Python 内置函数 描述 issubclass() 方法用于判断参数 class 是否是类型参数 classinfo 的子类. 语法 以下是 iss ...