对称加密、非对称加密、数字签名

  • 2021 年 2 月 26 日
  • 笔记

1.对称加密

在对称加密算法中,数据发送方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。接受方收到密文后,若想解读原文,则需要使用加密用过的密文及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。

常见的对称加密算法:

  • DES(data encryption standard):数据加密标准,一种使用密钥加密的块算法
  • AES(advanced encryption standard):高级加密标准,又称Rijndael加密法。

特点:

  • 加密速度块,可以加密大文件
  • 密文可逆,一旦密钥泄露,可能会导致数据爆露
  • 加密后在编码表找不到的字符,会出现乱码
  • 一般与base64结合使用

加密模式:

  • ECB(electronic code book):电子密码本,需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密。优点是可以可以并行处理数据,缺点是同样的原文加密生成的密文是一样的,不能很好的保护数据
  • CBC(cipher block chaining):密码块连接,每个明文块先与前一个密文块进行异或运算,再进行加密,在这种方式中,每个密文块都依赖于它前面的所有的明文块。优点是同样的原文生成的密文不一样,缺点是如果一个分组丢失,那么后面的分组将全部作废(同步错误)
  • CFB(cipher feedback mode):加密反馈模式,类似于自动同步序列密码,分组加密后,按8位分组将密文和明文进行移位异或后得到输出同时反馈回移位寄存器,优点是可以按字节进行加解密,缺点是明文的一个错误回影响后面的密文(错误扩散)
  • OFB(Output feedback mode):输出反馈模式,将分组密码作为同步序列密码运行,和CFB相似,不过OFB用的是前一个n位密文输出分组反馈回移位寄存器。OFB没有错误扩散问题。

填充模式:当需要按块处理的数据,数据长度不符合时,按照一定的方法填充满块的长度的规则

  • NoPadding:不填充,在DES加密算法下,要求原文的长度必须时8个字节的整数倍;在AES加密算法下,要求原文长度必须时16个字节的整数倍
  • PKCS5Padding:数据块不足8个字节的时候就补足

Demo代码:

package encryptAndDecrypt;

import com.sun.org.apache.xml.internal.security.utils.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * @className: AesDesDemo
 * @description: 使用DES加密解密,加密和解密都是使用同一个密钥
 * @author: charon
 * @create: 2021-02-25 08:27
 */
public class AesDesDemo {

    public static void main(String[] args) throws Exception {
        // 需要加密的内容,如果是不使用填充模式,那么密文的字节数必须是8的整数倍
        String input = "charon周1234567";
        // 密钥,DES加密的密钥必须是8位,如果是AES加密的密钥必须是16位
        String key = "12345678";
        // 加密算法,DES表示加密类型,CBC表示加密模式,NoPadding表示填充模式。
        // 默认情况下是使用 DES/ECB/PKCS5padding
        String algorithm = "DES/CBC/NoPadding";
        String algorithmType = "DES";
        //偏移量,是使用cbc的加密模式,在使用iv向量进行加密的时候,IV也必须是8位
        String offset = "12345678";
        // 加密
        String encryptDes = encryptDES(input, key, algorithm, algorithmType,offset);
        System.out.println("密文: " + encryptDes);
        // 解密
        String decryptDES = decryptDES(encryptDes, key, algorithm, algorithmType,offset);
        System.out.println("解密: "+ decryptDES);
    }

    /**
     * 解密
     * @param encryptDes 密文
     * @param key 密钥
     * @param algorithm 解密算法
     * @param algorithmType 解密类型
     * @param offset 偏移量
     * @return
     */
    private static String decryptDES(String encryptDes, String key, String algorithm, String algorithmType,String offset) throws Exception {
        // 获取解密对象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 创建加密规则
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithmType);
        // 创建iv向量
        IvParameterSpec ivParameterSpec = new IvParameterSpec(offset.getBytes());
        // 初始化解密规则
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
        // 加密
        byte[] bytes = cipher.doFinal(Base64.decode(encryptDes));
        return new String(bytes);
    }

    /**
     * 加密
     * @param input 加密内容
     * @param key 密钥
     * @param algorithm 加密算法
     * @param algorithmType 加密类型
     * @param  offset 偏移量
     * @return 密文
     */
    private static String encryptDES(String input, String key, String algorithm, String algorithmType,String offset) throws Exception {
        // 获取加密对象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 创建加密规则
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithmType);
        // 创建iv变量,iv向量,是使用cbc的加密模式
        // 在使用iv向量进行加密的时候,IV也必须是8位
        IvParameterSpec ivParameterSpec = new IvParameterSpec(offset.getBytes());
        // 初始化加密
        cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
        // 加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        // 返回加密后的数据
        return Base64.encode(bytes);
    }
}

2.非对称加密

与对称加密算法不同,非对称加密算法需要两个密钥:公钥和私钥。公钥与私钥是一对,如果使用公钥对数据进行加密,那么只能有对应的私钥才能解密;如果使用私钥进行加密,那么只能用对应的公钥才能解密。因为加密和解密是使用的两个不同的密钥。

常见的非对称加密算法:

  • RSA:根据数论,寻求两个大素数比较简单,而将它们的乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。
  • ECC:椭圆加密算法

特点:

  • 加密和解密使用不同的密钥
  • 如果使用私钥加密, 只能使用公钥解密
  • 如果使用公钥加密, 只能使用私钥解密
  • 处理数据的速度较慢, 因为安全级别高

demo代码:

package encryptAndDecrypt;

import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.apache.commons.io.FileUtils;

