How do I generate a SALT in Java for Salted-Hash?

后端 未结 4 2185
星月不相逢
星月不相逢 2020-11-29 17:09

I\'ve been looking around and the closest answer is : How to generate a random alpha-numeric string?

I want to follow this workflow according to this CrackStation tut

相关标签:
4条回答
  • 2020-11-29 17:17

    Here's my solution, i would love anyone's opinion on this, it's simple for beginners

    import java.security.NoSuchAlgorithmException;
    import java.security.SecureRandom;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.KeySpec;
    import java.util.Base64;
    import java.util.Base64.Encoder;
    import java.util.Scanner;
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.PBEKeySpec;
    
    public class Cryptography {
    
        public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
            Encoder encoder = Base64.getUrlEncoder().withoutPadding();
            System.out.print("Password: ");
            String strPassword = new Scanner(System.in).nextLine();
            byte[] bSalt = Salt();
            String strSalt = encoder.encodeToString(bSalt); // Byte to String
            System.out.println("Salt: " + strSalt);
            System.out.println("String to be hashed: " + strPassword + strSalt);
            String strHash = encoder.encodeToString(Hash(strPassword, bSalt)); // Byte to String
            System.out.println("Hashed value (Password + Salt value): " + strHash);
        }
    
        private static byte[] Salt() {
            SecureRandom random = new SecureRandom();
            byte salt[] = new byte[6];
            random.nextBytes(salt);
            return salt;
        }
    
        private static byte[] Hash(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
            KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            byte[] hash = factory.generateSecret(spec).getEncoded();
            return hash;
        }
    
    }
    

    You can validate by just decoding the strSalt and using the same hash method:

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
            Encoder encoder = Base64.getUrlEncoder().withoutPadding();
            Decoder decoder = Base64.getUrlDecoder();
            System.out.print("Password: ");
            String strPassword = new Scanner(System.in).nextLine();
            String strSalt = "Your Salt String Here";
            byte[] bSalt = decoder.decode(strSalt); // String to Byte
            System.out.println("Salt: " + strSalt);
            System.out.println("String to be hashed: " + strPassword + strSalt);
            String strHash = encoder.encodeToString(Hash(strPassword, bSalt)); // Byte to String
            System.out.println("Hashed value (Password + Salt value): " + strHash);
        }
    
    0 讨论(0)
  • 2020-11-29 17:28

    Another version using SHA-3, I am using bouncycastle:

    The interface:

    public interface IPasswords {
    
        /**
         * Generates a random salt.
         *
         * @return a byte array with a 64 byte length salt.
         */
        byte[] getSalt64();
    
        /**
         * Generates a random salt
         *
         * @return a byte array with a 32 byte length salt.
         */
        byte[] getSalt32();
    
        /**
         * Generates a new salt, minimum must be 32 bytes long, 64 bytes even better.
         *
         * @param size the size of the salt
         * @return a random salt.
         */
        byte[] getSalt(final int size);
    
        /**
         * Generates a new hashed password
         *
         * @param password to be hashed
         * @param salt the randomly generated salt
         * @return a hashed password
         */
        byte[] hash(final String password, final byte[] salt);
    
        /**
         * Expected password
         *
         * @param password to be verified
         * @param salt the generated salt (coming from database)
         * @param hash the generated hash (coming from database)
         * @return true if password matches, false otherwise
         */
        boolean isExpectedPassword(final String password, final byte[] salt, final byte[] hash);
    
        /**
         * Generates a random password
         *
         * @param length desired password length
         * @return a random password
         */
        String generateRandomPassword(final int length);
    }
    

    The implementation:

    import org.apache.commons.lang3.ArrayUtils;
    import org.apache.commons.lang3.Validate;
    import org.apache.log4j.Logger;
    import org.bouncycastle.jcajce.provider.digest.SHA3;
    
    import java.io.Serializable;
    import java.io.UnsupportedEncodingException;
    import java.security.SecureRandom;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Random;
    
    public final class Passwords implements IPasswords, Serializable {
    
        /*serialVersionUID*/
        private static final long serialVersionUID = 8036397974428641579L;
        private static final Logger LOGGER = Logger.getLogger(Passwords.class);
        private static final Random RANDOM = new SecureRandom();
        private static final int DEFAULT_SIZE = 64;
        private static final char[] symbols;
    
        static {
                final StringBuilder tmp = new StringBuilder();
                for (char ch = '0'; ch <= '9'; ++ch) {
                        tmp.append(ch);
                }
                for (char ch = 'a'; ch <= 'z'; ++ch) {
                        tmp.append(ch);
                }
                symbols = tmp.toString().toCharArray();
        }
    
        @Override public byte[] getSalt64() {
                return getSalt(DEFAULT_SIZE);
        }
    
        @Override public byte[] getSalt32() {
                return getSalt(32);
        }
    
        @Override public byte[] getSalt(int size) {
                final byte[] salt;
                if (size < 32) {
                        final String message = String.format("Size < 32, using default of: %d", DEFAULT_SIZE);
                        LOGGER.warn(message);
                        salt = new byte[DEFAULT_SIZE];
                } else {
                        salt = new byte[size];
                }
                RANDOM.nextBytes(salt);
                return salt;
        }
    
        @Override public byte[] hash(String password, byte[] salt) {
    
                Validate.notNull(password, "Password must not be null");
                Validate.notNull(salt, "Salt must not be null");
    
                try {
                        final byte[] passwordBytes = password.getBytes("UTF-8");
                        final byte[] all = ArrayUtils.addAll(passwordBytes, salt);
                        SHA3.DigestSHA3 md = new SHA3.Digest512();
                        md.update(all);
                        return md.digest();
                } catch (UnsupportedEncodingException e) {
                        final String message = String
                                .format("Caught UnsupportedEncodingException e: <%s>", e.getMessage());
                        LOGGER.error(message);
                }
                return new byte[0];
        }
    
        @Override public boolean isExpectedPassword(final String password, final byte[] salt, final byte[] hash) {
    
                Validate.notNull(password, "Password must not be null");
                Validate.notNull(salt, "Salt must not be null");
                Validate.notNull(hash, "Hash must not be null");
    
                try {
                        final byte[] passwordBytes = password.getBytes("UTF-8");
                        final byte[] all = ArrayUtils.addAll(passwordBytes, salt);
    
                        SHA3.DigestSHA3 md = new SHA3.Digest512();
                        md.update(all);
                        final byte[] digest = md.digest();
                        return Arrays.equals(digest, hash);
                }catch(UnsupportedEncodingException e){
                        final String message =
                                String.format("Caught UnsupportedEncodingException e: <%s>", e.getMessage());
                        LOGGER.error(message);
                }
                return false;
    
    
        }
    
        @Override public String generateRandomPassword(final int length) {
    
                if (length < 1) {
                        throw new IllegalArgumentException("length must be greater than 0");
                }
    
                final char[] buf = new char[length];
                for (int idx = 0; idx < buf.length; ++idx) {
                        buf[idx] = symbols[RANDOM.nextInt(symbols.length)];
                }
                return shuffle(new String(buf));
        }
    
    
        private String shuffle(final String input){
                final List<Character> characters = new ArrayList<Character>();
                for(char c:input.toCharArray()){
                        characters.add(c);
                }
                final StringBuilder output = new StringBuilder(input.length());
                while(characters.size()!=0){
                        int randPicker = (int)(Math.random()*characters.size());
                        output.append(characters.remove(randPicker));
                }
                return output.toString();
        }
    }
    

    The test cases:

    public class PasswordsTest {
    
        private static final Logger LOGGER = Logger.getLogger(PasswordsTest.class);
    
        @Before
        public void setup(){
                BasicConfigurator.configure();
        }
    
        @Test
        public void testGeSalt() throws Exception {
    
                IPasswords passwords = new Passwords();
                final byte[] bytes = passwords.getSalt(0);
                int arrayLength = bytes.length;
    
                assertThat("Expected length is", arrayLength, is(64));
        }
    
        @Test
        public void testGeSalt32() throws Exception {
                IPasswords passwords = new Passwords();
                final byte[] bytes = passwords.getSalt32();
                int arrayLength = bytes.length;
                assertThat("Expected length is", arrayLength, is(32));
        }
    
        @Test
        public void testGeSalt64() throws Exception {
                IPasswords passwords = new Passwords();
                final byte[] bytes = passwords.getSalt64();
                int arrayLength = bytes.length;
                assertThat("Expected length is", arrayLength, is(64));
        }
    
        @Test
        public void testHash() throws Exception {
                IPasswords passwords = new Passwords();
                final byte[] hash = passwords.hash("holacomoestas", passwords.getSalt64());
                assertThat("Array is not null", hash, Matchers.notNullValue());
        }
    
    
        @Test
        public void testSHA3() throws UnsupportedEncodingException {
                SHA3.DigestSHA3 md = new SHA3.Digest256();
                md.update("holasa".getBytes("UTF-8"));
                final byte[] digest = md.digest();
                 assertThat("expected digest is:",digest,Matchers.notNullValue());
        }
    
        @Test
        public void testIsExpectedPasswordIncorrect() throws Exception {
    
                String password = "givemebeer";
                IPasswords passwords = new Passwords();
    
                final byte[] salt64 = passwords.getSalt64();
                final byte[] hash = passwords.hash(password, salt64);
                //The salt and the hash go to database.
    
                final boolean isPasswordCorrect = passwords.isExpectedPassword("jfjdsjfsd", salt64, hash);
    
                assertThat("Password is not correct", isPasswordCorrect, is(false));
    
        }
    
        @Test
        public void testIsExpectedPasswordCorrect() throws Exception {
                String password = "givemebeer";
                IPasswords passwords = new Passwords();
                final byte[] salt64 = passwords.getSalt64();
                final byte[] hash = passwords.hash(password, salt64);
                //The salt and the hash go to database.
                final boolean isPasswordCorrect = passwords.isExpectedPassword("givemebeer", salt64, hash);
                assertThat("Password is correct", isPasswordCorrect, is(true));
        }
    
        @Test
        public void testGenerateRandomPassword() throws Exception {
                IPasswords passwords = new Passwords();
                final String randomPassword = passwords.generateRandomPassword(10);
                LOGGER.info(randomPassword);
                assertThat("Random password is not null", randomPassword, Matchers.notNullValue());
        }
    }
    

    pom.xml (only dependencies):

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.1.1</version>
            <scope>test</scope>
        </dependency>
    
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-all</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>
    
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.51</version>
            <type>jar</type>
        </dependency>
    
    
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>
    
    
    </dependencies>
    
    0 讨论(0)
  • 2020-11-29 17:36

    Inspired from this post and that post, I use this code to generate and verify hashed salted passwords. It only uses JDK provided classes, no external dependency.

    The process is:

    • you create a salt with getNextSalt
    • you ask the user his password and use the hash method to generate a salted and hashed password. The method returns a byte[] which you can save as is in a database with the salt
    • to authenticate a user, you ask his password, retrieve the salt and hashed password from the database and use the isExpectedPassword method to check that the details match
    /**
     * A utility class to hash passwords and check passwords vs hashed values. It uses a combination of hashing and unique
     * salt. The algorithm used is PBKDF2WithHmacSHA1 which, although not the best for hashing password (vs. bcrypt) is
     * still considered robust and <a href="https://security.stackexchange.com/a/6415/12614"> recommended by NIST </a>.
     * The hashed value has 256 bits.
     */
    public class Passwords {
    
      private static final Random RANDOM = new SecureRandom();
      private static final int ITERATIONS = 10000;
      private static final int KEY_LENGTH = 256;
    
      /**
       * static utility class
       */
      private Passwords() { }
    
      /**
       * Returns a random salt to be used to hash a password.
       *
       * @return a 16 bytes random salt
       */
      public static byte[] getNextSalt() {
        byte[] salt = new byte[16];
        RANDOM.nextBytes(salt);
        return salt;
      }
    
      /**
       * Returns a salted and hashed password using the provided hash.<br>
       * Note - side effect: the password is destroyed (the char[] is filled with zeros)
       *
       * @param password the password to be hashed
       * @param salt     a 16 bytes salt, ideally obtained with the getNextSalt method
       *
       * @return the hashed password with a pinch of salt
       */
      public static byte[] hash(char[] password, byte[] salt) {
        PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
        Arrays.fill(password, Character.MIN_VALUE);
        try {
          SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
          return skf.generateSecret(spec).getEncoded();
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
          throw new AssertionError("Error while hashing a password: " + e.getMessage(), e);
        } finally {
          spec.clearPassword();
        }
      }
    
      /**
       * Returns true if the given password and salt match the hashed value, false otherwise.<br>
       * Note - side effect: the password is destroyed (the char[] is filled with zeros)
       *
       * @param password     the password to check
       * @param salt         the salt used to hash the password
       * @param expectedHash the expected hashed value of the password
       *
       * @return true if the given password and salt match the hashed value, false otherwise
       */
      public static boolean isExpectedPassword(char[] password, byte[] salt, byte[] expectedHash) {
        byte[] pwdHash = hash(password, salt);
        Arrays.fill(password, Character.MIN_VALUE);
        if (pwdHash.length != expectedHash.length) return false;
        for (int i = 0; i < pwdHash.length; i++) {
          if (pwdHash[i] != expectedHash[i]) return false;
        }
        return true;
      }
    
      /**
       * Generates a random password of a given length, using letters and digits.
       *
       * @param length the length of the password
       *
       * @return a random password
       */
      public static String generateRandomPassword(int length) {
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
          int c = RANDOM.nextInt(62);
          if (c <= 9) {
            sb.append(String.valueOf(c));
          } else if (c < 36) {
            sb.append((char) ('a' + c - 10));
          } else {
            sb.append((char) ('A' + c - 36));
          }
        }
        return sb.toString();
      }
    }
    
    0 讨论(0)
  • 2020-11-29 17:36

    You were right regarding how you want to generate salt i.e. its nothing but a random number. For this particular case it would protect your system from possible Dictionary attacks. Now, for the second problem what you could do is instead of using UTF-8 encoding you may want to use Base64. Here, is a sample for generating a hash. I am using Apache Common Codecs for doing the base64 encoding you may select one of your own

    public byte[] generateSalt() {
            SecureRandom random = new SecureRandom();
            byte bytes[] = new byte[20];
            random.nextBytes(bytes);
            return bytes;
        }
    
    public String bytetoString(byte[] input) {
            return org.apache.commons.codec.binary.Base64.encodeBase64String(input);
        }
    
    public byte[] getHashWithSalt(String input, HashingTechqniue technique, byte[] salt) throws NoSuchAlgorithmException {
            MessageDigest digest = MessageDigest.getInstance(technique.value);
            digest.reset();
            digest.update(salt);
            byte[] hashedBytes = digest.digest(stringToByte(input));
            return hashedBytes;
        }
    public byte[] stringToByte(String input) {
            if (Base64.isBase64(input)) {
                return Base64.decodeBase64(input);
    
            } else {
                return Base64.encodeBase64(input.getBytes());
            }
        }
    

    Here is some additional reference of the standard practice in password hashing directly from OWASP

    0 讨论(0)
提交回复
热议问题