天天看點

【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例

文章目錄

    • 加密
    • 1、MD5加密
      • 安全通路認證
      • 示例代碼:
    • 2、UUID
      • 簡介:
      • 使用:
    • 3、加鹽
      • 原理:
      • 示例代碼:
    • 4、jwt
      • 認知:
      • JWT 結構:
      • 範例代碼:
【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例

加密

1、MD5加密

  • Message Digest Algorithm MD5(中文名為消息摘要算法第五版)
  • 應用程式的密碼通常不會明文儲存,會使用各種各樣的加密算法對密碼進行加密
  • MD5算法相對來說較為安全。
  • 初始的MD5算法是由C語言實作
  • Java版本的MD5算法是根據C語言的MD5算法演變而來的
  • MD5加解密線上工具:http://www.cmd5.com/

安全通路認證

當使用者登入的時候,系統把使用者輸入的密碼進行MD5 Hash運算,然後再去和儲存在檔案系統中的MD5值進行比較,進而确定輸入的密碼是否正确。這可以避免使用者的密碼被具有系統管理者權限的使用者知道。

MD5将任意長度的“位元組串”映射為一個128bit的大整數,并且是通過該128bit反推原始字元串是困難的,換句話說就是,即使你看到源程式和算法描述,也無法将一個MD5的值變換回原始的字元串,從數學原理上說,是因為原始的字元串有無窮多個,這有點象不存在反函數的數學函數。

正是因為這個原因,現在被黑客使用最多的一種破譯密碼的方法就是一種被稱為"跑字典"的方法。有兩種方法得到字典,一種是日常搜集的用做密碼的字元串表,另一種是用排列組合方法生成的,先用MD5程式計算出這些字典項的MD5值,然後再用目标的MD5值在這個字典中檢索。我們假設密碼的最大長度為8位位元組(8 Bytes),同時密碼隻能是字母和數字,共26+26+10=62個位元組,排列組合出的字典的項數則是P(62,1)+P(62,2)….+P(62,8),那也已經是一個很天文的數字了,存儲這個字典就需要TB級的磁盤陣列,而且這種方法還有一個前提,就是能獲得目标賬戶的密碼MD5值的情況下才可以。

示例代碼:

md5加密工具類:

  • 對字元串md5加密(小寫+數字)getMD5()
  • 對字元串md5加密(大寫+數字)toMD5()
