Oh, and I forgot to mention, cryptographically speaking, it is usually better to use something like a MAC instead of concatenating a private and public salt, so DefaultHasher's 'baseSalt' property will likely become 'privateHashKey' (or something similar) used to create a MAC of the normally-hashed password (value/salt/iterations).
That is, something similar to: salt = random bytes output = MAC(Hash(plaintextValue, salt, iterations)) I think that covers my thought process up to now, reflecting the current Hasher state of development. HTH, Les On Fri, Jun 3, 2011 at 10:23 AM, Les Hazlewood <[email protected]> wrote: > A very brief background: I created it while thinking of a way to > cleanly support the PasswordService concept. The Hasher was my idea > of supporting this as an implementation strategy - not necessarily > something people would use regularly. It could turn out that it is a > first-class citizen in Shiro's hashing support, but I haven't thought > about how to position it beyond supporting the PasswordService. > Ideas/thoughts are welcome! > > On Fri, Jun 3, 2011 at 10:21 AM, Les Hazlewood <[email protected]> wrote: >> Thanks for this Maria and Kalle. >> >> Just a quick note though - the Hasher stuff I wrote is still in flux - >> it's not necessarily scoped out to how it should/could be before being >> 'releasable'. Any thoughts or feedback is appreciated! >> >> Cheers, >> >> Les >> >> On Fri, Jun 3, 2011 at 7:52 AM, <[email protected]> wrote: >>> Author: kaosko >>> Date: Fri Jun 3 14:52:04 2011 >>> New Revision: 1131059 >>> >>> URL: http://svn.apache.org/viewvc?rev=1131059&view=rev >>> Log: >>> FIXED - issue SHIRO-302: DefaultHasher does not generate random salt >>> https://issues.apache.org/jira/browse/SHIRO-302 >>> - apply Maria Jurcovicova's patch as is, including unit tests >>> >>> Added: >>> shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/ >>> >>> shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java >>> Modified: >>> >>> shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java >>> >>> Modified: >>> shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java >>> URL: >>> http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java?rev=1131059&r1=1131058&r2=1131059&view=diff >>> ============================================================================== >>> --- >>> shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java >>> (original) >>> +++ >>> shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java >>> Fri Jun 3 14:52:04 2011 >>> @@ -153,7 +153,7 @@ public class DefaultHasher implements Co >>> publicSaltBytes = null; >>> } >>> if (publicSaltBytes == null) { >>> - getRandomNumberGenerator().nextBytes().getBytes(); >>> + publicSaltBytes = >>> getRandomNumberGenerator().nextBytes().getBytes(); >>> } >>> >>> String algorithmName = getHashAlgorithmName(); >>> >>> Added: >>> shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java >>> URL: >>> http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java?rev=1131059&view=auto >>> ============================================================================== >>> --- >>> shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java >>> (added) >>> +++ >>> shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java >>> Fri Jun 3 14:52:04 2011 >>> @@ -0,0 +1,154 @@ >>> +package org.apache.shiro.crypto.hash; >>> + >>> +import java.util.Arrays; >>> + >>> +import junit.framework.TestCase; >>> + >>> +import org.apache.shiro.crypto.SecureRandomNumberGenerator; >>> +import org.apache.shiro.util.ByteSource; >>> +import org.junit.Test; >>> + >>> +/** >>> + * Test for {@link DefaultHasher} class. >>> + * >>> + */ >>> +public class DefaultHasherTest { >>> + >>> + /** >>> + * If the same string is hashed twice and no salt was supplied, >>> hashed >>> + * result should be different in each case. >>> + */ >>> + @Test >>> + public void testOnlyRandomSaltRandomness() { >>> + Hasher hasher = createHasher(); >>> + >>> + HashResponse firstHash = hashString(hasher, "password"); >>> + HashResponse secondHash = hashString(hasher, "password"); >>> + >>> + assertNotEquals(firstHash.getHash().toBase64(), >>> secondHash.getHash().toBase64()); >>> + } >>> + >>> + /** >>> + * If a string is hashed and no salt was supplied, random salt is >>> generated. >>> + * Hash of the same string with generated random salt should return >>> the >>> + * same result. >>> + */ >>> + @Test >>> + public void testOnlyRandomSaltReturn() { >>> + Hasher hasher = createHasher(); >>> + >>> + HashResponse firstHash = hashString(hasher, "password"); >>> + HashResponse secondHash = hashString(hasher, "password", >>> firstHash.getSalt().getBytes()); >>> + >>> + TestCase.assertEquals(firstHash.getHash().toBase64(), >>> secondHash.getHash().toBase64()); >>> + } >>> + >>> + /** >>> + * Two different strings hashed with the same salt should result in >>> two different >>> + * hashes. >>> + */ >>> + @Test >>> + public void testOnlyRandomSaltHash() { >>> + Hasher hasher = createHasher(); >>> + >>> + HashResponse firstHash = hashString(hasher, "password"); >>> + HashResponse secondHash = hashString(hasher, "password2", >>> firstHash.getSalt().getBytes()); >>> + >>> + assertNotEquals(firstHash.getHash().toBase64(), >>> secondHash.getHash().toBase64()); >>> + } >>> + >>> + /** >>> + * If the same string is hashed twice and only base salt was >>> supplied, hashed >>> + * result should be different in each case. >>> + */ >>> + @Test >>> + public void testBothSaltsRandomness() { >>> + Hasher hasher = createHasherWithSalt(); >>> + >>> + HashResponse firstHash = hashString(hasher, "password"); >>> + HashResponse secondHash = hashString(hasher, "password"); >>> + >>> + assertNotEquals(firstHash.getHash().toBase64(), >>> secondHash.getHash().toBase64()); >>> + } >>> + >>> + /** >>> + * If a string is hashed and only base salt was supplied, random >>> salt is generated. >>> + * Hash of the same string with generated random salt should return >>> the >>> + * same result. >>> + */ >>> + @Test >>> + public void testBothSaltsReturn() { >>> + Hasher hasher = createHasherWithSalt(); >>> + >>> + HashResponse firstHash = hashString(hasher, "password"); >>> + HashResponse secondHash = hashString(hasher, "password", >>> firstHash.getSalt().getBytes()); >>> + >>> + TestCase.assertEquals(firstHash.getHash().toBase64(), >>> secondHash.getHash().toBase64()); >>> + } >>> + >>> + /** >>> + * Two different strings hashed with the same salt should result in >>> two different >>> + * hashes. >>> + */ >>> + @Test >>> + public void testBothSaltsHash() { >>> + Hasher hasher = createHasherWithSalt(); >>> + >>> + HashResponse firstHash = hashString(hasher, "password"); >>> + HashResponse secondHash = hashString(hasher, "password2", >>> firstHash.getSalt().getBytes()); >>> + >>> + assertNotEquals(firstHash.getHash().toBase64(), >>> secondHash.getHash().toBase64()); >>> + } >>> + >>> + /** >>> + * Hash result is different if the base salt is added. >>> + */ >>> + @Test >>> + public void testBaseSaltChangesResult() { >>> + Hasher saltedHasher = createHasherWithSalt(); >>> + Hasher hasher = createHasher(); >>> + >>> + HashResponse firstHash = >>> hashStringPredictable(saltedHasher, "password"); >>> + HashResponse secondHash = hashStringPredictable(hasher, >>> "password"); >>> + >>> + assertNotEquals(firstHash.getHash().toBase64(), >>> secondHash.getHash().toBase64()); >>> + } >>> + >>> + protected HashResponse hashString(Hasher hasher, String string) { >>> + return hasher.computeHash(new >>> SimpleHashRequest(ByteSource.Util.bytes(string))); >>> + } >>> + >>> + protected HashResponse hashString(Hasher hasher, String string, >>> byte[] salt) { >>> + return hasher.computeHash(new >>> SimpleHashRequest(ByteSource.Util.bytes(string), >>> ByteSource.Util.bytes(salt))); >>> + } >>> + >>> + private HashResponse hashStringPredictable(Hasher hasher, String >>> string) { >>> + byte[] salt = new byte[20]; >>> + Arrays.fill(salt, (byte) 2); >>> + return hasher.computeHash(new >>> SimpleHashRequest(ByteSource.Util.bytes(string), >>> ByteSource.Util.bytes(salt))); >>> + } >>> + >>> + private Hasher createHasher() { >>> + return new DefaultHasher(); >>> + } >>> + >>> + private Hasher createHasherWithSalt() { >>> + DefaultHasher defaultHasher = new DefaultHasher(); >>> + defaultHasher.setBaseSalt((new >>> SecureRandomNumberGenerator()).nextBytes().getBytes()); >>> + >>> + return defaultHasher; >>> + } >>> + >>> + private void assertNotEquals(String str1, String str2) { >>> + boolean equals = equals(str1, str2); >>> + if (equals) >>> + TestCase.fail("Strings are supposed to be >>> different."); >>> + } >>> + >>> + protected boolean equals(String str1, String str2) { >>> + if (str1 == null) >>> + return str2 == null; >>> + >>> + return str1.equals(str2); >>> + } >>> +} >> >
