Radius - 概述
RADIUS(Remote Authentication Dial In User Service,远程用户拨号认证)协议是一种分布式的、客户端/服务器结构的信息交互协议,能保护网络不受未授权访问的干扰,常应用在既要求较高安全性、又允许远程用户访问的各种网络环境中。RADIUS协议为标准协议,基本所有主流设备均支持,在实际网络中应用最多。RADIUS使用UDP(User Datagram Protocol,用户数据报协议)作为传输协议,具有良好的实时性;同时也支持重传机制和备用服务器机制,具有较好的可靠性;实现较为简单,适用于大用户量时服务器端的多线程结构
参考资料:
https://blog.csdn.net/m0_49864110/article/details/129584307
https://tonydeng.github.io/sdn-handbook/sdn/aaa/radius.html
抓包测试

请求报文:04 00 00 21 1C 6F 61 B9 FC 60 26 53 00 F4 AE BD 18 90 62 21 28 06 00 00 00 01 2C 07 31 36 32 34 30
通过报文:05 00 00 26 B4 9C 6E 23 71 F4 C5 D3 35 BB 68 B9 7F 67 CD 63 50 12 9A AB 65 EE DE 81 5A DF 4E 1C 37 CB D1 C0 03 06


报文数据:04 01 00 5E 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 01 02 1E 0A 38 34 30 39 30 32 31 35 1F 0A 36 32 37 35 30 31 31 34 28 06 00 00 00 01 06 06 00 00 00 01 08 06 FF 01 02 03 2A 06 00 00 00 64 2B 06 00 00 03 E8 2C 04 69 64 50 12 B8 38 33 DE C6 FC 34 4F 0E 6B 48 29 2A D0 E5 6E
通过报文:05 01 00 26 F3 0F BE 8A 6D 04 3F 20 33 48 F7 A1 DA BA 2A C0 50 12 F4 55 7C D4 20 C7 2B B1 DA 18 57 BE C8 AF 1C 56

Message-Authenticator校验规则
linux下抓包
sudo tcpdump -i any port 1812 or port 1813
sudo tcpdump -i any -w radius_packets.pcap port 1812 or port 1813
交互解读

Raduis服务器和NAS服务器通过UDP协议进行通信,Raduis服务器的1812端口负责认证,1813端口负责计费工作。

Identifier 包标识
1、用来匹配请求和响应报文
Radius客户端发送请求报文后,Radius服务器返回的响应报文中的Identifier值与请求报文中的Identifier值相同,将相同Identifier值的请求和响应报文进行匹配
2、用来检测在一段时间内重发的请求报文
如果在一个很短的时间内收到相同的源IP地址、源UDP端口号以及Identifier值的请求报文,Radius服务器就认为是重复的请求报文
Length 包长度
指定Radius报文的长度(Code、Identifier、Length、Authenticator、Avps五个字段)
接收到的报文的实际长度大于Length的取值,超过的字节将作为填充字符而忽略
接收到的报文的实际长度小于Length的取值,该报文会被丢弃
Authenticator 包验证字(对用户进行认证授权)
Authenticator=MD5(Code+Identifier+Length+请求验证字+Avps+共享密钥)
AVPs(Attributes Value Pairs)(Radius属性,可变字段)
携带认证、授权和记账信息(通过TLV结构表示)


