سؤال

أحتاج إلى تجزئة كلمات المرور للتخزين في قاعدة البيانات.كيف يمكنني القيام بذلك في جافا؟

كنت أتمنى الحصول على كلمة المرور ذات النص العادي، وإضافة ملح عشوائي، ثم تخزين الملح وكلمة المرور المجزأة في قاعدة البيانات.

ثم عندما يريد المستخدم تسجيل الدخول، يمكنني أخذ كلمة المرور المقدمة، وإضافة الملح العشوائي من معلومات حسابه، وتجزئتها ومعرفة ما إذا كانت تعادل كلمة مرور التجزئة المخزنة مع معلومات حسابه.

هل كانت مفيدة؟

المحلول

يمكنك بالفعل استخدام منشأة مدمجة في وقت تشغيل Java للقيام بذلك. يدعم Sunjce in Java 6 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);
  }

}

نصائح أخرى

هنا التنفيذ الكامل مع طريقتين تفعل بالضبط ما تريد:

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 ٪ من تجزئة الحوسبة.

Bcrypt هي مكتبة جيدة للغاية ، وهناك أ ميناء جافا منه.

يمكنك حساب التجزئة باستخدام MessageDigest, ، لكن هذا خطأ من حيث الأمن. لا يتعين استخدام التجزئة لتخزين كلمات المرور ، حيث يمكن كسرها بسهولة.

يجب عليك استخدام خوارزمية أخرى مثل BCrypt و PBKDF2 و Scrypt لتخزين كلمات المرور لك. انظر هنا.

يمكنك استخدام ال شيرو المكتبة (سابقا JSecurity) تطبيق لما وصفه owasp.

يبدو أيضًا أن مكتبة Jasypt لديها فائدة مماثلة.

بالإضافة إلى BCrypt و PBKDF2 المذكورة في إجابات أخرى ، أوصي بالبحث في Scrypt

لا ينصح MD5 و SHA-1 لأنهما سريعان نسبيًا ، وبالتالي فإن استخدام الحوسبة الموزعة "الإيجار" الموزعة (مثل EC2) أو وحدة معالجة الرسومات الحديثة في وحدة المعالجة المركزية يمكن للمرء "كسر" كلمة مرور باستخدام هجمات القوة / القاموس بتكاليف منخفضة نسبيًا ومعقولة زمن.

إذا كان يجب عليك استخدامها ، فعليك على الأقل تكرار الخوارزمية كمية كبيرة محددة مسبقًا (1000+).

أتفق تمامًا مع Erickson ذلك PBKDF2 هو الجواب الصحيح.

إذا لم يكن لديك هذا الخيار ، أو تحتاج فقط إلى استخدام علامة تجزئة ، فإن 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-cryptography-right-andswers.html

بينما ال توصية NIST PBKDF2 وقد سبق ذكره، أود أن أشير إلى أنه كان هناك جمهور مسابقة تجزئة كلمة المرور والتي امتدت من 2013 إلى 2015.في النهاية، الأرجون2 تم اختيارها كوظيفة تجزئة كلمة المرور الموصى بها.

هناك اعتماد جيد إلى حد ما ربط جافا للمكتبة الأصلية (C الأصلية) التي يمكنك استخدامها.

في حالة الاستخدام المتوسطة، لا أعتقد أنه من المهم من الناحية الأمنية اختيار PBKDF2 بدلاً من Argon2 أو العكس.إذا كانت لديك متطلبات أمنية قوية، أوصي بالنظر في Argon2 في تقييمك.

لمزيد من المعلومات حول أمان وظائف تجزئة كلمة المرور، راجع Security.se.

هنا لديك رابطان لـ MD5 Hashing وطرق التجزئة الأخرى:

Javadoc API: http://java.sun.com/j2se/1.4.2/docs/api/java/security/messagedigest.html

الدورة التعليمية: http://www.twmacinta.com/myjava/fast_md5.php

يمكنك استخدام أمن الربيع تشفير (لديه فقط 2 التبعيات التجميع الاختيارية) ، الذي يدعم PBKDF2, bcrypt و Scrypt تشفير كلمة المرور.

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

من بين جميع مخططات التجزئة القياسية ، يعد LDAP SSHA الأكثر أمانًا للاستخدام ،

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

أود فقط اتباع الخوارزميات المحددة هناك واستخدام MessageDigest للقيام بالتجزئة.

تحتاج إلى تخزين الملح في قاعدة البيانات الخاصة بك كما اقترحت.

انحنيت من ذلك من مقطع فيديو على 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);
}






}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top