密码学笔记

第一章 密码学

1.1 密码学基本概念

密码在我们的生活中有着重要的作用,那么密码究竟来自何方,为何会产生呢?

密码学是网络安全、信息安全、区块链等产品的基础,常见的非对称加密、对称加密、散列函数等,都属于密码学范畴。

密码学有数千年的历史,从最开始的替换法到如今的非对称加密算法,经历了古典密码学,近代密码学和现代密码学三个阶段。密码学不仅仅是数学家们的智慧,更是如今网络空间安全的重要基础。

1.1.1 古典密码学

① 替换法

替换法很好理解,就是用固定的信息将原文替换成无法直接阅读的密文信息。例如将 b 替换成 w ,e 替换成p ,这样bee 单词就变换成了wpp,不知道替换规则的人就无法阅读出原文的含义。

替换法有单表替换和多表替换两种形式。单表替换即只有一张原文密文对照表单,发送者和接收者用这张表单来加密解密。在上述例子中,表单即为:a b c d e - s w t r p 。

多表替换即有多张原文密文对照表单,不同字母可以用不同表单的内容替换。

例如约定好表单为:表单 1:abcde-swtrp 、表单2:abcde-chfhk 、表单 3:abcde-jftou。

规定第一个字母用第三张表单,第二个字母用第一张表单,第三个字母用第二张表单,这时 bee单词就变成了

(312)fpk ,破解难度更高,其中 312 又叫做密钥,密钥可以事先约定好,也可以在传输过程中标记出来。

② 移位法

移位法就是将原文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后得出密文,典型的移位法应用有 “ 恺撒密码 ”。

例如约定好向后移动2位(abcde - cdefg),这样 bee 单词就变换成了dgg 。

同理替换法,移位法也可以采用多表移位的方式,典型的多表案例是“维尼吉亚密码”(又译维热纳尔密码),属于多表密码的一种形式。

③ 古典密码破解方式

古典密码虽然很简单,但是在密码史上是使用的最久的加密方式,直到“概率论”的数学方法被发现,古典密码就被破解了。

英文单词中字母出现的频率是不同的,e以12.702%的百分比占比最高,z 只占到0.074%,感兴趣的可以去百科查字母频率详细统计数据。如果密文数量足够大,仅仅采用频度分析法就可以破解单表的替换法或移位法。

多表的替换法或移位法虽然难度高一些,但如果数据量足够大的话,也是可以破解的。以维尼吉亚密码算法为例,破解方法就是先找出密文中完全相同的字母串,猜测密钥长度,得到密钥长度后再把同组的密文放在一起,使用频率分析法破解。

1.1.2 近代密码学

古典密码的安全性受到了威胁,外加使用便利性较低,到了工业化时代,近现代密码被广泛应用。

恩尼格玛机

恩尼格玛机是二战时期纳粹德国使用的加密机器,后被英国破译,参与破译的人员有被称为计算机科学之父、人工智能之父的图灵。

恩尼格玛机

恩尼格玛机使用的加密方式本质上还是移位和替代,只不过因为密码表种类极多,破解难度高,同时加密解密机器化,使用便捷,因而在二战时期得以使用。

1.1.3 现代密码学

① 散列函数

散列函数,也见杂凑函数、摘要函数或哈希函数,可将任意长度的消息经过运算,变成固定长度数值,常见的有MD5、SHA-1、SHA256,多应用在文件校验,数字签名中。

MD5 可以将任意长度的原文生成一个128位(16字节)的哈希值

SHA-1可以将任意长度的原文生成一个160位(20字节)的哈希值

② 对称密码

对称密码应用了相同的加密密钥和解密密钥。对称密码分为:序列密码(流密码),分组密码(块密码)两种。流密码是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密,块密码是先对信息流分块,再对每一块分别加密。

例如原文为1234567890,流加密即先对1进行加密,再对2进行加密,再对3进行加密……最后拼接成密文;块加密先分成不同的块,如1234成块,5678成块,90XX(XX为补位数字)成块,再分别对不同块进行加密,最后拼接成密文。前文提到的古典密码学加密方法,都属于流加密。

③ 非对称密码

对称密码的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。

在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。

非对称密码有两支密钥,公钥(publickey)和私钥(privatekey),加密和解密运算使用的密钥不同。用公钥对原文进行加密后,需要由私钥进行解密;用私钥对原文进行加密后(此时一般称为签名),需要由公钥进行解密(此时一般称为验签)。公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文

1.1.4 如何设置密码才安全
  • 密码不要太常见,不要使用类似于123456式的常用密码。
  • 各应用软件密码建议不同,避免出现一个应用数据库被脱库,全部应用密码崩塌,
  • 可在设置密码时增加注册时间、注册地点、应用特性等方法。例如tianjin123456,表示在天津注册的该应用。

1.2 ASCII编码

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。

示例代码

创建maven项目 encrypt-decrypt

添加pom文件

<dependencies>
    <!--不是现在使用的,不导入也可-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
    </dependency>
</dependencies>

1.3 恺撒加密

1.3.1 中国古代加密

看一个小故事 , 看看古人如何加密和解密:

公元683年,唐中宗即位。随后,武则天废唐中宗,立第四子李旦为皇帝,但朝政大事均由她自己专断。

裴炎、徐敬业和骆宾王等人对此非常不满。徐敬业聚兵十万,在江苏扬州起兵。裴炎做内应,欲以拆字手段为其传递秘密信息。后因有人告密,裴炎被捕,未发出的密信落到武则天手中。这封密信上只有“青鹅”二字,群臣对此大惑不解。

