PasswordEncoder is an interface provided by Spring Security. It is called password parser. This interface is mainly used to process passwords. The source code is as follows:
public interface PasswordEncoder { /** * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or * greater hash combined with an 8-byte or greater randomly generated salt. */ String encode(CharSequence rawPassword); /** * Verify the encoded password obtained from storage matches the submitted raw * password after it too is encoded. Returns true if the passwords match, false if * they do not. The stored password itself is never decoded. * * @param rawPassword the raw password to encode and match * @param encodedPassword the encoded password from storage to compare with * @return true if the raw password, after encoding, matches the encoded password from * storage */ boolean matches(CharSequence rawPassword, String encodedPassword); /** * Returns true if the encoded password should be encoded again for better security, * else false. The default implementation always returns false. * @param encodedPassword the encoded password to check * @return true if the encoded password should be encoded again for better security, * else false. */ default boolean upgradeEncoding(String encodedPassword) { return false; } }
The interface provides three methods. The first method encrypts the plaintext password and returns a ciphertext. The second method is to match plaintext password and ciphertext and return Boolean value. The third method is to encrypt the ciphertext twice. This method is the default.
The PasswordEncoder interface has many implementation classes, the most important of which is the officially recommended BCryptPasswordEncoder class. This password parser is the most commonly used one. BCryptPasswordEncoder is a concrete implementation of bcrypt strong hash method. It is a one-way encryption based on hash algorithm. You can control the strength through strength. The default is 10.
The source code is as follows:
The encode method encrypts the plaintext password. The principle is to use a randomly generated salt, encrypt it with the plaintext password and the salt, and return the ciphertext. Because the salt is generated differently every time, even if the plaintext password is the same, the ciphertext encrypted at last is different, so as to ensure the security of the user's password.
The matches method is used to match plaintext passwords and ciphertexts, and the final result is returned with Boolean values.
Test encryption and matching:
@Test public void test1() { String password = "123456";// password PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); for (int i = 1; i <= 5; i++) { // Encrypt plaintext password and return ciphertext String encoder = passwordEncoder.encode(password); // Match plaintext and ciphertext boolean bool = passwordEncoder.matches(password, encoder); System.out.println(encoder + ": Match?" + bool); }
$2a$10$qcnrAAaJn0g4rnnBc0nz2emAwLXQPe8QYEVbN/YITEFpUZbCH.Pru: does it match? true
$2a$10$BN.YJOmTuHHhj279Lr1r/ue8G9iaYO62y7cjmeonD3toCit.uNfAG: match? true
$2a$10niTKuqRNbP/8DDAVCw8f.rOyzurdZ1.W0CQAucn8pCf2sihsUkE.: Match? true
$2a$10$fTRlKe3YDgSFnSdqgujw.h32cwjtNubcSgCtdc9mNjfGrEtoeJDi: match? true
$2a $10AIf3GBhWt9WjXc55mObfuRFhn1eyJKOeOdzprg65fmbsWeUrJGdm: match? true
It can be seen that the same password and the ciphertext encrypted five times are all different, but they can all match.
What is the reason? Continue to observe the source code:
When the plaintext password matches the encrypted password, the checkpw function will be called, and then take a look at the function source code
Check the hashpw source code:
/** * Hash a password using the OpenBSD bcrypt scheme * @param passwordb the password to hash, as a byte array * @param salt the salt to hash with (perhaps generated * using BCrypt.gensalt) * @return the hashed password */ public static String hashpw(byte passwordb[], String salt) { BCrypt B; String real_salt; byte saltb[], hashed[]; char minor = (char) 0; int rounds, off; StringBuilder rs = new StringBuilder(); if (salt == null) { throw new IllegalArgumentException("salt cannot be null"); } int saltLength = salt.length(); if (saltLength < 28) { throw new IllegalArgumentException("Invalid salt"); } if (salt.charAt(0) != '$' || salt.charAt(1) != '2') throw new IllegalArgumentException ("Invalid salt version"); if (salt.charAt(2) == '$') off = 3; else { minor = salt.charAt(2); if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') || salt.charAt(3) != '$') throw new IllegalArgumentException ("Invalid salt revision"); off = 4; } // Extract number of rounds if (salt.charAt(off + 2) > '$') throw new IllegalArgumentException ("Missing salt rounds"); if (off == 4 && saltLength < 29) { throw new IllegalArgumentException("Invalid salt"); } rounds = Integer.parseInt(salt.substring(off, off + 2)); //The above is to verify the effectiveness of salt, real_salt means that the encrypted real salt is saved in the ciphertext. After intercepting it, the plaintext password is encrypted and returned. Later use // The equalsNoEarlyReturn function performs match verification real_salt = salt.substring(off + 3, off + 25); saltb = decode_base64(real_salt, BCRYPT_SALT_LEN); if (minor >= 'a') // add null terminator passwordb = Arrays.copyOf(passwordb, passwordb.length + 1); B = new BCrypt(); hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0); rs.append("$2"); if (minor >= 'a') rs.append(minor); rs.append("$"); if (rounds < 10) rs.append("0"); rs.append(rounds); rs.append("$"); encode_base64(saltb, saltb.length, rs); encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs); return rs.toString(); }
Check the source code of the function generating salt again:
This indicates that after the salt is generated, base64 encrypted salt is also saved on the intervals off + 3 and off + 25.
result:
During registration, the password is encrypted and saved to the database by BCrypt. The salt of this hash is saved in a certain interval of the ciphertext password. Because the salt is randomly generated, even if the plaintext password is the same, the ciphertext password is inconsistent. During login verification, the plaintext password is hashed with the salt parsed from the ciphertext password obtained in the database, so as to match whether the password is correct.