当前位置: 首页 > 知识库问答 >
问题:

如何在Java中为Salted Hash生成SALT?

宦正诚
2023-03-14

我四处看看,最接近的答案是:如何生成随机的字母数字字符串?

我想根据本CrackStation教程遵循此工作流:

存储密码

>

  • 使用CSPRNG生成长随机盐。

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

    在用户的数据库记录中保存salt和哈希。

    验证密码的步骤

    >

  • 从数据库中检索用户的盐和哈希。

    将salt前置到给定密码,并使用相同的哈希函数对其进行哈希运算。

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

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

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

  • 共有3个答案

    杨景山
    2023-03-14

    另一个版本使用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();
        }
    }
    

    测试用例:

    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(仅限依赖项):

    <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>
    
    向修谨
    2023-03-14

    关于盐的生成方式,你是对的,也就是说,盐只是一个随机数。对于这种特殊情况,它可以保护您的系统免受可能的字典攻击。现在,对于第二个问题,您可以不使用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());
            }
        }
    

    下面是直接从OWASP获取的密码哈希标准实践的一些附加参考

    乜坚成
    2023-03-14

    受这篇文章和那篇文章的启发,我使用这段代码来生成和验证散列加盐密码。它只使用JDK提供的类,没有外部依赖。

    过程是:

    • 您使用getNextSalt
    • 创建一个盐
    • 您询问用户他的密码并使用hash方法生成加盐和散列密码。该方法返回一个byte[],您可以将其保存在带有盐的数据库中
    • 要对用户进行身份验证,您需要询问他的密码,从数据库中检索盐和散列密码,并使用isExplectedPassword方法检查详细信息是否匹配
    /**
     * 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();
      }
    }
    
     类似资料:
    • 问题内容: 在Java中以至少32个字节长的String形式生成SALT值的最佳方法是什么? 问题答案:

    • 在Java中,将SALT值生成为至少32字节长的字符串的最佳方法是什么?

    • 问题内容: 我想用Java生成一个.torrent文件,但是我不想要一个大的API,它可以执行诸如抓取跟踪器,种子等操作。这仅适用于生成元数据的客户端。存在哪些轻量级解决方案?我只生成一个.zip文件的.torrent。 谢谢! 问题答案: 我整理了这段独立的Java代码,以准备一个带有单个文件的.torrent文件。 通过调用.torrent文件的名称,共享文件的名称和跟踪器URL 来创建.to

    • 我的印象是,UUID规范需要一个有保证的、真实的、全球唯一的结果,不是99.999999999999%的唯一结果,而是100%的唯一结果。从规格来看: UUID为128位长,可以保证跨空间和时间的唯一性。 看起来java只支持UUID规范的V3和V4。V4并不是真正独特的。对于使用< code > namuuidfrombytes 的V3实现,下面的结果是重复的,因为计算机太快了(编辑:循环到10

    • 问题内容: 在java中如何生成随机数? 问题答案: 在Java 1.7或更高版本中,执行此操作的标准方法如下: 请参阅相关的JavaDoc。这种方法的优点是不需要显式初始化java.util.Random实例,如果使用不当,可能会引起混乱和错误。 但是,相反,没有办法明确设置种子,因此在有用的情况下(例如测试或保存游戏状态或类似情况),很难重现结果。在这种情况下,可以使用下面显示的Java 1.

    • 每当我看到这行代码 后面总是有一个很长的序列号..这个数字是如何产生的?如果我想随机生成这个值,我该怎么做呢?谢谢你的帮助。