/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed 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.wildfly.security.password.impl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.wildfly.security.password.interfaces.ScramDigestPassword.ALGORITHM_SCRAM_SHA_1; import static org.wildfly.security.password.interfaces.ScramDigestPassword.ALGORITHM_SCRAM_SHA_256; import java.nio.charset.StandardCharsets; import java.security.Provider; import java.security.Security; import java.util.Arrays; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.security.WildFlyElytronProvider; import org.wildfly.security.password.PasswordFactory; import org.wildfly.security.password.interfaces.ScramDigestPassword; import org.wildfly.security.password.spec.EncryptablePasswordSpec; import org.wildfly.security.password.spec.IteratedSaltedPasswordAlgorithmSpec; import org.wildfly.security.password.spec.IteratedSaltedHashPasswordSpec; import org.wildfly.security.util.Alphabet.Base64Alphabet; import org.wildfly.security.util.ByteIterator; import org.wildfly.security.util.CodePointIterator; /** * <p> * Tests for the SCRAM password implementation. The Base64-encoded digests and salts used in the tests were generated * by the Python Passlib scram hash function. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ public class ScramDigestPasswordTest { private static final Provider provider = new WildFlyElytronProvider(); @BeforeClass public static void registerProvider() { Security.addProvider(provider); } @AfterClass public static void removeProvider() { Security.removeProvider(provider.getName()); } @Test public void testBasicFunctionality() throws Exception { byte[] digest; IteratedSaltedHashPasswordSpec spec; ScramDigestPasswordImpl impl; digest = ScramDigestPasswordImpl.scramDigest(ALGORITHM_SCRAM_SHA_1, "password".getBytes(StandardCharsets.UTF_8), "salt".getBytes(StandardCharsets.UTF_8), 4096); assertEquals("4b007901b765489abead49d926f721d065a429c1", ByteIterator.ofBytes(digest).hexEncode().drainToString()); spec = new IteratedSaltedHashPasswordSpec(digest, "salt".getBytes(StandardCharsets.UTF_8), 4096); impl = new ScramDigestPasswordImpl(ALGORITHM_SCRAM_SHA_1, spec); assertTrue(impl.verify("password".toCharArray())); assertFalse(impl.verify("bad".toCharArray())); digest = ScramDigestPasswordImpl.scramDigest(ALGORITHM_SCRAM_SHA_256, "password".getBytes(StandardCharsets.UTF_8), "salt".getBytes(StandardCharsets.UTF_8), 1000); assertEquals("632c2812e46d4604102ba7618e9d6d7d2f8128f6266b4a03264d2a0460b7dcb3", ByteIterator.ofBytes(digest).hexEncode().drainToString()); spec = new IteratedSaltedHashPasswordSpec(digest, "salt".getBytes(StandardCharsets.UTF_8), 1000); impl = new ScramDigestPasswordImpl(ALGORITHM_SCRAM_SHA_256, spec); assertTrue(impl.verify("password".toCharArray())); assertFalse(impl.verify("bad".toCharArray())); } /** * Test of PBKDF2 with SHA-1 * <p> * Reference values by: * <li> http://www.ietf.org/rfc/rfc6070.txt * <li> http://www.neurotechnics.com/tools/xpassword */ @Test public void testDigestSha1() throws Exception { this.performTest(ALGORITHM_SCRAM_SHA_1, "password", "0c60c80f961f0e71f3a9b524af6012062fe037a6", "salt", 1); this.performTest(ALGORITHM_SCRAM_SHA_1, "password", "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957", "salt", 2); this.performTest(ALGORITHM_SCRAM_SHA_1, "password", "4b007901b765489abead49d926f721d065a429c1", "salt", 4096); this.performTest(ALGORITHM_SCRAM_SHA_1, "passwordPASSWORDpassword", "3d2eec4fe41c849b80c8d83662c0e44a8b291a96", "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096); this.performTest(ALGORITHM_SCRAM_SHA_1, "This is little longer password, used for testing of SCRAM digest password hashing.", "e99c31f453b4801a0951f4e9f7b289b01a54743e", "6e81991f001e5c568b05384d50b4159badf9f3b3e288d1e222c5a7cf599c1974", 1000); this.performTest(ALGORITHM_SCRAM_SHA_1, "a\u0438\u4F60\uD83C\uDCA1", "569c650be201904269bf160d52c426dcacda7521", "\uD83C\uDCA1\u4F60\u0438a", 4096); } /** * Test of PBKDF2 with SHA-256 * <p> * Reference values by: * <li> https://github.com/ircmaxell/PHP-PasswordLib/blob/master/test/Data/Vectors/pbkdf2-draft-josefsson-sha256.test-vectors * <li> http://www.neurotechnics.com/tools/xpassword */ @Test public void testDigestSha256() throws Exception { this.performTest(ALGORITHM_SCRAM_SHA_256, "password", "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b", "salt", 1); this.performTest(ALGORITHM_SCRAM_SHA_256, "password", "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43", "salt", 2); this.performTest(ALGORITHM_SCRAM_SHA_256, "password", "ad35240ac683febfaf3cd49d845473fbbbaa2437f5f82d5a415ae00ac76c6bfc", "salt", 3); this.performTest(ALGORITHM_SCRAM_SHA_256, "password", "632c2812e46d4604102ba7618e9d6d7d2f8128f6266b4a03264d2a0460b7dcb3", "salt", 1000); this.performTest(ALGORITHM_SCRAM_SHA_256, "This is little longer password, used for testing of SCRAM digest password hashing.", "e1b96e82421b2fc57ff462eb2f001a8a436ac88f70f46267d8c171afbf55ad0f", "6e81991f001e5c568b05384d50b4159badf9f3b3e288d1e222c5a7cf599c1974", 1000); this.performTest(ALGORITHM_SCRAM_SHA_256, "a\u0438\u4F60\uD83C\uDCA1", "99376f7a5a7b1ff232e148a7b6d6d5c07520cb79c32cfb744b38e3458c8380bf", "\uD83C\uDCA1\u4F60\u0438a", 1000); } @Test public void testNormalization(){ byte[] normalized; normalized = ScramDigestPasswordImpl.getNormalizedPasswordBytes("Password\uFFFF".toCharArray()); Assert.assertArrayEquals("Password\uFFFF".getBytes(StandardCharsets.UTF_8), normalized); normalized = ScramDigestPasswordImpl.getNormalizedPasswordBytes("a\u0041\u030Ab".toCharArray()); Assert.assertArrayEquals("a\u00C5b".getBytes(StandardCharsets.UTF_8), normalized); } @Test public void testScramDigestSHA1() throws Exception { this.performTest(ALGORITHM_SCRAM_SHA_1, "password".toCharArray(), "cRseQyJpnuPGn3e6d6u6JdJWk+0".toCharArray(), "+Z/znnNOKWUsBaCU".toCharArray(), 6400); this.performTest(ALGORITHM_SCRAM_SHA_1, "password".toCharArray(), "eE8dq1f1P1hZm21lfzsr3CMbiEA".toCharArray(), "Y0zp/R/DeO89h/De".toCharArray(), 8000); } @Test public void testScramDigestSHA256() throws Exception { this.performTest(ALGORITHM_SCRAM_SHA_256, "password".toCharArray(), "5GcjEbRaUIIci1r6NAMdI9OPZbxl9S5CFR6la9CHXYc".toCharArray(), "+Z/znnNOKWUsBaCU".toCharArray(), 6400); this.performTest(ALGORITHM_SCRAM_SHA_256, "password".toCharArray(), "NfkaDFMzn/yHr/HTv7KEFZqaONo6psRu5LBBFLEbZ+o".toCharArray(), "Y0zp/R/DeO89h/De".toCharArray(), 8000); } private void performTest(final String algorithm, String password, String hexDigest, String salt, final int iterationCount) throws Exception { performTest(algorithm, password.toCharArray(), CodePointIterator.ofString(hexDigest).hexDecode().drain(), salt.getBytes(StandardCharsets.UTF_8), iterationCount); } private void performTest(final String algorithm, char[] password, final char[] base64Digest, final char[] base64Salt, final int iterationCount) throws Exception { byte[] decodedDigest = CodePointIterator.ofChars(base64Digest).base64Decode(Base64Alphabet.STANDARD, false).drain(); byte[] decodedSalt = CodePointIterator.ofChars(base64Salt).base64Decode(Base64Alphabet.STANDARD, false).drain(); performTest(algorithm, password, decodedDigest, decodedSalt, iterationCount); } private void performTest(final String algorithm, char[] password, final byte[] decodedDigest, final byte[] decodedSalt, final int iterationCount) throws Exception { // use an encryptable spec to hash the password and compare the results with the expected hash. PasswordFactory factory = PasswordFactory.getInstance(algorithm); IteratedSaltedPasswordAlgorithmSpec algoSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, decodedSalt); EncryptablePasswordSpec encSpec = new EncryptablePasswordSpec(password, algoSpec); ScramDigestPassword scramPassword = (ScramDigestPassword) factory.generatePassword(encSpec); validatePassword(factory, password, scramPassword, decodedDigest, decodedSalt, iterationCount); // check the password -> key spec conversion. assertTrue("Convertable to key spec", factory.convertibleToKeySpec(scramPassword, IteratedSaltedHashPasswordSpec.class)); IteratedSaltedHashPasswordSpec sdps = factory.getKeySpec(scramPassword, IteratedSaltedHashPasswordSpec.class); assertTrue("Salt correctly passed", Arrays.equals(decodedSalt, sdps.getSalt())); assertTrue("Iteration count correctly passed", iterationCount == sdps.getIterationCount()); assertTrue("Digest correctly generated", Arrays.equals(decodedDigest, sdps.getHash())); // use the scram digest spec to build a password without hashing it (i.e., use the pre digested hash) sdps = new IteratedSaltedHashPasswordSpec(decodedDigest, decodedSalt, iterationCount); scramPassword = (ScramDigestPassword) factory.generatePassword(sdps); validatePassword(factory, password, scramPassword, decodedDigest, decodedSalt, iterationCount); } private void validatePassword(final PasswordFactory factory, char[] password, final ScramDigestPassword sdp, final byte[] decodedDigest, final byte[] decodedSalt, final int iterationCount) throws Exception { assertTrue("Salt correctly passed", Arrays.equals(decodedSalt, sdp.getSalt())); assertEquals("Iteration count correctly passed", iterationCount, sdp.getIterationCount()); assertTrue("Digest correctly generated", Arrays.equals(decodedDigest, sdp.getDigest())); assertTrue("Password validation", factory.verify(sdp, password)); assertFalse("Bad password rejection", factory.verify(sdp, "badpassword".toCharArray())); } }