Repository: SonarSource/sonar-java
Pull request #5070: SONARJAVA-5420 Improve S5344: Passwords should not be stored in plaintext or with a fast hashing algorithm
Author: login: antonioaversa
Created: 2025-04-02T08:28:08Z | Merged: 2025-04-03T13:57:47Z

[SONARJAVA-5420](https://sonarsource.atlassian.net/browse/SONARJAVA-5420)

RSPEC PR [TO MERGE]: https://github.com/SonarSource/rspec/pull/4822
RSPEC PR to update the quickfix [TO REVIEW]: https://github.com/SonarSource/rspec/pull/4866
RSPEC Page: https://sonarsource.github.io/rspec/#/rspec/S5344/java
Kotlin Implementation Jira: https://sonarsource.atlassian.net/browse/SONARKT-639
Kotlin Implementation PR: https://github.com/SonarSource/sonar-kotlin/pull/601  

[SONARJAVA-5420]: https://sonarsource.atlassian.net/browse/SONARJAVA-5420?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5344.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5344.json
index e42c7d31060..36c6ca8b8c4 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5344.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5344.json
@@ -1,6 +1,6 @@
 {
   "ruleKey": "S5344",
-  "hasTruePositives": false,
+  "hasTruePositives": true,
   "falseNegatives": 23,
   "falsePositives": 0
-}
\ No newline at end of file
+}
diff --git a/java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoderJavaxCryptoSample.java b/java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoderJavaxCryptoSample.java
new file mode 100644
index 00000000000..fc3d05c1e7e
--- /dev/null
+++ b/java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoderJavaxCryptoSample.java
@@ -0,0 +1,261 @@
+package checks.security;
+
+import java.security.spec.InvalidKeySpecException;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+class PasswordEncoderJavaxCryptoSample {
+  // region Noncompliant cases
+
+  public static class NoncompliantConstantIterations {
+    private static final int PBKDF2_ITERATIONS = 120000;
+    //                                           ^^^^^^>
+
+    public void noncompliantConstantIterations(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATIONS, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+      //                                                                ^^^^^^^^^^^^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+      //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public static class NoncompliantConstantAlgorithm {
+    private static final String SHA512_ALGORITHM = "PBKDF2withHmacSHA512";
+    //                                             ^^^^^^^^^^^^^^^^^^^^^^>
+
+    public void noncompliantConstantAlgorithm(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+      //                                                                ^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(SHA512_ALGORITHM);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public void noncompliantNoKeyLength(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000); // Noncompliant
+    //                                                                ^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha512(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha256(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 300000, 256); // Noncompliant {{Use at least 600000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha1(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1_200_000, 256); // Noncompliant {{Use at least 1300000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantLocalVariableIteration(String password, byte[] salt) throws Exception {
+    int iterations = 120000;
+    //               ^^^^^^>
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantLocalVariableIterationAndAlgorithm(String password, byte[] salt) throws Exception {
+    String algorithm = "PBKDF2withHmacSHA512";
+    //                 ^^^^^^^^^^^^^^^^^^^^^^>
+    int iterations = 120000;
+    //               ^^^^^^>
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantComplexFlow(String password, byte[] salt) throws Exception {
+    int iterations = 500_000;
+    //               ^^^^^^^>
+    if (salt.length > 10) {
+      System.out.print("Some flow between relevant code");
+    }
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    while (salt.length < 10) {
+      if (salt.hashCode() == 42) {
+        System.out.print("Some other flow between relevant code");
+
+        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
+        //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+        Runnable aLambda = () -> {
+          try {
+            secretKeyFactory.generateSecret(keySpec);
+          } catch (InvalidKeySpecException e) {
+            throw new RuntimeException(e);
+          }
+        };
+      }
+    }
+  }
+
+  public void noncompliantStringConcatenation(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmac" + "SHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantToString(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // FN
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512".toString());
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantInlineSecretKeyFactoryGetInstance(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+  }
+
+  public void noncompliantEverythingInline(String password, byte[] salt) throws Exception {
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(
+      //                         ^^^^^^^^^^^^^^^^^^^^^^>
+      new PBEKeySpec(password.toCharArray(), salt, 110000, 256) // Noncompliant
+      //                                           ^^^^^^
+    );
+  }
+
+  public void noncompliantEverythingInlineWithLet(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 110000, 256); // Noncompliant
+    //                                                                ^^^^^^
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+    //                           ^^^^^^^^^^^^^^^^^^^^^^<
+  }
+
+  public void noncompliantWithAliases(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+  }
+
+  public static class NoncompliantFinalAndNonFinalInlineFieldIterationAndAlgorithm {
+    private final int iteration = 110000;
+    //                            ^^^^^^>
+    private String algorithm = "PBKDF2withHmacSHA512";
+    //                         ^^^^^^^^^^^^^^^^^^^^^^>
+
+    public void test(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iteration, 256); // Noncompliant
+      //                                                                ^^^^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public static class NoncompliantFinalNonInlineCtorHardcodedFieldIterationAndAlgorithm {
+    private final int iteration;
+    private final String algorithm;
+
+    public NoncompliantFinalNonInlineCtorHardcodedFieldIterationAndAlgorithm(int iteration, String algorithm) {
+      this.iteration = 110000;
+      this.algorithm = "PBKDF2withHmacSHA512";
+    }
+
+    public void test(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iteration, 256); // FN: requires flow from ctor
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public void nonCompliantIterationNotInitializedInline(String password, byte[] salt) throws Exception {
+    int iterations;
+    iterations = 10;
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // FN: requires flow from assignment
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  // endregion
+
+  // region Compliant cases
+
+  public void compliantIntLiteralAboveThresholdForSHA512(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 210000, 256); // Compliant: 210_000 >= 210_000
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void compliantIntLiteralAboveThresholdForSHA256(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 700000, 256); // Compliant: 700_000 >= 600_000
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void compliantIntLiteralAboveThresholdForSHA1(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1_400_000, 256); // Compliant: 1_400_000 >= 1_300_000
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void compliantUnknownAlgorithm(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Compliant: unknown algorithm
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("unknown");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void compliantComplexFlow(String password, byte[] salt) throws Exception {
+    int iterations = salt.length > 10 ? 210000 : 60000;
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Compliant: salt.size can be anything
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void compliantWithoutSecretKeyFactoryGenerateSecret(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Compliant: no generateSecret
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+  }
+
+  public void compliantWithoutSecretKeyFactoryGetInstance(String password, byte[] salt, SecretKeyFactory secretKeyFactory) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Compliant: no getInstance
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void compliantMultipleKeySpec(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec1 = new PBEKeySpec(password.toCharArray(), salt, 200000, 256);
+    PBEKeySpec keySpec2 = new PBEKeySpec(password.toCharArray(), salt, 220000, 256);
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    // This is commented out: secretKeyFactory.generateSecret(keySpec1)
+    secretKeyFactory.generateSecret(keySpec2); // Compliant: keySpec2 has enough iterations
+  }
+
+  public static class CompliantFinalNonInlineExternallyDefinedFieldIterationAndAlgorithm {
+    private final int iteration;
+    private final String algorithm;
+
+    public CompliantFinalNonInlineExternallyDefinedFieldIterationAndAlgorithm(int iteration, String algorithm) {
+      this.iteration = iteration;
+      this.algorithm = algorithm;
+    }
+
+    public void test(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iteration, 256); // Compliant: unknown algorithm and iteration
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public void compliantIterationReassigned(String password, byte[] salt) throws Exception {
+    int iterations = 10;
+    iterations = 210000;
+    // FP: we only look at the initialization
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  // endregion
+}
diff --git a/java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoder.java b/java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoderSpringSample.java
similarity index 100%
rename from java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoder.java
rename to java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoderSpringSample.java
diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java
index 6a0947c80ad..2134b3eb242 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java
@@ -16,20 +16,41 @@
  */
 package org.sonar.java.checks.security;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import org.sonar.check.Rule;
 import org.sonar.java.model.ExpressionUtils;
 import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.JavaFileScannerContext.Location;
 import org.sonar.plugins.java.api.semantic.MethodMatchers;
+import org.sonar.plugins.java.api.semantic.Symbol;
 import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
+import org.sonar.plugins.java.api.tree.ExpressionTree;
+import org.sonar.plugins.java.api.tree.IdentifierTree;
+import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
 import org.sonar.plugins.java.api.tree.MethodInvocationTree;
 import org.sonar.plugins.java.api.tree.NewClassTree;
 import org.sonar.plugins.java.api.tree.Tree;
+import org.sonar.plugins.java.api.tree.VariableTree;
 
 @Rule(key = "S5344")
 public class PasswordEncoderCheck extends IssuableSubscriptionVisitor {
 
+  private static final String JAVAX_CRYPTO_MESSAGE_FORMAT = "Use at least %d PBKDF2 iterations.";
+
+  private static final Map<String, Integer> MIN_ITERATIONS_BY_ALGORITHM = Map.of(
+    "PBKDF2withHmacSHA1",
+    1_300_000,
+    "PBKDF2withHmacSHA256",
+    600_000,
+    "PBKDF2withHmacSHA512",
+    210_000
+  );
+
   private static final MethodMatchers JDBC_AUTHENTICATION = MethodMatchers.create()
     .ofSubTypes("org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder")
     .names("jdbcAuthentication")
@@ -67,6 +88,25 @@ public class PasswordEncoderCheck extends IssuableSubscriptionVisitor {
     .addWithoutParametersMatcher()
     .build();
 
+  private static final MethodMatchers SECRET_KEY_FACTORY_GENERATE_SECRET_METHOD = MethodMatchers.create()
+    .ofTypes("javax.crypto.SecretKeyFactory")
+    .names("generateSecret")
+    .addParametersMatcher("java.security.spec.KeySpec")
+    .build();
+
+  private static final MethodMatchers SECRET_KEY_FACTORY_GET_INSTANCE_METHOD = MethodMatchers.create()
+    .ofTypes("javax.crypto.SecretKeyFactory")
+    .names("getInstance")
+    .addParametersMatcher("java.lang.String")
+    .build();
+
+  private static final MethodMatchers PBE_KEY_SPEC_CONSTRUCTOR = MethodMatchers.create()
+    .ofTypes("javax.crypto.spec.PBEKeySpec")
+    .constructor()
+    .addParametersMatcher("char[]", "byte[]", "int")
+    .addParametersMatcher("char[]", "byte[]", "int", "int")
+    .build();
+
   @Override
   public List<Tree.Kind> nodesToVisit() {
     return Arrays.asList(Tree.Kind.METHOD, Tree.Kind.NEW_CLASS, Tree.Kind.METHOD_INVOCATION);
@@ -78,6 +118,8 @@ public void visitNode(Tree tree) {
       reportIssue(nct.identifier(), "Use secure \"PasswordEncoder\" implementation.");
     } else if (tree instanceof MethodInvocationTree mit && UNSAFE_PASSWORD_ENCODER_METHODS.matches(mit)) {
       reportIssue(ExpressionUtils.methodName(mit), "Use secure \"PasswordEncoder\" implementation.");
+    } else if (tree instanceof MethodInvocationTree mit && SECRET_KEY_FACTORY_GENERATE_SECRET_METHOD.matches(mit)) {
+      checkJavaxCrypto(mit);
     } else if (tree.is(Tree.Kind.METHOD)) {
       MethodInvocationVisitor visitor = new MethodInvocationVisitor();
       tree.accept(visitor);
@@ -87,6 +129,92 @@ public void visitNode(Tree tree) {
     }
   }
 
+  private void checkJavaxCrypto(MethodInvocationTree mit) {
+    var algorithmValueExpressionAndValue = Optional.of(mit.methodSelect())
+      .filter(e -> e.is(Tree.Kind.MEMBER_SELECT))
+      .map(MemberSelectExpressionTree.class::cast)
+      .map(MemberSelectExpressionTree::expression)
+      .flatMap(PasswordEncoderCheck::extractAlgorithm);
+    if (algorithmValueExpressionAndValue.isEmpty()) {
+      return;
+    }
+
+    var algorithmValueExpression = algorithmValueExpressionAndValue.get().initializerExpression();
+    var algorithm = algorithmValueExpressionAndValue.get().value();
+    if (!MIN_ITERATIONS_BY_ALGORITHM.containsKey(algorithm)) {
+      return;
+    }
+
+    var iterationCountExpressionsAndValue = extractIterationCount(mit.arguments().get(0));
+    if (iterationCountExpressionsAndValue.isEmpty()) {
+      return;
+    }
+
+    var iterationCountExpression = iterationCountExpressionsAndValue.get().expression();
+    var iterationCountValueExpression = iterationCountExpressionsAndValue.get().initializerExpression();
+    var iterationCount = iterationCountExpressionsAndValue.get().value();
+
+    var minIteration = MIN_ITERATIONS_BY_ALGORITHM.get(algorithm);
+    if (iterationCount < minIteration) {
+      var secondaryLocations = new ArrayList<Location>();
+      secondaryLocations.add(new Location("", algorithmValueExpression));
+      if (!Objects.equals(iterationCountValueExpression.firstToken(), iterationCountExpression.firstToken())) {
+        secondaryLocations.add(new Location("", iterationCountValueExpression));
+      }
+
+      reportIssue(iterationCountExpression, JAVAX_CRYPTO_MESSAGE_FORMAT.formatted(minIteration), secondaryLocations, null);
+    }
+  }
+
+  // Given secretKeyFactory.generateSecret(keySpec), where var keySpec = new PBEKeySpec(..., iterations, ...), where
+  // iterations is var iterations = y, returns the expressions "iterations", "y", as well as "y" value (e.g. 120000).
+  private static Optional<ExpressionsAndValue<Integer>> extractIterationCount(ExpressionTree keySpecArgumentExpression) {
+    return getInitializerIfExpressionIsVariableIdentifier(keySpecArgumentExpression)
+      // try to resolve the expression itself to a constructor invocation
+      .or(() -> Optional.of(keySpecArgumentExpression))
+      .filter(e -> e.is(Tree.Kind.NEW_CLASS))
+      .map(NewClassTree.class::cast)
+      .filter(PBE_KEY_SPEC_CONSTRUCTOR::matches)
+      .map(gi -> gi.arguments().get(2))
+      .flatMap(e -> getValueIfExpressionIsVariableIdentifier(e, Integer.class));
+  }
+
+  // Given secretKeyFactory.getInstance(algorithm), where var algorithm = y, returns the expressions "algorithm",
+  // "y", as well as "y" value (e.g. "PBKDF2withHmacSHA512").
+  private static Optional<ExpressionsAndValue<String>> extractAlgorithm(ExpressionTree secretKeyFactoryExpression) {
+    return getInitializerIfExpressionIsVariableIdentifier(secretKeyFactoryExpression)
+      // try to resolve the expression itself to a method invocation
+      .or(() -> Optional.of(secretKeyFactoryExpression))
+      .filter(e -> e.is(Tree.Kind.METHOD_INVOCATION))
+      .map(MethodInvocationTree.class::cast)
+      .filter(SECRET_KEY_FACTORY_GET_INSTANCE_METHOD::matches)
+      .map(gi -> gi.arguments().get(0))
+      .flatMap(e -> getValueIfExpressionIsVariableIdentifier(e, String.class));
+  }
+
+  // Given any expression e, attempts constant value resolution of the expression itself and of the initializer
+  // of the declaration of e, if any.
+  private static <T> Optional<ExpressionsAndValue<T>> getValueIfExpressionIsVariableIdentifier(ExpressionTree expression, Class<T> type) {
+    var initializerExpression = getInitializerIfExpressionIsVariableIdentifier(expression);
+    return initializerExpression
+      .flatMap(e -> e.asConstant(type))
+      .map(v -> new ExpressionsAndValue<>(expression, initializerExpression.get(), v))
+      .or(() -> expression.asConstant(type).map(t -> new ExpressionsAndValue<>(expression, expression, t)));
+  }
+
+  // Given any identifier "x", returns the initializer y from the declaration of x: "var x = y" (whether in the same
+  // statement or not, whether it was reassigned between declaration and reference or not).
+  private static Optional<ExpressionTree> getInitializerIfExpressionIsVariableIdentifier(ExpressionTree expression) {
+    return Optional.of(expression)
+      .filter(e -> e.is(Tree.Kind.IDENTIFIER))
+      .map(IdentifierTree.class::cast)
+      .map(IdentifierTree::symbol)
+      .map(Symbol::declaration)
+      .filter(e -> e.is(Tree.Kind.VARIABLE))
+      .map(VariableTree.class::cast)
+      .map(VariableTree::initializer);
+  }
+
   static class MethodInvocationVisitor extends BaseTreeVisitor {
 
     private boolean hasAuthentication;
@@ -105,4 +233,7 @@ public void visitMethodInvocation(MethodInvocationTree tree) {
       super.visitMethodInvocation(tree);
     }
   }
+
+  private record ExpressionsAndValue<T>(ExpressionTree expression, ExpressionTree initializerExpression, T value) {
+  }
 }
diff --git a/java-checks/src/test/java/org/sonar/java/checks/security/PasswordEncoderCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/security/PasswordEncoderCheckTest.java
index 34b5e810bad..8214dfb1f93 100644
--- a/java-checks/src/test/java/org/sonar/java/checks/security/PasswordEncoderCheckTest.java
+++ b/java-checks/src/test/java/org/sonar/java/checks/security/PasswordEncoderCheckTest.java
@@ -24,19 +24,36 @@
 class PasswordEncoderCheckTest {
 
   @Test
-  void test() {
+  void test_spring() {
     CheckVerifier.newVerifier()
-      .onFile(mainCodeSourcesPath("checks/security/PasswordEncoder.java"))
+      .onFile(mainCodeSourcesPath("checks/security/PasswordEncoderSpringSample.java"))
       .withCheck(new PasswordEncoderCheck())
       .verifyIssues();
   }
 
   @Test
-  void test_no_semantics() {
+  void test_spring_no_semantics() {
     CheckVerifier.newVerifier()
-      .onFile(mainCodeSourcesPath("checks/security/PasswordEncoder.java"))
+      .onFile(mainCodeSourcesPath("checks/security/PasswordEncoderSpringSample.java"))
       .withCheck(new PasswordEncoderCheck())
       .withoutSemantic()
       .verifyNoIssues();
   }
+
+  @Test
+  void test_javax_crypto() {
+    CheckVerifier.newVerifier()
+      .onFile(mainCodeSourcesPath("checks/security/PasswordEncoderJavaxCryptoSample.java"))
+      .withCheck(new PasswordEncoderCheck())
+      .verifyIssues();
+  }
+
+  @Test
+  void test_javax_crypto_no_semantics() {
+    CheckVerifier.newVerifier()
+      .onFile(mainCodeSourcesPath("checks/security/PasswordEncoderJavaxCryptoSample.java"))
+      .withCheck(new PasswordEncoderCheck())
+      .withoutSemantic()
+      .verifyIssues();
+  }
 }
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5344.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5344.html
index 4345c80ce58..4baa28ffa84 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5344.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5344.html
@@ -17,11 +17,48 @@ <h4>Regulatory Compliance</h4>
 <p>Many industries and jurisdictions have specific regulations and standards to protect user data and ensure its confidentiality. Storing passwords in
 plaintext or with weak hashing algorithms can lead to non-compliance with these regulations, potentially resulting in legal consequences, financial
 penalties, and damage to the reputation of the software application and its developers.</p>
+<h2>How to fix it in Java Cryptography Extension</h2>
+<h3>Code examples</h3>
+<h4>Noncompliant code example</h4>
+<p>The derived key is vulnerable because the cost factor (rounds) is too low for the chosen algorithm.</p>
+<pre data-diff-id="11" data-diff-type="noncompliant">
+private SecretKey deriveKey(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    return secretKeyFactory.generateSecret(keySpec);
+}
+</pre>
+<h4>Compliant solution</h4>
+<pre data-diff-id="11" data-diff-type="compliant">
+private SecretKey deriveKey(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 210000, 256);
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    return secretKeyFactory.generateSecret(keySpec);
+}
+</pre>
+<h3>How does this work?</h3>
+<h4>Select the correct PBKDF2 parameters</h4>
+<p>If PBKDF2 must be used, be aware that default values might not be considered secure.<br> Depending on the algorithm used, the number of iterations
+should be adjusted to ensure that the derived key is secure. The following are the recommended number of iterations for PBKDF2:</p>
+<ul>
+  <li> PBKDF2-HMAC-SHA1: 1,300,000 iterations </li>
+  <li> PBKDF2-HMAC-SHA256: 600,000 iterations </li>
+  <li> PBKDF2-HMAC-SHA512: 210,000 iterations </li>
+</ul>
+<p>Note that PBKDF2-HMAC-SHA256 is recommended by NIST.<br> Iterations are also called "rounds" depending on the library used.</p>
+<p>When recommended cost factors are too high in the context of the application or if the performance cost is unacceptable, a cost factor reduction
+might be considered. In that case, it should not be chosen under 100,000.</p>
+<h3>Going the extra mile</h3>
+<h4>Pepper</h4>
+<p>In a defense-in-depth security approach, <strong>peppering</strong> can also be used. This is a security technique where an external secret value
+is added to a password before it is hashed.<br> This makes it more difficult for an attacker to crack the hashed passwords, as they would need to know
+the secret value to generate the correct hash.<br> <a
+href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#peppering">Learn more here</a>.</p>
 <h2>How to fix it in Spring</h2>
 <h3>Code examples</h3>
 <h4>Noncompliant code example</h4>
 <p>The following code is vulnerable because it uses a legacy digest-based password encoding that is not considered secure.</p>
-<pre data-diff-id="1" data-diff-type="noncompliant">
+<pre data-diff-id="12" data-diff-type="noncompliant">
 @Autowired
 public void configureGlobal(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
   auth.jdbcAuthentication()
@@ -31,7 +68,7 @@ <h4>Noncompliant code example</h4>
 }
 </pre>
 <h4>Compliant solution</h4>
-<pre data-diff-id="1" data-diff-type="compliant">
+<pre data-diff-id="12" data-diff-type="compliant">
 @Autowired
 public void configureGlobal(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
   auth.jdbcAuthentication()
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5344.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5344.json
index 843370d3951..37b8b835281 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5344.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5344.json
@@ -53,5 +53,5 @@
       "V-222542"
     ]
   },
-  "quickfix": "unknown"
+  "quickfix": "targeted"
 }


Reviews:
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:44:22Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:53:08Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:58:51Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:59:52Z
user: login: erwan-serandour
body: Small suggestion about the names of the methods and I propose to add a new sample cases. The new test sample seems to be an FP, I am not sure if we want to handle it.

Otherwise, thanks a lot to have added the link to rspec in the PR :).
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T10:03:34Z
user: login: antonioaversa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T10:16:35Z
user: login: antonioaversa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T10:18:24Z
user: login: erwan-serandour
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T13:38:08Z
user: login: antonioaversa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T13:57:14Z

Review comments:
diff_hunk: @@ -0,0 +1,244 @@
+package checks.security;
+
+import java.security.spec.InvalidKeySpecException;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+class PasswordEncoderJavaxCryptoSample {
+  // region Noncompliant cases
+
+  public static class NoncompliantConstantIterations {
+    private static final int PBKDF2_ITERATIONS = 120000;
+    //                                           ^^^^^^>
+
+    public void noncompliantConstantIterations(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATIONS, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+      //                                                                ^^^^^^^^^^^^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+      //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public static class NoncompliantConstantAlgorithm {
+    private static final String SHA512_ALGORITHM = "PBKDF2withHmacSHA512";
+    //                                             ^^^^^^^^^^^^^^^^^^^^^^>
+
+    public void noncompliantConstantAlgorithm(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+      //                                                                ^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(SHA512_ALGORITHM);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public void noncompliantNoKeyLength(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000); // Noncompliant
+    //                                                                ^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha512(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha256(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 300000, 256); // Noncompliant {{Use at least 600000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha1(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1_200_000, 256); // Noncompliant {{Use at least 1300000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantLocalVariableIteration(String password, byte[] salt) throws Exception {
+    int iterations = 120000;
+    //               ^^^^^^>
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantLocalVariableIterationAndAlgorithm(String password, byte[] salt) throws Exception {
+    String algorithm = "PBKDF2withHmacSHA512";
+    //                 ^^^^^^^^^^^^^^^^^^^^^^>
+    int iterations = 120000;
+    //               ^^^^^^>
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantComplexFlow(String password, byte[] salt) throws Exception {
+    int iterations = 500_000;
+    //               ^^^^^^^>
+    if (salt.length > 10) {
+      System.out.print("Some flow between relevant code");
+    }
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    while (salt.length < 10) {
+      if (salt.hashCode() == 42) {
+        System.out.print("Some other flow between relevant code");
+
+        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
+        //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+        Runnable aLambda = () -> {
+          try {
+            secretKeyFactory.generateSecret(keySpec);
+          } catch (InvalidKeySpecException e) {
+            throw new RuntimeException(e);
+          }
+        };
+      }
+    }
+  }
+
+  public void noncompliantStringConcatenation(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmac" + "SHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantToString(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // FN
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512".toString());
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantInlineSecretKeyFactoryGetInstance(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+  }
+
+  public void noncompliantEverythingInline(String password, byte[] salt) throws Exception {
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(
+      //                         ^^^^^^^^^^^^^^^^^^^^^^>
+      new PBEKeySpec(password.toCharArray(), salt, 110000, 256) // Noncompliant
+      //                                           ^^^^^^
+    );
+  }
+
+  public void noncompliantEverythingInlineWithLet(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 110000, 256); // Noncompliant
+    //                                                                ^^^^^^
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+    //                           ^^^^^^^^^^^^^^^^^^^^^^<
+  }
+
+  public void noncompliantWithAliases(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+  }
+
+  public static class NoncompliantFinalAndNonFinalInlineFieldIterationAndAlgorithm {
+    private final int iteration = 110000;
+    //                            ^^^^^^>
+    private String algorithm = "PBKDF2withHmacSHA512";
+    //                         ^^^^^^^^^^^^^^^^^^^^^^>
+
+    public void test(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iteration, 256); // Noncompliant
+      //                                                                ^^^^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public static class NoncompliantFinalNonInlineCtorHardcodedFieldIterationAndAlgorithm {
+    private final int iteration;
+    private final String algorithm;
+
+    public NoncompliantFinalNonInlineCtorHardcodedFieldIterationAndAlgorithm(int iteration, String algorithm) {
+      this.iteration = 110000;
+      this.algorithm = "PBKDF2withHmacSHA512";
+    }
+
+    public void test(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iteration, 256); // FN: requires flow from ctor
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  // endregion
+
+  // region Compliant cases
+
+  public void compliantIntLiteralAboveThresholdForSHA512(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 210000, 256); // Compliant: 210_000 >= 210_000
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
path: java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoderJavaxCryptoSample.java
user: login: erwan-serandour
body: let's add a new case

  public void compliantIntLiteralAboveThresholdForSHA512(String password, byte[] salt) throws Exception {
    int it = 10;
    it = 210000;
    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, it, 256);
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
    secretKeyFactory.generateSecret(keySpec);
  }
created_at: 2025-04-03T09:44:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,6 +129,92 @@ public void visitNode(Tree tree) {
     }
   }
 
+  private void checkJavaxCrypto(MethodInvocationTree mit) {
+    var algorithmValueExpressionAndValue = Optional.of(mit.methodSelect())
+      .filter(e -> e.is(Tree.Kind.MEMBER_SELECT))
+      .map(MemberSelectExpressionTree.class::cast)
+      .map(MemberSelectExpressionTree::expression)
+      .flatMap(PasswordEncoderCheck::extractAlgorithm);
+    if (algorithmValueExpressionAndValue.isEmpty()) {
+      return;
+    }
+
+    var algorithmValueExpression = algorithmValueExpressionAndValue.get().initializerExpression();
+    var algorithm = algorithmValueExpressionAndValue.get().value();
+    if (!MIN_ITERATIONS_BY_ALGORITHM.containsKey(algorithm)) {
+      return;
+    }
+
+    var iterationCountExpressionsAndValue = extractIterationCount(mit.arguments().get(0));
+    if (iterationCountExpressionsAndValue.isEmpty()) {
+      return;
+    }
+
+    var iterationCountExpression = iterationCountExpressionsAndValue.get().expression();
+    var iterationCountValueExpression = iterationCountExpressionsAndValue.get().initializerExpression();
+    var iterationCount = iterationCountExpressionsAndValue.get().value();
+
+    var minIteration = MIN_ITERATIONS_BY_ALGORITHM.get(algorithm);
+    if (iterationCount < minIteration) {
+      var secondaryLocations = new ArrayList<Location>();
+      secondaryLocations.add(new Location("", algorithmValueExpression));
+      if (!Objects.equals(iterationCountValueExpression.firstToken(), iterationCountExpression.firstToken())) {
+        secondaryLocations.add(new Location("", iterationCountValueExpression));
+      }
+
+      reportIssue(iterationCountExpression, JAVAX_CRYPTO_MESSAGE_FORMAT.formatted(minIteration), secondaryLocations, null);
+    }
+  }
+
+  // Given secretKeyFactory.generateSecret(keySpec), where var keySpec = new PBEKeySpec(..., iterations, ...), where
+  // iterations is var iterations = y, returns the expressions "iterations", "y", as well as "y" value (e.g. 120000).
+  private static Optional<ExpressionsAndValue<Integer>> extractIterationCount(ExpressionTree keySpecArgumentExpression) {
+    return getInitializerExpressionOfVariableIdentifier(keySpecArgumentExpression)
+      // try to resolve the expression itself to a constructor invocation
+      .or(() -> Optional.of(keySpecArgumentExpression))
+      .filter(e -> e.is(Tree.Kind.NEW_CLASS))
+      .map(NewClassTree.class::cast)
+      .filter(PBE_KEY_SPEC_CONSTRUCTOR::matches)
+      .map(gi -> gi.arguments().get(2))
+      .flatMap(e -> getInitializerExpressionAndValue(e, Integer.class));
+  }
+
+  // Given secretKeyFactory.getInstance(algorithm), where var algorithm = y, returns the expressions "algorithm",
+  // "y", as well as "y" value (e.g. "PBKDF2withHmacSHA512").
+  private static Optional<ExpressionsAndValue<String>> extractAlgorithm(ExpressionTree secretKeyFactoryExpression) {
+    return getInitializerExpressionOfVariableIdentifier(secretKeyFactoryExpression)
+      // try to resolve the expression itself to a method invocation
+      .or(() -> Optional.of(secretKeyFactoryExpression))
+      .filter(e -> e.is(Tree.Kind.METHOD_INVOCATION))
+      .map(MethodInvocationTree.class::cast)
+      .filter(SECRET_KEY_FACTORY_GET_INSTANCE_METHOD::matches)
+      .map(gi -> gi.arguments().get(0))
+      .flatMap(e -> getInitializerExpressionAndValue(e, String.class));
+  }
+
+  // Given any expression e, attempts constant value resolution of the expression itself and of the initializer
+  // of the declaration of e, if any.
+  private static <T> Optional<ExpressionsAndValue<T>> getInitializerExpressionAndValue(ExpressionTree expression, Class<T> type) {
+    var initializerExpression = getInitializerExpressionOfVariableIdentifier(expression);
+    return initializerExpression
+      .flatMap(e -> e.asConstant(type))
+      .map(v -> new ExpressionsAndValue<>(expression, initializerExpression.get(), v))
+      .or(() -> expression.asConstant(type).map(t -> new ExpressionsAndValue<>(expression, expression, t)));
+  }
+
+  // Given any identifier "x", returns the initializer y from the declaration of x: "var x = y" (whether in the same
+  // statement or not, whether it was reassigned between declaration and reference or not).
+  private static Optional<ExpressionTree> getInitializerExpressionOfVariableIdentifier(ExpressionTree expression) {
path: java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java
user: login: erwan-serandour
body: I think we can update a bit the name to make it clearer. The return type already gives us information about the expression and the if make clearer when we return a value.
```suggestion
  private static Optional<ExpressionTree> getInitializerIfExpressionIsVariableIdentifier(ExpressionTree expression) {
```
created_at: 2025-04-03T09:53:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,6 +129,92 @@ public void visitNode(Tree tree) {
     }
   }
 
+  private void checkJavaxCrypto(MethodInvocationTree mit) {
+    var algorithmValueExpressionAndValue = Optional.of(mit.methodSelect())
+      .filter(e -> e.is(Tree.Kind.MEMBER_SELECT))
+      .map(MemberSelectExpressionTree.class::cast)
+      .map(MemberSelectExpressionTree::expression)
+      .flatMap(PasswordEncoderCheck::extractAlgorithm);
+    if (algorithmValueExpressionAndValue.isEmpty()) {
+      return;
+    }
+
+    var algorithmValueExpression = algorithmValueExpressionAndValue.get().initializerExpression();
+    var algorithm = algorithmValueExpressionAndValue.get().value();
+    if (!MIN_ITERATIONS_BY_ALGORITHM.containsKey(algorithm)) {
+      return;
+    }
+
+    var iterationCountExpressionsAndValue = extractIterationCount(mit.arguments().get(0));
+    if (iterationCountExpressionsAndValue.isEmpty()) {
+      return;
+    }
+
+    var iterationCountExpression = iterationCountExpressionsAndValue.get().expression();
+    var iterationCountValueExpression = iterationCountExpressionsAndValue.get().initializerExpression();
+    var iterationCount = iterationCountExpressionsAndValue.get().value();
+
+    var minIteration = MIN_ITERATIONS_BY_ALGORITHM.get(algorithm);
+    if (iterationCount < minIteration) {
+      var secondaryLocations = new ArrayList<Location>();
+      secondaryLocations.add(new Location("", algorithmValueExpression));
+      if (!Objects.equals(iterationCountValueExpression.firstToken(), iterationCountExpression.firstToken())) {
+        secondaryLocations.add(new Location("", iterationCountValueExpression));
+      }
+
+      reportIssue(iterationCountExpression, JAVAX_CRYPTO_MESSAGE_FORMAT.formatted(minIteration), secondaryLocations, null);
+    }
+  }
+
+  // Given secretKeyFactory.generateSecret(keySpec), where var keySpec = new PBEKeySpec(..., iterations, ...), where
+  // iterations is var iterations = y, returns the expressions "iterations", "y", as well as "y" value (e.g. 120000).
+  private static Optional<ExpressionsAndValue<Integer>> extractIterationCount(ExpressionTree keySpecArgumentExpression) {
+    return getInitializerExpressionOfVariableIdentifier(keySpecArgumentExpression)
+      // try to resolve the expression itself to a constructor invocation
+      .or(() -> Optional.of(keySpecArgumentExpression))
+      .filter(e -> e.is(Tree.Kind.NEW_CLASS))
+      .map(NewClassTree.class::cast)
+      .filter(PBE_KEY_SPEC_CONSTRUCTOR::matches)
+      .map(gi -> gi.arguments().get(2))
+      .flatMap(e -> getInitializerExpressionAndValue(e, Integer.class));
+  }
+
+  // Given secretKeyFactory.getInstance(algorithm), where var algorithm = y, returns the expressions "algorithm",
+  // "y", as well as "y" value (e.g. "PBKDF2withHmacSHA512").
+  private static Optional<ExpressionsAndValue<String>> extractAlgorithm(ExpressionTree secretKeyFactoryExpression) {
+    return getInitializerExpressionOfVariableIdentifier(secretKeyFactoryExpression)
+      // try to resolve the expression itself to a method invocation
+      .or(() -> Optional.of(secretKeyFactoryExpression))
+      .filter(e -> e.is(Tree.Kind.METHOD_INVOCATION))
+      .map(MethodInvocationTree.class::cast)
+      .filter(SECRET_KEY_FACTORY_GET_INSTANCE_METHOD::matches)
+      .map(gi -> gi.arguments().get(0))
+      .flatMap(e -> getInitializerExpressionAndValue(e, String.class));
+  }
+
+  // Given any expression e, attempts constant value resolution of the expression itself and of the initializer
+  // of the declaration of e, if any.
+  private static <T> Optional<ExpressionsAndValue<T>> getInitializerExpressionAndValue(ExpressionTree expression, Class<T> type) {
path: java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java
user: login: erwan-serandour
body: we can also make the name a bit clearer
```suggestion
  private static <T> Optional<ExpressionsAndValue<T>> getInitialLiteralIfExpressionIsVariable(ExpressionTree expression, Class<T> type) {
```
created_at: 2025-04-03T09:58:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,6 +129,92 @@ public void visitNode(Tree tree) {
     }
   }
 
+  private void checkJavaxCrypto(MethodInvocationTree mit) {
+    var algorithmValueExpressionAndValue = Optional.of(mit.methodSelect())
+      .filter(e -> e.is(Tree.Kind.MEMBER_SELECT))
+      .map(MemberSelectExpressionTree.class::cast)
+      .map(MemberSelectExpressionTree::expression)
+      .flatMap(PasswordEncoderCheck::extractAlgorithm);
+    if (algorithmValueExpressionAndValue.isEmpty()) {
+      return;
+    }
+
+    var algorithmValueExpression = algorithmValueExpressionAndValue.get().initializerExpression();
+    var algorithm = algorithmValueExpressionAndValue.get().value();
+    if (!MIN_ITERATIONS_BY_ALGORITHM.containsKey(algorithm)) {
+      return;
+    }
+
+    var iterationCountExpressionsAndValue = extractIterationCount(mit.arguments().get(0));
+    if (iterationCountExpressionsAndValue.isEmpty()) {
+      return;
+    }
+
+    var iterationCountExpression = iterationCountExpressionsAndValue.get().expression();
+    var iterationCountValueExpression = iterationCountExpressionsAndValue.get().initializerExpression();
+    var iterationCount = iterationCountExpressionsAndValue.get().value();
+
+    var minIteration = MIN_ITERATIONS_BY_ALGORITHM.get(algorithm);
+    if (iterationCount < minIteration) {
+      var secondaryLocations = new ArrayList<Location>();
+      secondaryLocations.add(new Location("", algorithmValueExpression));
+      if (!Objects.equals(iterationCountValueExpression.firstToken(), iterationCountExpression.firstToken())) {
+        secondaryLocations.add(new Location("", iterationCountValueExpression));
+      }
+
+      reportIssue(iterationCountExpression, JAVAX_CRYPTO_MESSAGE_FORMAT.formatted(minIteration), secondaryLocations, null);
+    }
+  }
+
+  // Given secretKeyFactory.generateSecret(keySpec), where var keySpec = new PBEKeySpec(..., iterations, ...), where
+  // iterations is var iterations = y, returns the expressions "iterations", "y", as well as "y" value (e.g. 120000).
+  private static Optional<ExpressionsAndValue<Integer>> extractIterationCount(ExpressionTree keySpecArgumentExpression) {
+    return getInitializerExpressionOfVariableIdentifier(keySpecArgumentExpression)
+      // try to resolve the expression itself to a constructor invocation
+      .or(() -> Optional.of(keySpecArgumentExpression))
+      .filter(e -> e.is(Tree.Kind.NEW_CLASS))
+      .map(NewClassTree.class::cast)
+      .filter(PBE_KEY_SPEC_CONSTRUCTOR::matches)
+      .map(gi -> gi.arguments().get(2))
+      .flatMap(e -> getInitializerExpressionAndValue(e, Integer.class));
+  }
+
+  // Given secretKeyFactory.getInstance(algorithm), where var algorithm = y, returns the expressions "algorithm",
+  // "y", as well as "y" value (e.g. "PBKDF2withHmacSHA512").
+  private static Optional<ExpressionsAndValue<String>> extractAlgorithm(ExpressionTree secretKeyFactoryExpression) {
+    return getInitializerExpressionOfVariableIdentifier(secretKeyFactoryExpression)
+      // try to resolve the expression itself to a method invocation
+      .or(() -> Optional.of(secretKeyFactoryExpression))
+      .filter(e -> e.is(Tree.Kind.METHOD_INVOCATION))
+      .map(MethodInvocationTree.class::cast)
+      .filter(SECRET_KEY_FACTORY_GET_INSTANCE_METHOD::matches)
+      .map(gi -> gi.arguments().get(0))
+      .flatMap(e -> getInitializerExpressionAndValue(e, String.class));
+  }
+
+  // Given any expression e, attempts constant value resolution of the expression itself and of the initializer
+  // of the declaration of e, if any.
+  private static <T> Optional<ExpressionsAndValue<T>> getInitializerExpressionAndValue(ExpressionTree expression, Class<T> type) {
path: java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java
user: login: erwan-serandour
body: maybe Init can be removed.
created_at: 2025-04-03T09:59:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,244 @@
+package checks.security;
+
+import java.security.spec.InvalidKeySpecException;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+class PasswordEncoderJavaxCryptoSample {
+  // region Noncompliant cases
+
+  public static class NoncompliantConstantIterations {
+    private static final int PBKDF2_ITERATIONS = 120000;
+    //                                           ^^^^^^>
+
+    public void noncompliantConstantIterations(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATIONS, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+      //                                                                ^^^^^^^^^^^^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+      //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public static class NoncompliantConstantAlgorithm {
+    private static final String SHA512_ALGORITHM = "PBKDF2withHmacSHA512";
+    //                                             ^^^^^^^^^^^^^^^^^^^^^^>
+
+    public void noncompliantConstantAlgorithm(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+      //                                                                ^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(SHA512_ALGORITHM);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public void noncompliantNoKeyLength(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000); // Noncompliant
+    //                                                                ^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha512(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant {{Use at least 210000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha256(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 300000, 256); // Noncompliant {{Use at least 600000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantIntLiteralIterationWithSha1(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1_200_000, 256); // Noncompliant {{Use at least 1300000 PBKDF2 iterations.}}
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantLocalVariableIteration(String password, byte[] salt) throws Exception {
+    int iterations = 120000;
+    //               ^^^^^^>
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantLocalVariableIterationAndAlgorithm(String password, byte[] salt) throws Exception {
+    String algorithm = "PBKDF2withHmacSHA512";
+    //                 ^^^^^^^^^^^^^^^^^^^^^^>
+    int iterations = 120000;
+    //               ^^^^^^>
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantComplexFlow(String password, byte[] salt) throws Exception {
+    int iterations = 500_000;
+    //               ^^^^^^^>
+    if (salt.length > 10) {
+      System.out.print("Some flow between relevant code");
+    }
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256); // Noncompliant
+    //                                                                ^^^^^^^^^^
+    while (salt.length < 10) {
+      if (salt.hashCode() == 42) {
+        System.out.print("Some other flow between relevant code");
+
+        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
+        //                                                               ^^^^^^^^^^^^^^^^^^^^^^<
+        Runnable aLambda = () -> {
+          try {
+            secretKeyFactory.generateSecret(keySpec);
+          } catch (InvalidKeySpecException e) {
+            throw new RuntimeException(e);
+          }
+        };
+      }
+    }
+  }
+
+  public void noncompliantStringConcatenation(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmac" + "SHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantToString(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // FN
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512".toString());
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
+  public void noncompliantInlineSecretKeyFactoryGetInstance(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+  }
+
+  public void noncompliantEverythingInline(String password, byte[] salt) throws Exception {
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(
+      //                         ^^^^^^^^^^^^^^^^^^^^^^>
+      new PBEKeySpec(password.toCharArray(), salt, 110000, 256) // Noncompliant
+      //                                           ^^^^^^
+    );
+  }
+
+  public void noncompliantEverythingInlineWithLet(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 110000, 256); // Noncompliant
+    //                                                                ^^^^^^
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+    //                           ^^^^^^^^^^^^^^^^^^^^^^<
+  }
+
+  public void noncompliantWithAliases(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); // Noncompliant
+    SecretKeyFactory.getInstance("PBKDF2withHmacSHA512").generateSecret(keySpec);
+  }
+
+  public static class NoncompliantFinalAndNonFinalInlineFieldIterationAndAlgorithm {
+    private final int iteration = 110000;
+    //                            ^^^^^^>
+    private String algorithm = "PBKDF2withHmacSHA512";
+    //                         ^^^^^^^^^^^^^^^^^^^^^^>
+
+    public void test(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iteration, 256); // Noncompliant
+      //                                                                ^^^^^^^^^
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  public static class NoncompliantFinalNonInlineCtorHardcodedFieldIterationAndAlgorithm {
+    private final int iteration;
+    private final String algorithm;
+
+    public NoncompliantFinalNonInlineCtorHardcodedFieldIterationAndAlgorithm(int iteration, String algorithm) {
+      this.iteration = 110000;
+      this.algorithm = "PBKDF2withHmacSHA512";
+    }
+
+    public void test(String password, byte[] salt) throws Exception {
+      PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iteration, 256); // FN: requires flow from ctor
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
+      secretKeyFactory.generateSecret(keySpec);
+    }
+  }
+
+  // endregion
+
+  // region Compliant cases
+
+  public void compliantIntLiteralAboveThresholdForSHA512(String password, byte[] salt) throws Exception {
+    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 210000, 256); // Compliant: 210_000 >= 210_000
+    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512");
+    secretKeyFactory.generateSecret(keySpec);
+  }
+
path: java-checks-test-sources/default/src/main/java/checks/security/PasswordEncoderJavaxCryptoSample.java
user: login: antonioaversa
body: Good catch! This one is actually an FP, which is good to document. 
For info, we discussed a similar scenario [here](https://github.com/SonarSource/sonar-kotlin/pull/598/files#r2007963988), where I filtered only for `val` (`final` in Java). 
However the use of local `final` in Java is way less common than the use of `val` in Kotlin, and we are thinking of removing the `?.takeIf { !it.isVar }` to have a higher reach, since:
- the chance of hardcoding the algorithm and then reassigning the iteration are very low
- these security rules have generally very low reach, as they signal potentially serious security issues
- this rule in particular is a vulnerability, so it's better to slightly err on the side of a very low probability FP than having a FN for the very common scenario where a non-`final` variable is used
- dead store would trigger if the initially assigned value (10) is never read and, if it is, there is at least one path where 10 is used - so it's worth triggering.
created_at: 2025-04-03T10:16:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,6 +129,92 @@ public void visitNode(Tree tree) {
     }
   }
 
+  private void checkJavaxCrypto(MethodInvocationTree mit) {
+    var algorithmValueExpressionAndValue = Optional.of(mit.methodSelect())
+      .filter(e -> e.is(Tree.Kind.MEMBER_SELECT))
+      .map(MemberSelectExpressionTree.class::cast)
+      .map(MemberSelectExpressionTree::expression)
+      .flatMap(PasswordEncoderCheck::extractAlgorithm);
+    if (algorithmValueExpressionAndValue.isEmpty()) {
+      return;
+    }
+
+    var algorithmValueExpression = algorithmValueExpressionAndValue.get().initializerExpression();
+    var algorithm = algorithmValueExpressionAndValue.get().value();
+    if (!MIN_ITERATIONS_BY_ALGORITHM.containsKey(algorithm)) {
+      return;
+    }
+
+    var iterationCountExpressionsAndValue = extractIterationCount(mit.arguments().get(0));
+    if (iterationCountExpressionsAndValue.isEmpty()) {
+      return;
+    }
+
+    var iterationCountExpression = iterationCountExpressionsAndValue.get().expression();
+    var iterationCountValueExpression = iterationCountExpressionsAndValue.get().initializerExpression();
+    var iterationCount = iterationCountExpressionsAndValue.get().value();
+
+    var minIteration = MIN_ITERATIONS_BY_ALGORITHM.get(algorithm);
+    if (iterationCount < minIteration) {
+      var secondaryLocations = new ArrayList<Location>();
+      secondaryLocations.add(new Location("", algorithmValueExpression));
+      if (!Objects.equals(iterationCountValueExpression.firstToken(), iterationCountExpression.firstToken())) {
+        secondaryLocations.add(new Location("", iterationCountValueExpression));
+      }
+
+      reportIssue(iterationCountExpression, JAVAX_CRYPTO_MESSAGE_FORMAT.formatted(minIteration), secondaryLocations, null);
+    }
+  }
+
+  // Given secretKeyFactory.generateSecret(keySpec), where var keySpec = new PBEKeySpec(..., iterations, ...), where
+  // iterations is var iterations = y, returns the expressions "iterations", "y", as well as "y" value (e.g. 120000).
+  private static Optional<ExpressionsAndValue<Integer>> extractIterationCount(ExpressionTree keySpecArgumentExpression) {
+    return getInitializerExpressionOfVariableIdentifier(keySpecArgumentExpression)
+      // try to resolve the expression itself to a constructor invocation
+      .or(() -> Optional.of(keySpecArgumentExpression))
+      .filter(e -> e.is(Tree.Kind.NEW_CLASS))
+      .map(NewClassTree.class::cast)
+      .filter(PBE_KEY_SPEC_CONSTRUCTOR::matches)
+      .map(gi -> gi.arguments().get(2))
+      .flatMap(e -> getInitializerExpressionAndValue(e, Integer.class));
+  }
+
+  // Given secretKeyFactory.getInstance(algorithm), where var algorithm = y, returns the expressions "algorithm",
+  // "y", as well as "y" value (e.g. "PBKDF2withHmacSHA512").
+  private static Optional<ExpressionsAndValue<String>> extractAlgorithm(ExpressionTree secretKeyFactoryExpression) {
+    return getInitializerExpressionOfVariableIdentifier(secretKeyFactoryExpression)
+      // try to resolve the expression itself to a method invocation
+      .or(() -> Optional.of(secretKeyFactoryExpression))
+      .filter(e -> e.is(Tree.Kind.METHOD_INVOCATION))
+      .map(MethodInvocationTree.class::cast)
+      .filter(SECRET_KEY_FACTORY_GET_INSTANCE_METHOD::matches)
+      .map(gi -> gi.arguments().get(0))
+      .flatMap(e -> getInitializerExpressionAndValue(e, String.class));
+  }
+
+  // Given any expression e, attempts constant value resolution of the expression itself and of the initializer
+  // of the declaration of e, if any.
+  private static <T> Optional<ExpressionsAndValue<T>> getInitializerExpressionAndValue(ExpressionTree expression, Class<T> type) {
+    var initializerExpression = getInitializerExpressionOfVariableIdentifier(expression);
+    return initializerExpression
+      .flatMap(e -> e.asConstant(type))
+      .map(v -> new ExpressionsAndValue<>(expression, initializerExpression.get(), v))
+      .or(() -> expression.asConstant(type).map(t -> new ExpressionsAndValue<>(expression, expression, t)));
+  }
+
+  // Given any identifier "x", returns the initializer y from the declaration of x: "var x = y" (whether in the same
+  // statement or not, whether it was reassigned between declaration and reference or not).
+  private static Optional<ExpressionTree> getInitializerExpressionOfVariableIdentifier(ExpressionTree expression) {
path: java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java
user: login: antonioaversa
body: Sure, it definitely reads better!
created_at: 2025-04-03T10:18:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,6 +129,92 @@ public void visitNode(Tree tree) {
     }
   }
 
+  private void checkJavaxCrypto(MethodInvocationTree mit) {
+    var algorithmValueExpressionAndValue = Optional.of(mit.methodSelect())
+      .filter(e -> e.is(Tree.Kind.MEMBER_SELECT))
+      .map(MemberSelectExpressionTree.class::cast)
+      .map(MemberSelectExpressionTree::expression)
+      .flatMap(PasswordEncoderCheck::extractAlgorithm);
+    if (algorithmValueExpressionAndValue.isEmpty()) {
+      return;
+    }
+
+    var algorithmValueExpression = algorithmValueExpressionAndValue.get().initializerExpression();
+    var algorithm = algorithmValueExpressionAndValue.get().value();
+    if (!MIN_ITERATIONS_BY_ALGORITHM.containsKey(algorithm)) {
+      return;
+    }
+
+    var iterationCountExpressionsAndValue = extractIterationCount(mit.arguments().get(0));
+    if (iterationCountExpressionsAndValue.isEmpty()) {
+      return;
+    }
+
+    var iterationCountExpression = iterationCountExpressionsAndValue.get().expression();
+    var iterationCountValueExpression = iterationCountExpressionsAndValue.get().initializerExpression();
+    var iterationCount = iterationCountExpressionsAndValue.get().value();
+
+    var minIteration = MIN_ITERATIONS_BY_ALGORITHM.get(algorithm);
+    if (iterationCount < minIteration) {
+      var secondaryLocations = new ArrayList<Location>();
+      secondaryLocations.add(new Location("", algorithmValueExpression));
+      if (!Objects.equals(iterationCountValueExpression.firstToken(), iterationCountExpression.firstToken())) {
+        secondaryLocations.add(new Location("", iterationCountValueExpression));
+      }
+
+      reportIssue(iterationCountExpression, JAVAX_CRYPTO_MESSAGE_FORMAT.formatted(minIteration), secondaryLocations, null);
+    }
+  }
+
+  // Given secretKeyFactory.generateSecret(keySpec), where var keySpec = new PBEKeySpec(..., iterations, ...), where
+  // iterations is var iterations = y, returns the expressions "iterations", "y", as well as "y" value (e.g. 120000).
+  private static Optional<ExpressionsAndValue<Integer>> extractIterationCount(ExpressionTree keySpecArgumentExpression) {
+    return getInitializerExpressionOfVariableIdentifier(keySpecArgumentExpression)
+      // try to resolve the expression itself to a constructor invocation
+      .or(() -> Optional.of(keySpecArgumentExpression))
+      .filter(e -> e.is(Tree.Kind.NEW_CLASS))
+      .map(NewClassTree.class::cast)
+      .filter(PBE_KEY_SPEC_CONSTRUCTOR::matches)
+      .map(gi -> gi.arguments().get(2))
+      .flatMap(e -> getInitializerExpressionAndValue(e, Integer.class));
+  }
+
+  // Given secretKeyFactory.getInstance(algorithm), where var algorithm = y, returns the expressions "algorithm",
+  // "y", as well as "y" value (e.g. "PBKDF2withHmacSHA512").
+  private static Optional<ExpressionsAndValue<String>> extractAlgorithm(ExpressionTree secretKeyFactoryExpression) {
+    return getInitializerExpressionOfVariableIdentifier(secretKeyFactoryExpression)
+      // try to resolve the expression itself to a method invocation
+      .or(() -> Optional.of(secretKeyFactoryExpression))
+      .filter(e -> e.is(Tree.Kind.METHOD_INVOCATION))
+      .map(MethodInvocationTree.class::cast)
+      .filter(SECRET_KEY_FACTORY_GET_INSTANCE_METHOD::matches)
+      .map(gi -> gi.arguments().get(0))
+      .flatMap(e -> getInitializerExpressionAndValue(e, String.class));
+  }
+
+  // Given any expression e, attempts constant value resolution of the expression itself and of the initializer
+  // of the declaration of e, if any.
+  private static <T> Optional<ExpressionsAndValue<T>> getInitializerExpressionAndValue(ExpressionTree expression, Class<T> type) {
path: java-checks/src/main/java/org/sonar/java/checks/security/PasswordEncoderCheck.java
user: login: antonioaversa
body: Discussed offline: we went for `getValueIfExpressionIsVariableIdentifier`, since what basically this method does on top of `getInitializerIfExpressionIsVariableIdentifier` is to calculate its value, using `asConstant`.

created_at: 2025-04-03T13:57:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqube-next[bot]
created_at: 2025-04-03T13:09:07Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.java%3Ajava&pullRequest=5070) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5070&resolved=false) [0 New issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5070&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/fixed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&fixedInPullRequest=5070) [0 Fixed issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&fixedInPullRequest=5070)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5070&issueStatuses=ACCEPTED) [0 Accepted issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5070&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.java%3Ajava&pullRequest=5070) [0 Security Hotspots](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.java%3Ajava&pullRequest=5070)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5070&metric=new_coverage&view=list) [100.0% Coverage on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5070&metric=new_coverage&view=list)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5070&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5070&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.java%3Ajava&pullRequest=5070)


author_association: NONE