报文数据:04ae01320ec270f4b1fd56d1b1c9e5a6e9fbe29c04060a1400911e0f676c6f62616c73696d2e63786e0706000000073d06000000051f1134363731393030313736383637393428060000000108060a6000282c12424335434138413342333436424536442e0600000000370667bd2207290600000000322236374244323230373030303030303638323430303830303137353533343530461a17000028af01113234303038303031373535333435301a0c000028af0306000000001a0c000028af0606dcceb5431a0d000028af080732343030381a09000028af0a03381a09000028af0c03301a0c000028af0d06306630301a18000028af1412333532393736313131363539343534321a09000028af1503061a15000028af160f8264f0105b5964f010081fc8031a0a000028af17042300
rcf2866计费只有密钥,没用户名密码
开源库
using System.Net; using System.Security.Cryptography; using System.Text; namespace RadiusNet { class RadiusPacket { public byte Code { get; set; } public byte Identifier { get; set; } public ushort Length { get; set; } public byte[] Authenticator { get; set; } public RadiusAttribute[] Attributes { get; set; } } public class RadiusAttribute { public byte Type { get; set; } public byte Length { get; set; } public byte[] Value { get; set; } } class RadiusUtil { /// <summary> /// 生成请求报文 /// </summary> public static byte[] CreateRadiusAccountRequest(byte[] secretBytes) { var attributes = WithAccountingRequestAttributes().ToArray(); byte code = 0x04; byte identifier = 0x00; ushort length = Convert.ToUInt16(4 + 16 + attributes.Length); byte[] request = new byte[length]; request[0] = code; request[1] = identifier; request[2] = (byte)((length >> 8) & 0xFF); // 包总长度,低字节 request[3] = (byte)(length & 0xFF); // 包总长度,高字节 Array.Copy(attributes, 0, request, 20, attributes.Length); // 拷贝属性 byte[] hash = CreateAuthenticator(code, identifier, length, attributes, secretBytes, new byte[16]);// 16字节零填充 Array.Copy(hash, 0, request, 4, hash.Length); // 拷贝Authenticator return request; } /// <summary> /// 请求属性 /// </summary> static List<byte> WithAccountingRequestAttributes() { // BitConverter.GetBytes(1).Reverse() // Encoding.UTF8.GetBytes(value); // StringToByteArray("9aab65eede815adf4e1c37cbd1c00306"); // List<byte> attrs = new(); // Acct-Status-Type (必须字段) var status = BitConverter.GetBytes(1).Reverse(); attrs.Add(Convert.ToByte(40)); // Type=40 attrs.Add(Convert.ToByte(2 + 4)); // Length=6 attrs.AddRange(status); // Acct-Session-Id string str = "16240"; attrs.Add(Convert.ToByte(44)); attrs.Add(Convert.ToByte(2 + str.Length));// Length:2 + 5 attrs.AddRange(Encoding.UTF8.GetBytes(str)); return attrs; } /// <summary> /// 生成回复报文 /// </summary> public static byte[] CreateRadiusAccountResponse(byte[] request, byte[] secretBytes) { byte[] requestAuthenticator = new byte[16]; Array.Copy(request, 4, requestAuthenticator, 0, requestAuthenticator.Length); var attributes = WithAccountingResponseAttributes().ToArray(); var b1 = BitConverter.ToString(attributes).Replace("-", " "); byte code = 0x05; byte identifier = 0x00; ushort length = Convert.ToUInt16(4 + 16 + attributes.Length); byte[] response = new byte[length]; // RADIUS报文最大128字节 response[0] = code; response[1] = identifier; response[2] = (byte)((length >> 8) & 0xFF); // 包总长度,低字节 response[3] = (byte)(length & 0xFF); // 包总长度,高字节 Array.Copy(attributes, 0, response, 20, attributes.Length); // 拷贝属性 // 计算Message-Authenticator // Authenticator:请求中的Authenticator // Message-Authenticator:值临时置为 16 字节零填充,HMAC-MD5(共享密钥, 完整报文数据) Array.Copy(requestAuthenticator, 0, response, 4, requestAuthenticator.Length);// 响应Message-Authenticator,用的是老的Authenticator var strMessageAuth = BitConverter.ToString(response).Replace("-", ""); byte[] messageAuth = CreateMessageAuthenticator(response, secretBytes); var strMessageMD5 = BitConverter.ToString(messageAuth).Replace("-", "");// 9AAB65EEDE815ADF4E1C37CBD1C00306 Array.Copy(messageAuth, 0, response, response.Length - 16, messageAuth.Length);// 更新最后的16字节 byte[] hash = CreateAuthenticator(code, identifier, length, attributes, secretBytes, requestAuthenticator);// requestAuthenticator Array.Copy(hash, 0, response, 4, hash.Length); // 更新Authenticator return response; } /// <summary> /// 回复属性 /// </summary> static List<byte> WithAccountingResponseAttributes() { // Message-Authenticator List<byte> attrs = new(); attrs.Add(Convert.ToByte(80)); // //attrs.Add(Convert.ToByte(18)); //attrs.AddRange(StringToByteArray("9aab65eede815adf4e1c37cbd1c00306")); // attrs.Add(Convert.ToByte(18)); // Length=18(2字节头+16字节值) attrs.AddRange(new byte[16]);// 计算前:临时置为16字节零填充后再计算;计算后:9aab65eede815adf4e1c37cbd1c00306 return attrs; } /// <summary> /// 生成Authenticator /// </summary> static byte[] CreateAuthenticator(byte code, byte identifier, ushort length, byte[] attributes, byte[] secretBytes, byte[] padding) { using (MD5 md5 = MD5.Create()) { byte[] input = new[] { code, identifier } .Concat(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)length))) .Concat(padding) .Concat(attributes) .Concat(secretBytes) .ToArray(); return md5.ComputeHash(input); } } /// <summary> /// 生成MessageAuthenticator /// </summary> static byte[] CreateMessageAuthenticator(byte[] data, byte[] secret) { var b1 = BitConverter.ToString(data).Replace("-", ""); using (var hmac = new HMACMD5(secret)) { byte[] hmacResult = hmac.ComputeHash(data); return hmacResult; } } /// <summary> /// 校验MessageAuthenticator /// </summary> public static bool IsMessageAuthenticator(byte[] response, byte[] request, byte[] secret) { bool result = false; var b1 = BitConverter.ToString(response).Replace("-", " "); // 1. 定位并置零 Message-Authenticator 属性(Type=80) //int offset = 20; // 跳过头部 //while (offset < response.Length) //{ // byte type = response[offset]; // byte length = response[offset + 1]; // if (type == 80) // 找到 Message-Authenticator // { // // 将 Value 置零(位置:offset+2 至 offset+2+16) // Array.Clear(response, offset + 2, 16); // break; // } // offset += length; //} var messageAuth = new byte[16]; Array.Copy(response, response.Length - 16, messageAuth, 0, 16);// 取出校验值MessageAuthenticator Array.Copy(request, 4, response, 4, 16);// 用请求中的Authenticator替换 Array.Clear(response, response.Length - 16, 16);// MessageAuthenticator置空 var b2 = BitConverter.ToString(response).Replace("-", ""); // 2. 计算 HMAC-MD5 using (var hmac = new HMACMD5(secret)) { var md5 = hmac.ComputeHash(response); result = md5.SequenceEqual(messageAuth); } return result; } /// <summary> /// 校验请求的Authentic /// </summary> public static bool IsAuthenticRequest(byte[] request, byte[] secret) { bool result = false; List<byte> auth = new List<byte>(request.Length + 16 + secret.Length); // Identifier + Length 4 个字节 for (int i = 0; i < 4; i++) { auth.Add(request[i]); } // Authenticator 置0 16字节 for (int i = 0; i < 16; i++) { auth.Add(0); } // AVPs for (int i = 20; i < request.Length; i++) { auth.Add(request[i]); } // secret auth.AddRange(secret); // 计算 MD5 哈希 using (MD5 md5 = MD5.Create()) { byte[] hash = md5.ComputeHash(auth.ToArray()); Console.WriteLine("MD5 Hash: " + BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()); byte[] requestAuth = new byte[16]; // request报文中Authenticator数据 Array.Copy(request, 4, requestAuth, 0, requestAuth.Length); Console.WriteLine("MD5 Request: " + BitConverter.ToString(requestAuth).Replace("-", "").ToLowerInvariant()); // 对比 result = hash.SequenceEqual(requestAuth); Console.WriteLine("Request Result:" + result); } return result; } /// <summary> /// 校验回复的Authentic /// </summary> public static bool IsAuthenticResponse(byte[] response, byte[] request, byte[] secret) { bool result = false; if (response.Length < 20 || request.Length < 20 || secret.Length == 0) { return false; } using (MD5 md5 = MD5.Create()) { using (var memoryStream = new System.IO.MemoryStream()) { memoryStream.Write(response, 0, 4); memoryStream.Write(request, 4, 16); memoryStream.Write(response, 20, response.Length - 20); memoryStream.Write(secret, 0, secret.Length); byte[] hash = md5.ComputeHash(memoryStream.ToArray()); Console.WriteLine("MD5 Hash: " + BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()); byte[] expectedHash = new byte[16]; Array.Copy(response, 4, expectedHash, 0, 16); Console.WriteLine("MD5 Response: " + BitConverter.ToString(expectedHash).Replace("-", "").ToLowerInvariant()); // 对比 result = hash.SequenceEqual(expectedHash); Console.WriteLine("Response Result:" + result); } } return result; } public static RadiusAttribute[] ParseAttributes(byte[] data) { var attributes = new System.Collections.Generic.List<RadiusAttribute>(); int index = 0; while (index < data.Length) { byte type = data[index]; byte length = data[index + 1]; byte[] value = data.Skip(index + 2).Take(length - 2).ToArray(); attributes.Add(new RadiusAttribute { Type = type, Length = length, Value = value }); // 更新索引 index += length; } return attributes.ToArray(); } public static byte[] StringToByteArray(string hex) { hex = hex.Replace(" ", ""); int numberChars = hex.Length; byte[] bytes = new byte[numberChars / 2]; for (int i = 0; i < numberChars; i += 2) { bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); } return bytes; } } class RadiusParse { // 共享密钥 static byte[] secret = Encoding.UTF8.GetBytes("R3QlUmFkaXVzMjQjNjU4YzRmYmNkY2Y0Mzc2NDY3MGNiYWM3MGQ2ODk4ZmY="); public static void Account() { // 请求报文 //RADIUS Protocol //Code: Accounting-Request(4) //Packet identifier: 0x0(0) //Length: 33 //Authenticator: 1c6f61b9fc60265300f4aebd18906221 //Attribute Value Pairs // AVP: t=Acct-Status-Type(40) l=6 val=Start(1) // AVP: t=Acct-Session-Id(44) l=7 val=16240 string strRequest = "04 00 00 21 1C 6F 61 B9 FC 60 26 53 00 F4 AE BD 18 90 62 21 28 06 00 00 00 01 2C 07 31 36 32 34 30"; // 响应报文 //RADIUS Protocol // Code: Accounting-Response(5) // Packet identifier: 0x0 (0) // Length: 38 // Authenticator: b49c6e2371f4c5d335bb68b97f67cd63 // Attribute Value Pairs // AVP: t=Message-Authenticator(80) l=18 val=9aab65eede815adf4e1c37cbd1c00306 string strResponse = "05 00 00 26 B4 9C 6E 23 71 F4 C5 D3 35 BB 68 B9 7F 67 CD 63 50 12 9A AB 65 EE DE 81 5A DF 4E 1C 37 CB D1 C0 03 06"; //Authenticator 计算规则 //请求报文:使用 MD5(Code +Identifier + Length + 16字节零填充 + 属性 + 共享密钥) //响应报文:使用 MD5(Code +Identifier + Length + 请求报文Authenticator + 属性 + 共享密钥) byte[] request = RadiusUtil.StringToByteArray(strRequest); byte[] response = RadiusUtil.StringToByteArray(strResponse); var c1 = RadiusUtil.IsAuthenticRequest(request, secret); var c2 = RadiusUtil.IsAuthenticResponse(response, request, secret); // 请求包 var p1 = RadiusUtil.CreateRadiusAccountRequest(secret); var b1 = BitConverter.ToString(p1).Replace("-", " "); // 响应包 var p2 = RadiusUtil.CreateRadiusAccountResponse(p1, secret); var b2 = BitConverter.ToString(p2).Replace("-", " "); // 校验Message-Authenticator var b3 = RadiusUtil.IsMessageAuthenticator(p2, request, secret); // 报文解析 Console.WriteLine("【报文解析】"); RadiusPacket packet = new RadiusPacket { Code = request[0], Identifier = request[1], Length = BitConverter.ToUInt16(request, 2), Authenticator = request.Skip(4).Take(16).ToArray(), Attributes = RadiusUtil.ParseAttributes(request.Skip(20).ToArray()) }; // 输出解析结果 Console.WriteLine($"Code: {packet.Code}"); Console.WriteLine($"Identifier: {packet.Identifier}"); Console.WriteLine($"Length: {packet.Length}"); Console.WriteLine($"Authenticator: {BitConverter.ToString(packet.Authenticator).Replace("-", "")}"); foreach (var attr in packet.Attributes) { Console.WriteLine($"Attribute Type: {attr.Type}, Length: {attr.Length}, Value: {BitConverter.ToString(attr.Value)}"); } Console.WriteLine("OK"); } } }

浙公网安备 33010602011771号