public class MD5Util {
  /**
    * 對字元串md5加密(小寫+數字)
    *
    * @param str傳入要加密的字元串
    * @return MD5加密後的字元串
    */
  public static String getMD5(String source) {
    try {
      // 生成一個MD5加密計算摘要
      MessageDigest md = MessageDigest.getInstance("MD5");
      // 計算md5函數
      md.update(source.getBytes());
      // digest()最後确定傳回md5 hash值,傳回值為8為字元串。因為md5 hash值是16位的hex值,實際上就是8位的字元
      // BigInteger函數則将8位的字元串轉換成16位hex值,用字元串來表示;得到字元串形式的hash值
      return new BigInteger(1, md.digest()).toString(16);
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }
  /**
    * 對字元串md5加密(大寫+數字)
    *
    * @param str傳入要加密的字元串
    * @return MD5加密後的字元串
    */
  public static String toMD5(String source) {
    char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    try {
      byte[] btInput = source.getBytes();
      // 獲得MD5摘要算法的 MessageDigest 對象
      MessageDigest mdInst = MessageDigest.getInstance("MD5");
      // 使用指定的位元組更新摘要
      mdInst.update(btInput);
      // 獲得密文
      byte[] md = mdInst.digest();
      // 把密文轉換成十六進制的字元串形式
      int j = md.length;
      char str[] = new char[j * 2];
      int k = 0;
      for (int i = 0; i < j; i++) {
        byte byte0 = md[i];
        str[k++] = hexDigits[byte0 >>> 4 & 0xf];
        str[k++] = hexDigits[byte0 & 0xf];
      }
      return new String(str);
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }
}
           

自定義類-測試方法:模拟前端使用者輸入和後端資料庫MD5加密比對

public static void main(String[] args) {
		//設定一個密碼
		String password = "123456";
		System.out.println("MD5加密格式(大寫+數字):" + MD5Util.toMD5(password);
		System.out.println("MD5加密格式(小寫+數字):" + MD5Util.getMD5(password));
		
		//模拟後端資料庫密碼經過MD5加密
		String pwd_database = MD5Util.toMD5("123456");
		//模拟前端使用者輸入密碼經過MD5加密
		String pwd_input = "123456";
		//列印
		System.out.println("前端使用者輸入和後端資料庫MD5加密比對:" + MD5Util.toMD5(pwd_input).equals(pwd_database));
	}
}
           
【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例
可以看到密碼明文一緻的時候,加密的資訊也是一緻的,是以可以後端儲存加密資訊,然後将使用者輸入的密碼明文進行MD5加密處理,來與後端資料庫進行比對,作為一個簡單的密碼保護。

2、UUID

簡介:

UUID 是 通用唯一識别碼(Universally Unique Identifier)的縮寫,是一種軟體建構的标準,亦為開放軟體基金會組織在分布式計算環境領域的一部分。其目的,是讓分布式系統中的所有元素,都能有唯一的辨識資訊,而不需要通過中央控制端來做辨識資訊的指定。如此一來,每個人都可以建立不與其它人沖突的UUID。在這樣的情況下,就不需考慮資料庫建立時的名稱重複問題。最廣泛應用的UUID,是微軟公司的全局唯一辨別符(GUID),而其他重要的應用,則有Linux ext2/ext3檔案系統、LUKS加密分區、GNOME、KDE、Mac OS X等等。另外我們也可以在e2fsprogs包中的UUID庫找到實作。

對UUID的詳細認識:https://baike.baidu.com/item/UUID/5921266?fr=aladdin

使用:

在ColdFusion中可以用CreateUUID()函數很簡單地生成UUID,其格式為:xxxxxxxx-xxxx- xxxx-xxxxxxxxxxxxxxxx(8-4-4-16),其中每個 x 是 0-9 或 a-f 範圍内的一個十六進制的數字。

而标準的UUID格式為:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)

public static void main(String[] args) {
    System.out.println(UUID.randomUUID());   //每次生成的編碼不一樣
}
           
【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例

結果串是用“-”來連接配接的,可以将它替換:

UUID uuid = UUID.randomUUID();   //生成結果串
String str = uuid.toString();   //結果串轉換為String類型
String temp = str.replace("-","");   //将連接配接符“-”去掉
System.out.println(temp);
           
【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例

3、加鹽

原理:

我們通常會将使用者的密碼進行 Hash 加密,如果不加鹽,即使是兩層的 md5 都有可能通過彩虹表的方式進行破譯。彩虹表就是在網上搜集的各種字元組合的 Hash 加密結果。而加鹽,就是人為的通過一組随機字元與使用者原密碼的組合形成一個新的字元,進而增加破譯的難度。

原理:

簡單來說:由原來的H( p) 變成了H(p+salt),相當于哈希函數H發生了變化,每次哈希計算使用的salt是随機的。

H如果發生了改變,則已有的彩虹表資料就完全無法使用,必須針對特定的H重新生成,這樣就提高了破解的難度。

示例代碼:

裡面的靜态方法包括:
  • 不加鹽MD5:MD5WithoutSalt()
  • 生成鹽:salt()
  • MD5加鹽:MD5WithSalt()

運作檔案的main方法:

【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例
package com.hz.springboot_crontab;

import java.security.MessageDigest;
import java.util.Random;

/**
     * 散列加密之32位哈希值的MD5算法,調用JDK裡的API
     *ps:準确來說散列加密不是加密算法,因為它是不可逆的(隻能加密,不能解密)
     */
    public class MyMD5 {

        private static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

        public static void main(String[] args) throws Exception {
            String input = "123456789";
            System.out.println("MD5加密" + "\n"
                    + "明文:" + input + "\n"
                    + "無鹽密文:" + MD5WithoutSalt(input));
            System.out.println("帶鹽密文:" + MD5WithSalt(input,0));
        }

