programing

자바에서 패스워드를 해시하려면 어떻게 해야 하나요?

kingscode 2022. 8. 25. 22:05
반응형

자바에서 패스워드를 해시하려면 어떻게 해야 하나요?

데이터베이스에 저장하기 위한 암호를 해시해야 합니다.자바에서는 어떻게 해야 하나요?

일반 텍스트 비밀번호를 가져와서 임의의 소금을 넣은 다음 salt와 해시 비밀번호를 데이터베이스에 저장하려고 합니다.

그 후 사용자가 로그인하고 싶을 때 사용자가 제출한 비밀번호를 가져와 계정 정보에서 랜덤 솔트를 추가하고 해시하여 저장된 해시 비밀번호와 동일한지 확인할 수 있습니다.

실제로 Java 런타임에 내장된 기능을 사용하여 이를 수행할 수 있습니다.Java 6의 SunJCE는 PBKDF2를 지원합니다.PBKDF2는 패스워드 해시에 사용하기 좋은 알고리즘입니다.

byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec("password".toCharArray(), salt, 65536, 128);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = f.generateSecret(spec).getEncoded();
Base64.Encoder enc = Base64.getEncoder();
System.out.printf("salt: %s%n", enc.encodeToString(salt));
System.out.printf("hash: %s%n", enc.encodeToString(hash));

다음은 PBKDF2 비밀번호 인증에 사용할 수 있는 유틸리티 클래스입니다.

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

/**
 * Hash passwords for storage, and test passwords against password tokens.
 * 
 * Instances of this class can be used concurrently by multiple threads.
 *  
 * @author erickson
 * @see <a href="http://stackoverflow.com/a/2861125/3474">StackOverflow</a>
 */
public final class PasswordAuthentication
{

  /**
   * Each token produced by this class uses this identifier as a prefix.
   */
  public static final String ID = "$31$";

  /**
   * The minimum recommended cost, used by default
   */
  public static final int DEFAULT_COST = 16;

  private static final String ALGORITHM = "PBKDF2WithHmacSHA1";

  private static final int SIZE = 128;

  private static final Pattern layout = Pattern.compile("\\$31\\$(\\d\\d?)\\$(.{43})");

  private final SecureRandom random;

  private final int cost;

  public PasswordAuthentication()
  {
    this(DEFAULT_COST);
  }

  /**
   * Create a password manager with a specified cost
   * 
   * @param cost the exponential computational cost of hashing a password, 0 to 30
   */
  public PasswordAuthentication(int cost)
  {
    iterations(cost); /* Validate cost */
    this.cost = cost;
    this.random = new SecureRandom();
  }

  private static int iterations(int cost)
  {
    if ((cost < 0) || (cost > 30))
      throw new IllegalArgumentException("cost: " + cost);
    return 1 << cost;
  }

  /**
   * Hash a password for storage.
   * 
   * @return a secure authentication token to be stored for later authentication 
   */
  public String hash(char[] password)
  {
    byte[] salt = new byte[SIZE / 8];
    random.nextBytes(salt);
    byte[] dk = pbkdf2(password, salt, 1 << cost);
    byte[] hash = new byte[salt.length + dk.length];
    System.arraycopy(salt, 0, hash, 0, salt.length);
    System.arraycopy(dk, 0, hash, salt.length, dk.length);
    Base64.Encoder enc = Base64.getUrlEncoder().withoutPadding();
    return ID + cost + '$' + enc.encodeToString(hash);
  }

  /**
   * Authenticate with a password and a stored password token.
   * 
   * @return true if the password and token match
   */
  public boolean authenticate(char[] password, String token)
  {
    Matcher m = layout.matcher(token);
    if (!m.matches())
      throw new IllegalArgumentException("Invalid token format");
    int iterations = iterations(Integer.parseInt(m.group(1)));
    byte[] hash = Base64.getUrlDecoder().decode(m.group(2));
    byte[] salt = Arrays.copyOfRange(hash, 0, SIZE / 8);
    byte[] check = pbkdf2(password, salt, iterations);
    int zero = 0;
    for (int idx = 0; idx < check.length; ++idx)
      zero |= hash[salt.length + idx] ^ check[idx];
    return zero == 0;
  }

  private static byte[] pbkdf2(char[] password, byte[] salt, int iterations)
  {
    KeySpec spec = new PBEKeySpec(password, salt, iterations, SIZE);
    try {
      SecretKeyFactory f = SecretKeyFactory.getInstance(ALGORITHM);
      return f.generateSecret(spec).getEncoded();
    }
    catch (NoSuchAlgorithmException ex) {
      throw new IllegalStateException("Missing algorithm: " + ALGORITHM, ex);
    }
    catch (InvalidKeySpecException ex) {
      throw new IllegalStateException("Invalid SecretKeyFactory", ex);
    }
  }

