国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Spring Security PasswordEncoder 密碼校驗和密碼加密流程

PasswordEncoder 使用

首先我們先來看看一個創(chuàng)建密碼編碼器工廠方法

org/springframework/security/crypto/factory/PasswordEncoderFactories.java

public static PasswordEncoder createDelegatingPasswordEncoder() {    String encodingId = "bcrypt";    Map<String, PasswordEncoder> encoders = new HashMap<>();    encoders.put(encodingId, new BCryptPasswordEncoder());    encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());    encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());    encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));    encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());    encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());    encoders.put("scrypt", new SCryptPasswordEncoder());    encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));    encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));    encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());    return new DelegatingPasswordEncoder(encodingId, encoders);}

上述代碼 encoders 的 Map 包含了很多種密碼編碼器,有 ldap 、MD4 、 MD5 、noop 、pbkdf2 、scrypt 、SHA-1 、SHA-256
上面靜態(tài)工廠方法可以看出,默認是創(chuàng)建并返回一個 BCryptPasswordEncoder,同時該 BCryptPasswordEncoder( PasswordEncoder 子類)也是 Spring Security 推薦的默認密碼編碼器,其中 noop 就是不做處理默認保存原密碼。

一般我們代碼中 @Autowired 注入并使用 PasswordEncoder 接口的實例,然后調(diào)用其 matches 方法去匹配原密碼和數(shù)據(jù)庫中保存的“密碼”;密碼的校驗方式有多種,從 PasswordEncoder 接口實現(xiàn)的類是可以知道。

業(yè)務代碼中注入 PasswordEncoder

@Autowiredprivate PasswordEncoder passwordEncoder;

科普一些基本知識

BCrypt 密碼散列函數(shù)的概念介紹可以看一下維基百科或百度百科的內(nèi)容 (因為只是使用,暫時不需要對算法的了解和實現(xiàn))

維基百科:BCrypt

百度百科:BCrypt

知識混淆點

加密/解密 與 Hash 這兩個概念不能混淆,比如:SHA 系列是 Hash 算法,不是加密算法,加密意味著可以解密,但是 Hash 是不可逆的(無法通過 Hash 值還原得到密碼,只能比對 Hash 值看看是否相等)。

安全性問題

目前很大一部分存在安全問題的系統(tǒng)一般僅僅使用密碼的 MD5 值進行保存,可以通過 MD5 查詢庫去匹配對大部分的密碼(可以直接從彩虹表里反推出來),而且 MD5 計算 Hash 值碰撞容易構(gòu)造,安全性大大降低。MD5 加鹽在本地計算速度也是很快,也是密碼短也是極其容易破解;更好的選擇是 SHA-256、BCrypt 等等等

密碼匹配流程的源碼解釋

本文簡單說一下 BCryptPasswordEncoder 密碼匹配的一個簡單流程或者過程。

重點

如果是使用 BCryptPasswordEncoder 調(diào)用 encode() 方法編碼輸入密碼的話,其實這個編碼后的“密碼”并不是我們平時輸入的真正密碼,而是密碼加鹽后的通過單向 Hash 算法(BCrypt)得到值。

這里面細心的同學可能會發(fā)現(xiàn)一些問題:

  • 同一個密碼計算 Hash 不應該是一樣的嗎?每次使用 BCryptPasswordEncoder 編碼同一個密碼都是不一樣的?

  • BCryptPasswordEncoder 編碼同一個密碼后結(jié)果都不一樣,怎么進行匹配?

下面通過源碼簡單說一下這個匹配的流程:
matches(CharSequence rawPassword, String encodedPassword) 方法根據(jù)兩個參數(shù)都可以知道

  • 第一個參數(shù)是原密碼
  • 第二個參數(shù)就是用 PasswordEncoder 調(diào)用 encode(CharSequence rawPassword) 編碼過后保存在數(shù)據(jù)庫的密碼。

org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java

public boolean matches(CharSequence rawPassword, String encodedPassword) {    if (encodedPassword == null || encodedPassword.length() == 0) {        logger.warn("Empty encoded password");        return false;    }    if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {        logger.warn("Encoded password does not look like BCrypt");        return false;    }    return BCrypt.checkpw(rawPassword.toString(), encodedPassword);}

上述代碼解讀:首先判斷是否數(shù)據(jù)庫保存的“密碼”(后面簡稱:“密碼”)是否為空或者 null ,在通過正則表達式匹配“密碼”是否符合格式,最后通過 BCryptcheckpw(String plaintext, String hashed) 方法進行密碼匹配


再詳細看看 BCryptcheckpw(String plaintext, String hashed) 方法:

org/springframework/security/crypto/bcrypt/BCrypt.java

public static boolean checkpw(String plaintext, String hashed) {    return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));}

第二個參數(shù) hashed 表明其實數(shù)據(jù)庫查詢出來的“密碼”也就是 Hash 值;equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed)) 代碼中通過調(diào)用 hashpw 計算輸入密碼的 Hash 值(參數(shù)分別是輸入的密碼和保存在數(shù)據(jù)庫的“密碼”)


再繼續(xù)看 hashpw 里面的部分代碼(內(nèi)容過長,省略部分代碼,看看代碼中的中文注釋):

org/springframework/security/crypto/bcrypt/BCrypt.java

