说明:对于snmpv3报文分为四个部分,版本号+全局数据+USM+ScopedPDU,其中的每个参数其实在RFC341X系列文档都是有说明的,但是有人可能不想找,我收集了一下,在下面展示。
目前使用的环境 :
stm32f407VET6+freertos9.0.0+lwip1.4.1+iReasoning MIB Broswer专业版+WireShark抓包软件
准备工作:使用freertos任务执行lwip的udp_socket,可进行ip地址修改
实现结果展示:可进行SNMPV3的所有PDU类型操作
MIB软件如下:
WireShark软件如下:
数据交互过程中会通过认证算法和加密算法对数据进行处理,使用的是HMAC_SHA1认证算法,底层使用的是SHA1算法实现,使用的是HMAC_DES_CBC加密算法,底层使用的DES_CBC算法实现,前后端使用的认证和加密算法必须一样,不然无法解析,选择什么算法实现,好像是RFC3414 和 RFC2103这两个文档最末尾有说明和源代码,同时iReasoning MIB Broswer专业版可进行配置,如下所示:
如下是部分重要操作的截图:
walk:
set:
具体说明GET/SET/GET-NEXT-REQUEST报文的流程:
首先对于iReasoning软件,发起这些GET/SET/GET-NEXT-REQUEST请求,都会先发起无认证加密的GET请求,向我的udp服务器获取USM的三个数据,服务器响应0xa8 Report请求
snmp.EnginedID:是前后端商量的,自己可以设置一个专属于自己的,同时它是唯一标识你的SNMPV3报文的,这个东西很重要,也是生成你的认证key和加密key的关键参数之一,就是下面这两种key,是由你的认证密码/加密密码+工程ID通过你选择的认证算法进行生成,具体代码在下面。
snmp.EnginedBOOT和snmp.EnginedTime是为了防止重攻击的,在客户端第一次发起GET请求向服务端获取USM参数时,由服务端上传给MIB软件,并且在整个snmp报文PDU类型交互中都是一样的。
snmp.msgAuthenticationParameters:认证参数,对整个snmp报文进行认证,认证算法使用HMAC_SHA1或者HMAC_MD5分别生成20字节或者16字节的认证参数,将其填入,注意哈!进行认证的时候,你的snmp报文的这个参数全部为0,认证完成后,将前12字节填入即可。
snmp.msgPrivacyParameters:加密/隐私参数,用于加密使用。数据值为8字节的随机数,与PrivKey生成IV,主要用于DES_CBC加密参数使用
以下是RFC文档中涉及的算法:
RFC 2104: HMAC: Keyed-Hashing for Message Authentication
/*
** Function: hmac_md5
*/
void
hmac_md5(text, text_len, key, key_len, digest)
unsigned char* text; /* pointer to data stream */
int text_len; /* length of data stream */
unsigned char* key; /* pointer to authentication key */
int key_len; /* length of authentication key */
caddr_t digest; /* caller digest to be filled in */
{
MD5_CTX context;
unsigned char k_ipad[65]; /* inner padding -
* key XORd with ipad
*/
unsigned char k_opad[65]; /* outer padding -
* key XORd with opad
*/
unsigned char tk[16];
int i;
/* if key is longer than 64 bytes reset it to key=MD5(key) */
if (key_len > 64) {
MD5_CTX tctx;
MD5Init(&tctx);
MD5Update(&tctx, key, key_len);
MD5Final(tk, &tctx);
key = tk;
key_len = 16;
}
/*
* the HMAC_MD5 transform looks like:
*
* MD5(K XOR opad, MD5(K XOR ipad, text))
*
* where K is an n byte key
* ipad is the byte 0x36 repeated 64 times
Krawczyk, et. al. Informational [Page 8]
RFC 2104 HMAC February 1997
* opad is the byte 0x5c repeated 64 times
* and text is the data being protected
*/
/* start out by storing key in pads */
bzero( k_ipad, sizeof k_ipad);
bzero( k_opad, sizeof k_opad);
bcopy( key, k_ipad, key_len);
bcopy( key, k_opad, key_len);
/* XOR key with ipad and opad values */
for (i=0; i<64; i++) {
k_ipad[i] ^= 0x36;
k_opad[i] ^= 0x5c;
}
/*
* perform inner MD5
*/
MD5Init(&context); /* init context for 1st
* pass */
MD5Update(&context, k_ipad, 64) /* start with inner pad */
MD5Update(&context, text, text_len); /* then text of datagram */
MD5Final(digest, &context); /* finish up 1st pass */
/*
* perform outer MD5
*/
MD5Init(&context); /* init context for 2nd
* pass */
MD5Update(&context, k_opad, 64); /* start with outer pad */
MD5Update(&context, digest, 16); /* then results of 1st
* hash */
MD5Final(digest, &context); /* finish up 2nd pass */
}
////通过md5生成key
void password_to_key_md5(
u_char *password, /* IN */
u_int passwordlen, /* IN */
u_char *engineID, /* IN - pointer to snmpEngineID */
u_int engineLength,/* IN - length of snmpEngineID */
u_char *key) /* OUT - pointer to caller 16-octet buffer */
{
MD5_CTX MD;
u_char *cp, password_buf[64];
u_long password_index = 0;
u_long count = 0, i;
MD5Init (&MD); /* initialize MD5 */
/**********************************************/
/* Use while loop until we've done 1 Megabyte */
/**********************************************/
while (count < 1048576) {
cp = password_buf;
for (i = 0; i < 64; i++) {
/*************************************************/
/* Take the next octet of the password, wrapping */
/* to the beginning of the password as necessary.*/
/*************************************************/
*cp++ = password[password_index++ % passwordlen];
}
MD5Update (&MD, password_buf, 64);
count += 64;
}
MD5Final (key, &MD); /* tell MD5 we're done */
/*****************************************************/
/* Now localize the key with the engineID and pass */
/* through MD5 to produce final key */
/* May want to ensure that engineLength <= 32, */
/* otherwise need to use a buffer larger than 64 */
/*****************************************************/
memcpy(password_buf, key, 16);
memcpy(password_buf+16, engineID, engineLength);
memcpy(password_buf+16+engineLength, key, 16);
MD5Init(&MD);
MD5Update(&MD, password_buf, 32+engineLength);
MD5Final(key, &MD);
return;
}
//通过sha1生成key
void password_to_key_sha(
u_char *password, /* IN */
u_int passwordlen, /* IN */
u_char *engineID, /* IN - pointer to snmpEngineID */
u_int engineLength,/* IN - length of snmpEngineID */
u_char *key) /* OUT - pointer to caller 20-octet buffer */
{
SHA_CTX SH;
u_char *cp, password_buf[72];
u_long password_index = 0;
u_long count = 0, i;
SHAInit (&SH); /* initialize SHA */
/**********************************************/
/* Use while loop until we've done 1 Megabyte */
/**********************************************/
while (count < 1048576) {
cp = password_buf;
for (i = 0; i < 64; i++) {
/*************************************************/
/* Take the next octet of the password, wrapping */
/* to the beginning of the password as necessary.*/
/*************************************************/
*cp++ = password[password_index++ % passwordlen];
}
SHAUpdate (&SH, password_buf, 64);
count += 64;
}
SHAFinal (key, &SH); /* tell SHA we're done */
/*****************************************************/
/* Now localize the key with the engineID and pass */
/* through SHA to produce final key */
/* May want to ensure that engineLength <= 32, */
/* otherwise need to use a buffer larger than 72 */
/*****************************************************/
memcpy(password_buf, key, 20);
memcpy(password_buf+20, engineID, engineLength);
memcpy(password_buf+20+engineLength, key, 20);
SHAInit(&SH);
SHAUpdate(&SH, password_buf, 40+engineLength);
SHAFinal(key, &SH);
return;
}