如何在Java中为SaltedHash生成SALT?

如何在Java中为SaltedHash生成SALT?,java,security,encryption,hash,salt,Java,Security,Encryption,Hash,Salt,我环顾四周,最接近的答案是: 我希望按照以下步骤遵循此工作流: 存储密码 使用CSPRNG生成长随机盐 在密码前添加salt,并使用标准加密哈希函数(如SHA256)对其进行哈希 在用户的数据库记录中保存salt和hash 验证密码 从数据库中检索用户的salt和hash 将salt前置到给定密码,并使用相同的散列函数对其进行散列 将给定密码的哈希值与数据库中的哈希值进行比较。如果它们匹配,则密码是正确的。否则,密码不正确 我不知道如何制造盐。我知道了如何使用MessageDiges

我环顾四周,最接近的答案是:

我希望按照以下步骤遵循此工作流:

存储密码

  • 使用CSPRNG生成长随机盐

  • 在密码前添加salt,并使用标准加密哈希函数(如SHA256)对其进行哈希

  • 在用户的数据库记录中保存salt和hash

  • 验证密码

  • 从数据库中检索用户的salt和hash

  • 将salt前置到给定密码,并使用相同的散列函数对其进行散列

  • 将给定密码的哈希值与数据库中的哈希值进行比较。如果它们匹配,则密码是正确的。否则,密码不正确

  • 我不知道如何制造盐。我知道了如何使用MessageDigest生成哈希。我尝试使用SecureRandom,但nextByte方法会产生乱码


    编辑:我不知道该选择哪个答案,它们对我来说太复杂了,我决定使用jBCrypt;jBCript易于使用,在幕后完成所有复杂的工作。因此,我将让社区投票选出最佳答案。

    关于如何生产盐,你是对的,也就是说,它只是一个随机数。对于这种特殊情况,它将保护您的系统免受可能的字典攻击。现在,对于第二个问题,您可以不使用UTF-8编码,而是使用Base64。这里是一个生成哈希的示例。我使用Apache通用编解码器进行base64编码,您可以选择自己的一种

    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());
            }
        }
    
    下面是直接从

    中获得的密码哈希标准实践的一些附加参考,灵感来自和,我使用此代码生成并验证哈希加密密码。它只使用JDK提供的类,没有外部依赖性

    这个过程是:

    • 您可以使用
      getNextSalt
    • 您向用户询问其密码,并使用
      hash
      方法生成一个经过加密和哈希处理的密码。该方法返回一个
      字节[]
      ,您可以将其保存为salt数据库中的原样
    • 要验证用户身份,您需要询问他的密码,从数据库中检索salt和hash密码,并使用
      isExpectedPassword
      方法检查详细信息是否匹配
    /**
    *用于哈希密码和检查密码与哈希值的实用程序类。它结合使用哈希和唯一性
    *盐。使用的算法是PBKDF2WithHmacSHA1,虽然不是哈希密码的最佳算法(与bcrypt相比),但是
    *仍然被认为是健壮和可靠的。
    *哈希值有256位。
    */
    公共类密码{
    private static final Random=new SecureRandom();
    私有静态最终整数迭代=10000;
    私有静态最终整数密钥长度=256;
    /**
    *静态效用类
    */
    私有密码(){}
    /**
    *返回用于散列密码的随机salt。
    *
    *@返回一个16字节的随机盐
    */
    公共静态字节[]getNextSalt(){
    字节[]salt=新字节[16];
    随机。下一个字节(盐);
    返盐;
    }
    /**
    *使用提供的哈希值返回经过加密和哈希处理的密码。
    *注意-副作用:密码被销毁(字符[]中填充了零) * *@param password要散列的密码 *@param salt一个16字节的salt,理想情况下使用getNextSalt方法获得 * *@用一撮盐返回哈希密码 */ 公共静态字节[]散列(字符[]密码,字节[]盐){ PBEKeySpec spec=新的PBEKeySpec(密码、salt、迭代次数、密钥长度); 数组.fill(密码、字符.MIN_值); 试一试{ SecretKeyFactory skf=SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”); 返回skf.generateScret(spec.getEncoded(); }捕获(NoSuchAlgorithmException | InvalidKeySpece异常){ 抛出新的断言错误(“哈希密码时出错:+e.getMessage(),e); }最后{ 规范clearPassword(); } } /** *如果给定的密码和salt与哈希值匹配,则返回true,否则返回false。
    *注意-副作用:密码被销毁(字符[]中填充了零) * *@param password要检查的密码 *@param salt用于哈希密码的salt *@param expectedHash密码的预期哈希值 * *@如果给定的密码和salt与哈希值匹配,则返回true,否则返回false */ 公共静态布尔isExpectedPassword(char[]password,byte[]salt,byte[]expectedHash){ 字节[]pwdHash=散列(密码,salt); 数组.fill(密码、字符.MIN_值); if(pwdHash.length!=expectedHash.length)返回false; 对于(int i=0;i如果(c另一个版本使用SHA-3,我使用的是bouncycastle:

    界面:

    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);
    }
    
    实施:

    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();
        }
    }
    
    pom.xml(仅依赖项):

    
    朱尼特
    朱尼特
    4.12
    测试
    org.testng
    testng
    6.1.1
    测试
    org.hamcrest
    汉克雷斯特酒店
    1.3
    测试
    log4j
    log4j
    1.2.17
    org.bouncycastle
    bcprov-jdk15on
    1.51
    罐子
    org.apache.commons
    commons-lang3
    3.3.2
    
    这是我的解决方案,我喜欢任何人的意见
    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();
        }
    }
    
    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());
        }
    }
    
    <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>
    
    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;
        }
    
    }
    
    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);
        }