        /**
         *@params: [inputStr] 輸入明文
         *@Descrption: 不加鹽MD5
         */
        public static String MD5WithoutSalt(String inputStr) {
            try {
            	//聲明使用MD5算法,更改參數為"SHA"就是SHA算法了
                MessageDigest md = MessageDigest.getInstance("MD5");
                return byte2HexStr(md.digest(inputStr.getBytes()));//哈希計算,轉換輸出
            } catch (Exception e) {
                e.printStackTrace();
                return e.toString();
            }
        }

        /**
         *@params: [inputStr, type] inputStr是輸入的明文;type是處理類型,0表示注冊存hash值到庫時,1表示登入驗證時
         *@Descrption:  MD5加鹽,鹽的擷取分兩種情況;輸入明文加鹽;輸出密文帶鹽(将salt存儲到hash值中)
         */
        public static String MD5WithSalt(String inputStr, int type) {
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                
                String salt = null;
                if (type == 0) {//注冊存hash值到庫時,new salt
                    salt = salt();   
                } else if (type == 1) {//登入驗證時,使用從庫中查找到的hash值提取出的salt
                    String queriedHash=null;//從庫中查找到的hash值
                    salt=getSaltFromHash(queriedHash);
                }

                String inputWithSalt = inputStr + salt;//加鹽,輸入加鹽
                String hashResult = byte2HexStr(md.digest(inputWithSalt.getBytes()));//哈希計算,轉換輸出
                System.out.println("加鹽密文:"+hashResult);

                /*将salt存儲到hash值中,用于登陸驗證密碼時使用相同的鹽*/
                char[] cs = new char[48];
                for (int i = 0; i < 48; i += 3) {
                    cs[i] = hashResult.charAt(i / 3 * 2);
                    cs[i + 1] = salt.charAt(i / 3);//輸出帶鹽,存儲鹽到hash值中;每兩個hash字元中間插入一個鹽字元
                    cs[i + 2] = hashResult.charAt(i / 3 * 2 + 1);
                }
                hashResult = new String(cs);
                return hashResult;
            } catch (Exception e) {
                e.printStackTrace();
                return e.toString();
            }
        }


        /**
         * @return: salt
         * @params:
         * @Descrption: 自定義簡單生成鹽,是一個随機生成的長度為16的字元串,每一個字元是随機的十六進制字元
         */
        public static String salt() {
            Random random = new Random();
            StringBuilder sb = new StringBuilder(16);
            for (int i = 0; i < sb.capacity(); i++) {
                sb.append(hex[random.nextInt(16)]);
            }
            return sb.toString();
        }

        /**
         * @return: 十六進制字元串
         * @params: [bytes]
         * @Descrption: 将位元組數組轉換成十六進制字元串
         */
        private static String byte2HexStr(byte[] bytes) {
            /**
             *@Author: DavidHuang
             *@Time: 19:41 2018/5/10
             *@return: java.lang.String
             *@params:  * @param bytes
             *@Descrption:
             */
            int len = bytes.length;
            StringBuffer result = new StringBuffer();
            for (int i = 0; i < len; i++) {
                byte byte0 = bytes[i];
                result.append(hex[byte0 >>> 4 & 0xf]);
                result.append(hex[byte0 & 0xf]);
            }
            return result.toString();
        }


        /**
         *@return: 提取的salt
         *@params: [hash] 3i byte帶鹽的hash值,帶鹽方法與MD5WithSalt中相同
         *@Descrption:  從庫中查找到的hash值提取出的salt
         */
        public static String getSaltFromHash(String hash){
            StringBuilder sb=new StringBuilder();
            char [] h=hash.toCharArray();
            for(int i=0;i<hash.length();i+=3){
                sb.append(h[i+1]);
            }
            return sb.toString();
        }

}
           

4、jwt

【好文章要分享:https://zhuanlan.zhihu.com/p/433674847,有圖檔了解内部原理】

認知:

什麼是jwt?
  • JWT隻是縮寫,全拼則是

    JSON Web Tokens

    ,是目前流行的

    跨域認證

    解決方案,一種基于JSON的、用于在網絡上聲明某種主張的令牌(token)。
    【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例