武则天破解了“青鹅”的秘密:“青”字拆开来就是“十二月”,而“鹅”字拆开来就是“我自与”。密信的意思是让徐敬业、骆宾王等率兵于十二月进发,裴炎在内部接应。“青鹅”破译后,裴炎被杀。接着,武则天派兵击败了徐敬业和骆宾王。

1.3.2 外国加密

在密码学中,恺撒密码是一种最简单且最广为人知的加密技术。

凯撒密码最早由古罗马军事统帅盖乌斯·尤利乌斯·凯撒在军队中用来传递加密信息,故称凯撒密码。这是一种位移加密方式,只对26个字母进行位移替换加密,规则简单,容易破解。下面是位移1次的对比:

将明文字母表向后移动1位,A变成了B,B变成了C……,Z变成了A。同理,若将明文字母表向后移动3位:

则A变成了D,B变成了E……,Z变成了C。

字母表最多可以移动25位。凯撒密码的明文字母表向后或向前移动都是可以的,通常表述为向后移动,如果要向前移动1位,则等同于向后移动25位,位移选择为25即可。

它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。

例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。

这个加密方法是以恺撒的名字命名的,当年恺撒曾用此方法与其将军们进行联系。

恺撒密码通常被作为其他更复杂的加密方法中的一个步骤。

简单来说就是当秘钥为n,其中一个待加密字符ch,加密之后的字符为ch+n,当ch+n超过’z’时,回到’a’计数。

1.3.3 凯撒位移加密

创建类 KaiserDemo,把 hello world 往右边移动3位

public class KaiserDemo {
    public static void main(String[] args) {
        //把原文向右移动三位
        int key = 3;
        //定义原文
        String input = "hello world";
        //凯撒加密
        String s = encryptKaiser(input,key);
        System.out.println("加密"+s);
        String s1 = discryptKaiser(s,key);
        System.out.println("解密"+s1);
    }

    private static String discryptKaiser(String s,int key) {
        char[] chars = s.toCharArray();
        StringBuilder sb = new StringBuilder();
        for (char aChar : chars) {
            int a = aChar;
            a = a-key;
            char b = (char) a;
            sb.append(b);
        }
        return sb.toString();
    }

    private static String encryptKaiser(String input,int key){
        //把字符串变成字节数组
        char[] chars = input.toCharArray();
        StringBuilder sb = new StringBuilder();
        for (char aChar : chars) {
            int a = aChar;
            a = a + key;
            char b = (char) a;
            sb.append(b);
        }
        return sb.toString();
    }
}

1.4 频度分析法破解恺撒加密

频率分析解密法

密码棒是不是太简单了些?

加密者选择将组成信息的字母替代成别的字母,比如说将a写成1,这样就不能被解密者直接拿到信息了。

这难不倒解密者,以英文字母为例,为了确定每个英文字母的出现频率,分析一篇或者数篇普通的英文文章,英文字母出现频率最高的是e,接下来是t,然后是a……,然后检查要破解的密文,也将每个字母出现的频率整理出来,假设密文中出现频率最高的字母是j,那么就可能是e的替身,如果密码文中出现频率次高的但是P,那么可能是t的替身,以此类推便就能解开加密信息的内容。这就是频率分析法。

将明文字母的出现频率与密文字母的频率相比较的过程
通过分析每个符号出现的频率而轻易地破译代换式密码
在每种语言中,冗长的文章中的字母表现出一种可对之进行分辨的频率。
e是英语中最常用的字母,其出现频率为八分之一
拷贝资料里面的 Util.java 和 FrequencyAnalysis.java 到项目的 com.atguigu.kaiser包下面 , article.txt 拷贝到项目文件夹的根目录

package com.zj.kaisar;

import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;

/**
 * 频率分析法破解凯撒密码
 */
public class FrequencyAnalysis {
    //英文里出现次数最多的字符
    private static final char MAGIC_CHAR = 'e';
    //破解生成的最大文件数
    private static final int DE_MAX_FILE = 4;

    public static void main(String[] args) throws Exception {
        //测试1,统计字符个数
        printCharCount("article.txt");

        //加密文件
		int key = 3;
		encryptFile("article.txt", "article_en.txt", key);

        //读取加密后的文件
        String artile = Util.file2String("article.txt");
        //解密(会生成多个备选文件)
        decryptCaesarCode(artile, "article.txt");
    }

    public static void printCharCount(String path) throws IOException {
        String data = Util.file2String(path);
        List<Entry<Character, Integer>> mapList = getMaxCountChar(data);
        for (Entry<Character, Integer> entry : mapList) {
            //输出前几位的统计信息
            System.out.println("字符'" + entry.getKey() + "'出现" + entry.getValue() + "次");
        }
    }

    public static void encryptFile(String srcFile, String destFile, int key) throws IOException {
        String artile = Util.file2String(srcFile);
        //加密文件
        String encryptData = KaiserDemo.encrypt(artile, key);
        //保存加密后的文件
        Util.string2File(encryptData, destFile);
    }

    /**
     * 破解凯撒密码
     * @param input 数据源
     * @return 返回解密后的数据
     */
    public static void decryptCaesarCode(String input, String destPath) {
        int deCount = 0;//当前解密生成的备选文件数
        //获取出现频率最高的字符信息(出现次数越多越靠前)
        List<Entry<Character, Integer>> mapList = getMaxCountChar(input);
        for (Entry<Character, Integer> entry : mapList) {
            //限制解密文件备选数
            if (deCount >= DE_MAX_FILE) {
                break;
            }

            //输出前几位的统计信息
            System.out.println("字符'" + entry.getKey() + "'出现" + entry.getValue() + "次");

            ++deCount;
            //出现次数最高的字符跟MAGIC_CHAR的偏移量即为秘钥
            int key = entry.getKey() - MAGIC_CHAR;
            System.out.println("猜测key = " + key + ", 解密生成第" + deCount + "个备选文件" + "\n");
            String decrypt = KaiserDemo.decrypt(input, key);

            String fileName = "de_" + deCount + destPath;
            Util.string2File(decrypt, fileName);
        }
    }

