SLING-10499 Provide a salt provider using a secure random number generator
diff --git a/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProvider.java b/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProvider.java
new file mode 100644
index 0000000..e2695fe
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.crypto.internal;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Objects;
+
+import org.apache.sling.commons.crypto.SaltProvider;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+    property = {
+        Constants.SERVICE_DESCRIPTION + "=Apache Sling Commons Crypto – SecureRandom Salt Provider",
+        Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+    }
+)
+@Designate(
+    ocd = SecureRandomSaltProviderConfiguration.class,
+    factory = true
+)
+public class SecureRandomSaltProvider implements SaltProvider {
+
+    private SecureRandom secureRandom;
+
+    private SecureRandomSaltProviderConfiguration configuration;
+
+    private final Logger logger = LoggerFactory.getLogger(SecureRandomSaltProvider.class);
+
+    public SecureRandomSaltProvider() { //
+    }
+
+    @Activate
+    protected void activate(final SecureRandomSaltProviderConfiguration configuration) throws IOException, NoSuchAlgorithmException {
+        logger.debug("activating");
+        this.configuration = configuration;
+        secureRandom = SecureRandom.getInstance(configuration.algorithm());
+
+    }
+
+    @Modified
+    protected void modified(final SecureRandomSaltProviderConfiguration configuration) throws IOException, NoSuchAlgorithmException {
+        logger.debug("modifying");
+        this.configuration = configuration;
+        secureRandom = SecureRandom.getInstance(configuration.algorithm());
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        logger.debug("deactivating");
+    }
+
+    @Override
+    public byte @NotNull [] getSalt() {
+        Objects.requireNonNull(configuration, "Configuration must not be null");
+        final byte[] bytes = new byte[configuration.keyLength()];
+        secureRandom.nextBytes(bytes);
+        return bytes;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderConfiguration.java b/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderConfiguration.java
new file mode 100644
index 0000000..71306e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderConfiguration.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.crypto.internal;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(
+    name = "Apache Sling Commons Crypto “SecureRandom Salt Provider”",
+    description = "Provides salts created from random bytes"
+)
+@interface SecureRandomSaltProviderConfiguration {
+
+    @AttributeDefinition(
+        name = "Names",
+        description = "names of this service",
+        required = false
+    )
+    String[] names() default {};
+
+    @AttributeDefinition(
+        name = "Algorithm",
+        description = "secure random number generation algorithm"
+    )
+    String algorithm() default "SHA1PRNG";
+
+    @AttributeDefinition(
+        name = "Key Length",
+        description = "length of the key"
+    )
+    int keyLength() default 8;
+
+}
diff --git a/src/test/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderTest.java b/src/test/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderTest.java
new file mode 100644
index 0000000..99972c3
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.crypto.internal;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SecureRandomSaltProviderTest {
+
+    @Rule
+    public ExpectedException exception = ExpectedException.none();
+
+    @Test
+    public void testMissingConfiguration() throws IOException, NoSuchAlgorithmException {
+        final SecureRandomSaltProvider provider = new SecureRandomSaltProvider();
+        exception.expect(NullPointerException.class);
+        exception.expectMessage("Configuration must not be null");
+        provider.getSalt();
+    }
+
+    @Test
+    public void testComponentLifecycle() throws IOException, NoSuchAlgorithmException {
+        final SecureRandomSaltProvider provider = new SecureRandomSaltProvider();
+        { // activate
+            final SecureRandomSaltProviderConfiguration configuration = mock(SecureRandomSaltProviderConfiguration.class);
+            when(configuration.algorithm()).thenReturn("SHA1PRNG");
+            when(configuration.keyLength()).thenReturn(8);
+            provider.activate(configuration);
+            assertThat(provider.getSalt()).hasLength(8);
+        }
+        { // modified
+            final SecureRandomSaltProviderConfiguration configuration = mock(SecureRandomSaltProviderConfiguration.class);
+            when(configuration.algorithm()).thenReturn("SHA1PRNG");
+            when(configuration.keyLength()).thenReturn(16);
+            provider.modified(configuration);
+            assertThat(provider.getSalt()).hasLength(16);
+        }
+        { // deactivate
+            provider.deactivate();
+            assertThat(provider.getSalt()).hasLength(16);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/commons/crypto/it/tests/SecureRandomSaltProviderIT.java b/src/test/java/org/apache/sling/commons/crypto/it/tests/SecureRandomSaltProviderIT.java
new file mode 100644
index 0000000..69f41e9
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/crypto/it/tests/SecureRandomSaltProviderIT.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.crypto.it.tests;
+
+import javax.inject.Inject;
+
+import org.apache.sling.commons.crypto.SaltProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.ops4j.pax.exam.util.Filter;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class SecureRandomSaltProviderIT extends CryptoTestSupport {
+
+    @Inject
+    @Filter(value = "(names=secure random)")
+    private SaltProvider saltProvider;
+
+    @Configuration
+    public Option[] configuration() {
+        return options(
+            baseConfiguration(),
+            factoryConfiguration("org.apache.sling.commons.crypto.internal.SecureRandomSaltProvider")
+                .put("names", "secure random")
+                .put("keyLength", 32)
+                .asOption()
+        );
+    }
+
+    @Test
+    public void testSaltProvider() {
+        assertThat(saltProvider).isNotNull();
+    }
+
+    @Test
+    public void testSalt() {
+        final byte[] salt = saltProvider.getSalt();
+        assertThat(salt).isNotNull();
+        assertThat(salt).hasLength(32);
+    }
+
+}