public static String hashpw(String password, String salt) throws IllegalArgumentException {    BCrypt B;    String real_salt;    byte passwordb[], saltb[], hashed[];    char minor = (char) 0;    int rounds, off = 0;    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' || salt.charAt(3) != '$') {            throw new IllegalArgumentException("Invalid salt revision");        }        off = 4;    }    if (saltLength - off < 25) {        throw new IllegalArgumentException("Invalid salt");    }    // Extract number of rounds    if (salt.charAt(off + 2) > '$') {        throw new IllegalArgumentException("Missing salt rounds");    }    rounds = Integer.parseInt(salt.substring(off, off + 2));        // 關(guān)鍵點:上面***一大堆就是校驗是否符合相應格式,然后下面這行就是取出密碼的鹽,real_salt就是 Hash 計算前的密碼鹽(關(guān)于鹽的介紹:https://zh.wikipedia.org/wiki/%E7%9B%90_(%E5%AF%86%E7%A0%81%E5%AD%A6))        real_salt = salt.substring(off + 3, off + 25);    try {        passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");    }    catch (UnsupportedEncodingException uee) {        throw new AssertionError("UTF-8 is not supported");    }    saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);    B = new BCrypt();    hashed = B.crypt_raw(passwordb, saltb, rounds);    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();}

其實上面代碼就是從數(shù)據(jù)庫得到的“密碼”(參數(shù): salt )進行一系列校驗(長度校驗等)并截取“密碼”中相應的密碼鹽,利用這個密碼鹽進行同樣的一系列計算 Hash 操作和 Base64 編碼拼接一些標識符 生成所謂的“密碼”,最后 equalsNoEarlyReturn 方法對同一個密碼鹽生成的兩個“密碼”進行匹配。

上述大致就是密碼匹配流程了,對于問題“ BCryptPasswordEncoder 編碼同一個密碼后結(jié)果都不一樣,怎么進行匹配”的簡單解答:

因為密碼鹽是隨機生成的,但是可以根據(jù)數(shù)據(jù)庫查詢出來的“密碼”拿到密碼鹽,同一個密碼鹽+原密碼計算 Hash 結(jié)果值是能匹配的。

密碼“加密”保存源碼解釋

看看加密的一個過程,

org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java

public String encode(CharSequence rawPassword) {    String salt;    if (strength > 0) {        if (random != null) {            // 生成隨機密碼鹽            salt = BCrypt.gensalt(strength, random);        }        else {            // 生成隨機密碼鹽            salt = BCrypt.gensalt(strength);        }    }    else {        // 生成隨機密碼鹽        salt = BCrypt.gensalt();    }    return BCrypt.hashpw(rawPassword.toString(), salt);}

encode 方法傳入是原密碼,其中 int strength, SecureRandom random 這兩個構(gòu)造參數(shù)是 BCryptPasswordEncoder(int strength, SecureRandom random) 構(gòu)造方法按需傳入,如果不指定strength和random,默認執(zhí)行 BCrypt.gensalt() 這行代碼生成也相應密碼隨機鹽。


先看看 gensalt(int log_rounds, SecureRandom random) 方法的代碼(可以看看中文注釋):

org/springframework/security/crypto/bcrypt/BCrypt.java

public static String gensalt(int log_rounds, SecureRandom random) {    // 一些檢驗    if (log_rounds < MIN_LOG_ROUNDS || log_rounds > MAX_LOG_ROUNDS) {        throw new IllegalArgumentException("Bad number of rounds");    }    StringBuilder rs = new StringBuilder();    byte rnd[] = new byte[BCRYPT_SALT_LEN];    // 生成隨機字節(jié)并將其置于rnd字節(jié)數(shù)組    random.nextBytes(rnd);    rs.append("$2a$");    if (log_rounds < 10) {        // 不夠長度補夠        rs.append("0");    }    // 拼接字符串得到相應的格式    rs.append(log_rounds);    rs.append("$");    encode_base64(rnd, rnd.length, rs);    return rs.toString();}

最終上面的 gensalt 方法得到一個 隨機密碼鹽+無用字符串(這個字符串可以理解為你輸入的密碼) 計算 Hash 操作和 Base64 編碼拼接一些標識符 生成假“密碼”(這個假“密碼”為了兼容方便調(diào)用 hashpw 方法),最后關(guān)鍵點就是調(diào)用 BCrypt.hashpw 方法取到密碼鹽生成相應的真實“密碼”(這個得到的密碼可以用于保存在數(shù)據(jù)庫中了)。

對于問題“同一個密碼計算 Hash 不應該是一樣的嗎?每次使用 BCryptPasswordEncoder 編碼同一個密碼都是不一樣的?”的簡單解答:

因為用到的隨機密碼鹽每次都是不一樣的,同一個密碼和不同的密碼鹽組合計算出來的 Hash 值肯定不一樣啦,所以編碼同一個密碼得到的結(jié)果都是不一樣。

建議和想法

本文主要講解一些安全性防護的思想,學習的過程思想很重要。

登錄注冊是每個系統(tǒng)都具備的功能,開發(fā)的同學記住一定不能保存明文密碼,否則被脫庫就會造成嚴重的后果。如果是通過上述的方法進行密碼保存,即便拿到“密碼”也非常難還原密碼。

上述在密碼編碼的過程中的思想還是需要掌握:

  1. 只是保存散列碼是不安全的,但是我們可以為密碼加鹽再通過一些 Hash 值 低概率碰撞且計算速度慢 的散列算法計算 Hash 值保存。

  2. Spring Security 每次 Hash 之前用的鹽都是隨機,鹽可以保存在最終生成的“密碼”中,這樣每個密碼都是用了相應不同的隨機鹽+原密碼計算 Hash 值得到,暴力破解難度也變大了。

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
最安全的加密算法 Bcrypt,再也不用擔心數(shù)據(jù)泄密了~
Spring Boot Actuator:介紹和使用
Spring Security 3.1 中功能強大的加密工具 PasswordEncoder
如何生成安全的密碼 Hash:MD5, SHA, PBKDF2, BCrypt 示例
PHP密碼散列算法的學習
c# – 使用PHP對ASP.NET成員資格中的用戶進行身份驗證
更多類似文章 >>
生活服務
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服