    //统计String里出现最多的字符
    public static List<Entry<Character, Integer>> getMaxCountChar(String data) {
        Map<Character, Integer> map = new HashMap<Character, Integer>();
        char[] array = data.toCharArray();
        for (char c : array) {
            if(!map.containsKey(c)) {
                map.put(c, 1);
            }else{
                Integer count = map.get(c);
                map.put(c, count + 1);
            }
        }

        //输出统计信息
        /*for (Entry<Character, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "出现" + entry.getValue() +  "次");
        }*/

        //获取获取最大值
        int maxCount = 0;
        for (Entry<Character, Integer> entry : map.entrySet()) {
            //不统计空格
            if (/*entry.getKey() != ' ' && */entry.getValue() > maxCount) {
                maxCount = entry.getValue();
            }
        }

        //map转换成list便于排序
        List<Entry<Character, Integer>> mapList = new ArrayList<Entry<Character,Integer>>(map.entrySet());
        //根据字符出现次数排序
        Collections.sort(mapList, new Comparator<Entry<Character, Integer>>(){

            public int compare(Entry<Character, Integer> o1,
                               Entry<Character, Integer> o2) {
                return o2.getValue().compareTo(o1.getValue());
            }
        });
        return mapList;
    }
}


package com.zj.kaisar;

import java.io.*;

public class Util {

    public static void print(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            sb.append(bytes[i]).append(" ");
        }
        System.out.println(sb);
    }

    public static String file2String(String path) throws IOException {
        FileReader reader = new FileReader(new File(path));
        char[] buffer = new char[1024];
        int len = -1;
        StringBuffer sb = new StringBuffer();
        while ((len = reader.read(buffer)) != -1) {
            sb.append(buffer, 0, len);
        }
        return sb.toString();
    }

    public static void string2File(String data, String path){
        FileWriter writer = null;
        try {
            writer = new FileWriter(new File(path));
            writer.write(data);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static String inputStream2String(InputStream in) throws IOException {
        int len = -1;
        byte[] buffer = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while((len = in.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        baos.close();

        return baos.toString("UTF-8");
    }
}

运行结果 # 出现次数最多, 我们知道在英文当中 e 出现的频率是最高的,我们假设现在 # 号,就是 e ,变形而来的 ,我们可以对照 ascii 编码表 ,我们的凯撒加密当中位移是加了一个 key ,所以我们 猜测 两个值直接相差 -66 ,我们现在就以 -66 进行解密 生成一个文件,我们查看第一个文件发现,根本读不懂,所以解密失败,我们在猜测 h 是 e ,h 和 e 之间相差3 ,所以我们在去看第二个解密文件,发现我们可以读懂,解密成功。

1.5 Byte和bit

Byte : 字节. 数据存储的基本单位,比如移动硬盘1T , 单位是byte

bit : 比特, 又叫位. 一个位要么是0要么是1. 数据传输的单位 , 比如家里的宽带100MB,下载速度并没有达到100MB,一般都是12-13MB,那么是因为需要使用 100 / 8

关系: 1Byte = 8bit

1.5.1 获取字符串byte

public class ByteBit {
    public static void main(String[] args) {
        String a = "a";
        byte[] bytes = a.getBytes();
        for (byte b : bytes) {
            int c=b;
            // 打印发现byte实际上就是ascii码
            System.out.println(c);
        }
    }
}
97

1.5.2 byte对应bit

public class ByteBit {
    public static void main(String[] args) {
        String a = "a";
        byte[] bytes = a.getBytes();
        for (byte b : bytes) {
            int c=b;
            // 打印发现byte实际上就是ascii码
            System.out.println(c);
            // 我们在来看看每个byte对应的bit,byte获取对应的bit
            String s = Integer.toBinaryString(c);
            System.out.println(s);
        }
    }
}
97
1100001

运行程序

打印出来应该是8个bit,但前面是0,没有打印 ,从打印结果可以看出来,一个英文字符 ,占一个字节

1.5.3 中文对应的字节

// 中文在GBK编码下, 占据2个字节
// 中文在UTF-8编码下, 占据3个字节

public class ByteBitDemo {
    public static void main(String[] args) throws Exception{

```
    String a = "尚";
    byte[] bytes = a.getBytes();
    for (byte b : bytes) {
        System.out.print(b + "   ");
        String s = Integer.toBinaryString(b);
        System.out.println(s);
    }
}
```

}
-27
-80
-102


运行程序:我们发现一个中文是有 3 个字节组成

运行程序:我们发现一个中文是有 3 个字节组成

我们修改 编码格式 , 编码格式改成 GBK ,我们在运行发现变成了 2 个字节

public static void main(String[] args) throws Exception{

```
String a = "尚";

// 在中文情况下,不同的编码格式,对应不同的字节
//GBK :编码格式占2个字节
// UTF-8:编码格式占3个字节
byte[] bytes = a.getBytes("GBK");
// byte[] bytes = a.getBytes("UTF-8");
for (byte b : bytes) {
    System.out.print(b + "   ");
    String s = Integer.toBinaryString(b);
    System.out.println(s);
}
```

}
-55
-48

1.5.4 英文对应的字节

我们在看看英文,在不同的编码格式占用多少字节

public class ByteBit {
    public static void main(String[] args) throws Exception{

```
    String a = "A";
    byte[] bytes = a.getBytes();
    // 在中文情况下,不同的编码格式,对应不同的字节
```

//        byte[] bytes = a.getBytes("GBK");
        for (byte b : bytes) {
            System.out.print(b + "   ");
            String s = Integer.toBinaryString(b);
            System.out.println(s);
        }
    }
}
都是65

1.6 常见加密方式

1.6.1 对称加密

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
示例:
我们现在有一个原文3要发送给B
设置密钥为108, 3 * 108 = 324, 将324作为密文发送给B
B拿到密文324后, 使用324/108 = 3 得到原文
常见加密算法:
DES : Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。
AES : Advanced Encryption Standard, 高级加密标准 .在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

特点:
加密速度快, 可以加密大文件
密文可逆, 一旦密钥文件泄漏, 就会导致数据暴露
加密后编码表找不到对应字符, 出现乱码
一般结合Base64使用,解决乱码的问题

1.6.2 DES加密

示例代码 des加密算法

Cipher :文档 https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html#getInstance-java.lang.String

DESpackage com.zj.desaes;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

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

public class DesAesDemo {
    public static void main(String[] args) throws Exception{
        //原文
        String input = "硅谷";
        //定义key
        String key = "12345678";

        String encode = encrypt(input,key);
        String s = decrypt(encode,key);
        System.out.println(s);


    }

    public  static String decrypt(String input, String key) throws Exception {
        //算法
        String transformation = "DES";
        String algorithm = "DES";
        Cipher cipher = Cipher.getInstance(transformation);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithm);
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);
        byte[] bytes = cipher.doFinal(Base64.decode(input));
        return new String(bytes);
    }
    public static String encrypt(String input, String key) throws Exception {
        //算法
        String transformation = "DES";
        String algorithm = "DES";
        //创建加密对象
        Cipher cipher = Cipher.getInstance(transformation);
        //创建加密规则
        //第一个参数表示key的字节
        //第二个表示加密的类型
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithm);
        //进行加密初始化
        //两个参数,第一个是模式,加密/解密模式
        //第二个是加密规则
        cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec);
        //调用加密方法
        //参数表示原文的字节数组
        byte[] bytes = cipher.doFinal(input.getBytes());
        //创建base64
        String encode = Base64.encode(bytes);
        System.out.println(encode);
        //打印密文
        //如果直接打印密文会出现乱码
        System.out.println(new String(bytes));
        return encode;
    }
}


