This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/clear-passwords-from-memory-sooner
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-crypto.git

commit f6a7a4899f88475fb0174b1c4e32eb99ada087a3
Author: Konrad Windszus <[email protected]>
AuthorDate: Tue Sep 15 17:00:24 2026 +0200

    Improve clearing passwords after use
---
 .../sling/commons/crypto/PasswordProvider.java     |  2 +-
 .../crypto/internal/PbeSecretKeyProvider.java      |  5 +-
 .../crypto/jca/internal/JcaPbeCryptoService.java   | 65 ++++++++++++++--------
 3 files changed, 46 insertions(+), 26 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/commons/crypto/PasswordProvider.java 
b/src/main/java/org/apache/sling/commons/crypto/PasswordProvider.java
index efa89dc..0b9c9db 100644
--- a/src/main/java/org/apache/sling/commons/crypto/PasswordProvider.java
+++ b/src/main/java/org/apache/sling/commons/crypto/PasswordProvider.java
@@ -32,7 +32,7 @@ public interface PasswordProvider {
     /**
      * Provides the password.
      *
-     * @return The password
+     * @return The password (a newly created character array which should be 
cleared after use).
      */
     public abstract char @NotNull [] getPassword();
 
diff --git 
a/src/main/java/org/apache/sling/commons/crypto/internal/PbeSecretKeyProvider.java
 
b/src/main/java/org/apache/sling/commons/crypto/internal/PbeSecretKeyProvider.java
index 226b750..ec2fcbb 100644
--- 
a/src/main/java/org/apache/sling/commons/crypto/internal/PbeSecretKeyProvider.java
+++ 
b/src/main/java/org/apache/sling/commons/crypto/internal/PbeSecretKeyProvider.java
@@ -20,7 +20,6 @@ package org.apache.sling.commons.crypto.internal;
 
 import java.security.NoSuchAlgorithmException;
 import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
 import java.util.Objects;
 
 import javax.crypto.SecretKey;
@@ -100,11 +99,13 @@ public final class PbeSecretKeyProvider implements 
SecretKeyProvider {
     public @NotNull SecretKey getSecretKey() {
         final var configuration = this.configuration;
         Objects.requireNonNull(configuration, "Configuration must not be 
null");
+        final PBEKeySpec keySpec = new 
PBEKeySpec(passwordProvider.getPassword(), saltProvider.getSalt(), 
configuration.iterationCount(), configuration.keyLength());
         try {
-            final KeySpec keySpec = new 
PBEKeySpec(passwordProvider.getPassword(), saltProvider.getSalt(), 
configuration.iterationCount(), configuration.keyLength());
             return factory.generateSecret(keySpec);
         } catch (InvalidKeySpecException e) {
             throw new IllegalArgumentException(e.getMessage(), e);
+        } finally {
+            keySpec.clearPassword();
         }
     }
 
diff --git 
a/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoService.java
 
b/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoService.java
index b394aa2..1cd9cdb 100644
--- 
a/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoService.java
+++ 
b/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoService.java
@@ -31,6 +31,7 @@ import java.security.SecureRandom;
 import java.security.Security;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
 import java.util.Base64;
 import java.util.Objects;
 import java.util.Optional;
@@ -122,31 +123,49 @@ public final class JcaPbeCryptoService implements 
CryptoService {
         }
     }
 
+    private static void destroyData(byte[] data) {
+        if (data != null) {
+            Arrays.fill(data, (byte) 0x00);
+        }
+    }
+
+    private static void destroyData(char[] data) {
+        if (data != null) {
+            Arrays.fill(data, '\0');
+        }
+    }
+
     private @NotNull SecretKey createKey(byte[] salt) throws 
NoSuchAlgorithmException, InvalidKeySpecException {
         final char[] password = passwordProvider.getPassword();
-        // for regular PBE key this is completely ignored except for the 
password (as all logic is encapsulated in the actual cipher
-        // implementation, see
-        // 
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/com/sun/crypto/provider/PBEKeyFactory.java
-        PBEKeySpec keySpec = new PBEKeySpec(
-                password,
-                salt,
-                configuration.numKeyIterations(),
-                configuration.keyLengthBits());
-        SecretKeyFactory secretKeyFactory = securityProvider.isPresent()
-                ? 
SecretKeyFactory.getInstance(configuration.secretKeyFactoryAlgorithm(), 
securityProvider.get())
-                : 
SecretKeyFactory.getInstance(configuration.secretKeyFactoryAlgorithm());
-        SecretKey originalKey = secretKeyFactory.generateSecret(keySpec);
-        keySpec.clearPassword(); // clear password from memory after use
-        if 
(configuration.secretKeyFactoryAlgorithm().equals(configuration.cipherAlgorithm()))
 {
-            // if the cipher algorithm is the same as the secret key factory 
algorithm then the cipher takes care of the actual logic and
-            // uses the key as is (which is just a wrapper around the given 
password)
-            return originalKey;
-        } else {
-            // wrap as key for the proper cipher algorithm (e.g., AES) instead 
of the PBE algorithm (e.g., PBKDF2WithHmacSHA512)
-            SecretKey derivedKey = new SecretKeySpec(originalKey.getEncoded(), 
extractAlgorithmName(configuration.cipherAlgorithm()));
-            destroyKey(originalKey); // destroy the original key as it is no 
longer needed
-            return derivedKey;
-        }
+        try {
+            // for regular PBE key this is completely ignored except for the 
password (as all logic is encapsulated in the actual cipher
+            // implementation, see
+            // 
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/com/sun/crypto/provider/PBEKeyFactory.java
+            PBEKeySpec keySpec = new PBEKeySpec(
+                    password,
+                    salt,
+                    configuration.numKeyIterations(),
+                    configuration.keyLengthBits());
+            SecretKeyFactory secretKeyFactory = securityProvider.isPresent()
+                    ? 
SecretKeyFactory.getInstance(configuration.secretKeyFactoryAlgorithm(), 
securityProvider.get())
+                    : 
SecretKeyFactory.getInstance(configuration.secretKeyFactoryAlgorithm());
+            SecretKey originalKey = secretKeyFactory.generateSecret(keySpec);
+            keySpec.clearPassword(); // clear password from memory after use
+            if 
(configuration.secretKeyFactoryAlgorithm().equals(configuration.cipherAlgorithm()))
 {
+                // if the cipher algorithm is the same as the secret key 
factory algorithm then the cipher takes care of the actual logic and
+                // uses the key as is (which is just a wrapper around the 
given password)
+                return originalKey;
+            } else {
+                // wrap as key for the proper cipher algorithm (e.g., AES) 
instead of the PBE algorithm (e.g., PBKDF2WithHmacSHA512)
+                byte[] keyBytes = originalKey.getEncoded();
+                SecretKey derivedKey = new SecretKeySpec(keyBytes, 
extractAlgorithmName(configuration.cipherAlgorithm()));
+                destroyData(keyBytes); // clear key bytes from memory after use
+                destroyKey(originalKey); // destroy the original key as it is 
no longer needed
+                return derivedKey;
+            }
+         } finally {
+             destroyData(password); // clear password from memory after use
+         }
     }
 
     /** Extracts the algorithm name from the cipher algorithm string.

Reply via email to