  /**
   * Hash a password in an immutable {@code String}. 
   * 
   * <p>Passwords should be stored in a {@code char[]} so that it can be filled 
   * with zeros after use instead of lingering on the heap and elsewhere.
   * 
   * @deprecated Use {@link #hash(char[])} instead
   */
  @Deprecated
  public String hash(String password)
  {
    return hash(password.toCharArray());
  }

  /**
   * Authenticate with a password in an immutable {@code String} and a stored 
   * password token. 
   * 
   * @deprecated Use {@link #authenticate(char[],String)} instead.
   * @see #hash(String)
   */
  @Deprecated
  public boolean authenticate(String password, String token)
  {
    return authenticate(password.toCharArray(), token);
  }

}

BCrypt는 매우 좋은 라이브러리이며, Java 포트가 있습니다.

PBKDF2, BCrypt, SCryptArgon2 비밀번호 암호화를 지원하는 Spring Security Crypt(옵션 컴파일 의존관계는 2개뿐)를 사용할 수 있습니다.

Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder();
String aCryptedPassword = argon2PasswordEncoder.encode("password");
boolean passwordIsValid = argon2PasswordEncoder.matches("password", aCryptedPassword);
SCryptPasswordEncoder sCryptPasswordEncoder = new SCryptPasswordEncoder();
String sCryptedPassword = sCryptPasswordEncoder.encode("password");
boolean passwordIsValid = sCryptPasswordEncoder.matches("password", sCryptedPassword);
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String bCryptedPassword = bCryptPasswordEncoder.encode("password");
boolean passwordIsValid = bCryptPasswordEncoder.matches("password", bCryptedPassword);
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder();
String pbkdf2CryptedPassword = pbkdf2PasswordEncoder.encode("password");
boolean passwordIsValid = pbkdf2PasswordEncoder.matches("password", pbkdf2CryptedPassword);

를 사용하여 해시를 계산할 수 있지만 이는 보안 측면에서 잘못된 것입니다.해시는 쉽게 깨질 수 있으므로 비밀번호 저장에는 사용하지 마십시오.

비밀번호를 저장하려면 bcrypt, PBKDF2, scrypt 등의 다른 알고리즘을 사용해야 합니다.여기 보세요.

OWASP에서 설명한 내용에 대한 Shiro 라이브러리(이전 JSecurity) 구현을 사용할 수 있습니다.

JASYPT 라이브러리에도 유사한 유틸리티가 있는 것 같습니다.

다른 답변에서 언급된 bcrypt와 PBKDF2 외에 스크립트를 볼 것을 권장합니다.

MD5 및 SHA-1은 비교적 고속이기 때문에 권장되지 않습니다.따라서 "시간당 임대" 분산 컴퓨팅(EC2)이나 최신 하이엔드 GPU를 사용하면 비교적 저렴한 비용과 합리적인 시간에 무차별적인 공격이나 사전 공격을 통해 비밀번호를 "크래킹"할 수 있습니다.

이러한 알고리즘을 사용할 필요가 있는 경우는, 적어도 사전에 정의된 유효 회수(1000 회 이상)를 알고리즘에 반복해 주세요.

PBKDF2가 해답이라는 Erickson의 의견에 전적으로 동의합니다.

이 옵션이 없거나 해시만 사용해야 하는 경우 Apache Commons Digest Utils는 JCE 코드를 올바르게 만드는 것보다 훨씬 쉽습니다.https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/DigestUtils.html

해시를 사용하는 경우는 sha256 또는 sha512로 합니다.이 페이지에는 패스워드 처리 및 해시에 관한 권장사항이 기재되어 있습니다(패스워드 처리에는 해시를 권장하지 않습니다).http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html

NIST 권장 사항 PBKDF2는 이미 언급되었지만, 2013년부터 2015년까지 공개 암호 해싱 대회가 있었다는 점을 지적하고 싶습니다.결국 권장 패스워드 해싱 함수로 Argon2가 선택되었습니다.

원래(네이티브 C) 라이브러리에는 사용할 수 있는 상당히 잘 채택된 Java 바인딩이 있습니다.

평균적인 사용 사례에서는 Argon2보다 PBKDF2를 선택하거나 그 반대로 선택하는 것은 보안 측면에서 문제가 되지 않는다고 생각합니다.강력한 보안 요건이 있다면 평가 시 Argon2를 고려할 것을 권장합니다.

비밀번호 해시 기능의 보안에 대한 자세한 내용은 security.se 를 참조하십시오.