1.6.3 AES加密解密

AES 加密解密和 DES 加密解密代码一样,只需要修改加密算法就行,拷贝 ESC 代码

注意key是16个字节即可,DES的key是8字节

1.6.4 toString()与new String ()用法区别

举例子

package com.atguigu;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

public class TestBase64 {
    public static void main(String[] args) {
        String str="TU0jV0xBTiNVYys5bEdiUjZlNU45aHJ0bTdDQStBPT0jNjQ2NDY1Njk4IzM5OTkwMDAwMzAwMA==";

```
    String rlt1=new 			String(Base64.decode(str));//MM#WLAN#Uc+9lGbR6e5N9hrtm7CA+A==#646465698#399900003000

    String rlt2=Base64.decode(str).toString();//[B@1540e19d

    System.out.println(rlt1);
    System.out.println(rlt2);
}
```

}

结果是:

结果是:

MM#WLAN#Uc+9lGbR6e5N9hrtm7CA+A==#646465698#399900003000

[B@1540e19d

哪一个是正确的?为什么?

​ 这里应该用new String()的方法,因为Base64加解密是一种转换编码格式的原理

toString()与new String ()用法区别

​ str.toString是调用了这个object对象的类的toString方法。一般是返回的是哈希值(地址)这么一个String:[class name]@[hashCode]

new String(str)是根据parameter是一个字节数组,使用java虚拟机默认的编码格式,将这个字节数组decode为对应的字符。若虚拟机默认的编码格式是ISO-8859-1,按照ascii编码表即可得到字节对应的字符。

什么时候用什么方法呢?

new String()一般使用字符转码的时候,byte[]数组的时候

toString()对象打印的时候使用

1.7 加密模式

加密模式:https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html

前面DES和AES都有key,而加密模式是说怎么用key。

ECB是同key并行,CBC是变key串行

1.7.1 ECB

ECB : Electronic codebook, 电子密码本. 需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密

但是每个块使用的是同一个key,所以可以并行处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GJp3FHOl-1608474746606)(C:\Users\Administrator\iCloudDrive\笔记\java系列\安全密码学\img\ECB.png)]

优点 : 可以并行处理数据
缺点 : 同样的原文生成同样的密文, 不能很好的保护数据
同时加密,原文是一样的,加密出来的密文也是一样的

1.7.2 CBC

CBC : Cipher-block chaining, 密码块链接. 每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块

在加密的时候,会取决于前面的iv相邻,把前面的相邻进行异或处理,后面的铭文进行加密的时候,会一直依赖于前面的加密key

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZPoSAC1W-1608474746609)(C:\Users\Administrator\iCloudDrive\笔记\java系列\安全密码学\img\CBC.png)]

优点 : 同样的原文生成的密文不一样
缺点 : 串行处理数据
注意CBC传参的时候不只要key,还要有一个IV向量

1.7.3 加密模式总结

前面DES和AES都有key,而加密模式是说怎么用key。

ECB是同key并行,CBC是变key串行

1.7.4 加密模式的使用

如前面的代码中,只要写成String transformation = “DES/CBC/NoPadding”;即可

