View Javadoc
1   // NAME
2   //      $RCSfile: SnmpUtilities.java,v $
3   // DESCRIPTION
4   //      [given below in javadoc format]
5   // DELTA
6   //      $Revision: 1.27 $
7   // CREATED
8   //      $Date: 2009/03/05 12:57:57 $
9   // COPYRIGHT
10  //      Westhawk Ltd
11  // TO DO
12  //
13  
14  /*
15   * Copyright (C) 2000 - 2006 by Westhawk Ltd
16   * <a href="www.westhawk.co.uk">www.westhawk.co.uk</a>
17   *
18   * Permission to use, copy, modify, and distribute this software
19   * for any purpose and without fee is hereby granted, provided
20   * that the above copyright notices appear in all copies and that
21   * both the copyright notice and this permission notice appear in
22   * supporting documentation.
23   * This software is provided "as is" without express or implied
24   * warranty.
25   * author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
26   */
27  
28  package uk.co.westhawk.snmp.util;
29  
30  /*-
31   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
32   * SNMP Java Client
33   * ჻჻჻჻჻჻
34   * Copyright 2023 MetricsHub, Westhawk
35   * ჻჻჻჻჻჻
36   * This program is free software: you can redistribute it and/or modify
37   * it under the terms of the GNU Lesser General Public License as
38   * published by the Free Software Foundation, either version 3 of the
39   * License, or (at your option) any later version.
40   *
41   * This program is distributed in the hope that it will be useful,
42   * but WITHOUT ANY WARRANTY; without even the implied warranty of
43   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
44   * GNU General Lesser Public License for more details.
45   *
46   * You should have received a copy of the GNU General Lesser Public
47   * License along with this program.  If not, see
48   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
49   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
50   */
51  
52  import java.security.MessageDigest;
53  import java.security.NoSuchAlgorithmException;
54  import java.util.*;
55  
56  import uk.co.westhawk.snmp.stack.*;
57  
58  import org.bouncycastle.crypto.digests.*;
59  import org.bouncycastle.crypto.params.*;
60  import org.bouncycastle.crypto.engines.*;
61  
62  import static uk.co.westhawk.snmp.stack.SnmpContextv3Face.MD5_PROTOCOL;
63  import static uk.co.westhawk.snmp.stack.SnmpContextv3Face.SHA1_PROTOCOL;
64  import static uk.co.westhawk.snmp.stack.SnmpContextv3Face.SHA224_PROTOCOL;
65  import static uk.co.westhawk.snmp.stack.SnmpContextv3Face.SHA256_PROTOCOL;
66  import static uk.co.westhawk.snmp.stack.SnmpContextv3Face.SHA384_PROTOCOL;
67  import static uk.co.westhawk.snmp.stack.SnmpContextv3Face.SHA512_PROTOCOL;
68  import static uk.co.westhawk.snmp.stack.SnmpContextv3Basis.AES128_KEY_LENGTH;
69  import static uk.co.westhawk.snmp.stack.SnmpContextv3Basis.AES192_KEY_LENGTH;
70  import static uk.co.westhawk.snmp.stack.SnmpContextv3Basis.AES256_KEY_LENGTH;
71  
72  /**
73   * This class contains utilities for key and authentication encoding.
74   * See <a href="http://www.ietf.org/rfc/rfc3414.txt">SNMP-USER-BASED-SM-MIB</a>.
75   *
76   * @author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
77   * @version $Revision: 1.27 $ $Date: 2009/03/05 12:57:57 $
78   */
79  public class SnmpUtilities extends Object {
80      /**
81       * The SHA-256 algorithm name.
82       */
83      private static final String SHA256_ALGORITHM = "SHA-256";
84      /**
85       * The SHA-512 algorithm name.
86       */
87      private static final String SHA512_ALGORITHM = "SHA-512";
88      /**
89       * The SHA-224 algorithm name.
90       */
91      private static final String SHA224_ALGORITHM = "SHA-224";
92      /**
93       * The SHA-384 algorithm name.
94       */
95      private static final String SHA384_ALGORITHM = "SHA-384";
96  
97      private static final String version_id = "@(#)$Id: SnmpUtilities.java,v 1.27 2009/03/05 12:57:57 birgita Exp $ Copyright Westhawk Ltd";
98  
99      final static int ONEMEG = 1048576;
100     final static int SALT_LENGTH = 8; // in bytes
101 
102     private static int salt_count = -1;
103     private static long asalt;
104 
105     // 12 zero octets
106     static byte[] dummySha1FingerPrint = new byte[12];
107 
108     // 24 zero octets
109     static byte[] dummySHA256FingerPrint = new byte[24];
110 
111     // 48 zero octets
112     static byte[] dummySHA512FingerPrint = new byte[48];
113 
114     // 16 zero octets
115     static byte[] dummySHA224FingerPrint = new byte[16];
116 
117     // 32 zero octets
118     static byte[] dummySHA384FingerPrint = new byte[32];
119 
120     private static final Map<Integer, byte[]> DUMMY_FINGERPRINT_MAP = new HashMap<>();
121     static {
122         DUMMY_FINGERPRINT_MAP.put(SnmpContextv3Basis.SHA256_PROTOCOL, dummySHA256FingerPrint);
123         DUMMY_FINGERPRINT_MAP.put(SnmpContextv3Basis.SHA1_PROTOCOL, dummySha1FingerPrint);
124         DUMMY_FINGERPRINT_MAP.put(SnmpContextv3Basis.MD5_PROTOCOL, dummySha1FingerPrint);
125         DUMMY_FINGERPRINT_MAP.put(SnmpContextv3Basis.SHA512_PROTOCOL, dummySHA512FingerPrint);
126         DUMMY_FINGERPRINT_MAP.put(SnmpContextv3Basis.SHA224_PROTOCOL, dummySHA224FingerPrint);
127         DUMMY_FINGERPRINT_MAP.put(SnmpContextv3Basis.SHA384_PROTOCOL, dummySHA384FingerPrint);
128     }
129 
130     /**
131      * Returns the String representation of the SNMP version number.
132      * 
133      * @param version The version number
134      * @return The corresponding String.
135      */
136     public static String getSnmpVersionString(int version) {
137         String versionString;
138 
139         switch (version) {
140             case SnmpConstants.SNMP_VERSION_1:
141                 versionString = "SNMPv1";
142                 break;
143             case SnmpConstants.SNMP_VERSION_2c:
144                 versionString = "SNMPv2c";
145                 break;
146             case SnmpConstants.SNMP_VERSION_3:
147                 versionString = "SNMPv3";
148                 break;
149             default:
150                 versionString = "Unsupported version no " + version;
151         }
152         return versionString;
153     }
154 
155     /**
156      * Converts a hexadecimal ASCII string to a byte array. The method is case
157      * insensitive, so F7 works as well as f7. The string should have
158      * the form F7d820 and should omit the '0x'.
159      * This method is the reverse of <code>toHexString</code>.
160      *
161      * @param hexStr The string representing a hexadecimal number
162      * @return the byte array of hexStr
163      * @see #toHexString(byte[])
164      */
165     public static byte[] toBytes(String hexStr) {
166         byte mask = (byte) 0x7F;
167         byte[] bytes = new byte[0];
168 
169         if (hexStr != null) {
170             hexStr = hexStr.toUpperCase();
171             int len = hexStr.length();
172             bytes = new byte[(len / 2)];
173             int sPos = 0; // position in hexStr
174             int bPos = 0; // position in bytes
175             while (sPos < len) {
176                 char a = hexStr.charAt(sPos);
177                 char b = hexStr.charAt(sPos + 1);
178 
179                 int v1 = Character.digit(a, 16);
180                 int v2 = Character.digit(b, 16);
181                 int v3 = (int) (v1 * 16 + v2);
182                 bytes[bPos] = (byte) v3;
183 
184                 sPos += 2;
185                 bPos++;
186             }
187         }
188         return bytes;
189     }
190 
191     /**
192      * Converts one long value to its byte value.
193      *
194      * @param l The long value
195      * @return It's byte value
196      * @throws IllegalArgumentException when l is not in between 0 and 255.
197      *
198      * @see #longToByte(long[])
199      * @since 4_14
200      */
201     public static byte longToByte(long l) throws IllegalArgumentException {
202         byte ret = 0;
203         if ((l < 0) || (l > 255)) {
204             throw new IllegalArgumentException("Valid byte values are between 0 and 255."
205                     + "Got " + l);
206         }
207         ret = (byte) (l);
208         return ret;
209     }
210 
211     /**
212      * Converts an array of long values to its array of byte values.
213      *
214      * @param l The array of longs
215      * @return The array of bytes
216      * @throws IllegalArgumentException when one of the longs is not in between 0
217      *                                  and 255.
218      *
219      * @see #longToByte(long)
220      * @since 4_14
221      */
222     public static byte[] longToByte(long[] l) throws IllegalArgumentException {
223         int len = l.length;
224         byte[] ret = new byte[len];
225         for (int i = 0; i < len; i++) {
226             ret[i] = longToByte(l[i]);
227         }
228         return ret;
229     }
230 
231     /**
232      * Dumps (prints) the byte array. Debug method.
233      * 
234      * @param headerStr String that will be printed as header
235      * @param bytes     Bytes to be dumped as hex.
236      */
237     public static void dumpBytes(String headerStr, byte[] bytes) {
238         StringBuffer buf = new StringBuffer(bytes.length);
239         buf.append("\n");
240         buf.append(headerStr).append("\n");
241         buf.append("bytes.length: ").append(bytes.length).append("\n");
242         int len = bytes.length;
243         int i = 0;
244         for (i = 0; i < len; i++) {
245             buf.append(toHex(bytes[i]) + " ");
246             if (0 == ((i + 1) % 8)) {
247                 buf.append("\n");
248             }
249         }
250         buf.append("\n");
251         System.out.println(buf.toString());
252     }
253 
254     /**
255      * Converts a byte array to a hexadecimal ASCII string.
256      * The string will be in upper case and does not start with '0x'.
257      * This method is the reverse of <code>toBytes</code>.
258      *
259      * @param bytes The byte array
260      * @return The string representing the byte array
261      * @see #toBytes(String)
262      */
263     public static String toHexString(byte[] bytes) {
264         String str = "";
265         if (bytes != null) {
266             int len = bytes.length;
267             for (int i = 0; i < len; i++) {
268                 str += toHex(bytes[i]);
269             }
270         }
271         return str;
272     }
273 
274     /**
275      * Converts one int to a hexadecimal ASCII string.
276      *
277      * @param val The integer
278      * @return The hex string
279      */
280     public static String toHex(int val) {
281         int val1, val2;
282 
283         val1 = (val >> 4) & 0x0F;
284         val2 = (val & 0x0F);
285 
286         return ("" + HEX_DIGIT[val1] + HEX_DIGIT[val2]);
287     }
288 
289     final static char[] HEX_DIGIT = { '0', '1', '2', '3', '4', '5', '6', '7',
290             '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
291 
292     /**
293      * Compaires two byte arrays and returns if they are equal.
294      *
295      * @param array1 the first byte array
296      * @param array2 the second byte array
297      * @return whether they are equal of not.
298      */
299     public static boolean areBytesEqual(byte[] array1, byte[] array2) {
300         boolean same = true;
301         int len1 = array1.length;
302         if (len1 == array2.length) {
303             int i = 0;
304             while (i < len1 && same) {
305                 same = (array1[i] == array2[i]);
306                 i++;
307             }
308         } else {
309             same = false;
310         }
311         return same;
312     }
313 
314     /**
315      * Converts the user's password and the SNMP Engine Id to the localized key
316      * using the MD5 protocol.
317      * Described in
318      * <a href="http://www.ietf.org/rfc/rfc3414.txt">SNMP-USER-BASED-SM-MIB</a>.
319      *
320      * @param passwKey The password key
321      * @param engineId The SNMP engine Id
322      * @see SnmpContextv3#setUserAuthenticationPassword(String)
323      * @see #passwordToKeyMD5(String)
324      */
325     public static byte[] getLocalizedKeyMD5(byte[] passwKey, String engineId) {
326         byte[] ret = null;
327         MD5Digest mdc = new MD5Digest();
328         mdc.reset();
329 
330         byte[] beid = toBytes(engineId);
331         if ((beid != null) && (passwKey != null)) {
332             // see page 169 of 0-13-021453-1 A Practical Guide to SNMP
333             mdc.update(passwKey, 0, passwKey.length);
334             mdc.update(beid, 0, beid.length);
335             mdc.update(passwKey, 0, passwKey.length);
336             ret = new byte[mdc.getDigestSize()];
337             mdc.doFinal(ret, 0);
338         }
339         return ret;
340     }
341 
342     /**
343      * Converts the user's password and the SNMP Engine Id to the localized key
344      * using the SHA protocol.
345      *
346      * @param passwKey The printable user password
347      * @param engineId The SNMP engine Id
348      * @see SnmpContextv3#setUserAuthenticationPassword(String)
349      */
350     public static byte[] getLocalizedKeySHA1(byte[] passwKey, String engineId) {
351         byte[] ret = null;
352         SHA1Digest mdc = new SHA1Digest();
353         mdc.reset();
354 
355         byte[] beid = toBytes(engineId);
356         if ((beid != null) && (passwKey != null)) {
357             // see page 169 of 0-13-021453-1 A Practical Guide to SNMP
358             mdc.update(passwKey, 0, passwKey.length);
359             mdc.update(beid, 0, beid.length);
360             mdc.update(passwKey, 0, passwKey.length);
361             ret = new byte[mdc.getDigestSize()];
362             mdc.doFinal(ret, 0);
363         }
364         return ret;
365     }
366 
367     /**
368      * Converts the user's password to an authentication key using the SHA1
369      * protocol. Note, this is not the same as generating the localized key
370      * as is
371      * described in
372      * <a href="http://www.ietf.org/rfc/rfc3414.txt">SNMP-USER-BASED-SM-MIB</a>.
373      *
374      * @param password The printable user password
375      * @see SnmpContextv3#setUserAuthenticationPassword(String)
376      * @see #getLocalizedKeyMD5
377      */
378     public static byte[] passwordToKeySHA1(String password) {
379         SHA1Digest sha;
380         byte[] ret = null;
381         sha = new SHA1Digest();
382         byte[] passwordBuf = new byte[64];
383         int pl = password.length();
384         byte[] pass = new byte[pl];
385 
386         // copy to byte array - stripping off top byte
387         for (int i = 0; i < pl; i++) {
388             pass[i] = (byte) (0xFF & password.charAt(i));
389         }
390 
391         int count = 0;
392         int passwordIndex = 0;
393         Date then = (AsnObject.debug > 1) ? new Date() : null;
394 
395         synchronized (sha) {
396             while (count < ONEMEG) {
397                 int cp = 0;
398                 int i = 0;
399                 while (i < 64) {
400                     int pim = passwordIndex % pl;
401                     int len = 64 - cp;
402                     int pr = pl - pim;
403                     if (len > pr) {
404                         len = pr;
405                     }
406                     System.arraycopy(pass, pim, passwordBuf, cp, len);
407                     i += len;
408                     cp += len;
409                     passwordIndex += len;
410                 }
411 
412                 // need to optimize this.....
413                 sha.update(passwordBuf, 0, passwordBuf.length);
414                 count += 64;
415             }
416             // implicit that ONEMEG % 64 == 0
417             ret = new byte[sha.getDigestSize()];
418             sha.doFinal(ret, 0);
419         }
420 
421         if (AsnObject.debug > 1) {
422             Date now = new Date();
423             long diff = now.getTime() - then.getTime();
424             System.out.println("(Complex) pass to key takes " + diff / 1000.0);
425         }
426 
427         return ret;
428     }
429 
430     /**
431      * Converts the user's password to an authentication key using the MD5
432      * protocol. Note, this is not the same as generating the localized key
433      * as is
434      * described in
435      * <a href="http://www.ietf.org/rfc/rfc3414.txt">SNMP-USER-BASED-SM-MIB</a>.
436      *
437      * @param password The printable user password
438      * @see SnmpContextv3#setUserAuthenticationPassword(String)
439      * @see #getLocalizedKeyMD5
440      */
441     public static byte[] passwordToKeyMD5(String password) {
442         MD5Digest mdc;
443         byte[] ret = null;
444         mdc = new MD5Digest();
445         byte[] passwordBuf = new byte[64];
446         int pl = password.length();
447         byte[] pass = new byte[pl];
448 
449         // copy to byte array - stripping off top byte
450         for (int i = 0; i < pl; i++) {
451             pass[i] = (byte) (0xFF & password.charAt(i));
452         }
453 
454         int count = 0;
455         int passwordIndex = 0;
456         Date then = (AsnObject.debug > 1) ? new Date() : null;
457         synchronized (mdc) {
458             while (count < ONEMEG) {
459                 int cp = 0;
460                 int i = 0;
461                 while (i < 64) {
462                     int pim = passwordIndex % pl;
463                     int len = 64 - cp;
464                     int pr = pl - pim;
465                     if (len > pr) {
466                         len = pr;
467                     }
468                     System.arraycopy(pass, pim, passwordBuf, cp, len);
469                     i += len;
470                     cp += len;
471                     passwordIndex += len;
472                 }
473                 mdc.update(passwordBuf, 0, passwordBuf.length);
474                 count += 64;
475             }
476             // implicit that ONEMEG % 64 == 0
477             ret = new byte[mdc.getDigestSize()];
478             mdc.doFinal(ret, 0);
479         }
480 
481         if (AsnObject.debug > 1) {
482             Date now = new Date();
483             long diff = now.getTime() - then.getTime();
484             System.out.println("(Complex) pass to key takes " + diff / 1000.0);
485         }
486 
487         return ret;
488     }
489 
490     /**
491      * Returns the 12 byte MD5 fingerprint.
492      * 
493      * @param key     The key
494      * @param message The message
495      * @see #getFingerPrintSHA1
496      */
497     public final static byte[] getFingerPrintMD5(byte[] key, byte[] message) {
498         if ((AsnObject.debug > 5) && (key.length != 16)) {
499             System.out.println("MD5 key length wrong");
500         }
501         return getFingerPrint(key, message, false);
502     }
503 
504     /**
505      * Returns the 12 byte SHA1 fingerprint.
506      * 
507      * @param key     The key
508      * @param message The message
509      * @see #getFingerPrintMD5
510      */
511     public final static byte[] getFingerPrintSHA1(byte[] key, byte[] message) {
512         if ((AsnObject.debug > 5) && (key.length != 20)) {
513             System.out.println("SHA1 key length wrong");
514         }
515         return getFingerPrint(key, message, true);
516     }
517 
518     /**
519      * Returns the DES salt.
520      * The "salt" value is generated by concatenating the 32-bit
521      * snmpEngineBoots value with a 32-bit counter value that the encryption
522      * engine maintains. This 32-bit counter will be initialised to some
523      * arbitrary value at boot time.
524      *
525      * <p>
526      * See "A Practical Guide to SNMPv3 and Network Management" section 6.8
527      * Privacy, p 194.
528      * </p>
529      *
530      * @param snmpEngineBoots The (estimated) boots of the authoritative engine
531      * @return The salt
532      */
533     public final static byte[] getSaltDES(int snmpEngineBoots) {
534         if (salt_count == -1) {
535             // initialise the 2nd part of the salt
536             Random rand = new Random();
537             salt_count = rand.nextInt();
538         }
539         byte[] salt = new byte[SALT_LENGTH];
540         setBytesFromInt(salt, snmpEngineBoots, 0);
541         setBytesFromInt(salt, salt_count, SALT_LENGTH / 2);
542         salt_count++;
543         return salt;
544     }
545 
546     /*
547      *
548      * The 64-bit integer is then put into the msgPrivacyParameters field encoded
549      * as an OCTET STRING of length 8 octets.
550      * The integer is then modified for the subsequent message.
551      * We recommend that it is incremented by one until it reaches its
552      * maximum value,
553      * at which time it is wrapped.
554      * An implementation can use any method to vary the value of the local
555      * 64-bit integer,
556      * providing the chosen method never generates a duplicate IV for the
557      * same key.
558      *
559      */
560 
561     /**
562      * Returns the AES salt.
563      * 
564      * @return The salt
565      */
566     public static byte[] getSaltAES() {
567         if (asalt == 0) {
568             java.security.SecureRandom rand = new java.security.SecureRandom();
569             asalt = rand.nextLong();
570         } else {
571             asalt++;
572         }
573         byte[] tsalt = new byte[8];
574         setBytesFromLong(tsalt, asalt, 0);
575         return tsalt;
576     }
577 
578     /**
579      * Returns the DES key.
580      * The 16-byte secret privacy key is made up of 8 bytes that
581      * make up the DES key and 8 bytes used as a preinitialisation
582      * vector.
583      *
584      * @param secretPrivacyKey The secret privacy key
585      * @return The key
586      */
587     public final static byte[] getDESKey(byte[] secretPrivacyKey)
588             throws PduException {
589         byte[] desKey = new byte[8];
590         if (secretPrivacyKey.length < 16) {
591             throw new PduException("SnmpUtilities.getDESKey():"
592                     + " secretPrivacyKey is < 16");
593         }
594         System.arraycopy(secretPrivacyKey, 0, desKey, 0, 8);
595         return desKey;
596     }
597 
598     /**
599      * Returns the first 128 bits of the localized key Kul are used as the
600      * AES encryption key.
601      * 
602      * @param secretPrivacyKey The secret privacy key
603      * @return The key
604      */
605     public final static byte[] getAESKey(byte[] secretPrivacyKey)
606         throws PduException {
607             int len = secretPrivacyKey.length;
608             if (len != AES128_KEY_LENGTH && len != AES192_KEY_LENGTH && len != AES256_KEY_LENGTH) {
609                 throw new PduException("Invalid AES key length: expected 16, 24, or 32 bytes but got " + len);
610             }
611 
612             return secretPrivacyKey;
613         }
614 
615     /**
616      * Returns the DES initial value.
617      * The 16-byte secret privacy key is made up of 8 bytes that
618      * make up the DES key and 8 bytes used as a preinitialisation
619      * vector.
620      * The initialization vector that is used by the DES algorithm is the
621      * result of the 8-byte preinitialisation vector XOR-ed with the 8-byte
622      * "salt".
623      *
624      * @param secretPrivacyKey The secret privacy key
625      * @param salt             The salt
626      * @return The initial value
627      */
628     public final static byte[] getDESInitialValue(byte[] secretPrivacyKey,
629             byte[] salt) throws PduException {
630         byte[] initV = new byte[8];
631         if (secretPrivacyKey.length < 16) {
632             throw new PduException("SnmpUtilities.getInitialValue():"
633                     + " secretPrivacyKey is < 16");
634         }
635 
636         int spk = 8;
637         for (int i = 0; i < initV.length; i++) {
638             initV[i] = (byte) (secretPrivacyKey[spk] ^ salt[i]);
639             spk++;
640         }
641         return initV;
642     }
643 
644     /**
645      * Returns the first 128 bits of the localized key Kul are used as the
646      * AES encryption key.
647      * RFC 3826 3.1.2.1 AES Encryption Key and IV.
648      *
649      * The 128-bit IV is obtained as the concatenation of the authoritative
650      * SNMP engine's 32-bit snmpEngineBoots, the SNMP engine's 32-bit
651      * snmpEngineTime, and a local 64-bit integer. The 64-bit integer is
652      * initialized to a pseudo-random value at boot time.
653      */
654     public static byte[] getAESInitialValue(int engineBoots, int engineTime, byte[] salt) {
655         byte ret[] = new byte[16];
656 
657         // The IV is concatenated as follows: the 32-bit snmpEngineBoots is
658         // converted to the first 4 octets (Most Significant Byte first),
659         setBytesFromInt(ret, engineBoots, 0);
660 
661         // the 32-bit snmpEngineTime is converted to the subsequent 4 octets
662         // (Most Significant Byte first),
663         setBytesFromInt(ret, engineTime, 4);
664 
665         // and the 64-bit integer is then converted to the last 8 octets
666         // (Most Significant Byte first).
667         ret[8] = salt[0];
668         ret[9] = salt[1];
669         ret[10] = salt[2];
670         ret[11] = salt[3];
671         ret[12] = salt[4];
672         ret[13] = salt[5];
673         ret[14] = salt[6];
674         ret[15] = salt[7];
675         return ret;
676     }
677 
678     /*
679      * Page 8.
680      * The 64-bit integer must be placed in the privParameters field to enable the
681      * receiving entity to compute the correct IV and to decrypt the message.
682      * This 64-bit value is called the "salt" in this document.
683      * 
684      * Note that the sender and receiver must use the same IV value, i.e., they
685      * must both use the same values of the individual components used to
686      * create the IV. In particular, both sender and receiver must use the
687      * values of snmpEngineBoots, snmpEngineTime, and the 64-bit integer which
688      * are contained in the relevant message (in the
689      * msgAuthoritativeEngineBoots, msgAuthoritativeEngineTime, and
690      * privParameters fields respectively).
691      * 
692      * 3.1.3 Data Encryption
693      * The data to be encrypted is treated as a sequence of octets.
694      * 
695      * The data is encrypted in Cipher Feedback mode with the parameter s set to 128
696      * according to the definition of CFB mode given in Section 6.3 of [AES-MODE].
697      * A clear diagram of the encryption and decryption process is given in
698      * Figure 3 of [AES-MODE].
699      * 
700      * The plaintext is divided into 128-bit blocks. The last block may have
701      * fewer than 128 bits, and no padding is required.
702      * 
703      * The first input block is the IV, and the forward cipher operation is
704      * applied to the IV to produce the first output block. The first
705      * ciphertext block is produced by exclusive-ORing the first plaintext
706      * block with the first output block. The ciphertext block is also used as
707      * the input block for the subsequent forward cipher operation.
708      * 
709      * The process is repeated with the successive input blocks until a ciphertext
710      * segment is produced from every plaintext segment.
711      * 
712      * The last ciphertext block is produced by exclusive-ORing the last
713      * plaintext segment of r bits (r is less than or equal to 128) with the
714      * segment of the r most significant bits of the last output block.
715      */
716 
717     /**
718      * Encrypts bytes using AES.
719      *
720      * @param plaintext        The plain bytes
721      * @param secretPrivacyKey The secret privacy key
722      * @param engineBoots
723      * @param engineTime
724      * @param salt             The salt
725      * @return The encrypted bytes
726      * @throws EncodingException
727      */
728     public static byte[] AESencrypt(byte[] plaintext,
729             byte[] secretPrivacyKey,
730             int engineBoots,
731             int engineTime,
732             byte[] salt)
733             throws EncodingException {
734         byte[] aesKey = null;
735         byte[] iv = null;
736         try {
737             aesKey = getAESKey(secretPrivacyKey);
738             iv = getAESInitialValue(engineBoots, engineTime, salt);
739         } catch (PduException exc) {
740             throw new EncodingException(exc.getMessage());
741         }
742         // do some stuff
743         AESEngine aes = new AESEngine();
744         KeyParameter param = new KeyParameter(aesKey);
745         aes.init(true, param);
746 
747         // no padding so
748         int newL = plaintext.length;
749         int bcount = newL / 16;
750         byte[] result = new byte[newL];
751         byte[] in = new byte[16];
752         byte[] out = new byte[16];
753         int posIn = 0;
754         int posResult = 0;
755         // initial input is the iv
756         System.arraycopy(iv, 0, in, 0, 16);
757         for (int b = 0; b < bcount; b++) {
758             aes.processBlock(in, 0, out, 0);
759             for (int i = 0; i < 16; i++) {
760                 in[i] = result[posResult] = (byte) (out[i] ^ plaintext[posIn]);
761                 posResult++;
762                 posIn++;
763             }
764         }
765         // and the leftovers.
766         if (posIn < newL) {
767             aes.processBlock(in, 0, out, 0);
768             for (int i = 0; posIn < newL; i++) {
769                 result[posResult] = (byte) (out[i] ^ plaintext[posIn]);
770                 posResult++;
771                 posIn++;
772             }
773         }
774         return result;
775     }
776 
777     /**
778      * Encrypts bytes using DES.
779      * The plaintext needs to be a multiple of 8 octets. If it isn't, it
780      * will be padded at the end.
781      * This plaintext will be divided into 64-bit blocks. The plaintext for
782      * each block is XOR-ed with the "ciphertext" of the previous block.
783      * The result is then encrypted, added to the encrypted PDU portion of
784      * the message, and used as the "ciphertext" for the next block. For the
785      * first block, the initialization vector is used as the "ciphertext".
786      *
787      * @param plain            The plain bytes
788      * @param secretPrivacyKey The secret privacy key
789      * @param salt             The salt
790      * @return The encrypted bytes
791      */
792     public final static byte[] DESencrypt(byte[] plain, byte[] secretPrivacyKey,
793             byte[] salt) throws EncodingException {
794         byte[] desKey = null;
795         byte[] iv = null;
796         try {
797             desKey = getDESKey(secretPrivacyKey);
798             iv = getDESInitialValue(secretPrivacyKey, salt);
799         } catch (PduException exc) {
800             throw new EncodingException(exc.getMessage());
801         }
802 
803         // First pad the plain message with 0's
804         int l = plain.length;
805         int div = l / 8;
806         int mod = l % 8;
807         if (mod > 0) {
808             div++;
809         }
810         int newL = div * 8;
811         byte[] paddedOrig = new byte[newL];
812         System.arraycopy(plain, 0, paddedOrig, 0, l);
813         for (int i = l; i < newL; i++) {
814             paddedOrig[i] = (byte) 0x0;
815         }
816 
817         DESEngine des = new DESEngine();
818         DESParameters param = new DESParameters(desKey);
819         des.init(true, param);
820 
821         byte[] result = new byte[newL];
822         byte[] in = new byte[8];
823         byte[] cipherText = iv;
824         int posIn = 0;
825         int posResult = 0;
826         for (int b = 0; b < div; b++) {
827             for (int i = 0; i < 8; i++) {
828                 in[i] = (byte) (paddedOrig[posIn] ^ cipherText[i]);
829                 posIn++;
830             }
831             des.processBlock(in, 0, cipherText, 0);
832             System.arraycopy(cipherText, 0, result, posResult, cipherText.length);
833             posResult += cipherText.length;
834         }
835         return result;
836     }
837 
838     /**
839      * Decryptes bytes using DES.
840      * <ul>
841      * <li>
842      * If the length of the data portion is not a multiple of 8 bytes, the
843      * message is discarded.
844      * </li>
845      * <li>
846      * The first encrypted text block is decrypted. The decryption result is
847      * XOR-ed with the initialization vector, and the result is the first
848      * plaintext block.
849      * </li>
850      * <li>
851      * The rest of the encrypted text blocks are treated similarly. They are
852      * decrypted, with the results being XOR-ed with the previous encrypted
853      * text block to obtain the plaintext block.
854      * </li>
855      * </ul>
856      *
857      * @param encryptedText    The encrypted text
858      * @param salt             The salt
859      * @param secretPrivacyKey The secret privacy key
860      * @return The decrypted bytes
861      */
862     public final static byte[] DESdecrypt(byte[] encryptedText, byte[] salt,
863             byte[] secretPrivacyKey) throws DecodingException {
864         int l = encryptedText.length;
865         int div = l / 8;
866         int mod = l % 8;
867         if (mod != 0) {
868             throw new DecodingException("SnmpUtilities.decrypt():"
869                     + " The encrypted scoped PDU should be a multiple of 8 bytes");
870         }
871 
872         byte[] desKey = null;
873         byte[] iv = null;
874         try {
875             desKey = getDESKey(secretPrivacyKey);
876             iv = getDESInitialValue(secretPrivacyKey, salt);
877         } catch (PduException exc) {
878             throw new DecodingException(exc.getMessage());
879         }
880 
881         DESEngine des = new DESEngine();
882         DESParameters param = new DESParameters(desKey);
883         des.init(false, param);
884 
885         byte[] plain = new byte[l];
886         byte[] in = new byte[8];
887         byte[] out = new byte[8];
888         byte[] cipherText = iv;
889         int posPlain = 0;
890         int posEncr = 0;
891         for (int b = 0; b < div; b++) {
892             System.arraycopy(encryptedText, posEncr, in, 0, in.length);
893             posEncr += in.length;
894             des.processBlock(in, 0, out, 0);
895             for (int i = 0; i < 8; i++) {
896                 plain[posPlain] = (byte) (out[i] ^ cipherText[i]);
897                 posPlain++;
898             }
899             System.arraycopy(in, 0, cipherText, 0, in.length);
900         }
901         return plain;
902     }
903 
904     /*
905      * 3.1.4 Data Decryption.
906      * In CFB decryption, the IV is the first input block, the first ciphertext
907      * is used for the second input block, the second ciphertext is used for
908      * the third input block, etc. The forward cipher function is applied to
909      * each input block to produce the output blocks. The output blocks are
910      * exclusive-ORed with the corresponding ciphertext blocks to recover the
911      * plaintext blocks.
912      * 
913      * Page 9.
914      * The last ciphertext block (whose size r is less than or equal to 128) is
915      * exclusive-ORed with the segment of the r most significant bits of the
916      * last output block to recover the last plaintext block of r bits.
917      */
918 
919     /**
920      * Decrypts using AES. Note that it uses the _forward_ cipher mode.
921      * 
922      * @param ciphertext
923      * @param secretPrivacyKey The secret privacy key
924      * @param engineBoots
925      * @param engineTime
926      * @param salt             The salt
927      * @return The dencrypted bytes
928      * @throws DecodingException
929      */
930     public final static byte[] AESdecrypt(byte[] ciphertext,
931             byte[] secretPrivacyKey,
932             int engineBoots,
933             int engineTime,
934             byte[] salt)
935             throws DecodingException {
936         byte[] aesKey = null;
937         byte[] iv = null;
938         try {
939             aesKey = getAESKey(secretPrivacyKey);
940             iv = getAESInitialValue(engineBoots, engineTime, salt);
941         } catch (PduException exc) {
942             throw new DecodingException(exc.getMessage());
943         }
944         // do some stuff
945         AESEngine aes = new AESEngine();
946         KeyParameter param = new KeyParameter(aesKey);
947         aes.init(true, param);
948 
949         // no padding so
950         int newL = ciphertext.length;
951         int bcount = newL / 16;
952         byte[] result = new byte[newL];
953         byte[] in = new byte[16];
954         byte[] out = new byte[16];
955         int posIn = 0;
956         int posResult = 0;
957         // initial input is the iv
958         System.arraycopy(iv, 0, in, 0, 16);
959         for (int b = 0; b < bcount; b++) {
960             aes.processBlock(in, 0, out, 0);
961             for (int i = 0; i < 16; i++) {
962                 result[posResult] = (byte) (out[i] ^ ciphertext[posIn]);
963                 in[i] = ciphertext[posIn];
964                 posResult++;
965                 posIn++;
966             }
967         }
968         // and the leftovers.
969         if (posIn < newL) {
970             aes.processBlock(in, 0, out, 0);
971             for (int i = 0; posIn < newL; i++) {
972                 result[posResult] = (byte) (out[i] ^ ciphertext[posIn]);
973                 posResult++;
974                 posIn++;
975             }
976         }
977         return result;
978     }
979 
980     // shared code for SHA and MD5
981     static byte[] getFingerPrint(byte[] key, byte[] message, boolean doSha) {
982         // see page 193 of 0-13-021453-1 A Practical Guide to SNMP
983         byte[] k1 = new byte[64];
984         byte[] k2 = new byte[64];
985         // make the subkeys
986         byte z1 = (byte) (0 ^ 0x36);
987         byte z2 = (byte) (0 ^ 0x5c);
988         int kl = key.length;
989         int i = 0;
990         while (i < kl) {
991             k1[i] = (byte) (ifb(key[i]) ^ 0x36);
992             k2[i] = (byte) (ifb(key[i]) ^ 0x5c);
993             i++;
994         }
995         while (i < 64) {
996             k1[i] = z1;
997             k2[i] = z2;
998             i++;
999         }
1000         // now prepend K1 to message and Hash the result
1001         byte[] interm = null;
1002         GeneralDigest mdc = doSha ? ((GeneralDigest) new SHA1Digest()) : ((GeneralDigest) new MD5Digest());
1003         mdc.reset();
1004         mdc.update(k1, 0, k1.length);
1005         mdc.update(message, 0, message.length);
1006         interm = new byte[mdc.getDigestSize()];
1007         mdc.doFinal(interm, 0);
1008 
1009         // prepend K2 to that and Hash it.
1010         byte[] rettmp = null;
1011         GeneralDigest mdc2 = doSha ? ((GeneralDigest) new SHA1Digest()) : ((GeneralDigest) new MD5Digest());
1012         mdc2.reset();
1013         mdc2.update(k2, 0, k2.length);
1014         mdc2.update(interm, 0, interm.length);
1015         rettmp = new byte[mdc2.getDigestSize()];
1016         mdc2.doFinal(rettmp, 0);
1017 
1018         // and shorten it to 12 bytes.
1019         byte[] ret = null;
1020         if (rettmp != null) {
1021             ret = new byte[12];
1022             System.arraycopy(rettmp, 0, ret, 0, 12);
1023         }
1024         return ret;
1025     }
1026 
1027     final static int ifb(byte b) {
1028         return intFromByteWithoutStupidJavaSignExtension(b);
1029     }
1030 
1031     final static int intFromByteWithoutStupidJavaSignExtension(byte val) {
1032         int ret = (0x7F) & val;
1033         if (val < 0) {
1034             ret += 128;
1035         }
1036         return ret;
1037     }
1038 
1039     final static void setBytesFromInt(byte[] ret, int value, int offs) {
1040         int v = value;
1041         int j = offs;
1042         ret[j++] = (byte) ((v >>> 24) & 0xFF);
1043         ret[j++] = (byte) ((v >>> 16) & 0xFF);
1044         ret[j++] = (byte) ((v >>> 8) & 0xFF);
1045         ret[j++] = (byte) ((v >>> 0) & 0xFF);
1046     }
1047 
1048     final static void setBytesFromLong(byte[] ret, long value, int offs) {
1049         long v = value;
1050         int j = offs;
1051         ret[j++] = (byte) ((v >>> 56) & 0xFF);
1052         ret[j++] = (byte) ((v >>> 48) & 0xFF);
1053         ret[j++] = (byte) ((v >>> 40) & 0xFF);
1054         ret[j++] = (byte) ((v >>> 32) & 0xFF);
1055         ret[j++] = (byte) ((v >>> 24) & 0xFF);
1056         ret[j++] = (byte) ((v >>> 16) & 0xFF);
1057         ret[j++] = (byte) ((v >>> 8) & 0xFF);
1058         ret[j++] = (byte) ((v >>> 0) & 0xFF);
1059     }
1060 
1061     /**
1062      * Converts the user's passphrase into a 32-byte SHA-256 key by hashing one
1063      * megabyte of repeated passphrase data. (Based on the MD5/SHA1 approach in RFC
1064      * 3414, extended for SHA-256.)
1065      * 
1066      * @param userPrivacyPassword The user's passphrase
1067      */
1068     public static byte[] passwordToKeySHA256(String userPrivacyPassword) {
1069         byte[] ret;
1070         byte[] passwordBuf = new byte[64];
1071         int pl = userPrivacyPassword.length();
1072         byte[] pass = new byte[pl];
1073 
1074         // Convert passphrase string to bytes
1075         for (int i = 0; i < pl; i++) {
1076             pass[i] = (byte) (0xFF & userPrivacyPassword.charAt(i));
1077         }
1078 
1079         int count = 0;
1080         int passwordIndex = 0;
1081 
1082         try {
1083             MessageDigest sha = MessageDigest.getInstance(SHA256_ALGORITHM);
1084 
1085             // Hash 1 MB of repeated passphrase blocks (64 bytes at a time)
1086             while (count < ONEMEG) {
1087                 int cp = 0;
1088                 int i = 0;
1089                 while (i < 64) {
1090                     int pim = passwordIndex % pl;
1091                     int len = 64 - cp;
1092                     int pr = pl - pim;
1093                     if (len > pr) {
1094                         len = pr;
1095                     }
1096                     System.arraycopy(pass, pim, passwordBuf, cp, len);
1097                     i += len;
1098                     cp += len;
1099                     passwordIndex += len;
1100                 }
1101                 sha.update(passwordBuf, 0, passwordBuf.length);
1102                 count += 64;
1103             }
1104 
1105             // Finalize the 32-byte key
1106             ret = sha.digest();
1107         } catch (NoSuchAlgorithmException e) {
1108             throw new IllegalStateException("SHA-256 not supported, failed to generate key", e);
1109         }
1110 
1111         return ret;
1112     }
1113 
1114     /**
1115      * Converts the user's passphrase into a 48-byte SHA-384 key by hashing one
1116      * megabyte of repeated passphrase data. (Based on the MD5/SHA1 approach in RFC
1117      * 3414, extended for SHA-384.)
1118      *
1119      * @param userPrivacyPassword The user's passphrase
1120      */
1121     public static byte[] passwordToKeySHA384(String userPrivacyPassword) {
1122         byte[] ret;
1123         byte[] passwordBuf = new byte[128]; // SHA-384 uses 128-byte buffer (for 48-byte key)
1124         int pl = userPrivacyPassword.length();
1125         byte[] pass = new byte[pl];
1126 
1127         // Convert passphrase string to bytes
1128         for (int i = 0; i < pl; i++) {
1129             pass[i] = (byte) (0xFF & userPrivacyPassword.charAt(i));
1130         }
1131 
1132         int count = 0;
1133         int passwordIndex = 0;
1134 
1135         try {
1136             MessageDigest sha = MessageDigest.getInstance(SHA384_ALGORITHM);
1137 
1138             // Hash 1 MB of repeated passphrase blocks (128 bytes at a time for SHA-384)
1139             while (count < ONEMEG) {
1140                 int cp = 0;
1141                 int i = 0;
1142                 while (i < 128) {
1143                     int pim = passwordIndex % pl;
1144                     int len = 128 - cp;
1145                     int pr = pl - pim;
1146                     if (len > pr) {
1147                         len = pr;
1148                     }
1149                     System.arraycopy(pass, pim, passwordBuf, cp, len);
1150                     i += len;
1151                     cp += len;
1152                     passwordIndex += len;
1153                 }
1154                 sha.update(passwordBuf, 0, passwordBuf.length);
1155                 count += 128;
1156             }
1157 
1158             // Finalize the 48-byte key
1159             ret = sha.digest();
1160         } catch (NoSuchAlgorithmException e) {
1161             throw new IllegalStateException("SHA-384 not supported, failed to generate key", e);
1162         }
1163 
1164         return ret;
1165     }
1166 
1167     /**
1168      * Converts the user's passphrase into a 28-byte SHA-224 key by hashing one
1169      * megabyte of repeated passphrase data. (Based on the MD5/SHA1 approach in RFC
1170      * 3414, extended for SHA-224.)
1171      *
1172      * @param userPrivacyPassword The user's passphrase
1173      */
1174     public static byte[] passwordToKeySHA224(String userPrivacyPassword) {
1175         byte[] ret; // Final key is 28 bytes (224 bits) for SHA-224
1176         byte[] passwordBuf = new byte[64]; // 64-byte buffer, aligns with SHA-224's block size
1177         int pl = userPrivacyPassword.length();
1178         byte[] pass = new byte[pl];
1179 
1180         // Convert passphrase string to bytes
1181         for (int i = 0; i < pl; i++) {
1182             pass[i] = (byte) (0xFF & userPrivacyPassword.charAt(i));
1183         }
1184 
1185         int count = 0;
1186         int passwordIndex = 0;
1187 
1188         try {
1189             MessageDigest sha = MessageDigest.getInstance(SHA224_ALGORITHM);
1190 
1191             // Hash 1 MB of repeated passphrase blocks (64 bytes at a time)
1192             while (count < ONEMEG) {
1193                 int cp = 0;
1194                 int i = 0;
1195                 while (i < 64) { // Process in 64-byte chunks
1196                     int pim = passwordIndex % pl;
1197                     int len = 64 - cp;
1198                     int pr = pl - pim;
1199                     if (len > pr) {
1200                         len = pr;
1201                     }
1202                     System.arraycopy(pass, pim, passwordBuf, cp, len);
1203                     i += len;
1204                     cp += len;
1205                     passwordIndex += len;
1206                 }
1207                 sha.update(passwordBuf, 0, passwordBuf.length); // Update the hash with the buffer
1208                 count += 64; // Update count for 64-byte block
1209             }
1210 
1211             // Finalize the 28-byte key (SHA-224 produces 28 bytes)
1212             ret = sha.digest();
1213         } catch (NoSuchAlgorithmException e) {
1214             throw new IllegalStateException("SHA-224 not supported, failed to generate key", e);
1215         }
1216 
1217         return ret;
1218     }
1219 
1220     /**
1221      * Converts the user's passphrase into a 64-byte SHA-512 key by hashing one
1222      * megabyte of repeated passphrase data. (Based on the MD5/SHA1 approach in RFC
1223      * 3414, extended for SHA-512.)
1224      *
1225      * @param userPrivacyPassword The user's passphrase
1226      * @return A 64-byte derived key
1227      */
1228     public static byte[] passwordToKeySHA512(String userPrivacyPassword) {
1229         byte[] ret;
1230         byte[] passwordBuf = new byte[64];
1231         int pl = userPrivacyPassword.length();
1232         byte[] pass = new byte[pl];
1233 
1234         // Convert passphrase string to bytes
1235         for (int i = 0; i < pl; i++) {
1236             pass[i] = (byte) (0xFF & userPrivacyPassword.charAt(i));
1237         }
1238 
1239         int count = 0;
1240         int passwordIndex = 0;
1241 
1242         try {
1243             MessageDigest sha = MessageDigest.getInstance(SHA512_ALGORITHM);
1244 
1245             // Hash 1 MB of repeated passphrase blocks (64 bytes at a time)
1246             while (count < 1024 * 1024) { // 1MB
1247                 int cp = 0;
1248                 int i = 0;
1249                 while (i < 64) {
1250                     int pim = passwordIndex % pl;
1251                     int len = 64 - cp;
1252                     int pr = pl - pim;
1253                     if (len > pr) {
1254                         len = pr;
1255                     }
1256                     System.arraycopy(pass, pim, passwordBuf, cp, len);
1257                     i += len;
1258                     cp += len;
1259                     passwordIndex += len;
1260                 }
1261                 sha.update(passwordBuf, 0, passwordBuf.length);
1262                 count += 64;
1263             }
1264 
1265             // Finalize the 64-byte key
1266             ret = sha.digest();
1267         } catch (NoSuchAlgorithmException e) {
1268             throw new IllegalStateException("SHA-512 not supported, failed to generate key", e);
1269         }
1270 
1271         return ret;
1272     }
1273 
1274     /**
1275      * Converts the user's password and the SNMP Engine Id to the localized key
1276      * 
1277      * @param passwKey     The password key
1278      * @param snmpEngineId The SNMP engine Id
1279      * @return localized key using the SHA-256 protocol
1280      */
1281     public static byte[] getLocalizedKeySHA256(final byte[] passwKey, final String snmpEngineId) {
1282         byte[] ret = null;
1283         byte[] beid = toBytes(snmpEngineId); // Convert engineId (Hex string?) to raw bytes
1284 
1285         if (passwKey == null) {
1286             return null;
1287         }
1288 
1289         try {
1290             final MessageDigest sha = MessageDigest.getInstance(SHA256_ALGORITHM);
1291             sha.update(passwKey, 0, passwKey.length);
1292             sha.update(beid, 0, beid.length);
1293             sha.update(passwKey, 0, passwKey.length);
1294             ret = sha.digest(); // 32-byte SHA-256 result
1295         } catch (NoSuchAlgorithmException e) {
1296             throw new IllegalStateException("SHA-256 not supported, failed to generate localized key", e);
1297         }
1298 
1299         return ret;
1300     }
1301 
1302     /**
1303      * Converts the user's password and the SNMP Engine Id to the localized key
1304      *
1305      * @param passwKey     The password key
1306      * @param snmpEngineId The SNMP engine Id
1307      * @return localized key using the SHA-384 protocol
1308      */
1309     public static byte[] getLocalizedKeySHA384(final byte[] passwKey, final String snmpEngineId) {
1310         byte[] ret = null;
1311         byte[] beid = toBytes(snmpEngineId); // Convert engineId (Hex string?) to raw bytes
1312 
1313         if (passwKey == null) {
1314             return null;
1315         }
1316 
1317         try {
1318             final MessageDigest sha = MessageDigest.getInstance(SHA384_ALGORITHM);
1319             sha.update(passwKey, 0, passwKey.length);
1320             sha.update(beid, 0, beid.length);
1321             sha.update(passwKey, 0, passwKey.length);
1322             ret = sha.digest(); // 48-byte SHA-384 result
1323         } catch (NoSuchAlgorithmException e) {
1324             throw new IllegalStateException("SHA-384 not supported, failed to generate localized key", e);
1325         }
1326 
1327         return ret;
1328     }
1329 
1330     /**
1331      * Converts the user's password and the SNMP Engine Id to the localized key
1332      *
1333      * @param passwKey     The password key
1334      * @param snmpEngineId The SNMP engine Id
1335      * @return localized key using the SHA-224 protocol
1336      */
1337     public static byte[] getLocalizedKeySHA224(final byte[] passwKey, final String snmpEngineId) {
1338         byte[] ret = null;
1339         byte[] beid = toBytes(snmpEngineId); // Convert engineId (Hex string?) to raw bytes
1340 
1341         if (passwKey == null) {
1342             return null;
1343         }
1344 
1345         try {
1346             final MessageDigest sha = MessageDigest.getInstance(SHA224_ALGORITHM);
1347             sha.update(passwKey, 0, passwKey.length);
1348             sha.update(beid, 0, beid.length);
1349             sha.update(passwKey, 0, passwKey.length);
1350             ret = sha.digest(); // 28-byte SHA-224 result
1351         } catch (NoSuchAlgorithmException e) {
1352             throw new IllegalStateException("SHA-224 not supported, failed to generate localized key", e);
1353         }
1354 
1355         return ret;
1356     }
1357 
1358     /**
1359      * Create a fingerprint using the SHA-256 algorithm with length 24 bytes.
1360      * 
1361      * @param key     The key to use for the first digest
1362      * @param message The message to use for the second digest
1363      * @return The fingerprint of the message
1364      */
1365     public static byte[] getFingerPrintSHA256(final byte[] key, final byte[] message) {
1366         if ((AsnObject.debug > 5) && (key.length != 32)) {
1367             System.out.println("SHA256 key length wrong");
1368         }
1369         try {
1370             return doFingerPrintSHA256(key, message);
1371         } catch (NoSuchAlgorithmException e) {
1372             throw new IllegalStateException("SHA-256 not supported, failed to generate fingerprint", e);
1373         }
1374     }
1375 
1376     /**
1377      * Converts the user's password and the SNMP Engine Id to the localized key
1378      * using the SHA-512 protocol.
1379      *
1380      * @param passwKey     The password key
1381      * @param snmpEngineId The SNMP engine Id
1382      * @return localized key using the SHA-512 protocol
1383      */
1384     public static byte[] getLocalizedKeySHA512(final byte[] passwKey, final String snmpEngineId) {
1385         byte[] ret = null;
1386         byte[] beid = toBytes(snmpEngineId); // Convert engineId (Hex string?) to raw bytes
1387 
1388         if (passwKey == null) {
1389             return null;
1390         }
1391 
1392         try {
1393             final MessageDigest sha = MessageDigest.getInstance(SHA512_ALGORITHM);
1394             sha.update(passwKey, 0, passwKey.length);
1395             sha.update(beid, 0, beid.length);
1396             sha.update(passwKey, 0, passwKey.length);
1397             ret = sha.digest(); // 64-byte SHA-512 result
1398         } catch (NoSuchAlgorithmException e) {
1399             throw new IllegalStateException("SHA-512 not supported, failed to generate localized key", e);
1400         }
1401 
1402         return ret;
1403     }
1404 
1405     /**
1406      * Create a fingerprint using the SHA-512 algorithm with length 48 bytes.
1407      * 
1408      * @param key     The key to use for the first digest
1409      * @param message The message to use for the second digest
1410      * @return The fingerprint of the message
1411      */
1412     public static byte[] getFingerPrintSHA512(final byte[] key, final byte[] message) {
1413         if ((AsnObject.debug > 5) && (key.length != 64)) {
1414             System.out.println("SHA-512 key length wrong");
1415         }
1416         try {
1417             return doFingerPrintSHA512(key, message);
1418         } catch (NoSuchAlgorithmException e) {
1419             throw new IllegalStateException("SHA-512 not supported, failed to generate fingerprint", e);
1420         }
1421     }
1422 
1423     /**
1424      * Create a fingerprint using the SHA-224 algorithm with length 28 bytes for
1425      * SNMPv3.
1426      * 
1427      * @param key     The key to use for the first digest
1428      * @param message The message to use for the second digest
1429      * @return The fingerprint of the message
1430      */
1431     public static byte[] getFingerPrintSHA224(final byte[] key, final byte[] message) {
1432         if ((AsnObject.debug > 5) && (key.length != 28)) {
1433             System.out.println("SHA-224 key length wrong");
1434         }
1435         try {
1436             return doFingerPrintSHA224(key, message);
1437         } catch (NoSuchAlgorithmException e) {
1438             throw new IllegalStateException("SHA-224 not supported, failed to generate fingerprint", e);
1439         }
1440     }
1441 
1442     /**
1443      * Create a fingerprint using the SHA-224 algorithm with length 28 bytes for
1444      * SNMPv3.
1445      * 
1446      * @param key     The key to use for the first digest
1447      * @param message The message to use for the second digest
1448      * @return The fingerprint of the message
1449      * @throws NoSuchAlgorithmException
1450      */
1451     private static byte[] doFingerPrintSHA224(final byte[] key, final byte[] message) throws NoSuchAlgorithmException {
1452         // Build k1, k2 (56 bytes each for SHA-224)
1453         byte[] k1 = new byte[64];
1454         byte[] k2 = new byte[64];
1455 
1456         // 0x36, 0x5C for iPad/oPad in HMAC
1457         for (int i = 0; i < 64; i++) {
1458             byte theByte = (i < key.length) ? key[i] : 0;
1459             k1[i] = (byte) ((theByte & 0xFF) ^ 0x36); // iPad
1460             k2[i] = (byte) ((theByte & 0xFF) ^ 0x5C); // oPad
1461         }
1462 
1463         // Inner digest: SHA-224(k1 || message)
1464         MessageDigest digest1 = MessageDigest.getInstance(SHA224_ALGORITHM);
1465         digest1.update(k1);
1466         digest1.update(message);
1467         byte[] innerHash = digest1.digest();
1468 
1469         // Outer digest: SHA-224(k2 || innerHash)
1470         MessageDigest digest2 = MessageDigest.getInstance(SHA224_ALGORITHM);
1471         digest2.update(k2);
1472         digest2.update(innerHash);
1473         byte[] fullHmac = digest2.digest();
1474 
1475         // Return the result as a 28-byte fingerprint (truncated or directly 28 bytes)
1476         byte[] ret = new byte[16];
1477         System.arraycopy(fullHmac, 0, ret, 0, 16);
1478         return ret;
1479     }
1480 
1481     /**
1482      * Create a fingerprint using the SHA-256 algorithm with length 24 bytes.
1483      * 
1484      * @param key     The key to use for the first digest
1485      * @param message The message to use for the second digest
1486      * @return The fingerprint of the message
1487      * @throws NoSuchAlgorithmException
1488      */
1489     private static byte[] doFingerPrintSHA256(final byte[] key, final byte[] message) throws NoSuchAlgorithmException {
1490         // Build k1, k2 (64 bytes each)
1491         byte[] k1 = new byte[64];
1492         byte[] k2 = new byte[64];
1493 
1494         // 0x36, 0x5C for iPad/oPad in HMAC
1495         for (int i = 0; i < 64; i++) {
1496             byte theByte = (i < key.length) ? key[i] : 0;
1497             k1[i] = (byte) ((theByte & 0xFF) ^ 0x36);
1498             k2[i] = (byte) ((theByte & 0xFF) ^ 0x5C);
1499         }
1500 
1501         // Inner digest: SHA256(k1 || message)
1502         MessageDigest digest1 = MessageDigest.getInstance(SHA256_ALGORITHM);
1503         digest1.update(k1);
1504         digest1.update(message);
1505         byte[] innerHash = digest1.digest();
1506 
1507         // Outer digest: SHA256(k2 || innerHash)
1508         MessageDigest digest2 = MessageDigest.getInstance(SHA256_ALGORITHM);
1509         digest2.update(k2);
1510         digest2.update(innerHash);
1511         byte[] fullHmac = digest2.digest();
1512 
1513         // Truncate to 24 bytes for usmHMAC192SHA256AuthProtocol
1514         byte[] ret = new byte[24];
1515         System.arraycopy(fullHmac, 0, ret, 0, 24);
1516         return ret;
1517     }
1518 
1519     /**
1520      * Create a fingerprint using the SHA-384 algorithm with length 36 bytes.
1521      * 
1522      * @param key     The key to use for the first digest
1523      * @param message The message to use for the second digest
1524      * @return The fingerprint of the message
1525      */
1526     public static byte[] getFingerPrintSHA384(final byte[] key, final byte[] message) {
1527         if ((AsnObject.debug > 5) && (key.length != 48)) {
1528             System.out.println("SHA384 key length wrong");
1529         }
1530         try {
1531             return doFingerPrintSHA384(key, message);
1532         } catch (NoSuchAlgorithmException e) {
1533             throw new IllegalStateException("SHA-384 not supported, failed to generate fingerprint", e);
1534         }
1535     }
1536 
1537     /**
1538      * Create a fingerprint using the SHA-384 algorithm with length 36 bytes.
1539      * 
1540      * @param key     The key to use for the first digest
1541      * @param message The message to use for the second digest
1542      * @return The fingerprint of the message
1543      * @throws NoSuchAlgorithmException
1544      */
1545     private static byte[] doFingerPrintSHA384(final byte[] key, final byte[] message) throws NoSuchAlgorithmException {
1546         // Build k1, k2 (128 bytes each for SHA-384 HMAC)
1547         byte[] k1 = new byte[128];
1548         byte[] k2 = new byte[128];
1549 
1550         // 0x36, 0x5C for iPad/oPad in HMAC
1551         for (int i = 0; i < 128; i++) {
1552             byte theByte = (i < key.length) ? key[i] : 0;
1553             k1[i] = (byte) ((theByte & 0xFF) ^ 0x36);
1554             k2[i] = (byte) ((theByte & 0xFF) ^ 0x5C);
1555         }
1556 
1557         // Inner digest: SHA384(k1 || message)
1558         MessageDigest digest1 = MessageDigest.getInstance(SHA384_ALGORITHM);
1559         digest1.update(k1);
1560         digest1.update(message);
1561         byte[] innerHash = digest1.digest();
1562 
1563         // Outer digest: SHA384(k2 || innerHash)
1564         MessageDigest digest2 = MessageDigest.getInstance(SHA384_ALGORITHM);
1565         digest2.update(k2);
1566         digest2.update(innerHash);
1567         byte[] fullHmac = digest2.digest();
1568 
1569         // Truncate to 36 bytes for usmHMAC288SHA384AuthProtocol
1570         byte[] ret = new byte[32];
1571         System.arraycopy(fullHmac, 0, ret, 0, 32);
1572         return ret;
1573     }
1574 
1575     /**
1576      * Create a fingerprint using the SHA-512 algorithm with length 48 bytes.
1577      * 
1578      * @param key     The key to use for the first digest
1579      * @param message The message to use for the second digest
1580      * @return The fingerprint of the message
1581      * @throws NoSuchAlgorithmException
1582      */
1583     private static byte[] doFingerPrintSHA512(final byte[] key, final byte[] message) throws NoSuchAlgorithmException {
1584         // Build k1, k2 (128 bytes each for SHA-512)
1585         byte[] k1 = new byte[128];
1586         byte[] k2 = new byte[128];
1587 
1588         // 0x36, 0x5C for iPad/oPad in HMAC
1589         for (int i = 0; i < 128; i++) {
1590             byte theByte = (i < key.length) ? key[i] : 0;
1591             k1[i] = (byte) ((theByte & 0xFF) ^ 0x36);
1592             k2[i] = (byte) ((theByte & 0xFF) ^ 0x5C);
1593         }
1594 
1595         // Inner digest: SHA-512(k1 || message)
1596         MessageDigest digest1 = MessageDigest.getInstance(SHA512_ALGORITHM);
1597         digest1.update(k1);
1598         digest1.update(message);
1599         byte[] innerHash = digest1.digest();
1600 
1601         // Outer digest: SHA-512(k2 || innerHash)
1602         MessageDigest digest2 = MessageDigest.getInstance(SHA512_ALGORITHM);
1603         digest2.update(k2);
1604         digest2.update(innerHash);
1605         byte[] fullHmac = digest2.digest();
1606 
1607         // Truncate to 48 bytes for usmHMAC256SHA384AuthProtocol
1608         byte[] ret = new byte[48];
1609         System.arraycopy(fullHmac, 0, ret, 0, 48);
1610         return ret;
1611     }
1612 
1613     /**
1614      * Returns an `AsnOctets` object with a dummy fingerprint based on the
1615      * authentication protocol.
1616      * Returns an empty `AsnOctets` if authentication is not used.
1617      *
1618      * @param authenticationProtocol The authentication protocol (e.g., SHA256,
1619      *                               SHA1, etc.).
1620      * @return The corresponding fingerprint byte array
1621      */
1622     public static byte[] initFingerprint(final int authenticationProtocol) {
1623         // Return the mapped fingerprint if it exists, otherwise return an empty array.
1624         return DUMMY_FINGERPRINT_MAP.getOrDefault(authenticationProtocol, new byte[0]);
1625     }
1626 
1627     /**
1628      * Copies the calculated fingerprint to the message at the specified position.
1629      *
1630      * @param authenticationProtocol The authentication protocol (e.g., SHA256,
1631      *                               SHA1, etc.).
1632      * @param computedFingerprint    The calculated fingerprint.
1633      * @param message                The message to which the fingerprint will be
1634      *                               copied.
1635      * @param fpPos                  The position in the message where the
1636      *                               fingerprint will be copied.
1637      */
1638     public static void copyFingerprintToSnmpMessage(
1639             int authenticationProtocol,
1640             byte[] computedFingerprint,
1641             byte[] message,
1642             int fpPos) {
1643         int lengthToCopy = 0;
1644         switch (authenticationProtocol) {
1645             case SHA256_PROTOCOL:
1646                 lengthToCopy = dummySHA256FingerPrint.length;
1647                 break;
1648             case SHA1_PROTOCOL:
1649             case MD5_PROTOCOL:
1650                 lengthToCopy = dummySha1FingerPrint.length;
1651                 break;
1652             case SHA512_PROTOCOL:
1653                 lengthToCopy = dummySHA512FingerPrint.length;
1654                 break;
1655             case SHA224_PROTOCOL:
1656                 lengthToCopy = dummySHA224FingerPrint.length;
1657                 break;
1658             case SHA384_PROTOCOL:
1659                 lengthToCopy = dummySHA384FingerPrint.length;
1660                 break;
1661             default:
1662                 // unknown protocol; do nothing
1663                 break;
1664         }
1665         if (lengthToCopy > 0) {
1666             System.arraycopy(computedFingerprint, 0, message, fpPos, lengthToCopy);
1667         }
1668     }
1669 }