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.