另外在CBC中还要在加密解密中都写好IV向量IvParameterSpec iv = new IvParameterSpec(key.getBytes());

public class DesAesDemo {
    public static void main(String[] args) throws Exception{
        // 原文 如果使用的是不填充的模式,那么原文必须是8个字节的整数倍
        String input = "硅谷12";
        // 定义key
        // 如果使用des进行加密,那么密钥必须是8个字节
        String key = "12345678";
        // 算法 qANksk5lvqM=
//        String transformation = "DES";
        // ECB:表示加密模式
        // PKCS5Padding:表示填充模式 qANksk5lvqM=
//        String transformation = "DES/ECB/PKCS5Padding";
        // 如果默认情况,没有写填充模式和加密模式,那么默认就使用DES/ECB/PKCS5Padding
//        8Ze/OtPlSaU=
//        String transformation = "DES/CBC/PKCS5Padding";
        //Y6htKI/ceJg=
        String transformation = "DES/CBC/NoPadding";
        // 加密类型
        String algorithm = "DES";
        // 指定获取密钥的算法
        String encryptDES = encryptDES(input, key, transformation, algorithm);
        System.out.println("加密:" + encryptDES);

        String s = decryptDES(encryptDES, key, transformation, algorithm);
        System.out.println("解密:" + s);
    }

    /**
     * 解密
     * @param encryptDES  密文
     * @param key         密钥
     * @param transformation 加密算法
     * @param algorithm   加密类型
     * @return
     */
    private static String decryptDES(String encryptDES, String key, String transformation, String algorithm) throws Exception{
        Cipher cipher = Cipher.getInstance(transformation);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithm);
        // 创建iv向量 //CBC加密模式需要有IV向量
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        //Cipher.DECRYPT_MODE:表示解密
        // 解密规则
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,iv);
        // 解密,传入密文
        byte[] bytes = cipher.doFinal(Base64.decode(encryptDES));

        return new String(bytes);
    }

    /**
     * 使用DES加密数据
     *
     * @param input          : 原文
     * @param key            : 密钥(DES,密钥的长度必须是8个字节)
     * @param transformation : 获取Cipher对象的算法
     * @param algorithm      : 获取密钥的算法
     * @return : 密文
     * @throws Exception
     */
    private static String encryptDES(String input, String key, String transformation, String algorithm) throws Exception {
        // 获取加密对象
        Cipher cipher = Cipher.getInstance(transformation);
        // 创建加密规则
        // 第一个参数key的字节
        // 第二个参数表示加密算法
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
        // 创建iv向量,iv向量,是使用到CBC加密模式
        // 在使用iv向量进行加密的时候,iv的字节也必须是8个字节
        IvParameterSpec iv = new IvParameterSpec("12345678".getBytes());
        // ENCRYPT_MODE:加密模式
        // DECRYPT_MODE: 解密模式
        // 初始化加密模式和算法
        cipher.init(Cipher.ENCRYPT_MODE,sks,iv);
        // 加密
        byte[] bytes = cipher.doFinal(input.getBytes());

        // 输出加密后的数据
        String encode = Base64.encode(bytes);

        return encode;
    }
}


1.8 填充模式

当需要按块处理的数据, 数据长度不符合块处理需求时, 按照一定的方法填充满块长的规则
使用方法:和加密模式一样,写在String transformation = “DES/CBC/NoPadding”;
但要注意DES和AES的限制,不能不俗8/16字节的整数倍
iv字节也必须是8个字节
NoPadding
不填充.
在DES加密算法下, 要求原文长度必须是8byte的整数倍
在AES加密算法下, 要求原文长度必须是16byte的整数倍
PKCS5Padding
数据块的大小为8位, 不够就补足

Tips
默认情况下, 加密模式和填充模式为 : ECB/PKCS5Padding
如果使用CBC模式, 在初始化Cipher对象时, 需要增加参数, 初始化向量IV : IvParameterSpec iv = new IvParameterSpec(key.getBytes());

1.9 消息摘要

消息摘要(Message Digest)又称为数字摘要(Digital Digest)
它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生
输入长度不定,输出长度一定。单向不可逆
使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全

1.9.1 特点

无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出

只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出

消息摘要是单向、不可逆的

常见算法 :

  • MD5

  • SHA1

  • SHA256

  • SHA512

    在线获取消息摘要:比如有的网站下载软件有sha256等值,可以下载该软件,然后得到其消息摘要,比对官网的消息摘要,就知道该软件有没有被修改过

百度搜索 tomcat ,进入官网下载 ,会经常发现有 sha1sha512 , 这些都是数字摘要

数字摘要

public class DigestDemo1 {
    public static void main(String[] args) throws Exception {
        //原文
        String input = "aa";
        //算法
        String algorithm = "MD5";
        //创建信息摘要对象
        MessageDigest digest = MessageDigest.getInstance(algorithm);
        //执行消息摘要算法(通过执行操作来完成哈希计算)
        byte[] digest1 = digest.digest(input.getBytes());//得到的是密文,需要进行base64转码

        System.out.println(Base64.encode(digest1));

    }
}


运行:QSS8CpM1wn8IbyS6IHpJEg==

使用在线 md5 加密 ,发现我们生成的值和代码生成的值不一样,那是因为消息摘要不是使用base64进行编码的,所以我们需要把值转成16进制

数字摘要转换成 16 进制:虽然我们不能显示负数,但是我们可以显示字符串,把原来的字节码不用ASCII表示,改用16进制表示即可

