데이터베이스에 저장하기 위해 비밀번호를 해시해야합니다. Java로 어떻게 할 수 있습니까?
나는 일반 텍스트 암호를 가져 와서 임의의 소금을 추가 한 다음 소금과 해시 된 암호를 데이터베이스에 저장하기를 바랐습니다.
그런 다음 사용자가 로그인하고 싶을 때 제출 한 비밀번호를 가져 와서 계정 정보에서 임의의 소금을 추가하고 해시하여 저장된 해시 비밀번호와 계정 정보가 동일한 지 확인할 수 있습니다.
데이터베이스에 저장하기 위해 비밀번호를 해시해야합니다. Java로 어떻게 할 수 있습니까?
나는 일반 텍스트 암호를 가져 와서 임의의 소금을 추가 한 다음 소금과 해시 된 암호를 데이터베이스에 저장하기를 바랐습니다.
그런 다음 사용자가 로그인하고 싶을 때 제출 한 비밀번호를 가져 와서 계정 정보에서 임의의 소금을 추가하고 해시하여 저장된 해시 비밀번호와 계정 정보가 동일한 지 확인할 수 있습니다.
답변:
실제로 Java 런타임에 내장 된 기능을 사용하여이를 수행 할 수 있습니다. Java 6의 SunJCE는 비밀번호 해싱에 사용하기에 적합한 알고리즘 인 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);
}
}
BigInteger
. 그것은 빠른 디버그에는 괜찮지 만 그 효과로 인해 프로덕션 코드에서 버그를 보았습니다.
다음은 원하는 것을 정확하게 수행하는 두 가지 방법 으로 완벽한 구현 입니다.
String getSaltedHash(String password)
boolean checkPassword(String password, String stored)
요점은 공격자가 데이터베이스와 소스 코드에 모두 액세스하더라도 암호는 여전히 안전하다는 것입니다.
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import org.apache.commons.codec.binary.Base64;
public class Password {
// The higher the number of iterations the more
// expensive computing the hash is for us and
// also for an attacker.
private static final int iterations = 20*1000;
private static final int saltLen = 32;
private static final int desiredKeyLen = 256;
/** Computes a salted PBKDF2 hash of given plaintext password
suitable for storing in a database.
Empty passwords are not supported. */
public static String getSaltedHash(String password) throws Exception {
byte[] salt = SecureRandom.getInstance("SHA1PRNG").generateSeed(saltLen);
// store the salt with the password
return Base64.encodeBase64String(salt) + "$" + hash(password, salt);
}
/** Checks whether given plaintext password corresponds
to a stored salted hash of the password. */
public static boolean check(String password, String stored) throws Exception{
String[] saltAndHash = stored.split("\\$");
if (saltAndHash.length != 2) {
throw new IllegalStateException(
"The stored password must have the form 'salt$hash'");
}
String hashOfInput = hash(password, Base64.decodeBase64(saltAndHash[0]));
return hashOfInput.equals(saltAndHash[1]);
}
// using PBKDF2 from Sun, an alternative is https://github.com/wg/scrypt
// cf. http://www.unlimitednovelty.com/2012/03/dont-use-bcrypt.html
private static String hash(String password, byte[] salt) throws Exception {
if (password == null || password.length() == 0)
throw new IllegalArgumentException("Empty passwords are not supported.");
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey key = f.generateSecret(new PBEKeySpec(
password.toCharArray(), salt, iterations, desiredKeyLen));
return Base64.encodeBase64String(key.getEncoded());
}
}
우리는 저장하고 'salt$iterated_hash(password, salt)'
있습니다. 소금은 32 개의 임의 바이트이며 목적은 두 사람이 같은 암호를 선택하면 저장된 암호가 여전히 다르게 보일 것입니다.
iterated_hash
기본적으로 이는, hash(hash(hash(... hash(password, salt) ...)))
추측 암호 해시 그들에게 데이터베이스에 액세스 할 수있는 잠재적 인 공격자가 매우 비싼 만들고, 데이터베이스에 해시를 찾아보십시오. iterated_hash
사용자가 로그인 할 때마다 이를 계산해야 하지만 해시 계산에 거의 100 %를 소비하는 공격자와 비교할 때 비용이 많이 들지 않습니다.
char[] password
대신로 변경해야합니다 String password
.
OWASP에서 설명 하는 Shiro 라이브러리 (이전의 JSecurity ) 구현 을 사용할 수 있습니다 .
또한 JASYPT 라이브러리에도 비슷한 유틸리티 가있는 것 같습니다 .
을 사용하여 해시를 계산할 수 MessageDigest
있지만 보안 측면에서는 잘못되었습니다. 해시는 암호를 쉽게 해독 할 수 있으므로 암호를 저장하는 데 사용해서는 안됩니다.
bcrypt, PBKDF2 및 scrypt와 같은 다른 알고리즘을 사용하여 비밀번호를 저장해야합니다. 여기를 참조하십시오 .
또한 bcrypt하고 PBKDF2는 다른 답변에 언급, 내가보고 추천 할 scrypt
MD5 및 SHA-1은 상대적으로 빠르므로 "시간당 대여"분산 컴퓨팅 (예 : EC2)을 사용하거나 최신 고급 GPU를 사용하면 비교적 저렴한 비용으로 합리적인 무차별 강제 / 사전 공격을 사용하여 암호를 "크랙"할 수 있으므로 권장하지 않습니다. 시각.
이를 사용해야하는 경우 최소한 미리 정의 된 상당한 횟수 (1000+)의 알고리즘을 반복하십시오.
자세한 내용은 여기를 참조하십시오 : /security/211/how-to-securely-hash-passwords
그리고 여기 : http://codahale.com/how-to-safely-store-a-password/ (비밀번호 해싱 목적으로 SHA 가족, MD5 등을 비판합니다)
PBKDF2 가 정답 이라는 Erickson의 의견에 전적으로 동의합니다 .
해당 옵션이 없거나 해시 만 사용해야하는 경우 Apache Commons DigestUtils가 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
PBKDF2 , BCrypt , SCrypt 및 Argon2 비밀번호 암호화 를 지원 하는 Spring Security Crypto ( 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);
그동안 NIST 추천 PBKDF2가 이미 언급 된, 나는 공공 있다는 것을 지적하고 싶습니다 암호 해시 대회 2013 결국 2015 년에 달렸다는 것을 Argon2가 권장되는 암호 해시 함수로 선택되었다가.
사용할 수있는 원래 (기본 C) 라이브러리에 대해 상당히 잘 채택 된 Java 바인딩 이 있습니다.
평균 사용 사례에서 Argon2 대신 PBKDF2를 선택하거나 그 반대의 경우 보안 관점에서 문제가되지 않는다고 생각합니다. 강력한 보안 요구 사항이있는 경우 평가에서 Argon2를 고려하는 것이 좋습니다.
암호 해시 기능의 보안에 대한 자세한 내용은 다음을 참조 security.se을 .
MD5 해싱 및 기타 해시 방법에 대한 두 개의 링크가 있습니다.
Javadoc API : https://docs.oracle.com/javase/1.5.0/docs/api/java/security/MessageDigest.html
모든 표준 해시 체계 중에서 LDAP ssha가 가장 안전합니다.
http://www.openldap.org/faq/data/cache/347.html
여기에 지정된 알고리즘을 따르고 MessageDigest를 사용하여 해시를 수행합니다.
제안한대로 소금을 데이터베이스에 저장해야합니다.
가장 신뢰할 수 있고 유연한 알고리즘 인 2020 년부터
하드웨어에 따라 강도를 최적화 할 가능성이 가장 높은
이다 Argon2id 또는 Argon2i .
대상 해싱 시간과 사용 된 하드웨어에 대해 최적화 된 강도 매개 변수를 찾는 데 필요한 보정 도구를 제공합니다.
메모리 욕심 해싱은 크래킹에 GPU 사용을 방지하는 데 도움이됩니다.
Spring security / Bouncy Castle 구현은 공격자가 사용할 수있는 것을 감안할 때 최적화되지 않았으며 상대적으로 일주일에 이루어집니다. cf : 스프링 문서
현재 구현에서는 암호 크래커가 수행 할 병렬 처리 / 최적화를 활용하지 않는 탄력성을 사용하므로 공격자와 방어자 사이에 불필요한 비대칭 성이 있습니다.
자바에 사용되는 가장 신뢰할만한 구현은 mkammerer입니다 .
Rust로 작성된 공식 네이티브 구현의 래퍼 jar / 라이브러리
잘 작성되어 있고 사용하기 쉽습니다.
임베디드 버전은 Linux, Windows 및 OSX를위한 기본 빌드를 제공합니다.
예를 들어, jpmorganchase 는 Ethereum cryptocurency 구현 인 Quorum 을 보호하는 데 사용되는 테 세라 보안 프로젝트 에서 jpmorganchase에 의해 사용됩니다 .
다음 은 tessera의 예제 코드입니다.
de.mkammerer.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);
}
}