2013-08-09 10 views
31

나는 주위를 찾고 있었어요와 가장 가까운 답은 How to generate a random alpha-numeric string?Salted-Hash 용 Java에서 SALT를 생성하려면 어떻게해야합니까?

나는 CrackStation tutorial이에 따라이 워크 플로를 따르십시오 :

  1. 생성 암호를 저장하려면 CSPRNG를 사용하는 긴 무작위 소금.

  2. 소금을 암호 앞에 추가하고 SHA256과 같은 표준 암호 해시 함수로 해시합니다.

  3. 소금과 해시를 모두 사용자의 데이터베이스 레코드에 저장하십시오.

는 데이터베이스에서 사용자의 소금과 해시를 검색 암호

  1. 의 유효성을 검사합니다.

  2. 소금을 주어진 암호 앞에 추가하고 동일한 해시 함수를 사용하여 해시합니다.

  3. 주어진 암호의 해시와 데이터베이스의 해시를 비교하십시오. 일치하면 암호가 정확합니다. 그렇지 않으면 암호가 올바르지 않습니다.

나는 염을 생성하는 방법을 모르겠어요. MessageDigest를 사용하여 해시를 생성하는 방법을 알아 냈습니다. SecureRandom을 사용해 보았지만 nextByte 메서드가 깨진 코드를 생성합니다.

편집 : 어떤 대답을 선택해야할지 모르겠지만 너무 복잡해서 jBCrypt를 사용하기로 결정했습니다. jBCript는 사용하기 쉽고 뒤에서 복잡한 모든 작업을 수행합니다. 그래서 나는 최선의 대답을 위해 지역 사회에 투표하도록 할 것이다.

+2

당신의 암호를 암호화 할 수있는 쉬운 방법을 원한다면 자바, [jBcrypt] (http://www.mindrot.org/projects/jBCrypt/)를보십시오 :) – NiziL

+0

@ Nizil 고마워요! 이것은 내가 지금 사용하려고하는 것입니다. –

+0

@Louie 정말 간단하지만 인증 된 보안 공급자를 사용하는 것이 옳은 일이지만, –

답변

9

소금을 생성하려는 방식 즉, 난수 이외의 값을 입력하는 것과 관련하여 귀하가 옳았습니다. 이 특별한 경우에는 가능한 사전 공격으로부터 시스템을 보호합니다. 이제, 두 번째 문제는 UTF-8 인코딩 대신 Base64를 사용하는 것입니다. 여기에 해시를 생성하는 샘플이 있습니다. 나는 여기에 this postthat post에서 영감을 OWASP

+0

솔트를 올바른 방법으로 생성합니까? 그것이 유효한지 알고 싶기 때문에 나는 그것을 인쇄했다. –

+0

어쨌든 각 암호에 사용 된 소금을 저장해야 할 경우 암호를 인증 할 수 없게됩니다. –

+0

인코딩이 잘못 되었어도 여전히 암호가 올바르게 추가되고 해시가 생성된다고 가정 할 수 있습니까? –

34

에서 직접 해시 암호 표준 관행의 일부 추가 참조가

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

자신 중 하나를 선택할 수 있습니다 당신을 인코딩 base64로 작업을 수행하는 아파치 공통 코덱을 사용하고 있습니다 이 코드를 사용하여 암호가 해시 된 암호를 생성하고 확인합니다. JDK 제공 클래스 만 사용하며 외부 종속성은 없습니다.

과정은 다음과 같습니다

  • 당신은 사용자에게 자신의 암호를 물어 getNextSalt
  • 과 염을 생성하고 소금과 해시 된 암호를 생성하는 hash 방법을 사용합니다. 이 메서드는 을 반환하며 데이터베이스를 암호로 저장하여
  • 사용자를 인증하고 암호를 묻고 데이터베이스에서 소금과 해시 암호를 검색 한 다음 isExpectedPassword 메서드를 사용하여 세부 정보가 일치하는지 확인합니다
/** 
* 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

바이트를 BLOB로 SQL에 저장합니까? –

+0

@ 루위 나는 당신이 사용하는 데이터베이스에 따라 다르지만 BLOB가 적절하다고 생각한다. 조언이 필요한 경우 특정 시점에 대한 별도의 질문을 할 수 있습니다. – assylias

3

SHA-3, 내가 사용하고 bouncycastle 사용하여 또 다른 버전 :

인터페이스 :

을 695,

구현 :

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> 
관련 문제