import javax.crypto.Cipher;
import java.io.File;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @className: RsaDemo
 * @description: 非对称加密之RSA
 * @author: charon
 * @create: 2021-02-25 11:05
 */
public class RsaDemo {

    public static void main(String[] args) throws Exception {
        String algorithm = "RSA";
        // 生成密钥对并保存在本地文件中,这样就可以不用每次都重新申请了。
        // 也有系统为了更安全,每次都会新生成密钥,然后将公钥返回给客户端,私钥存入redis中,解密的时候从根据session从redis中取对应的私钥
        generateKeyToFile(algorithm, "pub.txt", "pri.txt");
        // 获取公钥
        PublicKey publicKey = getPublicKey("pub.txt", "RSA");
        // 获取私钥
        PrivateKey privateKey = getPrivateKey("pri.txt", "RSA");
        // 原文
        String input = "charon周";
        // 加密 
        String encryptRSA = encryptRSA(input,algorithm,publicKey);
        System.out.println("密文:"+ encryptRSA);
        // 解密
        String decryptRSA = decryptRSA(encryptRSA,algorithm,privateKey);
        System.out.println("原文:"+ decryptRSA);

    }

    /**
     * 解密
     * @param encryptRSA 密文
     * @param algorithm 算法
     * @param privateKey 私钥
     * @return 原文
     */
    private static String decryptRSA(String encryptRSA, String algorithm, PrivateKey privateKey) throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE,privateKey);
        byte[] bytes = cipher.doFinal(Base64.decode(encryptRSA));
        return new String(bytes);
    }

    /**
     * 加密
     * @param input 原文
     * @param algorithm 加密算法
     * @param publicKey 公钥
     * @return 密文
     */
    private static String encryptRSA(String input, String algorithm, PublicKey publicKey) throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE,publicKey);
        byte[] bytes = cipher.doFinal(input.getBytes());
        return Base64.encode(bytes);
    }

    /**
     * 获取私钥
     * @param privateKeyPath 私钥文件路径
     * @param algorithm 算法
     * @return 私钥
     */
    public static PrivateKey getPrivateKey(String privateKeyPath, String algorithm) throws Exception {
        // 读取文件内容
        String strPrivateKey = FileUtils.readFileToString(new File(privateKeyPath), Charset.defaultCharset());
        // 创建key工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 创建私钥规则
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(strPrivateKey));
        return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 获取公钥
     * @param publicKeyPath 公钥文件路径
     * @param algorithm  算法
     * @return 公钥
     */
    public static PublicKey getPublicKey(String publicKeyPath, String algorithm) throws Exception{
        // 读取文件内容
        String strPublicKey = FileUtils.readFileToString(new File(publicKeyPath), Charset.defaultCharset());
        // 创建key工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 创建公钥规则
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decode(strPublicKey));
        return keyFactory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 生成密钥对,并放入本地文件
     * @param algorithm
     * @param publicKeyPath
     * @param privateKeyPath
     */
    private static void generateKeyToFile(String algorithm, String publicKeyPath, String privateKeyPath) throws Exception{
        // 生成实例
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 生成私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 获取私钥字节数组
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 生成公钥
        PublicKey publicKey = keyPair.getPublic();
        // 获取公钥的字节数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        // base64编码
        String strPrivateKeyEncoded = Base64.encode(privateKeyEncoded);
        String strPublicKeyEncoded = Base64.encode(publicKeyEncoded);
        // 存到文件中
        FileUtils.write(new File(privateKeyPath),strPrivateKeyEncoded, Charset.forName("UTF-8"));
        FileUtils.write(new File(publicKeyPath),strPublicKeyEncoded, Charset.forName("UTF-8"));
    }
}

3.数字签名

数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名是非对称密钥加密技术与数字摘要技术的应用。

demo代码:

package encryptAndDecrypt;

import com.sun.org.apache.xml.internal.security.utils.Base64;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;

/**
 * @className: SignatureDemo
 * @description: 数字签名
 * @author: charon
 * @create: 2021-02-25 15:40
 */
public class SignatureDemo {
    public static void main(String[] args) throws Exception{
        // 这里直接使用之前的公钥和私钥
        // 获取公钥
        PublicKey publicKey = RsaDemo.getPublicKey("pub.txt", "RSA");
        // 获取私钥
        PrivateKey privateKey = RsaDemo.getPrivateKey("pri.txt", "RSA");
        String input = "charon周";
        // 获取签名
        String signature = getSignature(input, "Sha256withrsa", privateKey);
        System.out.println("签名:" + signature);
        // 校验签名
        boolean verifySignature = verifySignature(input, "Sha256withrsa", publicKey, signature);
        System.out.println("结果: "+ verifySignature);
    }

    /**
     * 校验签名
     * @param input 原文
     * @param algorithm 算法
     * @param publicKey 公钥
     * @param signatureData 签名
     * @return
     */
    private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signatureData) throws Exception{
        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(publicKey);
        signature.update(input.getBytes());
        // 校验数据
        boolean verify = signature.verify(Base64.decode(signatureData));
        return verify;
    }

    /**
     * 签名
     * @param input 原文
     * @param algorithm 算法
     * @param privateKey 私钥
     * @return 签名
     */
    private static String getSignature(String input, String algorithm, PrivateKey privateKey) throws Exception{
        // 获取签名对象
        Signature signature = Signature.getInstance(algorithm);
        // 初始化签名
        signature.initSign(privateKey);
        signature.update(input.getBytes());
        // 签名
        byte[] bytes = signature.sign();
        return Base64.encode(bytes);
    }
}

参考文章:

//blog.csdn.net/u014294681/article/details/86705999