여기에서는 MD5 해시 및 기타 해시 방식용 2개의 링크가 있습니다.

Javadoc API: https://docs.oracle.com/javase/1.5.0/docs/api/java/security/MessageDigest.html

튜토리얼: http://www.twmacinta.com/myjava/fast_md5.php

모든 표준 해시 방식 중에서 LDAP ssha를 사용하는 것이 가장 안전합니다.

http://www.openldap.org/faq/data/cache/347.html

여기에 지정된 알고리즘을 따라 MessageDigest를 사용하여 해시를 수행합니다.

제안하신 대로 소금을 데이터베이스에 저장해야 합니다.

2020년 현재 사용 중인 가장 신뢰성 높은 패스워드 해싱 알고리즘은 Argon2id 또는 Argon2i이지만 Spring 구현은 아닙니다.

PBKDF2 표준은 블록 암호 BCRYPT algo의 CPU 요구/계산 비용이 많이 드는 기능을 포함하고 스트림 암호 기능을 추가합니다.PBKDF2는 메모리 기하급수적으로 필요한 SCRYPT에 의해 압도되고 다음으로 사이드 채널 공격에 강한 Argon2에 의해 압도되었습니다.

Argon2는 목표 해싱 시간 및 사용된 하드웨어에 대해 최적화된 강도 파라미터를 찾는 데 필요한 교정 도구를 제공합니다.

  • Argon2i는 메모리 그리디 해시에 특화되어 있습니다.
  • Argon2d는 CPU 그리디 해시에 특화되어 있습니다.
  • Argon2id는 두 가지 방법을 모두 사용합니다.

메모리가 많은 해시를 사용하면 GPU가 크래킹에 사용되는 것을 방지할 수 있습니다.

스프링 보안 / Bouncy Castle 구현은 최적화되어 있지 않으며 공격자가 사용할 수 있는 것을 고려할 때 비교적 주 단위입니다.cf: 스프링 문서 Argon2Scrypt

현재 구현에서는 Bouncy Castle을 사용하고 있으며, Bouncy Castle은 패스워드 크래커에 의한 병렬화/최적화를 이용하지 않기 때문에 공격자와 방어자 사이에 불필요한 비대칭이 존재합니다.

Java에서 사용되는 가장 신뢰할 수 있는 구현은 mkammerer의 구현입니다.

C로 작성된 공식 네이티브 구현의 포장지 병/라이브러리.

그것은 잘 쓰여져 있고 사용하기 쉽다.

임베디드 버전은 Linux, Windows 및 OSX용 네이티브 빌드를 제공합니다.

예를 들어, jpmorganchase는 자사의 tesera 보안 프로젝트에서 Etherin cryptocurency 구현인 Quorum을 보호하기 위해 사용합니다.

다음은 예를 제시하겠습니다.

    final char[] password = "a4e9y2tr0ngAnd7on6P১M°RD".toCharArray();
    byte[] salt = new byte[128];
    new SecureRandom().nextBytes(salt);
    final Argon2Advanced argon2 = Argon2Factory.createAdvanced(Argon2Factory.Argon2Types.ARGON2id);
    byte[] hash = argon2.rawHash(10, 1048576, 4, password, salt);

(테세라 참조)

POM에서 lib를 선언합니다.

<dependency>
    <groupId>de.mkammerer</groupId>
    <artifactId>argon2-jvm</artifactId>
    <version>2.7</version>
</dependency>

또는 그래들 포함:

compile 'de.mkammerer:argon2-jvm:2.7'

보정 de.mkamerer.argon2를 사용하여 수행할 수 있습니다.Argon2Helper #findIterations

SCRYPT 및 Pbkdf2 알고리즘도 몇 가지 간단한 벤치마크를 작성하여 교정할 수 있지만 현재 최소 안전 반복 값은 더 높은 해시 시간이 필요합니다.

나는 그것을 udemy의 비디오에서 기대어 더 강한 랜덤 패스워드로 편집했다.

}

private String pass() {
        String passswet="1234567890zxcvbbnmasdfghjklop[iuytrtewq@#$%^&*" ;

        char icon1;
        char[] t=new char[20];

         int rand1=(int)(Math.random()*6)+38;//to make a random within the range of special characters

            icon1=passswet.charAt(rand1);//will produce char with a special character

        int i=0;
        while( i <11) {

             int rand=(int)(Math.random()*passswet.length());
             //notice (int) as the original value of Math>random() is double

             t[i] =passswet.charAt(rand);

             i++;
                t[10]=icon1;
//to replace the specified item with icon1
         }
        return new String(t);
}






}

언급URL : https://stackoverflow.com/questions/2860943/how-can-i-hash-a-password-in-java

반응형