加密 笔记

简单异或加密

异或运算特点:

  1. 两个二进制数字相同为0,不同为1
  2. 一个数字两次异或后,得到的是原数字本身

参与运算的参数必须要转化成二进制后才能参与运算

package com.ung.myEncryptor.utils;

/**
 * @author: wenyi
 * @create: 2022/9/24
 * @Description: 异或加密
 */
public class XORUtils {

    /**
     * 加密算法及秘钥
     *
     * @param content
     * @return
     */
    public static String xor(String content, Integer key) {
        char[] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i] = (char) (chars[i] ^ key);
        }
        return new String(chars);
    }


    public static void main(String[] args) {
        String content = "异或加密你好";
        Integer key = 1000;

        // 用户A发消息前,通过某种方式加密
        String encryptStr = xor(content, key);
        System.out.println(encryptStr);

        // 密文通过网络传输

        // 用户B收到密文后使用相同的方式解密
        String decryptStr = xor(encryptStr, key);
        System.out.println(decryptStr);
    }
}

结果:

峪懾先堮䲈媕
异或加密你好

对称加密

对称加密

在这里插入图片描述

优点:

  • 速度快。相对于非对称加密,对称加密的性能更好,加解密速度更快。
  • 安全。只能说相对还是安全的。
  • 紧凑。加密后内容的长度基本变化不大。

缺点:

  • 如果双方通讯时,通过明文传输共享密钥,容易出现中途劫持和窃听的问题。
  • 随着通讯的参与者数量的增加,密钥数量急剧膨胀((n×(n-1))/2)。
  • 因为密钥数量过多,对密钥的管理和存储是一个很大的问题(后面我会专门开一期说秘钥管理及系统设计)。
  • 不支持数字签名和不可否认性。

DES加密

DES(Data Encryption Standard) 算法是美国政府机关为了保护信息处理中的计算机数据而使用的一种加密方式,是一种常规密码体制的密码算法,目前已广泛使用。

该算法输入的是64比特的明文,在64比特密钥的控制下产生64比特的密文;反之输入64比特的密文,输出64比特的明文。64比特 的密钥中含有8个比特的奇偶校验位,所以实际有效密钥长度为56比特。使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行"异或"运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 个循环,使用异或,置换,代换,移位操作四种基本运算。

秘钥 不能小于8位,超过8位的部分无效

package com.ung.myEncryptor.utils;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.Key;
import java.util.Base64;

/**
 * @author: wenyi
 * @create: 2022/9/24
 * @Description: DES加密
 */
public class DESUtils {
    /**
     * 偏移变量,固定占8位字节
     */
    private final static String IV_PARAMETER = "12345678";
    /**
     * 加密算法
     */
    private static final String ALGORITHM = "DES";
    /**
     * 加密/解密算法-工作模式-填充模式
     */
    private static final String CIPHER_ALGORITHM = "DES/CBC/PKCS5Padding";
    /**
     * 默认编码
     */
    private static final String CHARSET = "utf-8";
    /**
     * 秘钥
     * 不能小于8位,超过8位的部分无效
     */
    private static final String KEY = "12345678";


    public static void main(String[] args) {
        String content = "abc123";

        String encptStr = encrypt(KEY, content);
        System.out.println(encptStr);

        String decptStr = decrypt(KEY, encptStr);
        System.out.println(decptStr);
    }

    /**
     * 生成key
     *
     * @param password
     * @return
     * @throws Exception
     */
    private static Key generateKey(String password) throws Exception {
        DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
        return keyFactory.generateSecret(dks);
    }


    /**
     * DES加密字符串
     *
     * @param password 加密密码,长度不能够小于8位
     * @param data     待加密字符串
     * @return 加密后内容
     */
    public static String encrypt(String password, String data) {
        if (password == null || password.length() < 8) {
            throw new RuntimeException("加密失败,key不能小于8位");
        }
        if (data == null)
            return null;
        try {
            Key secretKey = generateKey(password);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);

            // 加密
            byte[] bytes = cipher.doFinal(data.getBytes(CHARSET));

            // base64编码  JDK1.8及以上可直接使用Base64,JDK1.7及以下可以使用BASE64Encoder
            byte[] encode = Base64.getEncoder().encode(bytes);

            return new String(encode);

        } catch (Exception e) {
            e.printStackTrace();
            return data;
        }
    }

    /**
     * DES解密字符串
     *
     * @param password 解密密码,长度不能够小于8位
     * @param data     待解密字符串
     * @return 解密后内容
     */
    public static String decrypt(String password, String data) {
        if (password == null || password.length() < 8) {
            throw new RuntimeException("加密失败,key不能小于8位");
        }
        if (data == null)
            return null;
        try {
            Key secretKey = generateKey(password);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);

            // base64解码
            byte[] decode = Base64.getDecoder().decode(data.getBytes(CHARSET));

            // 解密
            byte[] decrypt = cipher.doFinal(decode);

            return new String(decrypt, CHARSET);
        } catch (Exception e) {
            e.printStackTrace();
            return data;
        }
    }
}

DES是一种分组数据加密技术,(先将数据分成固定长度的小数据块,之后进行加密),速度较快,适用于大量数据加密,比如文件加密

1997 年RSA数据安全公司发起了一项“DES 挑战赛”的活动,志愿者四次分别用四个月、41天、56个小时和22个小时破解了其用56bit DES算法加密的密文。即DES加密算法在计算机速度提升后的今天被认为是不安全的。所以针对保密级别特别高的数据推荐使用非对称加密算法。

不安全,还是可以被破解

AES加密

高级加密标准(Advanced Encryption Standard)

一种区块加密标准,使用128,192和256位密钥,可以用128位分组加密解密数据,相对来说安全很多

这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

1.简单的加密解密逻辑

  • AES的加密是对称加密的 密钥可以是128位,192位,256位长度
  • 加密方式是将原明文拆分成不用的区块,进行加密。

例如:一个长256位的数据使用128的密钥加密的过程

明文1(128位) 明文2(128位)

加密

密文1(128位)密文2(128位)

注意:

  • 如果明文不是128位(16字节)的需要填充,也就是在明文莫个地方补充到相应的16字节长度
  • 加密解密需要相同的填充方式,否则无法成功加解密