for (byte b : digest) {
            // 转成 16进制
            String s = Integer.toHexString(b & 0xff);
            //System.out.println(s);
            if (s.length() == 1){
                // 如果生成的字符只有一个,前面补0
                s = "0"+s;
            }
            sb.append(s);
        }
        System.out.println(sb.toString());


  • MD5算法 : 摘要结果16个字节, 转16进制后32个字节

  • SHA1算法 : 摘要结果20个字节, 转16进制后40个字节

  • SHA256算法 : 摘要结果32个字节, 转16进制后64个字节

  • SHA512算法 : 摘要结果64个字节, 转16进制后128个字节

1.10 非对称加密

简介:

① 非对称加密算法又称现代加密算法。

② 非对称加密是计算机通信安全的基石,保证了加密数据不会被破解。

③ 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey) 和私有密(privatekey)

④ 公开密钥和私有密钥是一对

⑤ 如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密。

⑥ 如果用私有密钥对数据进行加密,只有用对应的公开密钥才能解密。

⑦ 因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

示例
首先生成密钥对, 公钥为(5,14), 私钥为(11,14)
现在A希望将原文2发送给B
A使用公钥加密数据, 2的5次方mod 14 = 4 , 将密文4发送给B
B使用私钥解密数据, 4的11次方mod14 = 2, 得到原文2
特点
加密和解密使用不同的密钥
如果使用私钥加密, 只能使用公钥解密
如果使用公钥加密, 只能使用私钥解密
处理数据的速度较慢, 因为安全级别高
常见算法
RSA
ECC

RSAdemo

public class RSADemo {
    public static void main(String[] args) throws Exception {
        String input = "硅谷";
        //算法
        String algorithm = "RSA";
        //创建密钥生成器对象
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        //生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //生成公钥和私钥
        PublicKey aPublic = keyPair.getPublic();
        PrivateKey aPrivate = keyPair.getPrivate();

        //获取私钥的字节数组
        byte[] aPrivateencoded = aPrivate.getEncoded();
        //获取公钥的字节数组
        byte[] aPublicencoded = aPublic.getEncoded();

        //使用base64进行编码
        String privateEncode = Base64.encode(aPrivateencoded);
        String PublicEncode = Base64.encode(aPublicencoded);

        System.out.println(privateEncode);
        System.out.println(PublicEncode);

        //创建加密对象
        Cipher cipher = Cipher.getInstance(algorithm);
        //加密进行初始化
        //参数1加密模式,参数2用公钥加密还是私钥加密
        cipher.init(Cipher.ENCRYPT_MODE,aPrivate);

        //使用私钥进行加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        System.out.println(Base64.encode(bytes));

        //公钥解密
        cipher.init(Cipher.DECRYPT_MODE,aPublic);
        byte[] bytes1 = cipher.doFinal(bytes);
        System.out.println(new String(bytes1));
    }
}

1.10.1 保存公钥和私钥

前面代码每次都会生成 加密和解密 ,咱们需要把加密和解密的方法全部到本地的根目录下面。

public class RSAdemo {
    public static void main(String[] args) throws Exception {
        String input = "硅谷";
        // 加密算法
        String algorithm = "RSA";

        //生成密钥对并保存在本地文件中
        generateKeyToFile(algorithm, "a.pub", "a.pri");

          //加密
//        String s = encryptRSA(algorithm, privateKey, input);
        // 解密
//        String s1 = decryptRSA(algorithm, publicKey, s);
//        System.out.println(s1);


    }

    /**
     * 生成密钥对并保存在本地文件中
     *
     * @param algorithm : 算法
     * @param pubPath   : 公钥保存路径
     * @param priPath   : 私钥保存路径
     * @throws Exception
     */
    private static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {
        // 获取密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 获取密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 获取公钥
        PublicKey publicKey = keyPair.getPublic();
        // 获取私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 获取byte数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 进行Base64编码
        String publicKeyString = Base64.encode(publicKeyEncoded);
        String privateKeyString = Base64.encode(privateKeyEncoded);
        // 保存文件
        FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));
        FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));

    }

    /**
     * 解密数据
     *
     * @param algorithm      : 算法
     * @param encrypted      : 密文
     * @param key            : 密钥
     * @return : 原文
     * @throws Exception
     */
    public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{
         // 创建加密对象
        // 参数表示加密算法
        Cipher cipher = Cipher.getInstance(algorithm);
        // 私钥进行解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        // 由于密文进行了Base64编码, 在这里需要进行解码
        byte[] decode = Base64.decode(encrypted);
        // 对密文进行解密,不需要使用base64,因为原文不会乱码
        byte[] bytes1 = cipher.doFinal(decode);
        System.out.println(new String(bytes1));
        return new String(bytes1);

    }
    /**
     * 使用密钥加密数据
     *
     * @param algorithm      : 算法
     * @param input          : 原文
     * @param key            : 密钥
     * @return : 密文
     * @throws Exception
     */
    public static String encryptRSA(String algorithm,Key key,String input) throws Exception{
        // 创建加密对象
        // 参数表示加密算法
        Cipher cipher = Cipher.getInstance(algorithm);
        // 初始化加密
        // 第一个参数:加密的模式
        // 第二个参数:使用私钥进行加密
        cipher.init(Cipher.ENCRYPT_MODE,key);
        // 私钥加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        // 对密文进行Base64编码
        System.out.println(Base64.encode(bytes));
        return Base64.encode(bytes);
    }
}


1.10.2 读取私钥

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

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;

public class RSAdemo {
    public static void main(String[] args) throws Exception {
        String input = "硅谷";
        // 加密算法
        String algorithm = "RSA";
        // 从文件读取私钥 ,这个是自定义的方法
        PrivateKey privateKey = getPrivateKey("a.pri", algorithm);
    }

