This is an automated email from the ASF dual-hosted git repository.
kwin pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-crypto.git
The following commit(s) were added to refs/heads/master by this push:
new 905565e Improve clearing passwords after use
905565e is described below
commit 905565e53f9f4ec07c68ba75e1c52df8e9278af4
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 | 8 +++-
.../crypto/jca/internal/JcaPbeCryptoService.java | 50 +++++++++++++++-------
.../jca/internal/JcaPbeCryptoServiceTest.java | 2 +-
4 files changed, 43 insertions(+), 19 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..35f782a 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,7 @@ package org.apache.sling.commons.crypto.internal;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
+import java.util.Arrays;
import java.util.Objects;
import javax.crypto.SecretKey;
@@ -100,11 +100,15 @@ public final class PbeSecretKeyProvider implements
SecretKeyProvider {
public @NotNull SecretKey getSecretKey() {
final var configuration = this.configuration;
Objects.requireNonNull(configuration, "Configuration must not be
null");
+ char[] password = passwordProvider.getPassword();
+ final PBEKeySpec keySpec = new PBEKeySpec(password,
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 {
+ Arrays.fill(password, '\0');
+ 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 8946ff0..c78357b 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;
@@ -121,6 +122,18 @@ 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
@@ -131,21 +144,28 @@ public final class JcaPbeCryptoService implements
CryptoService {
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 {
+ SecretKeyFactory secretKeyFactory = securityProvider.isPresent()
+ ?
SecretKeyFactory.getInstance(configuration.secretKeyFactoryAlgorithm(),
securityProvider.get())
+ :
SecretKeyFactory.getInstance(configuration.secretKeyFactoryAlgorithm());
+ SecretKey originalKey = secretKeyFactory.generateSecret(keySpec);
+
+ 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 {
+ keySpec.clearPassword(); // clear password from memory after use
+ destroyData(password); // clear password from memory after use
+ }
}
/** Extracts the algorithm name from the cipher algorithm string.
diff --git
a/src/test/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoServiceTest.java
b/src/test/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoServiceTest.java
index 348c827..df3383e 100644
---
a/src/test/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoServiceTest.java
+++
b/src/test/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoServiceTest.java
@@ -88,7 +88,7 @@ class JcaPbeCryptoServiceTest {
@BeforeEach
void setUp() throws NoSuchAlgorithmException {
passwordProvider = mock(PasswordProvider.class);
-
when(passwordProvider.getPassword()).thenReturn("+AQ?aDes!'DBMkrCi:FE6q\\sOn=Pbmn=PK8n=PK?".toCharArray());
+ when(passwordProvider.getPassword()).thenAnswer(i ->
"+AQ?aDes!'DBMkrCi:FE6q\\sOn=Pbmn=PK8n=PK?".toCharArray()); // long password to
test PBKDF2 with high iteration count
salt = new byte[16];
Random random = new Random();
random.nextBytes(salt);