SNMPv3 Message Format
一个SNMPv3的Message包括以下部分:
1) msgVersion
2) msgID
3) msgMaxSize
发送方支持的最大消息长度/the maximum message size supported by a sender of an SNMP message.
4) msgFlags
8位字段,包含3个flag,最后三位从高到低依次为 reportableFlag, privFlag, authFlag.
reportableFlag如果设置为1,那么如果满足产生report的条件,就会返回一个report给发送方。
所有的request(GET,SET)或者inform,reportableFlag设置为1;Response,Trap,Report中需要设置为0。
privFlag为1表示PDU是加密的。authFlag为1表示PDU是认证的。
5) msgSecurityModel
0 reserved for 'any'
1 reserved for SNMPv1
2 reserved for SNMPv2c
3 User-Based Security Model (USM)
6) msgAuthoritativeEngineID
7) msgAuthoritativeEngineBoots
8) msgAuthoritativeEngineTime
9) msgUserName
10) msgAuthenticationParameters
12个字节的HMAC消息摘要(HMAC-MD5-96 或者 HMAC-SHA-96)
11) msgPrivacyParameters
用来产生DES加密算法的初始向量(IV)。
12) contextEngineID
13) contextName
14) PDU
包含了实际请求或者应答数据。
12+13+14一起称为scopedPDU
SNMPv3发送和接受消息的处理过程
RFC 3414中详细的描述了这个过程。
下载SNMP4J(http://www.snmp4j.org/html/download.html)的源代码,亦可以比较清楚地看到发送和接受消息的处理过程。
发送消息主要的处理在org.snmp4j.security.USM.generateRequestMessage()中(此函数又调用了generateResponseMessage(),实际上的处理在这个函数中)。
接受消息主要的处理在org.snmp4j.security.USM.processIncomingMsg()中。
发送消息首先根据请求数据生成平文的PDU,在PDU头上加上contextEngineID和contextName,得到scopedPDU。
然后对上面所说的SNMPv3 Message Format的1~14各部分设定相应的值,得到整个消息体,
其中msgAuthenticationParameters的12个字节数据置为全零。
然后对scopedPDU部分进行加密。
最后使用HMAC算法作用在加密后的整个消息体上,生成12个字节信息摘要,替换msgAuthenticationParameters。
接受消息和发送相反,首先把收到的消息体中msgAuthenticationParameters的12个字节数据替换为全零。
然后使用HMAC算法作用在替换了msgAuthenticationParameters后的消息体上,生成12个字节信息摘要,
把这个信息摘要和原始的msgAuthenticationParameters比较,如果不等,返回错误。
之后解密scopedPDU。
1) Key Localization
用户的认证(authKey)和加密密码(privKey)不会直接使用。
对于同一个用户的密码,通过Key Localization,用于不同的authoritative SNMP engine的密钥实际上是不一样的。
对于认证密码,Key Localization的过程如下:
org.snmp4j.security.AuthGeneric.passwordToKey(OctetString passwordString, byte[] engineID)
a) 使用用户密码重复填充1个64字节块,使用此64字节块重复填充得到1M字节的数据,
b) 使用认证算法(MD5/SHA1)作用于上面得到的1M字节的数据,得到的摘要为Ku
c) 在snmp engine id的前后加上Ku, 然后再次使用认证算法(MD5/SHA1),得到的摘要即为localized key: Kul
加密密码的处理过程如下:
org.snmp4j.security.SecurityProtocols.passwordToKey(OID privProtocolID, OID authProtocolID, OctetString passwordString, byte[] engineID)
authProtocolID和privProtocolID分别是当前用户的Authentication Protocol和Privacy Protocol.
a) 根据authProtocolID得到当前的AuthenticationProtocol
b) 调用org.snmp4j.security.AuthGeneric.passwordToKey(passwordString, engineID),实际上就是上面认证密码的Key Localization过程。
c) 根据privProtocolID得到加密算法,比较加密算法要求的密钥最小/最大长度和上面得到的localized privKey的长度,
如果过长截断,如果过短,调用privProtocol.extendShortKey()方法延长。
2) 认证
org.snmp4j.security.AuthGeneric.authenticate(byte[] authenticationKey, byte[] message, int messageOffset , int messageLength, ByteArrayWindow digest)
认证实际上就是根据HMAC计算消息摘要(digest)的过程。
传入的authenticationKey就是localized authKey.
计算摘要的过程见HMAC算法->http://blog.csdn.net/fw0124/article/details/8473858
注意只使用计算得到摘要的前12个字节(96位)
3) 加密解密
CBC-DES加密的处理在函数
org.snmp4j.security.PrivDES.encrypt(byte[] unencryptedData, int offset, int length, byte[] encryptionKey, long engineBoots, long engineTime, DecryptParams decryptParams)
里面。
参数encryptionKey就是16字节的localized privKey, 它的前8个字节用来作为DES key, 因为DES只使用56 bits,每个字节的最低位实际上被丢弃。
The first 8 octets of the 16-octet secret (private privacy key) are used as a DES key. Since DES uses only 56 bits, the Least Significant Bit in each octet is disregarded.
CBC模式需要一个64 bit的IV(Initialization Vector)。
encryptionKey的后8个字节用作pre-IV。
为了让每个packet能有不同的IV, 需要对pre-IV进行salt。
使用32-bit的snmpEngineBoots和一个32-bit的随机数一起产生一个64-bit(8字节)的salt。
salt的前4个字节是32-bit的snmpEngineBoots,后4个字节是32-bit的随机数。
然后salt和pre-IV进行异或运算得到IV, IV=salt XOR pre-IV。
最后salt放入msgPrivacyParameters字段发送给接受方用来产生正确的IV进行解密。
解密的处理在函数org.snmp4j.security.PrivDES.decrypt(byte[], int, int, byte[], long, long, DecryptParams)里面。