Search code examples
encryptionsnmpdes

SNMP V3 CBC-DES: How to perform encryption of PDU with an 8 bytes long encryption key? (DES, Prev IV)


In the past I have implemented my own v1/v2 SNMP manager. Now I want to support also v3 and therefore have to implement the encryption of the PDU. According to RFC2274 chapter 8.1.1.1 the first 8 bytes of the 16 bytes private privacy key represent the DES key, and the last 8 bytes of the private privacy key represent the Prev InitVector... both values required to perform the CBC-DES encryption of the PDU.

Now my question: Most of SNMP v3 manager tools e.g. Paessler SNMP Tester expect from the user at the input (at least) an 8 bytes long V3 encryption key. I suppose that this V3 encryption key must cover the complete 16 bytes private privacy key information as no other information related to the encryption can be passed. How do the tools internally calculate the DES key and Prev InitVector out of this short 8 bytes V3 encryption key?

Tests I have already done:

  • I used the 8 bytes long V3 encryption key as DES key and assumed the PrevIV is 0000 0000
  • I used the 8 bytes long V3 encryption key as DES key and also as PrevIV

-> The PDU I have encrypted in my code according to the algorithm described in the RFC2274 chapter 8.1.1.1 result always in something completly different compared to the encrypted PDU of Paessler I read out with Wireshark. (For this test I used the same PDU and salt as Paessler which I read out in the privParameters field through Wireshark)

Please no discussion if CBC-DES is secure or not, thx! ;-)


Solution

  • The piece you are missing is the Key Localization step. The "encryption key" that you mentioned is more accurately called a "shared secret" (the RFCs usually call it a "password"). In order to communicate with an SNMP engine, you have to generate a "localized key", by combining the shared secret with the authoritative engine ID (in every interaction between two engines, one of them is authoritative -- you can read about it in RFC 3414 section 2.1). The key localization algorithm is described in RFC 3414 section 2.6. It's not explained very clearly, so I'll try to summarize.

    The first step in the localization algorithm is to generate an intermediate key from the password. Create a buffer of length 1048576 (2^20) and fill it up by repeating the password over and over (truncate the last copy of the password if needed). Then compute the hash of the buffer using the user's Authentication Protocol. (Okay, so you don't actually have to create a buffer of that size, you can hash it one chunk at a time -- details depend on the implementation of the hashing algorithm).

    After computing Ku, compute the hash of Ku + engineID + Ku (where + represents concatenation). This produces the "localized" key (called "Kul" -- the "l" means "localized", I guess) that you then truncate to 16 bytes (if needed), and split into the 8-byte DES key (of which only 7 bytes are actually used), and the 8-byte pre-IV.

    Appendix A.3 provides a sample password and engineID, as well as the expected Ku and Kul with both HMAC-MD5 and HMAC-SHA. Here's some code that performs the key localization for these sample inputs in Python, using the python-snmp library (pip install snmp==0.5.0):

    from snmp.security.usm.auth import *
    
    secret = b"maplesyrup"
    engineID = bytes.fromhex("00 00 00 00 00 00 00 00 00 00 00 02")
    
    Ku = HmacMd5.computeKey(secret)
    Kul = HmacMd5.localizeKey(Ku, engineID)
    print(f"MD5 Ku:  {Ku.hex()}")
    print(f"MD5 Kul: {Kul.hex()}")
    
    Ku = HmacSha.computeKey(secret)
    Kul = HmacSha.localizeKey(Ku, engineID)
    print(f"SHA Ku:  {Ku.hex()}")
    print(f"SHA Kul: {Kul.hex()}")
    

    This gives the expected keys for each example:

    MD5 Ku:  9faf3283884e92834ebc9847d8edd963
    MD5 Kul: 526f5eed9fcce26f8964c2930787d82b
    SHA Ku:  9fb5cc0381497b3793528939ff788d5d79145211
    SHA Kul: 6695febc9288e36282235fc7151f128497b38f3f