public interface IDigest
{
public byte [] process ( byte [] data);
public int getSize ();
}
public class SHA256 implements IDigest
{
private SHA256Digest m_SHA256 = new SHA256Digest ();
@Override
public byte [] process ( byte [] data)
{
m_SHA256.reset ();
m_SHA256.update (data, 0, data.length);
byte [] result = new byte [m_SHA256.getDigestSize ()];
m_SHA256.doFinal (result, 0);
return result;
}
@Override
public int getSize ()
{
return m_SHA256.getDigestSize ();
}
}
public class SlowHasher
{
private final static int BITS_IN_BYTE = 8;
private static final int [] s_primeIndices = new int [] {7, 11, 17, 23, 31, 41, 47, 53, 61};
/ **
* this method hashes password 0x50000 times adding round salt each round
*
* param digest
* param password
* return
* /
public byte [] calculateSlowHash (IDigest digest, String password, byte [] salt)
{
int roundSaltSize = digest.getSize () / BITS_IN_BYTE;
byte [] bPasswd = password.getBytes ();
byte [] toHash = new byte [bPasswd.length + salt.length];
/ *
* composing array of bytes that will be hashed
* /
System.arraycopy (salt, 0, toHash, 0, salt.length);
System.arraycopy (bPasswd, 0, toHash, salt.length, bPasswd.length);
byte [] res = digest.process (toHash);
byte [] temp = new byte [res.length + roundSaltSize];
for ( int i = 0; i <0x50000; i ++)
{
System.arraycopy (res, 0, temp, 0, res.length);
/ ***
* calculating salt
* /
for ( int j = 0; j <roundSaltSize; j ++)
{
int btmp = res [s_primeIndices [j]] & 0xFF;
for ( int k = 1; k <BITS_IN_BYTE; k ++)
{
btmp = ror ((btmp + (res [ror (btmp, k)% res.length] & 0xFF))% 256, BITS_IN_BYTE-k);
}
temp [res.length + j] = ( byte ) btmp;
}
res = digest.process (temp);
}
return res;
}
/ **
* rotate bits right treating input as byte
* param value 0 <= value <= 255
* param n number of bits to shift
* return
* /
public static int ror ( int value , int n)
{
return (( value >> (n% BITS_IN_BYTE)) | (( value << (8 - (n% BITS_IN_BYTE))) & 0xFF));
}
}
People who are not familiar with java I will say right away that you should not be afraid of the many inserts& 0xFF
. This is just converting signed byte to unsigned by converting it to an int and applying such a bitmask. And all because there are no unsigned types in java! Totally! Well, okay, this is not deadly.
All the beauty here is the formation of round salt, so the focus will be on it.
A little bit about variables:
- res is an array of 32 bytes for SHA-256 or 64 bytes for SHA-512. It stores the hash result.
- roundSaltSize is the size of the round salt. 4 bytes for SHA-256 and 8 bytes for SHA-512
- temp is an array of res array size elements plus the size of the round salt roundSaltSize
- s_primeIndices is an array of indices of elements in the res array, starting from which we will calculate the corresponding byte of the round salt. That is, we will start the computation of the first byte of the round salt for SHA-256 with res [7], the second with res [11], etc. All indexes will be used for SHA-512
- btemp - byte, which after a series of clever transformations will take its place in the round salt
Another remark before we proceed to the explanation of the formation of the algorithm of the round salt. The whole algorithm is not the result of any scientific research and tests. It was created to generate a value that would be dependent on some bytes of the resulting hash and would help to slow down and complicate it. Well, now the description:
- At the beginning, the main salt and password are collected in the toHash array.
- The toHash array is hashed and the result is stored in the res array. Everything, about the main salt and password forgot
- Run a long loop at 0x50000 iterations
- Next we work with the temp array, into which we copy the res array. At the end of the temp array, there are still 4 or 8 bytes for salt. We have to fill them
- For each of the round salt bytes:
- We remember in btmp an element that is located in one of the indices (7, 11, 17 ...) depending on the number of the current byte (j)
- Then 7 (k) times we do the following:
- Take the remainder of the btmp division, in which the bits are shifted to the right by k positions, by the length of the res array
- the previous number is used as an index for the array res, we pull out the number at this index
- btmp assign this number folded with btmp modulo 256 and scrolled to the right by 8-k bits
- We place btmp in its place after the hash in the temp array
- Hash temp
The call to this method is as follows:
byte [] salt = new byte [16];
new SecureRandom (). nextBytes (salt); // generate random 16byte salt
byte [] hashedPassword = new SlowHasher (). calculateSlowHash ( new SHA256 (), password, salt);
The result is a hash (hash (hash + ranoid salt) + round salt) ... which can be used as a 256-bit key to encrypt important information for AES-256, for example.
On my machine (C2D 2.6) the generation of one hash accounts for about 0.25 seconds, which I consider acceptable for use. in their projects. You can increase the number of rounds and then the time will grow accordingly.
If it’s interesting for a respected public, I can tell you about other applied aspects of working with the BouncyCastle library like symmetric / asymmetric encryption, certificate generation, etc.
UPD:
In the comments indicated a link to the dock, in which this kind of scheme takes even greater scope
Source: https://habr.com/ru/post/100138/