3939import javax .crypto .IllegalBlockSizeException ;
4040import javax .crypto .NoSuchPaddingException ;
4141import javax .crypto .SecretKey ;
42- import javax .crypto .spec .GCMParameterSpec ;
42+ import javax .crypto .spec .IvParameterSpec ;
4343import javax .crypto .spec .SecretKeySpec ;
4444import javax .inject .Inject ;
4545import java .io .Closeable ;
@@ -564,9 +564,10 @@ void releaseSession(PKCS11Session session) {
564564 * {@link KMSException.ErrorType} values for proper retry logic and error reporting.
565565 */
566566 private static class PKCS11Session {
567- private static final String ALGORITHM = "AES/GCM/NoPadding" ;
568- private static final int GCM_IV_LENGTH = 12 ; // 96 bits
569- private static final int GCM_TAG_LENGTH = 16 ; // 128 bits
567+ // Use AES-CBC with PKCS5Padding for key wrapping
568+ // This is FIPS-compliant (NIST SP 800-38A) and has universal PKCS#11 support
569+ private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding" ;
570+ private static final int IV_LENGTH = 16 ; // 128 bits for CBC
570571 private static final String PROVIDER_PREFIX = "CloudStackPKCS11-" ;
571572
572573 private final Map <String , String > config ;
@@ -658,7 +659,7 @@ private void connect() throws KMSException {
658659 // Zeroize PIN from memory
659660 Arrays .fill (pinChars , '\0' );
660661
661- logger .debug ("Successfully connected to PKCS#11 HSM at {}" , config .get ("library" ));
662+ logger .debug ("aSuccessfully connected to PKCS#11 HSM at {}" , config .get ("library" ));
662663 } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException e ) {
663664 handlePKCS11Exception (e , "Failed to initialize PKCS#11 connection" );
664665 } catch (IOException e ) {
@@ -937,19 +938,18 @@ private void validateKeySize(int keyBits) throws KMSException {
937938 /**
938939 * Wraps (encrypts) a plaintext DEK using a KEK stored in the HSM.
939940 *
940- * <p>Uses AES-GCM for authenticated encryption :
941+ * <p>Uses AES-CBC with PKCS5Padding (FIPS 197 + NIST SP 800-38A) :
941942 * <ul>
942- * <li>Generates a random 96-bit IV</li>
943- * <li>Encrypts the DEK using the KEK from HSM</li>
944- * <li>Appends a 128-bit authentication tag</li>
945- * <li>Returns format: [IV (12 bytes)][ciphertext+tag]</li>
943+ * <li>Generates a random 128-bit IV</li>
944+ * <li>Encrypts the DEK using AES-CBC with the KEK from HSM</li>
945+ * <li>Returns format: [IV (16 bytes)][ciphertext]</li>
946946 * </ul>
947947 *
948948 * <p>Security: The plaintext DEK should be zeroized by the caller after wrapping.
949949 *
950950 * @param plainDek Plaintext DEK to wrap (will be encrypted)
951951 * @param kekLabel Label of the KEK stored in the HSM
952- * @return Wrapped blob: [IV][ciphertext+tag ]
952+ * @return Wrapped key blob: [IV][ciphertext]
953953 * @throws KMSException with appropriate ErrorType:
954954 * <ul>
955955 * <li>{@code INVALID_PARAMETER} if plainDek is null or empty</li>
@@ -966,23 +966,30 @@ byte[] wrapKey(byte[] plainDek, String kekLabel) throws KMSException {
966966 try {
967967 kek = getKekFromKeyStore (kekLabel );
968968
969- // Generate random IV for GCM
970- byte [] iv = new byte [GCM_IV_LENGTH ];
969+ // Generate random IV for CBC
970+ byte [] iv = new byte [IV_LENGTH ];
971971 new SecureRandom ().nextBytes (iv );
972972
973- // Create and initialize AES-GCM cipher in ENCRYPT_MODE
974- Cipher cipher = createGCMCipher (kek , iv , Cipher .ENCRYPT_MODE );
973+ // Create cipher with AES-CBC
974+ Cipher cipher = Cipher .getInstance (CIPHER_ALGORITHM , provider );
975+ cipher .init (Cipher .ENCRYPT_MODE , kek , new IvParameterSpec (iv ));
975976
976- // Encrypt the plaintext DEK using doFinal (GCM includes authentication tag)
977- byte [] wrappedBlob = cipher .doFinal (plainDek );
977+ // Encrypt the plaintext DEK
978+ byte [] ciphertext = cipher .doFinal (plainDek );
978979
979- // Prepend IV to wrapped blob: [IV][ciphertext+tag]
980- byte [] result = prependIV (iv , wrappedBlob );
980+ // Prepend IV to ciphertext: [IV][ciphertext]
981+ byte [] result = new byte [IV_LENGTH + ciphertext .length ];
982+ System .arraycopy (iv , 0 , result , 0 , IV_LENGTH );
983+ System .arraycopy (ciphertext , 0 , result , IV_LENGTH , ciphertext .length );
981984
982- logger .debug ("Wrapped key with KEK '{}'" , kekLabel );
985+ logger .debug ("Wrapped key with KEK '{}' using AES-CBC " , kekLabel );
983986 return result ;
984- } catch (IllegalBlockSizeException e ) {
985- handlePKCS11Exception (e , "Invalid block size for wrapping" );
987+ } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException e ) {
988+ handlePKCS11Exception (e , "Invalid key or data for wrapping" );
989+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e ) {
990+ handlePKCS11Exception (e , "AES-CBC not supported by HSM" );
991+ } catch (InvalidAlgorithmParameterException e ) {
992+ handlePKCS11Exception (e , "Invalid IV for CBC mode" );
986993 } catch (Exception e ) {
987994 handlePKCS11Exception (e , "Failed to wrap key with HSM" );
988995 } finally {
@@ -1019,104 +1026,71 @@ private SecretKey getKekFromKeyStore(String kekLabel) throws KMSException {
10191026 return null ; // Unreachable
10201027 }
10211028
1022- /**
1023- * Prepends IV to data, creating a new byte array.
1024- *
1025- * @param iv Initialization vector
1026- * @param data Data to prepend IV to
1027- * @return Combined array: [IV][data]
1028- */
1029- private byte [] prependIV (byte [] iv , byte [] data ) {
1030- byte [] result = new byte [GCM_IV_LENGTH + data .length ];
1031- System .arraycopy (iv , 0 , result , 0 , GCM_IV_LENGTH );
1032- System .arraycopy (data , 0 , result , GCM_IV_LENGTH , data .length );
1033- return result ;
1034- }
1035-
1036- /**
1037- * Creates and initializes an AES-GCM cipher.
1038- *
1039- * @param kek Key Encryption Key
1040- * @param iv Initialization vector
1041- * @param mode Cipher mode (ENCRYPT_MODE or DECRYPT_MODE)
1042- * @return Initialized Cipher instance
1043- * @throws KMSException if cipher creation or initialization fails
1044- */
1045- private Cipher createGCMCipher (SecretKey kek , byte [] iv , int mode ) throws KMSException {
1046- try {
1047- Cipher cipher = Cipher .getInstance (ALGORITHM , provider );
1048- GCMParameterSpec gcmSpec = new GCMParameterSpec (GCM_TAG_LENGTH * 8 , iv );
1049- cipher .init (mode , kek , gcmSpec );
1050- return cipher ;
1051- } catch (NoSuchPaddingException e ) {
1052- handlePKCS11Exception (e , "GCM padding not supported" );
1053- } catch (InvalidKeyException e ) {
1054- handlePKCS11Exception (e , "Invalid KEK" );
1055- } catch (InvalidAlgorithmParameterException e ) {
1056- handlePKCS11Exception (e , "Invalid GCM parameters" );
1057- } catch (NoSuchAlgorithmException e ) {
1058- handlePKCS11Exception (e , String .format ("Algorithm %s not supported." , ALGORITHM ));
1059- }
1060- return null ; // Unreachable
1061- }
10621029
10631030 /**
10641031 * Unwraps (decrypts) a wrapped DEK using a KEK stored in the HSM.
10651032 *
1066- * <p>Process :
1033+ * <p>Uses AES-CBC with PKCS5Padding (FIPS 197 + NIST SP 800-38A) :
10671034 * <ol>
10681035 * <li>Extracts IV from the wrapped blob</li>
10691036 * <li>Retrieves KEK from HSM using the label</li>
1070- * <li>Decrypts using AES-GCM (verifies authentication tag) </li>
1037+ * <li>Decrypts using AES-CBC </li>
10711038 * <li>Returns plaintext DEK</li>
10721039 * </ol>
10731040 *
10741041 * <p>Security: The returned plaintext DEK must be zeroized by the caller after use.
10751042 *
1076- * <p>Expected format: [IV (12 bytes)][ciphertext+tag ]
1043+ * <p>Expected format: [IV (16 bytes)][ciphertext]
10771044 *
1078- * @param wrappedBlob Wrapped DEK blob (IV + ciphertext + tag )
1045+ * @param wrappedBlob Wrapped DEK blob (IV + ciphertext)
10791046 * @param kekLabel Label of the KEK stored in the HSM
10801047 * @return Plaintext DEK
10811048 * @throws KMSException with appropriate ErrorType:
10821049 * <ul>
10831050 * <li>{@code INVALID_PARAMETER} if wrappedBlob is null, empty, or too short</li>
10841051 * <li>{@code KEK_NOT_FOUND} if KEK with label doesn't exist or is not accessible</li>
1085- * <li>{@code WRAP_UNWRAP_FAILED} if unwrapping fails (e.g., authentication tag
1086- * verification fails)</li>
1052+ * <li>{@code WRAP_UNWRAP_FAILED} if unwrapping fails</li>
10871053 * </ul>
10881054 */
10891055 byte [] unwrapKey (byte [] wrappedBlob , String kekLabel ) throws KMSException {
10901056 if (wrappedBlob == null || wrappedBlob .length == 0 ) {
10911057 throw KMSException .invalidParameter ("Wrapped blob cannot be null or empty" );
10921058 }
10931059
1094- if (wrappedBlob .length < GCM_IV_LENGTH + GCM_TAG_LENGTH ) {
1060+ // Minimum size: IV (16) + at least one block of ciphertext (16)
1061+ if (wrappedBlob .length < IV_LENGTH + 16 ) {
10951062 throw KMSException .invalidParameter ("Wrapped blob too short: expected at least " +
1096- ( GCM_IV_LENGTH + GCM_TAG_LENGTH ) + " bytes" );
1063+ ( IV_LENGTH + 16 ) + " bytes" );
10971064 }
10981065
10991066 SecretKey kek = null ;
11001067 try {
11011068 kek = getKekFromKeyStore (kekLabel );
11021069
11031070 // Extract IV and ciphertext from wrapped blob
1104- IVAndCiphertext extracted = extractIVAndCiphertext (wrappedBlob );
1071+ byte [] iv = new byte [IV_LENGTH ];
1072+ System .arraycopy (wrappedBlob , 0 , iv , 0 , IV_LENGTH );
1073+ byte [] ciphertext = new byte [wrappedBlob .length - IV_LENGTH ];
1074+ System .arraycopy (wrappedBlob , IV_LENGTH , ciphertext , 0 , ciphertext .length );
11051075
1106- // Create and initialize AES-GCM cipher in DECRYPT_MODE
1107- Cipher cipher = createGCMCipher (kek , extracted .iv , Cipher .DECRYPT_MODE );
1076+ // Create cipher with AES-CBC
1077+ Cipher cipher = Cipher .getInstance (CIPHER_ALGORITHM , provider );
1078+ cipher .init (Cipher .DECRYPT_MODE , kek , new IvParameterSpec (iv ));
11081079
1109- // Decrypt the ciphertext to get plaintext DEK (GCM verifies authentication tag)
1110- byte [] plainDek = cipher .doFinal (extracted . ciphertextWithTag );
1080+ // Decrypt the ciphertext to get plaintext DEK
1081+ byte [] plainDek = cipher .doFinal (ciphertext );
11111082
1112- logger .debug ("Unwrapped key with KEK '{}'" , kekLabel );
1083+ logger .debug ("Unwrapped key with KEK '{}' using AES-CBC " , kekLabel );
11131084 return plainDek ;
11141085 } catch (BadPaddingException e ) {
1115- // GCM authentication tag verification failed
11161086 throw KMSException .wrapUnwrapFailed (
1117- "Authentication failed: wrapped key may be corrupted or KEK is incorrect" , e );
1118- } catch (IllegalBlockSizeException e ) {
1119- handlePKCS11Exception (e , "Invalid block size for unwrapping" );
1087+ "Decryption failed: wrapped key may be corrupted or KEK is incorrect" , e );
1088+ } catch (IllegalBlockSizeException | InvalidKeyException e ) {
1089+ handlePKCS11Exception (e , "Invalid key or data for unwrapping" );
1090+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e ) {
1091+ handlePKCS11Exception (e , "AES-CBC not supported by HSM" );
1092+ } catch (InvalidAlgorithmParameterException e ) {
1093+ handlePKCS11Exception (e , "Invalid IV for CBC mode" );
11201094 } catch (Exception e ) {
11211095 handlePKCS11Exception (e , "Failed to unwrap key with HSM" );
11221096 } finally {
@@ -1126,24 +1100,6 @@ byte[] unwrapKey(byte[] wrappedBlob, String kekLabel) throws KMSException {
11261100 return null ; // Unreachable
11271101 }
11281102
1129- /**
1130- * Extracts IV and ciphertext from a wrapped blob.
1131- *
1132- * @param wrappedBlob Wrapped blob containing IV and ciphertext
1133- * @return IVAndCiphertext containing extracted IV and ciphertext
1134- * @throws KMSException if wrapped blob is too short
1135- */
1136- private IVAndCiphertext extractIVAndCiphertext (byte [] wrappedBlob ) throws KMSException {
1137- if (wrappedBlob .length < GCM_IV_LENGTH + GCM_TAG_LENGTH ) {
1138- throw KMSException .invalidParameter ("Wrapped blob too short: expected at least " +
1139- (GCM_IV_LENGTH + GCM_TAG_LENGTH ) + " bytes" );
1140- }
1141- byte [] iv = new byte [GCM_IV_LENGTH ];
1142- System .arraycopy (wrappedBlob , 0 , iv , 0 , GCM_IV_LENGTH );
1143- byte [] ciphertextWithTag = new byte [wrappedBlob .length - GCM_IV_LENGTH ];
1144- System .arraycopy (wrappedBlob , GCM_IV_LENGTH , ciphertextWithTag , 0 , ciphertextWithTag .length );
1145- return new IVAndCiphertext (iv , ciphertextWithTag );
1146- }
11471103
11481104 /**
11491105 * Deletes a key from the HSM.
@@ -1212,18 +1168,5 @@ boolean checkKeyExists(String label) throws KMSException {
12121168 return false ;
12131169 }
12141170 }
1215-
1216- /**
1217- * Helper class to hold IV and ciphertext extracted from wrapped blob.
1218- */
1219- private static class IVAndCiphertext {
1220- final byte [] iv ;
1221- final byte [] ciphertextWithTag ;
1222-
1223- IVAndCiphertext (byte [] iv , byte [] ciphertextWithTag ) {
1224- this .iv = iv ;
1225- this .ciphertextWithTag = ciphertextWithTag ;
1226- }
1227- }
12281171 }
12291172}
0 commit comments