jwt原理:
  • jwt驗證方式是将使用者資訊通過

    加密生成token

    ,每次請求服務端隻需要使用儲存的密鑰驗證token的正确性,不用再儲存任何session資料了,進而服務端變得無狀态,容易實作拓展。

JWT 使用:

1、服務端根據使用者登入狀态,将使用者資訊加密到token中,返給用戶端

2、用戶端收到服務端傳回的token,存儲在cookie中

3、用戶端和服務端每次通信都帶上token,可以放在http請求頭資訊中,如:Authorization字段裡面

4、服務端解密token,驗證内容,完成相應邏輯

JWT 特點:
  • JWT更加簡潔,更适合在HTML和HTTP環境中傳遞
  • JWT适合一次性驗證,如:激活郵件
  • JWT适合無狀态認證
  • JWT适合服務端CDN分發内容
  • 相對于資料庫Session查詢更加省時
  • JWT預設不加密
  • 使用期間不可取消令牌或更改令牌的權限
  • JWT建議使用HTTPS協定來傳輸代碼

JWT 結構:

  • 一個token分為3部分:頭部(header)、載荷(payload)、簽名(signature)
  • 三部分用

    點.

    隔開
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
           
  • header裡面包含兩部分資訊,token的類型,算法的名稱
    【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例
  • Payload
    【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例
  • Signature簽名:将加密之後的上面兩部分用點拼接的再次加密
    【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例

範例代碼:

jar:jdk大于1.8的版本會報錯,要導入其他依賴

<!--jwt依賴-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
        <!--因為jdk的版本過高要加入的依賴,避免報錯;  jdk1.8可以不加-->
<!--        <dependency>-->
<!--            <groupId>javax.xml.bind</groupId>-->
<!--            <artifactId>jaxb-api</artifactId>-->
<!--            <version>2.3.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.sun.xml.bind</groupId>-->
<!--            <artifactId>jaxb-impl</artifactId>-->
<!--            <version>2.3.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.sun.xml.bind</groupId>-->
<!--            <artifactId>jaxb-core</artifactId>-->
<!--            <version>2.3.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>javax.activation</groupId>-->
<!--            <artifactId>activation</artifactId>-->
<!--            <version>1.1.1</version>-->
<!--        </dependency>-->
           

JWTTest類:加密解密的方法

/**
 * jwt加密解密測試代碼
 */
public class JWTTest {

    private long time = 1000*60*60*24;   //設定24小時之後失效
    private String signature = "admin";  //設定一個簽名資訊,加密解密都要通過他

    /**
     * 加密
     */
    @Test
    public void jwt() {
        //建立JwtBuilder對象
        JwtBuilder jwtBuilder = Jwts.builder();
        String jwtToken = jwtBuilder
                //header
                .setHeaderParam("typ","JWT")
                .setHeaderParam("alg","HS256")
                //payload
                .claim("username","Jules")
                .claim("role","admin")
                .setSubject("admin-test")   //主題
                .setExpiration(new Date(System.currentTimeMillis()+time))   //有效時間
                .setId(UUID.randomUUID().toString())
                //signature
                .signWith(SignatureAlgorithm.HS256,signature)
                //将上面三部分拼起來
                .compact();
        System.out.println(jwtToken);
    }

    /**
     * 解密
     */
   ……
}
           

加密後得到username和role的token

【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例
/**
 * 解密
 */
 @Test
    public void parse() {
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9………………";  //我省略了,複制自己的
        JwtParser jwtParser = Jwts.parser();
        Jws<Claims> claimsJws = jwtParser.setSigningKey(signature).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();   //加密封裝的東西都放到了claims裡面
        //直接取:
        System.out.println(claims.get("username"));
        System.out.println(claims.get("role"));
        System.out.println(claims.getId());
        System.out.println(claims.getSubject());
        System.out.println(claims.getExpiration());

    }
           

将原來的資料解密:

【密碼學】MD5、UUID,加鹽,JWT的了解與使用範例