    public static PrivateKey getPrivateKey(String priPath,String algorithm) throws Exception{
        // 将文件内容转为字符串
        String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());
        // 获取密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 构建密钥规范 进行Base64解码
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));
        // 生成私钥
        return keyFactory.generatePrivate(spec);
    }

    /**
     * 生成密钥对并保存在本地文件中
     *
     * @param algorithm : 算法
     * @param pubPath   : 公钥保存路径
     * @param priPath   : 私钥保存路径
     * @throws Exception
     */
    private static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {
        // 获取密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 获取密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 获取公钥
        PublicKey publicKey = keyPair.getPublic();
        // 获取私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 获取byte数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 进行Base64编码
        String publicKeyString = Base64.encode(publicKeyEncoded);
        String privateKeyString = Base64.encode(privateKeyEncoded);
        // 保存文件
        FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));
        FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));

    }

    /**
     * 解密数据
     *
     * @param algorithm      : 算法
     * @param encrypted      : 密文
     * @param key            : 密钥
     * @return : 原文
     * @throws Exception
     */
    public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{
         // 创建加密对象
        // 参数表示加密算法
        Cipher cipher = Cipher.getInstance(algorithm);
        // 私钥进行解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        // 由于密文进行了Base64编码, 在这里需要进行解码
        byte[] decode = Base64.decode(encrypted);
        // 对密文进行解密,不需要使用base64,因为原文不会乱码
        byte[] bytes1 = cipher.doFinal(decode);
        System.out.println(new String(bytes1));
        return new String(bytes1);

    }
    /**
     * 使用密钥加密数据
     *
     * @param algorithm      : 算法
     * @param input          : 原文
     * @param key            : 密钥
     * @return : 密文
     * @throws Exception
     */
    public static String encryptRSA(String algorithm,Key key,String input) throws Exception{
        // 创建加密对象
        // 参数表示加密算法
        Cipher cipher = Cipher.getInstance(algorithm);
        // 初始化加密
        // 第一个参数:加密的模式
        // 第二个参数:使用私钥进行加密
        cipher.init(Cipher.ENCRYPT_MODE,key);
        // 私钥加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        // 对密文进行Base64编码
        System.out.println(Base64.encode(bytes));
        return Base64.encode(bytes);
    }
}


1.10.3 读取公钥

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

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RSAdemo {
    public static void main(String[] args) throws Exception {
        String input = "硅谷";
        // 加密算法
        String algorithm = "RSA";
        // 从文件读取私钥公钥
        PrivateKey privateKey = getPrivateKey("a.pri", algorithm);
        PublicKey publicKey = getPublicKey("a.pub", algorithm);

        String s = encryptRSA(algorithm, privateKey, input);
        String s1 = decryptRSA(algorithm, publicKey, s);
        System.out.println(s1);


    }

    public static PublicKey getPublicKey(String pulickPath,String algorithm) throws Exception{
        // 将文件内容转为字符串
        String publicKeyString = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset());
        // 获取密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 构建密钥规范 进行Base64解码
        X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyString));
        // 生成公钥
        return keyFactory.generatePublic(spec);
    }

    public static PrivateKey getPrivateKey(String priPath,String algorithm) throws Exception{
        // 将文件内容转为字符串
        String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());
        // 获取密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 构建密钥规范 进行Base64解码
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));
        // 生成私钥
        return keyFactory.generatePrivate(spec);
    }

    /**
     * 生成密钥对并保存在本地文件中
     *
     * @param algorithm : 算法
     * @param pubPath   : 公钥保存路径
     * @param priPath   : 私钥保存路径
     * @throws Exception
     */
    public static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {
        // 获取密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 获取密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 获取公钥
        PublicKey publicKey = keyPair.getPublic();
        // 获取私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 获取byte数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 进行Base64编码
        String publicKeyString = Base64.encode(publicKeyEncoded);
        String privateKeyString = Base64.encode(privateKeyEncoded);
        // 保存文件
        FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));
        FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));

    }

    /**
     * 解密数据
     *
     * @param algorithm      : 算法
     * @param encrypted      : 密文
     * @param key            : 密钥
     * @return : 原文
     * @throws Exception
     */
    public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{
         // 创建加密对象
        // 参数表示加密算法
        Cipher cipher = Cipher.getInstance(algorithm);
        // 私钥进行解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        // 由于密文进行了Base64编码, 在这里需要进行解码
        byte[] decode = Base64.decode(encrypted);
        // 对密文进行解密,不需要使用base64,因为原文不会乱码
        byte[] bytes1 = cipher.doFinal(decode);
        return new String(bytes1);

    }
    /**
     * 使用密钥加密数据
     *
     * @param algorithm      : 算法
     * @param input          : 原文
     * @param key            : 密钥
     * @return : 密文
     * @throws Exception
     */
    public static String encryptRSA(String algorithm,Key key,String input) throws Exception{
        // 创建加密对象
        // 参数表示加密算法
        Cipher cipher = Cipher.getInstance(algorithm);
        // 初始化加密
        // 第一个参数:加密的模式
        // 第二个参数:使用私钥进行加密
        cipher.init(Cipher.ENCRYPT_MODE,key);
        // 私钥加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        // 对密文进行Base64编码
        return Base64.encode(bytes);
    }
}


1.11 数字签名

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

数字签名是非对称密钥加密技术与数字摘要技术的应用。

1.11.1 简单认识

相信我们都写过信,在写信的时候落款处总是要留下自己的名字,用来表示写信的人是谁。我们签的这个字就是生活中的签名:

