相关文章推荐
含蓄的汽水  ·  Element ...·  11 月前    · 
飘逸的小摩托  ·  java - ...·  1 年前    · 

MD5(Message Digest Algorithm 5)是一种广泛使用的加密哈希函数,可将任意长度的消息转换为128位的哈希值(通常以32个十六进制字符表示)。MD5 算法是一种单向加密算法,也就是说只能进行加密,无法解密。因此,它常用于密码的加密存储和数字签名验证等场景。

MD5 算法的主要原理是通过多次对输入数据进行压缩、迭代和变换,最终生成一个哈希值。MD5 算法的具体实现包括以下几个步骤:

  • 填充消息:首先需要对消息进行填充,使其长度满足一个特定的条件(例如长度为64的倍数),以便于后续的处理。
  • 初始值设置:设置4个32位的寄存器(A、B、C、D)的初始值,作为哈希值的组成部分。
  • 消息分块:将填充后的消息按照长度为512位的块进行分组,每组包含16个32位的字。
  • 循环迭代:对每个消息块进行循环迭代,共进行64轮,每轮都包含4个变换步骤。
  • 输出结果:最后将4个寄存器的值连接起来,形成128位的哈希值。
  • MD5 算法被广泛应用于互联网安全领域,例如密码加密、数字签名、消息认证等。但是,由于 MD5 算法存在一些漏洞,例如碰撞攻击,因此在一些安全性要求更高的场合,如用户密码存储,建议使用更为安全的加密算法,例如 SHA-2 系列的哈希函数。

    彩虹表攻击 :彩虹表攻击是一种密码破解方法,通常用于破解哈希函数加密的密码。彩虹表是一种预先计算好的表,其中包含了大量可能的明文和它们对应的哈希值。攻击者使用彩虹表可以快速地反推出哈希值对应的明文密码。这种攻击方法相对于暴力破解等其他破解方法来说速度非常快,因为攻击者不需要每次都重新计算哈希值,而是直接在预先计算好的彩虹表中查找匹配。

    简单来说,MD5 加密是单向的,是没法从加密后的密文解密得到明文的。因此主要的应用就是密码的密文保存、对比数据是否被篡改。

    MD5Util

    MD5 加密的应用比较简单,放一个 MD5Util 看一下:

    public class MD5Util {
        public static String encipherHeader(String loginName, String loginPwd, String timestamp) {
            //String timestampStr = Long.toString(timestamp);
            StringBuffer sb = new StringBuffer();
            sb.append("appkey=");
            sb.append(loginName);
            sb.append(",secret=RSA,signt=");
            sb.append(timestamp);
            sb.append(",secretKey=");
            sb.append(loginPwd);
            String header = sb.toString();
            String md5Header = md5Encipher(header);
            return md5Header;
        public static String md5Encipher(String str){
            String md5Str = null;
            StringBuffer sb = new StringBuffer();
            try {
                // 创建 MessageDigest 对象并指定算法为 MD5
                MessageDigest md = MessageDigest.getInstance("MD5");
                // 将字符串转换为字节数组并进行加密
                byte[] md5Bytes = md.digest(str.getBytes());
                // 将字节数组转换为字符串
                for (byte b : md5Bytes) {
                    sb.append(String.format("%02x", b));
                md5Str = sb.toString();
                System.out.println("MD5 加密前原始数据:" + str);
                System.out.println("MD5 加密结果:" + md5Str);
            } catch (NoSuchAlgorithmException e) {
                System.err.println("不支持的加密算法:MD5");
                e.printStackTrace();
            return md5Str;
    

    其中 md5Encipher 是对字符串进行加密的方法,headerEncipher 是我自己要用的对 HTTPHeader 加密的方法。

    写一个 Main 方法测试一下加密:

        public static void main(String[] args) {
            // 接收对方传来的 MD5 加密后的密文
            String pwd = "4d7ad62f7d74f7defd845740661f53e2";
            // 根据对方传来的数据计算 MD5 加密的结果
            String encipher = MD5Util.md5Encipher("appkey=1234567890,secret=RSA,signt=1637109703238,secretKey=abcdefghijklmnopqrstuvwxyz");
            System.out.println("加密结果:" + encipher);
            if(pwd.equals(encipher)){
                // 两次结果一致,数据没有被篡改/签名有效
                System.out.println("数据相等");
            }else {
                // 两次结果不一致,数据被篡改了/签名无效
                System.out.println("数据不相等");
    

    直接写在工具类里,运行,控制台输出:

    MD5 加密前原始数据:appkey=1234567890,secret=RSA,signt=1637109703238,secretKey=abcdefghijklmnopqrstuvwxyz
    MD5 加密结果:4d7ad62f7d74f7defd845740661f53e2
    加密结果:4d7ad62f7d74f7defd845740661f53e2
    ==========数据相等==========
    进程已结束,退出代码0
    

    验证成功,没什么问题。

    RSA是一种公钥加密算法,其加密和解密过程都需要用到两个密钥,一个是公钥,另一个是私钥。公钥可以自由发布,私钥则是保密的。

    RSA的安全性基于大数分解难题,也就是说,对于一个足够大的整数,要找到它的所有质因数是非常困难的。RSA算法的核心是选择两个大质数p和q,然后计算它们的乘积N=p * q。然后根据一定的算法计算出一个公钥和一个私钥。

    公钥包含两个参数N和e,私钥包含两个参数N和d。其中,N为大质数p和q的乘积,e是一个小于N的正整数,且e与(p-1) * (q-1)互质,d是e的模逆元,满足 (e * d) % ((p-1) * (q-1)) = 1。

    RSA加密过程如下:

  • 接收方生成一对密钥,其中一个是公钥,公开给发送方;另一个是私钥,保存在自己手中。
  • 发送方获取接收方的公钥,将明文进行加密后发送。
  • 接收方接收到密文后,使用自己的私钥进行解密,得到明文。
  • RSA算法的优点是安全性高,加密速度较快,可以用于数据的加密、数字签名等方面。缺点是加密和解密的速度较慢,且加密的数据长度有限制。

    RSA 加密在网上找了找都是讲 RSA 算法的,或者应用起来比较复杂。所以搞个简单的工具类,万一以后还要用到就可以直接用了。

    RSAUtil

    也是放一个 RSAUtil,其中包括了生成公钥私钥、使用公钥加密、使用私钥解密的方法:

    public class RSAUtil {
        //密钥长度
        private final static int KEY_SIZE = 1024;
        //保存产生的公钥与私钥
        private static Map<String, String> keyMap = new HashMap<String, String>();
         * RSA公钥加密
         * @param str       加密字符串
         * @param publicKey 公钥
         * @return 密文
         * @throws Exception
        public static String encrypt(String str, String publicKey) throws Exception {
            // 将公钥解码为 base64
            byte[] decoded = decryptBASE64(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
            // RSA加密
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            String outStr = encryptBASE64(cipher.doFinal(str.getBytes("UTF-8")));
            return outStr;
         * RSA私钥解密
         * @param str        加密字符串
         * @param privateKey 私钥
         * @return 明文
         * @throws Exception
        public static String decrypt(String str, String privateKey) throws Exception {
            //64位解码加密后的字符串
            byte[] inputByte = decryptBASE64(str);
            //base64编码的私钥
            byte[] decoded = decryptBASE64(privateKey);
            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
            //RSA解密
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            String outStr = new String(cipher.doFinal(inputByte));
            return outStr;
        // 编码返回字符串
        public static String encryptBASE64(byte[] key) throws Exception {
            return (new BASE64Encoder()).encodeBuffer(key);
        // 解码返回byte
        public static byte[] decryptBASE64(String key) throws Exception {
            return (new BASE64Decoder()).decodeBuffer(key);
         * 随机生成密钥对
         * @throws Exception
        public static void generateKeyPair() throws Exception {
            // KeyPairGenerator 基于RSA算法,用于生成一对公钥和私钥
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
            // 初始化密钥对生成器
            keyPairGen.initialize(KEY_SIZE, new SecureRandom());
            // 生成一个密钥对,保存在keyPair中
            KeyPair keyPair = keyPairGen.generateKeyPair();
            // 获取公钥
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            // 获取私钥
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            // 获取公钥字符串
            String publicKeyString = encryptBASE64(publicKey.getEncoded());
            // 得到私钥字符串
            String privateKeyString = encryptBASE64(privateKey.getEncoded());
            // 保存公钥和私钥
            keyMap.put("pubKey", publicKeyString);
            keyMap.put("privateKey", privateKeyString);
    

    其中用到了 common-codec 的依赖:

            <dependency>
                <groupId>commons-codec</groupId>
                <artifactId>commons-codec</artifactId>
                <version>1.15</version>
            </dependency>
    

    写一个 Main 方法测试一下获取密钥和加密解密的过程:

        public static void main(String[] args) {
            try {
                // 生成公钥和私钥
                generateKeyPair();
                String publicKey = keyMap.get("pubKey");
                System.out.println("公钥:" + publicKey);
                String privateKey = keyMap.get("privateKey");
                System.out.println("私钥:" + privateKey);
                String orgData = "然而也应当记住黑暗的时日";
                System.out.println("原始数据:" + orgData);
                String encryptStr = encrypt(orgData,publicKey);
                System.out.println("加密数据:" + encryptStr);
                String decryptStr = decrypt(encryptStr,privateKey);
                System.out.println("解密数据:" + decryptStr);
                if(orgData.equals(decryptStr)){
                    System.out.println("==========原始数据与解密数据一致==========");
                }else {
                    System.out.println("==========原始数据与解密数据不一致==========");
            } catch (Exception e) {
                e.printStackTrace();
    

    也是直接在工具类里写的,运行,控制台输出:

    公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/+dP4fJbr4EjhLxjUMcI7zOpnOES5VBnM03Uf
    w5yuPglwc2UVeV0B12AC3GIwTZ/94Ohf0+a3g9ecx6CVEzyYeuIfnOjXhM7waVZdR8LcbwsN8kgX
    92lpc2qNSquMRU57Wt/cxSoarBXyh1DcCRxzQFACEc04voeVRHzuB6QKZwIDAQAB
    私钥:MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL/50/h8luvgSOEvGNQxwjvM6mc4
    RLlUGczTdR/DnK4+CXBzZRV5XQHXYALcYjBNn/3g6F/T5reD15zHoJUTPJh64h+c6NeEzvBpVl1H
    wtxvCw3ySBf3aWlzao1Kq4xFTnta39zFKhqsFfKHUNwJHHNAUAIRzTi+h5VEfO4HpApnAgMBAAEC
    gYBLBwt1yNN++hfhkfOFMrEzh+FwV8hcGec/asESmfOJEYvE3AR8gQL9bjwCwjjJofzOTvDiSsGX
    pTpF9qrmuC7sxqKN+xVdAJzoTIzUSXd/Zd/RLhxDWHLSppyJ5c1n1pikDKrAkUqfWnp2gjMUT4Pd
    pU4WH/W7IFV9H0o+AOCbcQJBAPV/KN871ydNkwQL8+zu2L3iXTHMWKmEv40AXI+vh9c5iS08fiJi
    VL3TQjC/W8SQ4Emj2lF48XSIdTO7gPFF7I8CQQDIMHkrFjeWYyk6QuCC5mV9F95RyQDk0yvrcLtg
    aEvA9wZVv/FcZOq7I2ejcYWt1wi99sXpFwdZ0oiI3GZFByCpAkEAo+RIfP+OG4cGZuUz6zFpMRs1
    7FDnwAQHfTKImMQug9i9Y53G911+BVxMDA80TH4Lvh3NWibLy2huFiNPacOssQJAU2fmw+3gwRaV
    ccG1WrR1alYMeZS+e5gD/3cbioJJtZ72E7oB7JXbOpb4sh81LAWgjc0IDiJbHLBb1HHHZlEe6QJB
    ANzDR19vzmit+miwFkNbVfCPSFX0oKbWZaNfkJ+zmg4scCDppny4S2pxZQ+zyOGATXUlJH3YvHd2
    lqDpIbXxong=
    原始数据:然而也应当记住黑暗的时日
    加密数据:fi//U40WewohsDAva8u0j71pzwOsIoCAxpR1aTvNr7HnU3pmU1MmePonePePab0cZBwIvl/3TQyV
    GLduPS2XOdIIqKrT28bXLL02f7UXkFCICrD/JiotClG6gbKOtEKCpgOIKKoLEXA7RPDLzw7QFSjz
    8m9z4s3/uHjrCcaaTwY=
    解密数据:然而也应当记住黑暗的时日
    ==========原始数据与解密数据一致==========
    进程已结束,退出代码0
    

    没什么问题,加解密能够应用就行了,其中的原理不必深究了。。。