2.填充方式

  1. noPadding

    不进行填充,要求明文必须是16字节的整数倍,可以有使用者字节实现填充。

    除了此模式以外的其他填充模式,如果已经是16字节整数倍,会再填充一个16字节的数据。

  2. PKCS5Padding(默认)

    在明文的末尾进行填充,填充的数据是当前和16字节相差的数量;

    例如:

    未填充明文:

    1,2,3,4,5,6,7,8,9,10,11

    填充后明文:(缺少五个满足16个字节,如果是缺少6个,填充的就是6)

    1,2,3,4,5,6,7,8,9,10,11,5,5,5,5,5

    由于使用PKCS7Padding/PKCS5Padding填充时,最后一个字节肯定为填充数据的长度,所以在解密后可以准确删除填充的数据

  3. ISO10126Padding

    在明文末尾随机填充,补齐16字节

    例如:

    未填充明文
    1,2,3,4,5,6,7,8,9,10,11

    填充明文(缺少五个满足16个字节)
    1,2,3,4,5,6,7,8,9,10,11,c,b,4,1,5

3.加密模式

AES对明文加密的5种模式,加密步骤上不同,解密需要相同的模式解密,否则无法成功

1、ECB模式(默认)

电码本模式 Electronic Codebook Book

默认的加密模式,根据密钥的位数,将数据原文分为不同的区块进行加密,加密完后再拼接起来

过程:

​ 明文 (64字节) 密钥(16字节)

​ 进行拆分

​ 明文1(16字节) 明文2(16字节) 明文3(16字节) 明文4(16字节)

​ 拆分的区块加密

​ 密文1(16字节) 密文2(16字节) 密文3(16字节) 密文4(16字节)

​ 加密后拼接

​ 密文 (64字节)

优点:简单、快速、可并行

缺点:如果明文块相同,则生成的密文块也是相同的,降低安全性

(即明文1和明文2是一样的内容,呐生成的密文1和密文2也是一样的内容)

2、CBC模式

密码分组链接模式 Cipher Block Chaining

为了解决ECB的密文块内容相同的缺点,开始引入初始向量,该向量必须与密钥长度一致

在第一次加密前,初始向量先与第一个数据块进行异或运算,对新生成的数据再进行加密;

加密第二块数据前,会拿第一块数据加密后的密文块1与第二块数据进行异或运算,再加密;

后续以此类推,解密时也是再解密后 进行异或运算,解出原文。

过程例如:

​ 明文(63字节) 密钥 (16字节) 初始向量iv(16字节)

​ 明文1(16字节) 明文2(16字节) 明文3(16字节) 明文4+一个0(16字节)

​ 异或 +初始向量 +密文1 +密文2 +密文3

​ 密文1(16字节) 密文2(16字节) 密文3(16字节) 密文4(16字节)

​ 密文(64字节)

注意:

  1. 向量的长度必须与密钥的长度相同
  2. 因为在加密解密前会做异或运算,所以明文可以不用补全,不是16字节的倍数也可以,会自动用0补全进行异或运算
  3. 加密过程是先异或再加密,解密过程是先解密再异或操作
  4. 因为自动补全了0,解密后的数据后会补0,获取到数据后,需要将末尾的0去掉,或根据原明文的长度截取解密操作后的数据

优点:每次对数据块的加密密钥不同,加强了安全性

缺点:

  • 加密过程不能并行运算,解密过程可以并行运算,必须在前一个数据快加密后,才能加密后续的数据块,还会填充0在末尾,不适合流数据(必须要满足16字节长度的原文才不会自动补充0)
  • 如果前面的数据加密出错,会影响后续的数据加密
  • 加密解密两段需要同时约定好初始向量iv
3、CFB模式

密码反馈模式 Cipher FeedBack

只用了加密方法,原理是一个数据异或运算后,在进行一次异或运算,值不发生改变的原理

加密时,如果数据不满足一个密钥的字节,那么值做保存,待满足一个密钥的字节后进行再加密

过程:

加密:

​ 明文(260个字节) iv(128个字节)

​ 明文1(128个字节) 明文2(128个字节) 明文3(4个字节)

​ (iv+key)异或 明文1 (密文1+key)异或 明文1 (密文1+key)异或明文3

​ 密文1(128个字节) 密文2(128个字节) 密文3(4个字节)

解密:

​ 密文(260个字节) iv(128个字节)密钥(128字节)

​ 密文1(128个字节) 密文2(128个字节) 密文3(4个字节)

​ (iv+key)异或密文1 (密文1+key)异或密文2 (密文1+key)异或密文3

​ 明文1 (128个字节) 明文2 (128个字节) 明文3(4个字节)

注意:

  1. 加解密会返回一个num,表示还需要几个数字,才会使用上一个密文加密,否则一直使用上一个
  2. 加解密需要传入字符串的长度
  3. 解密时也是使用密文解密,没有使用上一次解密的明文,所以可以并行处理
  4. CFB模式不需要补全不足密钥的数据,可以适合流传输

优点:可以并行,可以传入非16字节倍数长度的数据

4、OFB模式

输出反馈模式 Output FeedBack

与CFB模式相似,将iv或上一个iv加密后的数据加密,生成的key再与明文异或运算,解密也是相同方式

加密/解密:

​ CFB:
​ (iv+key)异或 明文1 (密文1+key)异或 明文1 (密文1+key)异或明文3

​ OFB
​ (iv+key)异或明文1 ((iv+key)+key)异或明文1 (((iv+key)+key)+key)异或明文3

优点:和CFB一样可以传输流数据

缺点:依赖上一次的加密结果,不能并行处理

5、CTR模式

计算器模式 Counter

解决OFB不能并行的缺点,将(iv+key)+key 替换成(iv+1)+key,这样就不需要依赖上一次加密结果

​ OFB
​ (iv+key)异或明文1 ((iv+key)+key)异或明文1 (((iv+key)+key)+key)异或明文3

​ CTR
​ (iv+key)异或明文1 ((iv+1)+key)异或明文1 (((iv+1)+1)+key)异或明文3

​ 优点:由于加解密可以并行,因此CTR模式的加解密速度也很快

​ 缺点:iv+1的获取比较负责,需要获取瞬时iv

代码案例

ECB加密和CBC加密

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

/**
 * @Description: AES加解密
 */
public class AESUtils {
    //ECB的加密方式
    private static final String ECB_TYPE = "AES/ECB/PKCS5Padding";
    //CBC的加密方式
    private static final String CBC_TYPE = "AES/CBC/NoPadding";