而数字签名呢?其实也是同样的道理,他的含义是:在网络中传输数据时候,给数据添加一个数字签名,表示是谁发的数据,而且还能证明数据没有被篡改。

OK,数字签名的主要作用就是保证了数据的有效性(验证是谁发的)和完整性(证明信息没有被篡改)。下面我们就来好好地看一下他的底层实现原理是什么样子的。

1.11.2 基本原理

为了理解得清楚,我们通过案例一步一步来讲解。话说张三有俩好哥们A、B。由于工作原因,张三和AB写邮件的时候为了安全都需要加密。于是张三想到了数字签名:

整个思路是这个样子的:

第一步:加密采用非对称加密,张三有三把钥匙,两把公钥,送给朋友。一把私钥留给自己。

第二步:A或者B写邮件给张三:A先用公钥对邮件加密,然后张三收到邮件之后使用私钥解密。

第三步:张三写邮件给A或者B:

(1)张三写完邮件,先用hash函数生成邮件的摘要,附着在文章上面,这就完成了数字签名,然后张三再使用私钥加密。就可以把邮件发出去了。
(2)A或者是B收到邮件之后,先把数字签名取下来,然后使用自己的公钥解密即可。这时候取下来的数字签名中的摘要若和张三的一致,那就认为是张三发来的,再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。
上面的流程我们使用一张图来演示一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wr6yFfBN-1691393511639)(C:\Users\86152\AppData\Roaming\Typora\typora-user-images\1690183255332.png)]

服务器把内容(证书明文)经过hash算法得到证书的摘要,再经过私钥加密得到证书的数字签名,然后发送证书和数字签名给浏览器。

浏览器得到了证书和数字签名,先拿证书里的公钥对对数字签名解密得到摘要。再拿证书里的hash算法对证书进行hash得到摘要。对比连个摘要是否相同。如果相同,则代表证书没有被修改过,也就证明了是该服务器发过来的。

但是如果别人伪造了一个证书呢?比如证书里写的颁发给了baidu,而且公钥是第三者的,你发送的内容就被别人拦截下来用第三者的私钥解密,偷看后为了不被发现再用第三者的私钥加密发送给baidu。

为了验证证书没被篡改过,我们可以取认证中心鉴定。

1.11.3 数字证书

上面提到我们对签名进行验证时,需要用到公钥。如果公钥是伪造的,那我们无法验证数字签名了,也就根本不可能从数字签名确定对方的合法性了。这时候证书就闪亮登场了。我们可能都有考各种证书的经历,比如说普通话证书,四六级证书等等,但是归根结底,到任何场合我们都能拿出我们的证书来证明自己确实已经考过了普通话,考过了四六级。这里的证书也是同样的道理。

如果不理解证书的作用,我们可以举一个例子,比如说我们的毕业证书,任何公司都会承认。为什么会承认?因为那是国家发得,大家都信任国家。也就是说只要是国家的认证机构,我们都信任它是合法的。

服务器有公钥A,私钥A。认证机构有私钥B,公钥B

服务器把公钥A给认证机构,为了让认证中心给自己开个证明说我这个公钥确实是我的,而认证中心自己系统里也知道我给谁鉴定过以及他的公钥。

即使浏览器把公钥丢了也可以继续从证书里获取到

点击浏览器链接的左面,可以看到证书。

1.11.4 网页加密

我们看一个应用“数字证书”的实例:https协议。这个协议主要用于网页加密

首先,客户端向服务器发出加密请求。

服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。

客户端(浏览器)的“证书管理器”,有“受信任的根证书颁发机构”列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内

如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。

如果数字证书是可靠的,客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。

1.11.5数字签名的加密和校验

public class SignatureDemo {
    public static void main(String[] args) throws Exception {
        String input = "硅谷";
        // 创建密钥对
        // KeyPairGenerator:密钥对生成器对象
        String algorithm = "RSA";
        // 读取私钥
        PrivateKey privateKey = getPrivateKey("a.pri", algorithm);
        // 读取公钥key
        PublicKey publicKey = getPublicKey("a.pub", algorithm);

        //获取数字签名
        String signaturedData = getSignature(input,"sha256withrsa",privateKey);
        System.out.println(signaturedData);

        //校验签名
        boolean b = verifySignature(input,"sha256withrsa",publicKey,signaturedData);
        System.out.println(b);
    }

    private static boolean verifySignature(String input, String sha256withrsa, PublicKey publicKey, String signaturedData) throws Exception {
        //获取签名对象
        Signature signature = Signature.getInstance(sha256withrsa);
        //初始化校验
        signature.initVerify(publicKey);
        //传入原文
        signature.update(input.getBytes());
        //校验数据
        return signature.verify(Base64.decode(signaturedData));


    }

    private static String getSignature(String input, String sha256withrsa, PrivateKey privateKey) throws Exception {
        //获取签名对象
        Signature signature = Signature.getInstance(sha256withrsa);
        //初始化签名
        signature.initSign(privateKey);
        //传入原文
        signature.update(input.getBytes());
        //开始签名
        byte[] sign = signature.sign();
        //使用base进行编码
        return Base64.encode(sign);
    }

    public static PublicKey getPublicKey(String publicPath, String algorithm) throws Exception{
        String publicKeyString = FileUtils.readFileToString(new File(publicPath), Charset.defaultCharset());
        // 创建key的工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 创建公钥规则
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(publicKeyString));
        return keyFactory.generatePublic(keySpec);
    }

    public static PrivateKey getPrivateKey(String priPath, String algorithm) throws Exception{
        String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());
        // 创建key的工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 创建私钥key的规则
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));
        // 返回私钥对象
        return keyFactory.generatePrivate(keySpec);
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值