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