常见的信息摘要和加密算法

1,211 阅读10分钟

@TOC


散列(Hash)算法


MD5

MD5信息摘要算法(MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以将任意数据产生出一个128位(16字节)的散列值,用于确保信息传输完整一致。

可以在登录注册模块使用MD5加密用户的密码再存入数据库,防止有人直接从数据库获取用户信息。

MD5算法有以下特点:

  • 压缩性:无论数据长度是多少,计算出来的MD5值长度相同
  • 容易计算性:由原数据容易计算出MD5值
  • 抗修改性:即便修改一个字节,计算出来的MD5值也会巨大差异
  • 抗碰撞性:知道数据和MD5值,很小概率找到相同MD5值相同的原数据。
import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class TestMD5 {
    public static String getMD5(String str){
        MessageDigest messageDigest = null;
        try {
            messageDigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        byte[] bytes = messageDigest.digest(str.getBytes());
        System.out.println(bytes.length); //16字节 128bit
        return HexBin.encode(bytes); //结果是32位16进制
    }
}

MD5是一种信息摘要算法,不可逆的算法,只能说是单向加密的算法。


MD5和Base64

提到MD5可能也会想到Base64。

Base64是用文本表示二进制的编码方式,使用4个字节的文本来表示3个字节的原始二进制数据。
它将二进制数据转换成一个64个可打印的字符组成的序列。
Base64常用于网络传输,在某些基于文本的协议中,如果需要传输图片或文件等,比如说图片的存储格式是二进制数据而不是文本格式,必须要将二进制的数据编码成文本格式,这时候就可以使用Base64。
某些系统只能使用ASCII字符,Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方式。
Base64编码后一般比原数据要大。

Base64只是一种数据编码格式,是可逆的,而且编码方式是公开的,就不存在加密一说了。


SHA系列

安全散列算法(Secure Hash Algorithm,SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法。 能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。

也就是说SHA-1加密算法有碰撞的可能性,虽然很小。

SHA256都会产生一个256bit长的散列值(哈希值),用于确保信息传输完整一致,称作消息摘要。这个摘要相当于是个长度为32个字节的数组,通常用一个长度为64的十六进制字符串来表示。

public static String getSHA256(String str){
        MessageDigest messageDigest = null;
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        byte[] bytes = messageDigest.digest(str.getBytes());
        return HexBin.encode(bytes);
}

HMAC系列

HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,它可以与任何迭代散列函数捆绑使用。

MAC(Message Authentication Code,消息认证码算法)是含有密钥的散列函数算法,兼容了MD和SHA算法的特性,并在此基础上加入了密钥。消息的散列值由只有通信双方知道的秘密密钥K来控制,因次,我们也常把MAC称为HMAC(keyed-Hash Message Authentication Code)。

HMAC算法更像是一种加密算法,它引入了密钥,其安全性已经不完全依赖于所使用的Hash算法

public static String getHMAC(String str, SecretKeySpec secretKey){
        Mac mac = null;
        try {
            mac = Mac.getInstance("HmacSHA256");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        try {
            //初始化mac
            mac.init(secretKey);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        byte[] bytes = mac.doFinal(str.getBytes());
        return new String(Base64.encode(bytes));
}

如果要使用不可逆加密,推荐使用SHA256、SHA384、SHA512以及HMAC-SHA256、HMAC-SHA384、HMAC-SHA512这几种算法。

在这里插入图片描述


对称加密算法


对称加密算法是应用比较早的算法,在数据加密和解密的时用的都是同一个密钥。

常见的对称加密算法有DES、3DES、AES128、AES192、AES256 其中AES后面的数字代表的是密钥长度。对称加密算法的安全性相对较低,比较适用的场景就是内网环境中的加解密。


DES

DES全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法。

DES是对称加密算法领域中的典型算法,其密钥默认长度为56位。

DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。 密钥长64位,密钥事实上是56位参与DES运算(第8、16、24、32、40、48、56、64位是校验位,使得每个密钥都有奇数个1),分组后的明文组和56位的密钥按位替代或交换的方法形成密文组

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;

public class TestDES {

    /**
     * 加密
     */
    public static String encrypt(byte[] dataSource, String pwd){
        SecureRandom random = new SecureRandom();
        try {
            DESKeySpec desKeySpec = new DESKeySpec(pwd.getBytes());
            //创建一个密钥工厂
            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);

            //加密
            Cipher cipher = Cipher.getInstance("DES");
            //密钥初始化Chiper对象
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, random);
            //正式执行加密操作
            return Base64.encode(cipher.doFinal(dataSource));
        }catch (Throwable e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解密
     */
    public static String decrypt(String str,String pwd) 
            throws InvalidKeyException, 
            NoSuchAlgorithmException, 
            InvalidKeySpecException, 
            NoSuchPaddingException, 
            BadPaddingException, 
            IllegalBlockSizeException {
        
        SecureRandom random = new SecureRandom();
        DESKeySpec desKeySpec = new DESKeySpec(pwd.getBytes());
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);

        //解密
        Cipher cipher = Cipher.getInstance("DES");
        cipher.init(Cipher.DECRYPT_MODE,secretKey,random);
        return new String(cipher.doFinal(Base64.decode(str)));
    }
}


3DES

由于计算机运算能力的增强,原版DES密码的密钥长度变得容易被暴力破解;3DES即是设计用来提供一种相对简单的方法,即通过增加DES的密钥长度来避免类似的攻击,而不是设计一种全新的块密码算法。

3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。是DES向AES过渡的加密算法。

它使用3条56位的密钥对数据进行三次加密。是DES的一个更安全的变形。它以DES为基本模块,通过组合分组方法设计出分组加密算法。比起最初的DES,3DES更为安全。密钥长度默认为168位,还可以选择128位。

public static String encryptThreeDESECB(String src, String key) {
    try{
        DESedeKeySpec dks = new DESedeKeySpec(key.getBytes("UTF-8"));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
        SecretKey securekey = keyFactory.generateSecret(dks);

        Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, securekey);
        byte[] b = cipher.doFinal(src.getBytes("UTF-8"));

        String ss = new String(Base64.encodeBase64(b));
        ss = ss.replaceAll("\\+", "-");
        ss = ss.replaceAll("/", "_");
        return ss;
    } catch(Exception ex){
        ex.printStackTrace();
        return src;
    }
}

public static String decryptThreeDESECB(String src, String key) {
    try{
        src = src.replaceAll("-", "+");
        src = src.replaceAll("_", "/");
        byte[] bytesrc = Base64.decodeBase64(src.getBytes("UTF-8"));
        // --解密的key
        DESedeKeySpec dks = new DESedeKeySpec(key.getBytes("UTF-8"));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
        SecretKey securekey = keyFactory.generateSecret(dks);

        // --Chipher对象解密
        Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, securekey);
        byte[] retByte = cipher.doFinal(bytesrc);

        return new String(retByte, "UTF-8");
    } catch(Exception ex){
        ex.printStackTrace();
        return src;
    }
}

AES

AES 高级数据加密标准,能够有效抵御已知的针对DES算法的所有攻击。

AES支持三种长度的密钥: 128位,192位,256位 (bit)

AES128,AES192,AES256,实际上就是指的AES算法对不同长度密钥的使用。

  • 把明文按照128bit拆分成若干个明文块。
  • 按照选择的填充方式来填充最后一个明文块。
  • 每一个明文块利用AES加密器和密钥,加密成密文块。
  • 拼接所有的密文块,成为最终的密文结果。
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class TestAES {

    private static final String KEY_AES = "AES";
    private static final String KEY_MD5 = "MD5";
    private static MessageDigest MD5Digest;

    static {
        try {
            MD5Digest = MessageDigest.getInstance(KEY_MD5);
        }catch (NoSuchAlgorithmException e){
            e.printStackTrace();
        }
    }

    private static String doAES(String data,String key,int mode){
        try {
            boolean encrypt = mode == Cipher.ENCRYPT_MODE;
            byte[] content;
            if(encrypt){
                content = data.getBytes(StandardCharsets.UTF_8);
            }else {
                content = Base64.decode(data);
            }
            SecretKeySpec keySpec = new SecretKeySpec(MD5Digest.digest(key.getBytes(StandardCharsets.UTF_8)),KEY_AES);
            //创建密码器
            Cipher cipher = Cipher.getInstance(KEY_AES);
            cipher.init(mode,keySpec); //初始化
            byte[] result = cipher.doFinal(content);
            if(encrypt){
                return new String(Base64.encode(result));
            }else {
                return new String(result, StandardCharsets.UTF_8);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    public static String encrypt(String data,String key){
        return doAES(data,key,Cipher.ENCRYPT_MODE);
    }

    public static String decrypt(String data,String key){
        return doAES(data,key,Cipher.DECRYPT_MODE);
    }


}


非对称加密算法

非对称加密算法有两个密钥,这两个密钥完全不同但又完全匹配。只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。常见的非对称加密有RSA、SM2等。


RSA

1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,叫做RSA算法。从那时直到现在,RSA算法一直是最广为使用的"非对称加密算法"。毫不夸张地说,只要有计算机网络的地方,就有RSA算法。

这种算法非常可靠,密钥越长,它就越难破解。1024位的RSA密钥基本安全,2048位的密钥极其安全。

RSA密钥至少为500位长,一般推荐使用1024位。

RSA算法的核心是:欧拉函数、互质关系、模反元素

【具体步骤】
找出质数:P Q
计算公共模数:N = P*Q
欧拉函数:φ(N) = (P-1)(Q-1) (这就是欧拉函数,任意给定正整数N,在小于等于N的正整数之中有多少个与N构成互质关系)
计算公钥E: 1 < E < φ(N) (E的取值必须是整数,E 和 φ(N) 必须是互质数)
计算私钥D: E * D % φ(N) = 1 (这个就是模反元素,E和φ(N)互质,那一定可以找到D,使得 E * D - 1 被φ(N)整除)
加密: C = M^E^ mod N ; C:密文 M:明文 解密: M =C^D^ mod N ; C:密文 M:明文

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
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.HashMap;
import java.util.Map;

public class TestRSA {

    //密钥长度
    private static final int KEY_SIZE = 1024;
    //公钥
    private static final String PUBLIC_KEY = "RSAPublicKey";
    //私钥
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 初始化密钥对
     */
    public static Map<String ,Object> initKey(){
        //实例化密钥生成器
        KeyPairGenerator keyPairGenerator = null;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        //初始化密钥生成器
        keyPairGenerator.initialize(KEY_SIZE);
        //生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        //将密钥存在map中
        Map<String,Object> keyMap = new HashMap<String,Object>();
        keyMap.put(PUBLIC_KEY,publicKey);
        keyMap.put(PRIVATE_KEY,privateKey);
        return keyMap;
    }

    /**
     * 私钥加密
     */
    public static byte[] encryptByPrivateKey(byte[] data,byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        //取得私钥
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = null;
        try {
            keyFactory = KeyFactory.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        //生成私钥
        PrivateKey privateKey = null;
        try {
            privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE,privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥加密
     */
    public static byte[] encryptByPublicKey(byte[] data,byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        //实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        //初始化公钥
        //密钥材料转换
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(key);
        //产生公钥
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE,publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥解密
     */
    public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
        //取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        //生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     */
    public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {

        //实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        //初始化公钥
        //密钥材料转换
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
        //产生公钥
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
        //数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, pubKey);
        return cipher.doFinal(data);
    }

    /**
     * 取得私钥
     */
    public static byte[] getPrivateKey(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return key.getEncoded();
    }

    /**
     * 取得公钥
     */
    public static byte[] getPublicKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return key.getEncoded();
    }

}


ECDHE

基于椭圆曲线, 支持前向安全性。

HTTPS 常用的密钥交换算法有两种,分别是 RSA 和 ECDHE 算法。 其中,RSA 是比较传统的密钥交换算法,它不具备前向安全的性质,因此现在很少服务器使用的。而 ECDHE 算法具有前向安全,所以被广泛使用。

下面来详细阐述一下这个算法的发展历程:

  • 先是DH算法,这也是一个非对称加密算法,原理是离散对数
  • 然后到DHE算法,(首先公钥是会变的),让双方的私钥的私钥也在每次密钥交换通信时,随机生成,成为临时的。所以就算被破解到了一次,也不影响其他次通信
  • 但是这个DHE算法计算性能不佳(因为需要大量的乘法运算)
  • ECDHE是在DHE的基础上利用了ECC椭圆曲线特性,可以用更少的计算量计算出公钥,以及最终的会话密钥。