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);
>>> +       }
>>> +}
>>
>

Reply via email to