    /**
     * ECB加密方式
     *
     * @param sSrc 需要加密的明文
     * @param sKey 密钥
     * @return 加密后的密文
     * @throws Exception
     */
    public static String ecbEncrypt(String sSrc, String sKey) {
        //判断是否为空和长度是否是16
        if (sKey != null && sKey.length() == 16) {
            byte[] encrypted = new byte[0];
            try {
                SecretKeySpec keySpec = new SecretKeySpec(sKey.getBytes("UTF-8"), "AES");
                Cipher cipher = Cipher.getInstance(ECB_TYPE);
                //加密模式
                cipher.init(Cipher.ENCRYPT_MODE, keySpec);
                encrypted = cipher.doFinal(sSrc.getBytes("UTF-8"));
                //最后用base64加密处理
                return Base64.getEncoder().encodeToString(encrypted);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 解密
     *
     * @param sSrc 解密的密文
     * @param sKey 密钥
     * @return 解密后的明文
     * @throws Exception
     */
    public static String ecbDecrypt(String sSrc, String sKey) {
        //判断是否为空和长度是否是16
        if (sKey != null && sKey.length() == 16) {
            try {
                //先用base64解密处理
                byte[] base64Decode = Base64.getDecoder().decode(sSrc);
                //AES解密处理
                SecretKeySpec keySpec = new SecretKeySpec(sKey.getBytes("UTF-8"), "AES");
                Cipher cipher = Cipher.getInstance(ECB_TYPE);
                //解密模式
                cipher.init(Cipher.DECRYPT_MODE, keySpec);
                byte[] decrypt = cipher.doFinal(base64Decode);
                return new String(decrypt, "UTF-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * AES-128-CBC加密方式
     *
     * @param sSrc 需要加密的明文
     * @param sKey 密钥 16位
     * @param iv   初始向量 16位
     * @return 加密后的密文
     * @throws Exception
     */
    public static String cbcEncrypt(String sSrc, String sKey, String iv) {
        //判断是否为空、长度是否16
        if (sKey != null && sKey.length() == 16 & iv != null & iv.length() == 16) {
            try {
                Cipher cipher = Cipher.getInstance(CBC_TYPE);
                //获取加密的数据块的字节长度
                int blockSize = cipher.getBlockSize();
                byte[] sSrcBytes = sSrc.getBytes("UTF-8");
                //获取加密数据的字节长度
                int sSrcBytesLength = sSrcBytes.length;
                //满足加密数据块的字节的倍数
                if (sSrcBytesLength % blockSize != 0) {
                    sSrcBytesLength = sSrcBytesLength + (blockSize - (sSrcBytesLength % blockSize));
                    /**
                     * 因为模式是不用自动填充,就需要自己控制是数据块字节的倍数
                     * 这里的sSrcBytesLength发生改变,需要一个新的字节数组,长度是变化后的长度,数据是原先的sSrcBytes的数据,补0
                     */
                    byte[] plaintext = new byte[sSrcBytesLength];
                    /**
                     * 也就是将  sSrcBytes 里的数据复制到 plaintext
                     * 从 sSrcBytes作为源数组,从0开始复制,plaintext作为目标数组,复制 sSrcBytes.length长度的数据
                     */
                    System.arraycopy(sSrcBytes, 0, plaintext, 0, sSrcBytes.length);
                    sSrcBytes = plaintext;
                }
                //开始加密
                SecretKeySpec keySpec = new SecretKeySpec(sKey.getBytes("UTF-8"), "AES");
                // CBC模式,需要一个向量iv,可增加加密算法的强
                IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes("UTF-8"));
                cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
                byte[] encrypted = cipher.doFinal(sSrcBytes);
                //base64加密处理
                return Base64.getEncoder().encodeToString(encrypted).trim();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * AES-128-CBC解密
     *
     * @param sSrc 解密的密文
     * @param sKey 密钥
     * @param iv   初始向量 16位
     * @return 解密后的明文
     * @throws Exception
     */
    public static String cbcDecrypt(String sSrc, String sKey, String iv) {
        //判断是否为空和长度是否是16
        if (sKey != null && sKey.length() == 16 & iv != null & iv.length() == 16) {
            try {
                //先用base64解密处理
                byte[] base64Decode = Base64.getDecoder().decode(sSrc);

                //AES解密处理
                Cipher cipher = Cipher.getInstance(CBC_TYPE);
                SecretKeySpec keySpec = new SecretKeySpec(sKey.getBytes("UTF-8"), "AES");
                IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes("UTF-8"));
                //解密模式
                cipher.init(Cipher.DECRYPT_MODE, keySpec, ivspec);
                byte[] decrypt = cipher.doFinal(base64Decode);
                return new String(decrypt, "UTF-8").trim();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

测试

package com.letu.test.utils;


import org.junit.jupiter.api.Test;

public class AESUtilsTest {
    @Test
    public void ecbEncrypt() {
        String data = "123abc";
        System.out.println("data:" + data);

        //16位
        String key = "hj7x89H$yuBI0456";
        String iv = "NIfb&95GUY86Gfgh";

        //ECB模式加密
        String ecbEncrypt = AESUtils.ecbEncrypt(data, key);
        System.out.println("ecbEncrypt:" + ecbEncrypt);
        //ECB解密
        String ecbDecrypt = AESUtils.ecbDecrypt(ecbEncrypt, key);
        System.out.println("ecbDecrypt:" + ecbDecrypt);

        System.out.println("*****************************");

        //CBC加密
        String cbcEncrypt = AESUtils.cbcEncrypt(data, key, iv);
        System.out.println("cbcEncrypt:" + cbcEncrypt);
        //CBC解密
        String cbcDecrypt = AESUtils.cbcDecrypt(cbcEncrypt, key, iv);
        System.out.println("cbcDecrypt:" + cbcDecrypt);
    }
}

相较于DES而言,AES算法有着更高的速度和资源使用效率,安全级别也较之更高了,被称为下一代加密标准。目前世界上还有组织在研究如何攻破AES这堵坚厚的墙,但是因为破解时间太长,AES得到保障,但是所用的时间不断缩小。随着计算机计算速度的增快,新算法的出现,AES遭到的攻击只会越来越猛烈,不会停止的。

AES现在广泛用于金融财务、在线交易、无线通信、数字存储等领域,经受了最严格的考验,但说不定哪天就会步DES的后尘。

非对称加密

事先生成一对密匙(公钥、私钥),然后双方交换公钥

在这里插入图片描述

优点:

  • 安全,适用于对安全性要求较高的数据
  • 由于不必担心交换的公钥被劫持,所以非对称密钥的分发更安全。
  • 密钥数目和参与者数目相同。
  • 在交换公钥之前,不需要预先建立某种信任关系。
  • 支持数字签名和不可否认性。

缺点:

  • 加密速度很慢。要不用来加密上KB的数据,可以用来加密key,key的数据量较小。
  • 加密后,密文会变长。

RSA加密

一种非对称加密算法,通过RSA算法创建一对公钥和私钥,

公钥对明文进行加密,得到后的密文,再使用密钥进行解密

也可以使用密钥加密,然后公钥进行解密

加密使用的是对方的公钥,解密使用的是对应的私钥

加密公钥,解密私钥;

代码

package com.ung.myEncryptor.utils;

import org.apache.commons.io.IOUtils;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class RSAUtils {
    public static final String CHARSET = "UTF-8";
    public static final String RSA_ALGORITHM = "RSA"; // ALGORITHM ['ælgərɪð(ə)m] 算法的意思

    public static Map<String, String> createKeys(int keySize) {
        // 为RSA算法创建一个KeyPairGenerator对象
        KeyPairGenerator kpg;
        try {
            kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
        }

        // 初始化KeyPairGenerator对象,密钥长度
        kpg.initialize(keySize);
        // 生成密匙对
        KeyPair keyPair = kpg.generateKeyPair();
        // 得到公钥
        Key publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        // 得到私钥
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        // map装载公钥和私钥
        Map<String, String> keyPairMap = new HashMap<String, String>();
        keyPairMap.put("publicKey", publicKeyStr);
        keyPairMap.put("privateKey", privateKeyStr);
        // 返回map
        return keyPairMap;
    }

    /**
     * 得到公钥
     *
     * @param publicKey 密钥字符串(经过base64编码)
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 通过X509编码的Key指令获得公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
        RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
        return key;
    }

    /**
     * 得到私钥
     *
     * @param privateKey 密钥字符串(经过base64编码)
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 通过PKCS#8编码的Key指令获得私钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
        RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
        return key;
    }

    /**
     * 公钥加密
     *
     * @param data
     * @param publicKey
     * @return
     */
    public static String publicEncrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.getEncoder().encodeToString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥解密
     *
     * @param data
     * @param privateKey
     * @return
     */

    public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(data), privateKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥加密
     *
     * @param data
     * @param privateKey
     * @return
     */

    public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            //每个Cipher初始化方法使用一个模式参数opmod,并用此模式初始化Cipher对象。此外还有其他参数,包括密钥key、包含密钥的证书certificate、算法参数params和随机源random。
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return Base64.getEncoder().encodeToString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 公钥解密
     *
     * @param data
     * @param publicKey
     * @return
     */

    public static String publicDecrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(data), publicKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    //rsa切割解码  , ENCRYPT_MODE,加密数据   ,DECRYPT_MODE,解密数据
    private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
        int maxBlock = 0;  //最大块
        if (opmode == Cipher.DECRYPT_MODE) {
            maxBlock = keySize / 8;
        } else {
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        try {
            while (datas.length > offSet) {
                if (datas.length - offSet > maxBlock) {
                    //可以调用以下的doFinal()方法完成加密或解密数据:
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                } else {
                    buff = cipher.doFinal(datas, offSet, datas.length - offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
        } catch (Exception e) {
            throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
        }
        byte[] resultDatas = out.toByteArray();
        IOUtils.closeQuietly(out);
        return resultDatas;
    }

    // 简单测试____________
//    public static void main(String[] args) throws Exception {
//        Map<String, String> keyMap = RSAUtils.createKeys(1024);
//        String publicKey = keyMap.get("publicKey");
//        String privateKey = keyMap.get("privateKey");
//        System.out.println("公钥: \n\r" + publicKey);
//        System.out.println("私钥: \n\r" + privateKey);
//        System.out.println("公钥加密——私钥解密");
//        String str = "站在大明门前守卫的禁卫军,事先没有接到\n" + "有关的命令,但看到大批盛装的官员来临,也就\n" + "以为确系举行大典,因而未加询问。进大明门即\n" + "为皇城。文武百官看到端门午门之前气氛平静,\n" + "城楼上下也无朝会的迹象,既无几案,站队点名\n" + "的御史和御前侍卫“大汉将军”也不见踪影,不免\n"
//                + "心中揣测,互相询问:所谓午朝是否讹传?";
//        System.out.println("\r明文:\r\n" + str);
//        System.out.println("\r明文大小:\r\n" + str.getBytes().length);
//        String encodedData = RSAUtils.publicEncrypt(str, RSAUtils.getPublicKey(publicKey));  //传入明文和公钥加密,得到密文
//        System.out.println("密文:\r\n" + encodedData);
//        String decodedData = RSAUtils.privateDecrypt(encodedData, RSAUtils.getPrivateKey(privateKey)); //传入密文和私钥,得到明文
//        System.out.println("解密后文字: \r\n" + decodedData);
//    }


    // 简单测试____________
    public static void main(String[] args) throws Exception {
        Map<String, String> keyMapA = RSAUtils.createKeys(1024);
        String publicKeyA = keyMapA.get("publicKey");
        String privateKeyA = keyMapA.get("privateKey");
//        System.out.println("A公钥: \n\r" + publicKeyA);
//        System.out.println("A私钥: \n\r" + privateKeyA);
        Map<String, String> keyMapB = RSAUtils.createKeys(1024);
        String publicKeyB = keyMapB.get("publicKey");
        String privateKeyB = keyMapB.get("privateKey");
//        System.out.println("B公钥: \n\r" + publicKeyB);
//        System.out.println("B私钥: \n\r" + privateKeyB);


        System.out.println("A 来加密");
        String strA = "aaaaaaaaaaaaaaaaaaaaa";
        System.out.println("A明文:" + strA);
        //A用B的公钥加密
        String encodeA = encode(strA, publicKeyB);
        System.out.println("A密文:" + encodeA);

        //B的私钥解密
        String decodeA = decode(encodeA, privateKeyB);
        System.out.println("B来解密后的明文" + decodeA);

        //B来加密
        System.out.println("B 来加密");
        String strB = "bbbbbbbbbbbbbbbbb";
        System.out.println("B明文:" + strB);
        //B用A的公钥加密
        String encodeB = encode(strB, publicKeyA);
        System.out.println("B密文:" + encodeB);

        //A的私钥解密
        String decodeB = decode(encodeB, privateKeyA);
        System.out.println("A来解密后的明文" + decodeB);

    }


    /**
     * 加密 使用对方的公钥
     *
     * @param data
     * @return
     */
    public static String encode(String data, String key) throws InvalidKeySpecException, NoSuchAlgorithmException {
        return RSAUtils.publicEncrypt(data, RSAUtils.getPublicKey(key));
    }

    /**
     * 解密 使用对方的私钥
     *
     * @param data
     * @return
     */
    public static String decode(String data, String key) throws InvalidKeySpecException, NoSuchAlgorithmException {
        return RSAUtils.privateDecrypt(data, RSAUtils.getPrivateKey(key));
    }
}

AES和RSA混合加密

在这里插入图片描述

加密文件时使用对称加密,秘钥使用生成的随机数

然后使用B的公钥对随机数做非对称加密。然后将对称加密的文件和非对称加密的随机数秘钥发给用户B。

用户B收到后,先使用自己的私钥非对称解密获得随机数,然后在使用随机数和对称解密算法,解密文件

导入依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

RSA和AES的混个使用工具类

package com.ung.myEncryptor.utils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Random;


/**
 * RSA和AES加密
 */
public class MyEncryptor {
    /**
     * 公钥
     */
    private static final String publicKeyStr = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0jQ1Bo3gLVD/HXIO37TI8nVPqR1kk+K9aAU5D9J9x9y5u0SzRGDjK0U4CP3iPdlPXB9KkrlawZPEgddx1/+JU/IOw/B2BL7LiTl3YGvCH3pO4g9/4wWmNC+lH/DX0JjpGOjtoVvt0ylVHrP6PFpWwebXObNb78+8IRXw2JhZkxxYIgXkWKXA3JatQMOGfPaS9odgVHthQSPgNd4LxDXP2iamk1Q8nwKpCl3vcIsR3BprneWt2obAcYKnECnhKCypQqomUccENFtQKXy4bSNWc89cP33UDm9RsJ9HWbKgg3JxVGUfGOoZ8eagEbu9stvcJdMZHgLniNTU41i5lsmVpwIDAQAB";
    //私钥
    private static final String privateKeyStr = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSNDUGjeAtUP8dcg7ftMjydU+pHWST4r1oBTkP0n3H3Lm7RLNEYOMrRTgI/eI92U9cH0qSuVrBk8SB13HX/4lT8g7D8HYEvsuJOXdga8Ifek7iD3/jBaY0L6Uf8NfQmOkY6O2hW+3TKVUes/o8WlbB5tc5s1vvz7whFfDYmFmTHFgiBeRYpcDclq1Aw4Z89pL2h2BUe2FBI+A13gvENc/aJqaTVDyfAqkKXe9wixHcGmud5a3ahsBxgqcQKeEoLKlCqiZRxwQ0W1ApfLhtI1Zzz1w/fdQOb1Gwn0dZsqCDcnFUZR8Y6hnx5qARu72y29wl0xkeAueI1NTjWLmWyZWnAgMBAAECggEAWOMMvS3Ha0JB7vCpS33GL4jd27R7MHIEB/WVcJRg7d/vhjoB2FtkFIsbq+m3/tFi1qDAxxSsRQk4YnN5R45eQuorxj1rmowXw3pSeap3lxoAO2W0fYqJAq5XFA+jIylWAFuXCNG32sAyp14R5e2JnEsCHfszWFAuzIrboquhzSu9uAxjSVIT94l6o/Lp031cpN3joxPzUWAJ1oaEDBNs96xJjZ7fMcDC5POd4XxGuM05YhXD2AYIB3ngB/+OXGbk23Ezwhxup55x4Qh1PiMz3ZUMqzcJzU9JtDoId2toZmoNNlwcT3GBxFaSFIBOiIBaE6OMDwV7qj4z/UJQEAqJ2QKBgQD1JeEluxcg+ZJ4ydb8ApO2seHLK6E0jp5jU2/uSbdhsJcVMlk0Pn7jUIdfXWMcvIpSw28SB4+hAPEV1lKDjh1HKdEOd7HaYZYNFxI+Twcfuo2EcAWHJK6kjhYj4JHjvwZrDH54NG8CoeSlm1ActCA+7yY0Kj37zhVxZQe0KyoiLQKBgQDbglO7na7ewtQmTKg43rHlRcvsTDCNG0f2va4PtYbRTtdXbGA0OMsw6Wp+jN5VF2W7ZPEi14n/zkG0PMX8ixfJdkkDbL7QIFZXnJ/6cwSAP0tynrUoPv7v7UPlk9NWcdWMZZ18AbgmNtBXWGwscAEx9z6g6/Vf50Fb1MuqQHf/owKBgH/wm/dQE/sOpSXK2Rs+0Q6I+XlKNOC0Ce92jTUEXeNrAP57/1gnwZsq3OulVmOZ9mrJOj9jmM8DKc8mwnuMALWyGjv9teFuCpycTNYd8m7Zsp/KwufL+iVBTka9HgaJHHVj2OEtJD5wdV8ElqPUUfvmXb7dyOmQnVpHyuR5noylAoGBAIrNWWc8qVQg/IePV5xGbVUeEnnEH7tKcfH7NTKnxa1a3l5goGDFPO8b2gRVzU0fM2wQw04V3yRLZ06yUzuDNLJmwnqQ7AOtPwu9dYen7UOvQmUjKEhftVM/w/xKwotaBf/2TWQZrjkz1gA1YImk9YyD3C1jp1BSagjIgVSR2Y9jAoGBAMtiGfmkTAiugoh1Ge+pyKE/9SNHyQu850IuFaBPZ9LOMUQSv2Y4pfzvitfKiAEXp5HJ1DyAU21b/E1kzR+AA1O+T4/wgsAoI15zQQAUSbAxGfsamx6ZEoI5awSdVP9ZfKCv6ODuW49/IBsW0QTITFRh5hl3x9LbQgRqfmKwRFzU";


    /**
     * 将Base64编码后的公钥转换成PublicKey对象
     *
     * @param pubStr
     * @return
     * @throws Exception
     */
    public static PublicKey string2PublicKey(String pubStr) throws Exception {
        byte[] keyBytes = base642Byte(pubStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    /**
     * 将Base64编码后的私钥转换成PrivateKey对象
     *
     * @param priStr
     * @return
     * @throws Exception
     */
    public static PrivateKey string2PrivateKey(String priStr) throws Exception {
        byte[] keyBytes = base642Byte(priStr);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

    /**
     * 公钥加密
     *
     * @param content
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static byte[] publicEncrypt(byte[] content, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] bytes = cipher.doFinal(content);
        return bytes;
    }

    /**
     * 私钥解密
     *
     * @param content
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static byte[] privateDecrypt(byte[] content, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] bytes = cipher.doFinal(content);
        return bytes;
    }

    /**
     * Base64编码转字节数组
     *
     * @param base64Key
     * @return
     * @throws IOException
     */
    public static byte[] base642Byte(String base64Key) {
        return Base64.getDecoder().decode(base64Key);
    }

    public static byte[] encryptAES(byte[] source, String aesKey) throws Exception {
        SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(source);
    }

    public static byte[] decryptAES(byte[] source, String aesKey) throws Exception {
        SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(source);
    }

    public static class Result {
        public int pwdSize;//加密后的密码长度
        public int dataSize;//加密后的数据长度
        public int size;//整数据长度
        public byte[] data;//整数据

        public Result(byte[] data, int pwdSize, int dataSize, int size) {
            this.pwdSize = pwdSize;
            this.dataSize = dataSize;
            this.size = size;
            this.data = data;
        }
    }

    /**
     * 解密
     *
     * @return
     * @throws IOException
     */
    public static byte[] decrypt(byte[] data, int pwdSize) throws Exception {
        //base64解码获取 密文 和加密后的 AES的密文key
        byte[] temp = Base64.getDecoder().decode(data);
        byte[] pwd = new byte[pwdSize];
        byte[] result = new byte[temp.length - pwdSize];
        System.arraycopy(temp, 0, pwd, 0, pwd.length);
        System.arraycopy(temp, pwd.length, result, 0, result.length);

        //RSA的key用 私钥解密
        byte[] keyData = privateDecrypt(pwd, string2PrivateKey(privateKeyStr));
        //然后AES解密
        SecretKeySpec key = new SecretKeySpec(keyData, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(result);
    }

    /**
     * 加密
     *
     * @param data
     * @return
     * @throws IOException
     */
    public static Result encrypt(String data) throws Exception {
        //AES的密钥
        String aesKey = getRandomPassword(16);

        //RSA的公钥加密 AES的密钥
        byte[] aesKeyEncrypt = publicEncrypt(aesKey.getBytes(), string2PublicKey(publicKeyStr));
        //AES密钥加密后的长度
        int pwdSize = aesKeyEncrypt.length;

        //AES加密data
        SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encryptedData = cipher.doFinal(data.getBytes("UTF-8"));

        //拼接
        byte[] mergeData = new byte[pwdSize + encryptedData.length];
        System.arraycopy(aesKeyEncrypt, 0, mergeData, 0, aesKeyEncrypt.length);
        System.arraycopy(encryptedData, 0, mergeData, pwdSize, encryptedData.length);

        //结果data 进行base64处理
        byte[] dataByte = Base64.getEncoder().encodeToString(mergeData).getBytes();
        return new Result(dataByte, pwdSize, encryptedData.length, mergeData.length);
    }


    public static String getRandomPassword(int len) {
        String result = null;
        while (len >= 6) {
            result = makeRandomPassword(len);
            if (result.matches(".*[a-z]{1,}.*") && result.matches(".*[A-Z]{1,}.*") && result.matches(".*\\d{1,}.*") && result.matches(".*[~!@#$%^&*\\.?]{1,}.*")) {
                return result;
            }
            result = makeRandomPassword(len);
        }
        return "长度不得少于6位!";
    }

    public static String makeRandomPassword(int len) {
        char charr[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*.?".toCharArray();
        StringBuilder sb = new StringBuilder();
        Random r = new Random();
        for (int x = 0; x < len; ++x) {
            sb.append(charr[r.nextInt(charr.length)]);
        }
        return sb.toString();
    }


    // 简单测试____________
    public static void main(String[] args) throws Exception {
        String originStr = "aaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccccaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbcccccccccccccccccccc";
        Result encrypt = MyEncryptor.encrypt(originStr);
        System.out.println("加密后的密码长度" + encrypt.pwdSize);
        System.out.println("加密后的数据长度" + encrypt.dataSize);
        System.out.println("插入的数据长度" + encrypt.size);
        System.out.println("加密后的结果" + new String(encrypt.data, "UTF-8"));

        //解密
        byte[] decrypt = MyEncryptor.decrypt(encrypt.data, encrypt.pwdSize);
        String data = new String(decrypt, "UTF-8");
        System.out.println("解密后:" + data);
    }
}

哈希散列算法

参考:https://blog.csdn.net/wuxiaolongah/article/details/108230800

分布式的多个设备服务,需要保证一个唯一的ID,避免重复操作;

数据传输过程中,文件的数据完整性,传输过程中是否被篡改,散列函数的主要任务是验证数据的完整性

实现的方式就是Hash算法(也叫散列算法)。

什么是哈希算法?

将目标文本转化为具有相同长度的,不可逆的杂乱字符串,也叫消息摘要;

而加密则是将目标文本转为具有不同长度的,可逆的密文;

哈希算法严格来讲不算是加密算法,但是和加密有关系;有加密就有解密,但是哈希算法是不可逆的,不能根据处理后的再逆转为原文,因此不能算是加密算法。

常见的哈希算法有:MD5,SHA-1,HMAC,HMAC-MD5,HMAC-SHA1等

哈希算法的特征
  1. 固定长度

    散列函数可以接受任意大小的数据,并输出固定长度的散列值。

    比如MD5这个hash函数为例,不管原始数据有多大,计算得到的hash散列值总是128比特。

  2. 雪崩效应

    原始数据哪怕只有一个字节的修改,得到的hash值都会发生巨大的变化。

  3. 单向

    只能从原始数据计算得到hash值,不能从hash值计算得到原始数据。

    所以散列算法不是加密解密算法,加密解密是可逆的,散列算法是不可逆的

  4. 避免冲突

    几乎不可能找到一个数据和当前计算的这个数据计算出一样的hash值

    因此散列函数能够确保数据的唯一性。目前标准的MD5算法理论碰撞概率是2的128次方分之一。

    正是因为这种算法的碰撞概率很小,所以说我们在实际使用的过程之中才是可以无视这个数而直接使用MD5数据确定唯一性。

哈希算法的使用场景
  1. 文件传输

    在文件传输时,散列算法就是一种以较短的信息来保证文件唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。因此,当原有文件发生改变时,其标志值也会发生改变,从而告诉文件使用者当前的文件已经不是你所需求的文件。

    这种场景,对hash碰撞的要求要低于计算的速度,因为文件较大时,计算的速度会更重要。

  2. 消息摘要

    在密码学中,hash算法的作用主要是用于消息摘要(Message Digest),它主要用于对整个消息的完整性进行校验。

    举个例子,我们登陆B站的时都需要输入密码,那么B站的数据库会保存明文的密码吗?如果会明文保存,B站的DBA肯定会看到每个人的密码是什么,很不安全;同时如果用户在注册登录时也是明文在网络上传输账号密码,这个信息也会被人恶意截取,都会有很多安全问题。

    通常一个系统都不会明文存储用户的密码,一般,用户在注册的时候,密码在用户侧还未提交时,就会使用密码的明文计算一个hash值,然后传输到后端系统,并将密文记录到数据库中,用户登录时,在用户侧在使用相同的算法对密码计算一个hash值,传到后端后,将这个hash值和数据库中的hash值进行比较,如果相同就登录成功;这样就避免了在网络传输或公司的DBA泄露用户密码,而且密码始终是在用户侧,所以只要用户知道密码的明文是什么。

    在这些应用场景里,对于抗碰撞和抗篡改能力要求较高,对速度的要求在其次。一个设计良好的hash算法,其抗碰撞能力是很高的。以MD5为例,其输出长度为128位,碰撞的概率是2的128次方分之一

  3. 数据结构

    在用到hash进行管理的数据结构中,就对速度比较重视,对抗碰撞不太看中,只要保证hash均匀分布就可以。比如Hashmap,hash值(key)存在的目的是加速键值对的查找,key的作用是为了将元素适当地放在各个桶里,对于抗碰撞的要求没有那么高。换句话说,hash出来的key,只要保证value大致均匀的放在不同的桶里就可以了。但整个算法的set性能,直接与hash值产生的速度有关,所以这时候的hash值的产生速度就尤为重要

    简洁的一个乘加迭代运算,在不少的hash算法中,使用的是异或+加法进行迭代

    /**
     * HashMap对象的hash()
     */
    static final int hash(Object key) {
        int h;
        //计算hashCode,并无符号移动到低位
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    /**
     * Object对象的hashCode()
     */
    public int hashCode() {
        int h = hash;
        //hash default value : 0 
        if (h == 0 && value.length > 0) {
            //value : char storage
            char val[] = value;
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
    
MD5算法

Message Digest Algorithm MD5(消息摘要算法5)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。是计算机广泛使用的杂凑算法之一,将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。

MD5算法具有以下特点:

1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2、容易计算:从原数据计算出MD5值很容易。
3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4、强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。

应用场景

1、一致性验证
2、数字签名
3、安全访问认证

package com.ung.myEncryptor.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @author: wenyi
 * @create: 2022/9/24
 * @Description: MD5加密
 */
public class MD5Utils {

    /**
     * 计算字符串的MD5值
     *
     * @param string 明文
     * @return 字符串的MD5值
     */
    public static String md5(String string) {
        if (string.isEmpty()) {
            return "";
        }
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(string.getBytes("UTF-8"));
            String result = "";
            for (byte b : bytes) {
                String temp = Integer.toHexString(b & 0xff);
                if (temp.length() == 1) {
                    temp = "0" + temp;
                }
                result += temp;
            }
            return result;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 计算文件的MD5值
     *
     * @param file 文件File
     * @return 文件的MD5值
     */
    public static String md5(File file) {
        if (file == null || !file.isFile() || !file.exists()) {
            return "";
        }
        FileInputStream in = null;
        String result = "";
        byte buffer[] = new byte[0124];
        int len;
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            in = new FileInputStream(file);
            while ((len = in.read(buffer)) != -1) {
                md5.update(buffer, 0, len);
            }
            byte[] bytes = md5.digest();

            for (byte b : bytes) {
                String temp = Integer.toHexString(b & 0xff);
                if (temp.length() == 1) {
                    temp = "0" + temp;
                }
                result += temp;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != in) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }


    public static void main(String[] args) {
        // 字符串的Md5
        String content = "aaaaaabbbbbbb";
        String hashStr = md5(content);
        System.out.println(content +"  => "+ hashStr);

        //文件的MD5
        String filePath = "D:\\1111111.xlsx";
        File file = new File(filePath);
        String fileHash = md5(file);
        System.out.println("file hash =>" + fileHash);
    }
}

结果:

aaaaaabbbbbbb  => 1db2a5333256501c53ba3fb84669086e
file hash =>a4982d66fbf4574c3fc615abf05cc06a
SHA1算法

安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。

SHA1算法原理:

首先进行SHA1分组:对于任意长度的明文,SHA1可以产生160位的摘要。对明文的分组处理过程如下:

1)对数据流尾部添加0x80标记。任意长度的明文首先需要添加位数,使明文总长度为448(mod512)位。将0x80字节追加到数据流尾部以后,源数据流的整个长度将会发生变化,考虑到还要添加64位(8个字节)的位长度,必须填充0 以使修改后的源数据流是64字节(512位)的倍数。在明文后添加位的方法是第一个添加位是1,其余都是0。

2)然后将真正明文的长度(没有添加位以前的明文长度)以64位表示,附加于前面已添加过位的明文后,此时的明文长度正好是 512位的倍数。当明文长度大于2的64次方时,仅仅使用低64位比特填充,附加到最后一个分组的末尾。

3)经过添加处理的明文,其长度正好为512位的整数倍,然后按512位的长度进行分组(block),可以划分成L份明文分组,我们用Y0,Y1,……,YL-1表示这些明文分组。

4)Sha1默认数据流以big endian 方式存放。

分组之后,对所得到的若干分组反复重复处理。对每个明文分组的摘要生成过程如下

​ 1)将512位划分成16个子明文分组,每个子分组32位

​ 2)申请5个链接变量a、b、c、d、e,初始为H0、H1、H2、H3、H4

​ 3)将16个子分组扩展为80份

​ 4)80个子分组进行4轮运算,得到新的a、b、c、d、e值

​ 5)新的链接变量与原始链接变量进行求和

​ 6)链接变量作为下一个明文分组的初始链接变量

​ 7)最后一个分组的5个链接变量就是SHA1摘要

SHA1有如下特性:

不可以从消息摘要中复原信息;两个不同的消息不会产生同样的消息摘要

MessageDigest.getInstance("MD5"); //将"MD5"替换为"SHA1"。
MurmurHash算法

加密哈希函数旨在保证安全性,很难找到碰撞。即:给定的散列h很难找到的消息m;很难找到产生相同的哈希值的消息m1和m2。

非加密哈希函数只是试图避免非恶意输入的冲突。作为较弱担保的交换,它们通常更快。如果数据量小,或者不太在意哈希碰撞的频率,甚至可以选择生成哈希值小的哈希算法,占用更小的空间

MurmurHash 是一种非加密型哈希函数,适用于一般的哈希检索操作。 由Austin Appleby在2008年发明, 并出现了多个变种,都已经发布到了公有领域。与其它流行的哈希函数相比,对于规律性较强的key,MurmurHash的随机分布特征表现更良好。其在Redis,Memcached,Cassandra,HBase,Lucene都使用了这种hash算法。所有很有必要说一下。

一种经过广泛测试且速度很快的非加密哈希函数,MurmurHash3可以产生32位或128位哈希,旧版本MurmurHash2产生32位或64位值,MurmurHash2A变体添加了Merkel-Damgard构造,以便可以逐步调用它。MurmurHash64A针对64位处理器进行了优化,针对32位处理器进行MurmurHash64B优化

MurMurHash3 128 位版本的速度是 MD5 的十倍。MurMurHash3 生成 32 位哈希的用时比生成 128 位哈希的用时要长。原因在于生成 128 位哈希的实现受益于现代处理器的特性。32 位哈希值发生碰撞的可能性就比 128 位的要高得多,当数据量达到十万时,就很有可能发生碰撞。

Redis在实现字典时用到了两种不同的哈希算法,MurmurHash便是其中一种(另一种是djb)。MurmurHash在Redis中应用十分广泛,包括数据库、集群、哈希键、阻塞操作等功能都用到了这个算法。发明算法的作者被邀到google工作,该算法最新版本是MurmurHash3,基于MurmurHash2改进了一些小瑕疵,使得速度更快,实现了32位(低延时)、128位HashKey,尤其对大块的数据,具有较高的平衡性与低碰撞率。

与MD5这些讲究安全性的摘要算法比,MurmurHash并不关注安全性,比如在Redis内部只是为主键做个Hash而已,就不需要安全性了。因此MurmurHash是一种non-cryptographic的hash算法,比安全散列算法快几十倍。

在Java中,有很多地方都使用了MurmurHash,比如Guava包、Jedis包,Cassandra包中都有这种hash算法。

package com.ung.myUrl.utils;

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * @author: wenyi
 * @create: 2022/9/23
 * @Description: Guava的 MurMurHash 一致性Hash的一种算法 高效低碰撞率
 */
public class GuavaMurMurHashUtils {

    /**
     * MurMurHash算法, 性能高, 碰撞率低
     *
     * @param str String
     * @return Long
     */
    public static Long hash(String str) {
        HashFunction hashFunction = Hashing.murmur3_128();
        return hashFunction.hashString(str, StandardCharsets.UTF_8).asLong();
    }
    public static String hashToString(String str) {
        HashFunction hashFunction = Hashing.murmur3_128();
        return hashFunction.hashString(str, StandardCharsets.UTF_8).toString();
    }

    /**
     * Long转换成无符号长整型(C中数据类型)
     * Java的数据类型long与C语言中无符号长整型uint64_t有区别,导致Java输出版本存在负数
     *
     * @param value long
     * @return Long
     */
    public static Long readUnsignedLong(long value) {
        if (value >= 0) {
            return value;
        }
        return value & Long.MAX_VALUE;
    }

    /**
     * 返回无符号murmur hash值
     *
     * @param key
     * @return
     */
    public static Long hashUnsigned(String key) {
        return readUnsignedLong(hash(key));
    }


    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("CHONGQING");
        list.add("CHANGSHA213");
        list.add("GUANGZHOU34434");
        list.add("SHENZHEN14geerrf");
        list.add("001c454397001c4becd89f49f7b1c52fe4fcd54397001c4becd89f49f7b1c52fe4fcd54397");
        list.add("0043543254325525002b320c0e0347a8bcea7663192d8303002b320c0e0347a8bcea7663192d8303");
        list.add("0035420515a24d9d875e6c4399bec8e30035420515a24d9d875e6c4399bec8e30035420515a24d9d87543c8e3");
        list.add("00701f4c12364bedb626dd136cbc998b00701f4c12364bedb626dd136cbc998b00701f4c12364bedb626dd13bc99jjjj8b");
        list.add("008d028903da483fbee8d721b2e73934008d028903da483fbee8d721b2e73934008d028903da483fbee8d721b2e73b2e73fdgds934");
        list.add("00b9dce4ec2747c0aea6eb0494e3871700b9dce4ec2747c0aea6eb0494e3871700b9dce4ec2747c0aea6eb0494e3871700b9dce4ec2747c0aea6");
        for (String city : list) {
            String hash = GuavaMurMurHashUtils.hashToString(city);
            System.out.println(hash);
        }
    }
}

结果

91b249691a3b3dd8ef3ecc76b3224517
c86d6797e765e4a2be43f51e0f736977
1f1047c2343519a38b0754c52700d7b7
121d1edfc5e8785d20ea408f3234095b
e89a0d64e7fc52846885354f60604dd5
647e6b20b627bbd1b669e3765d3ee173
8ddf3fcb71875d69655bdfdd11eaa1f3
3e7f44a608fb4c54b573e79d875c5df9
e09077ba71c353d74cf759d15d0faa2a
5d5374709aa58bcfad290c0cdb3ed162

MurmurHash的应用除了上面说的redis,在很多时候都可以应用到,比如短连接服务生成短连接、BloomFilter都可以使用。

哈希算法的安全性

MD5、SHA1等hash算法作为一种不可逆算法,一定程度上保证了密码的安全性,但是MD5等hash算法真的是完全安全的吗,其实不然。

从概率来说,2的128次方遍历后至少出现两个相同的MD5值,但是2的128次方有多大?3402823669209384634633746074317.7亿,就算全世界最快的超级计算机也要跑几十亿年才能跑完。可是,王小云院士破解了MD5。这里所说的破解,并不是给王小云院士一个MD5散列值,然后她就能通过计算还原出原文来。从密文推算出明文理论上是不可能的,所以王小云的研究成果不能通过 MD5 的散列值逆向推算出明文。王小云的研究成果是给定消息 M1,能够计算获取 M2,使得 M2 产生的散列值与 M1 产生的散列值相同。这样,MD5 的抗碰撞性就不满足了,使得 MD5 不再是安全的散列算法。从而导致MD5 用于数字签名将存在严重问题,因为可以篡改原始消息,而生成相同的 Hash 值。因此,业界专家普林斯顿计算机教授Edward Felten等强烈呼吁信息体系的设计者赶快更换签名算法,而且他们侧重这是一个需要当即处理的问题。

同时美国国家技能与规范局(NIST)于2004年8月24日宣布专门谈论,谈论的首要内容为:“在近来的世界暗码学会议(Crypto 2004)上,研究人员宣布他们发现了破解数种HASH算法的办法,其间包含MD4,MD5,HAVAL-128,RIPEMD还有 SHA-0。剖析标明,于1994年代替SHA-0成为联邦信息处理规范的SHA-1的削弱条件的变种算法能够被破解;但完好的SHA-1并没有被破解,也没有找到SHA-1的碰撞。研究结果阐明SHA-1的安全性暂时没有问题,但随着技能的发展,技能与规范局计划在2010年之前逐步筛选SHA-1,换用别的更长更安全的算法(如SHA-224、SHA-256、SHA-384和SHA-512)来代替。”

所以从这里也可以看出,单步的hash算法还是存在很大的漏洞,容易被碰撞。那么该如何进一步的加强hash算法的安全性呢,可以使用如下的办法:

  1. hash+盐(salt)
    salt可以简单的理解成:随机产生的一定长度的,可以和密码相结合,从而使hash算法产生不同结果的字符串。也就相当于你的新密码 = 旧密码 + 随机的盐值,然后对新密码进行hash。
    优点:这种方法会极大防止受到彩虹表的攻击,因为即便攻击者构造出彩虹表,因为你使用了 hash(密码+ salt),攻击者彩虹表里的哈希值hash(密码)和你数据库中的哈希值是不同的

  2. 增加计算的时间(哈希+salt+Iteration)

    通过迭代计算的方式增加计算密码的成本。迭代的周期控制在用户可以接受范围内,这样攻击者的计算和时间成本就会大大增加。

    一般到此时,hash结果就比较安全了。但是如果还需要更加地安全,可以继续对这种方法计算出来的hash值使用加密算法加密

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值