Java中常见的密码学知识

现代密码学

  • 散列函数
    散列函数,也见杂凑函数、摘要函数或哈希函数,可将任意长度的消息经过运算,变成固定长度数值,常见的有MD5、SHA-1、SHA256,多应用在文件校验,数字签名中。
    MD5 可以将任意长度的原文生成一个128位(16字节)的哈希值
    SHA-1可以将任意长度的原文生成一个160位(20字节)的哈希值
  1. 对称密码
    对称密码应用了相同的加密密钥和解密密钥。对称密码分为:序列密码(流密码),分组密码(块密码)两种。流密码是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密,块密码是先对信息流分块,再对每一块分别加密。
    例如原文为1234567890,流加密即先对1进行加密,再对2进行加密,再对3进行加密……最后拼接成密文;块加密先分成不同的块,如1234成块,5678成块,90XX(XX为补位数字)成块,再分别对不同块进行加密,最后拼接成密文。

  2. 非对称密码
    对称密码的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。
    在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。
    非对称密码有两支密钥,公钥(publickey)和私钥(privatekey),加密和解密运算使用的密钥不同。用公钥对原文进行加密后,需要由私钥进行解密;用私钥对原文进行加密后(此时一般称为签名),需要由公钥进行解密(此时一般称为验签)。公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文。

如何设置密码才安全

  • 密码不要太常见,不要使用类似于12345678这样的常用密码
  • 各应用软件密码建议不要一样,容易被攻击者撞库破解
  • 可在设置密码时增加注册时间、注册地点等方法

ASCII编码

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

Byte和Bit

  • Byte : 字节. 数据存储的基本单位,比如移动硬盘1T , 单位是byte
  • bit : 比特, 又叫位. 一个位要么是0要么是1. 数据传输的单位 , 比如家里的宽带100MB,下载速度并没有达到100MB,一般都是12-13MB,那么是因为需要使用 100 / 8
  • 关系: 1Byte = 8bit

案例如下:

package com.example.test.security;

import java.io.UnsupportedEncodingException;

public class test {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String a = "ab";
        byte[] bytes = a.getBytes();//byte实际上就是ascii码,一个英文占用一个字节
        for (byte b : bytes) {
            int c = b;
            System.out.println(c);
            //获取byte对应的bit
            String s = Integer.toBinaryString(c);
            System.out.println(s);
        }
        System.out.println("========================");
        a = "中国";
        bytes = a.getBytes("gbk");//byte实际上就是ascii码.中文的编码格式默认是utf-8,一个中文占用3个字节,gbk格式,一个中文占用2个字节
        for (byte b : bytes) {
            int c = b;
            System.out.println(c);
            //获取byte对应的bit
            String s = Integer.toBinaryString(c);
            System.out.println(s);
        }
    }
}

运行截图:
Java中常见的密码学知识_第1张图片

常见的加密方式

Java中常见的密码学知识_第2张图片

对称加密

  • 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
  • 示例
    • 我们现在有一个原文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使用

DES加解密案例如下

package com.example.test.security;

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

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
public class test {
    // DES加密算法,key的大小必须是8个字节

    public static void main(String[] args) throws Exception {
        String input ="中国";
        // DES加密算法,key的大小必须是8个字节
        String key = "12dfdade";

        String transformation = "DES"; // 9PQXVUIhaaQ=
        // 指定获取密钥的算法
        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);

    }

    /**
     * 使用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);
        // ENCRYPT_MODE:加密模式
        // DECRYPT_MODE: 解密模式
        // 初始化加密模式和算法
        cipher.init(Cipher.ENCRYPT_MODE,sks);
        // 加密
        byte[] bytes = cipher.doFinal(input.getBytes());

        // 输出加密后的数据
        String encode = Base64.encode(bytes);//如果不使用base64,直接使用new String(bytes)那么输出的结果有可能是乱码,因为对应的字节可能出现负数,没有出现在ascii码中

        return encode;
    }

    /**
     * 使用DES解密
     *
     * @param input          : 密文
     * @param key            : 密钥
     * @param transformation : 获取Cipher对象的算法
     * @param algorithm      : 获取密钥的算法
     * @throws Exception
     * @return: 原文
     */
    private static String decryptDES(String input, String key, String transformation, String algorithm) throws Exception {
        // 1,获取Cipher对象
        Cipher cipher = Cipher.getInstance(transformation);
        // 指定密钥规则
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
        cipher.init(Cipher.DECRYPT_MODE, sks);
        // 3. 解密,上面使用的base64编码,下面直接用密文
        byte[] bytes = cipher.doFinal(Base64.decode(input));
        //  因为是明文,所以直接返回
        return new String(bytes);
    }
}

AES加解密案例如下

package com.example.test.security;

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

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
public class test {
    // DES加密算法,key的大小必须是8个字节

    public static void main(String[] args) throws Exception {
        String input ="中国";
        // AES加密算法,key的大小必须是16个字节
        String key = "1234567812345678";

        String transformation = "AES";
        // 指定获取密钥的算法
        String algorithm = "AES";
        String encryptDES = encryptAES(input, key, transformation, algorithm);
        System.out.println("加密:" + encryptDES);
        String s = decryptAES(encryptDES, key, transformation, algorithm);
        System.out.println("解密:" + s);

    }

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

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

        return encode;
    }

    /**
     * 使用DES解密
     *
     * @param input          : 密文
     * @param key            : 密钥
     * @param transformation : 获取Cipher对象的算法
     * @param algorithm      : 获取密钥的算法
     * @throws Exception
     * @return: 原文
     */
    private static String decryptAES(String input, String key, String transformation, String algorithm) throws Exception {
        // 1,获取Cipher对象
        Cipher cipher = Cipher.getInstance(transformation);
        // 指定密钥规则
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
        cipher.init(Cipher.DECRYPT_MODE, sks);
        // 3. 解密,上面使用的base64编码,下面直接用密文
        byte[] bytes = cipher.doFinal(Base64.decode(input));
        //  因为是明文,所以直接返回
        return new String(bytes);
    }
}


  • Base64算法简介
    Base64是网络上最常见的用于传输8Bit字节码的可读性编码算法之一,可读性编码算法不是为了保护数据的安全性,而是为了可读性;可读性编码不改变信息内容,只改变信息内容的表现形式
    所谓Base64,即是说在编码过程中使用了64种字符:大写A到Z、小写a到z、数字0到9、“+”和“/”
    Base58是Bitcoin(比特币)中使用的一种编码方式,主要用于产生Bitcoin的钱包地址。相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"i",以及"+“和”/"符号。

  • Base64算法原理
    base64 是 3个字节为一组,一个字节 8位,一共 就是24位 ,然后,把3个字节转成4组,每组6位,3 * 8 = 4 * 6 = 24 ,每组6位,缺少的2位,会在高位进行补0 ,这样做的好处在于 ,base取的是后面6位,去掉高2位 ,那么base64的取值就可以控制在0-63位了,所以就叫base64,111 111 = 32 + 16 + 8 + 4 + 2 + 1 。

  • Base64构成原则

    • 小写 a-z=26个字母
    • 大写A-Z=26个字母
    • 数字0-9=10个数字
    • +/=2个符号,大家可能发现一个问题,base64有个 = 号,但是在映射表里面没有发现 = 号,这个地方需要注意,等号非常特殊,因为base64是三个字节一组 ,如果当我们的位数不够的时候,会使用等号来补齐。
      Java中常见的密码学知识_第3张图片
  • base64补等号测试

public class TestBase64 {
    public static void main(String[] args) {
        //  1:MQ== 表示一个字节,不够三个字节,所以需要后面通过 == 号补齐
        System.out.println(Base64.encode("1".getBytes()));//后面补2个等号==
//        System.out.println(Base64.encode("12".getBytes()));//补1个等号=
//        System.out.println(Base64.encode("123".getBytes()));//不用补等号
//        // 中国:中文占6个字节,6 * 8 = 48 ,刚刚好被整除,所以没有等号
//        System.out.println(Base64.encode("中国".getBytes()));
    }
}

加密模式

Java中常见的密码学知识_第4张图片

填充模式

Java中常见的密码学知识_第5张图片

加密模式和填充模式案例

package com.example.test.security;


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

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

public class test {
    // DES加密算法,key的大小必须是8个字节

    public static void main(String[] args) throws Exception {
        String input = "硅谷12";
        // DES加密算法,key的大小必须是8个字节
        String key = "12345678";
        // 指定获取Cipher的算法,如果没有指定加密模式和填充模式,ECB/PKCS5Padding就是默认值
        //     String transformation = "DES"; // 9PQXVUIhaaQ=
        //String transformation = "DES/ECB/PKCS5Padding"; // 9PQXVUIhaaQ=
        // CBC模式,必须指定初始向量,初始向量中密钥的长度必须是8个字节
        //String transformation = "DES/CBC/PKCS5Padding"; // 9PQXVUIhaaQ=
        // NoPadding模式,原文的长度必须是8个字节的整倍数 ,所以必须把 硅谷改成硅谷12,PKCS5Padding则不需要,会自动填充
        String transformation = "DES/CBC/NoPadding";
        // 指定获取密钥的算法
        String algorithm = "DES";
        String encryptDES = encryptDES(input, key, transformation, algorithm);
        System.out.println("加密:" + encryptDES);
        String s = dncryptDES(encryptDES, key, transformation, algorithm);
        System.out.println("解密:" + s);

    }

    /**
     * 使用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);
        // ENCRYPT_MODE:加密模式
        // DECRYPT_MODE: 解密模式
        // 初始向量,参数表示跟谁进行异或,初始向量的长度必须是8位
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        // 初始化加密模式和算法
        cipher.init(Cipher.ENCRYPT_MODE, sks, iv);
        // 加密
        byte[] bytes = cipher.doFinal(input.getBytes());

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

        return encode;
    }

    /**
     * 使用DES解密
     *
     * @param input          : 密文
     * @param key            : 密钥
     * @param transformation : 获取Cipher对象的算法
     * @param algorithm      : 获取密钥的算法
     * @throws Exception
     * @return: 原文
     */
    private static String dncryptDES(String input, String key, String transformation, String algorithm) throws Exception {
        // 1,获取Cipher对象
        Cipher cipher = Cipher.getInstance(transformation);
        // 指定密钥规则
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, sks, iv);
        // 3. 解密
        byte[] bytes = cipher.doFinal(Base64.decode(input));

        return new String(bytes);
    }

}

在测试 AES 的时候需要注意,key需要16个字节,加密向量也需要16个字节 ,其他方式跟 DES 一样

消息摘要

  • 消息摘要(Message Digest)又称为数字摘要(Digital Digest)

  • 它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生

  • 使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全

  • 特点:无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出,只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。消息摘要是单向、不可逆的。

获取字符串消息摘要

package com.example.test.security;

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

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;

public class test {
    // DES加密算法,key的大小必须是8个字节

    public static void main(String[] args) throws Exception{
        // 4124bc0a9335c27f086f24ba207a4912     md5 在线校验
        // QSS8CpM1wn8IbyS6IHpJEg==             消息摘要使用的是16进制
        // 原文
        String input = "aa";
        // 算法
        String algorithm = "MD5";
        // 获取数字摘要对象
        MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
        // 消息数字摘要
        byte[] digest = messageDigest.digest(input.getBytes());
//        System.out.println(new String(digest));//会有乱码出现
        // base64编码
//        System.out.println(Base64.encode(digest));//base64编码的形式
        // 创建对象用来拼接
        StringBuilder sb = new StringBuilder();

        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());
    }
}

其他数字摘要算法

public class test {
    public static void main(String[] args) throws Exception{
        // 4124bc0a9335c27f086f24ba207a4912     md5 在线校验
        // QSS8CpM1wn8IbyS6IHpJEg==             消息摘要使用的是16进制
        // 原文
        String input = "aa";
        // 算法
        String algorithm = "MD5";
        // 获取数字摘要对象
        String md5 = getDigest(input, "MD5");
        System.out.println(md5);

        String sha1 = getDigest(input, "SHA-1");
        System.out.println(sha1);

        String sha256 = getDigest(input, "SHA-256");
        System.out.println(sha256);

        String sha512 = getDigest(input, "SHA-512");
        System.out.println(sha512);


    }

    private static String toHex(byte[] digest) throws Exception {

//        System.out.println(new String(digest));
        // base64编码
//        System.out.println(Base64.encode(digest));
        // 创建对象用来拼接
        StringBuilder sb = new StringBuilder();

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

    private static String getDigest(String input, String algorithm) throws Exception {
        MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
        // 消息数字摘要
        byte[] digest = messageDigest.digest(input.getBytes());
        System.out.println("密文的字节长度:" + digest.length);

        return toHex(digest);
    }

}

获取文件消息摘要

public class DigestDemo {

    public static void main(String[] args) throws Exception{
        String algorithm = "MD5";

        String sha1 = getDigestFile("apache-tomcat-9.0.10-windows-x64.zip", "SHA-1");
        System.out.println(sha1);

        String sha512 = getDigestFile("apache-tomcat-9.0.10-windows-x64.zip", "SHA-512");
        System.out.println(sha512);

    }

    private static String getDigestFile(String filePath, String algorithm) throws Exception{
        FileInputStream fis = new FileInputStream(filePath);
        int len;
        byte[] buffer = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while ( (len =  fis.read(buffer))!=-1){
            baos.write(buffer,0,len);
        }
        // 获取消息摘要对象
        MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
        // 获取消息摘要
        byte[] digest = messageDigest.digest(baos.toByteArray());
        System.out.println("密文的字节长度:"+digest.length);
        return toHex(digest);
    }

    private static String toHex(byte[] digest) {
        //        System.out.println(new String(digest));
        // 消息摘要进行表示的时候,是用16进制进行表示
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            // 转成16进制

            String s = Integer.toHexString(b & 0xff);
            // 保持数据的完整性,前面不够的用0补齐
            if (s.length()==1){
                s="0"+s;
            }
            sb.append(s);
        }
        System.out.println("16进制数据的长度:"+ sb.toString().getBytes().length);
        return sb.toString();
    }
}

总结

  • MD5算法 : 摘要结果16个字节, 转16进制后32个字节
  • SHA1算法 : 摘要结果20个字节, 转16进制后40个字节
  • SHA256算法 : 摘要结果32个字节, 转16进制后64个字节
  • SHA512算法 : 摘要结果64个字节, 转16进制后128个字节

非对称加密

  1. 非对称加密算法又称现代加密算法。
  2. 非对称加密是计算机通信安全的基石,保证了加密数据不会被破解。
  3. 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey) 和私有密(privatekey)
  4. 公开密钥和私有密钥是一对
  5. 如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密。
  6. 如果用私有密钥对数据进行加密,只有用对应的公开密钥才能解密。
  7. 因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
  • 示例
    • 首先生成密钥对, 公钥为(5,14), 私钥为(11,14)
    • 现在A希望将原文2发送给B
    • A使用公钥加密数据. 2的5次方mod 14 = 4 , 将密文4发送给B
    • B使用私钥解密数据. 4的11次方mod14 = 2, 得到原文2
  • 特点
    • 加密和解密使用不同的密钥
    • 如果使用私钥加密, 只能使用公钥解密
    • 如果使用公钥加密, 只能使用私钥解密
    • 处理数据的速度较慢, 因为安全级别高
  • 常见算法
    • RSA
    • ECC

生成公钥和私钥

package com.example.test.security;

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

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;

public class test {

    public static void main(String[] args) throws Exception {
        // 加密算法
        String algorithm = "RSA";
        //  创建密钥对生成器对象
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 生成私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 生成公钥
        PublicKey publicKey = keyPair.getPublic();
        // 获取私钥字节数组
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 获取公钥字节数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        // 对公私钥进行base64编码
        String privateKeyString = Base64.encode(privateKeyEncoded);
        String publicKeyString = Base64.encode(publicKeyEncoded);
        // 打印私钥
        System.out.println(privateKeyString);
        // 打印公钥
        System.out.println(publicKeyString);
    }
}

利用公钥和私钥进行加解密

package com.example.test.security;

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

import javax.crypto.Cipher;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;

public class test {

    public static void main(String[] args) throws Exception {
        // 加密算法
        String algorithm = "RSA";
        //  创建密钥对生成器对象
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 生成私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 生成公钥
        PublicKey publicKey = keyPair.getPublic();
        // 获取私钥字节数组
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 获取公钥字节数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        // 对公私钥进行base64编码
        String privateKeyString = Base64.encode(privateKeyEncoded);
        String publicKeyString = Base64.encode(publicKeyEncoded);
        // 打印私钥
//        System.out.println(privateKeyString);
        // 打印公钥
//        System.out.println(publicKeyString);

        //创建加密对象
        //参数表示加密算法
        Cipher cipher = Cipher.getInstance(algorithm);
        //私钥加密
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        byte[] bytes = cipher.doFinal("中国".getBytes());
        System.out.println(Base64.encode(bytes));
        //公钥解密
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        byte[] bytes1 = cipher.doFinal(bytes);
        System.out.println(new String(bytes1));

        //公钥加密
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        bytes = cipher.doFinal("中国".getBytes());
        System.out.println(Base64.encode(bytes));
        //私钥解密
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        bytes1 = cipher.doFinal(bytes);
        System.out.println(new String(bytes1));

    }

}


数字签名

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

数字签名的含义是:在网络中传输数据时候,给数据添加一个数字签名,表示是谁发的数据,而且还能证明数据没有被篡改。
OK,数字签名的主要作用就是保证了数据的有效性(验证是谁发的)和完整性(证明信息没有被篡改)。下面我们就来好好地看一下他的底层实现原理是什么样子的。

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

    整个思路是这个样子的:

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

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

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

    (1)张三写完邮件,先用hash函数生成邮件的摘要,附着在文章上面,这就完成了数字签名,然后张三再使用私钥对摘要加密。就可以把邮件发出去了。

    (2)A或者是B收到邮件之后,先把数字签名取下来,然后使用自己的公钥解密即可。这时候取下来的数字签名中的摘要若和张三的一致,那就认为是张三发来的,再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。

  • 数字证书
    上面提到我们对签名进行验证时,需要用到公钥。如果公钥是伪造的,那我们无法验证数字签名了,也就根本不可能从数字签名确定对方的合法性了。这时候证书就闪亮登场了。那么这个证书是如何生成的呢?
    Java中常见的密码学知识_第6张图片

关于证书这块,我们通一个实例:“https协议”来讲解。
Java中常见的密码学知识_第7张图片

Java中常见的密码学知识_第8张图片
Java中常见的密码学知识_第9张图片
Java中常见的密码学知识_第10张图片

Java中常见的密码学知识_第11张图片

  • 代码实现
import java.security.*;
import com.sun.org.apache.xml.internal.security.utils.Base64;
public class SignatureDemo {
    public static void main(String[] args) throws Exception {
        String a = "123";

        //将前面获取公私钥的代码抽出一个工具类来实现
        PublicKey publicKey = RsaDemo.loadPublicKeyFromFile("RSA", "a.pub");
        PrivateKey privateKey = RsaDemo.loadPrivateKeyFromFile("RSA", "a.pri");

        String signaturedData = getSignature(a, "sha256withrsa", privateKey);

        boolean b = verifySignature(a, "sha256withrsa", publicKey, signaturedData);

    }

    /**
     * 生成签名
     *
     * @param input      : 原文
     * @param algorithm  : 算法
     * @param privateKey : 私钥
     * @return : 签名
     * @throws Exception
     */
    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[] sign = signature.sign();
        // 对签名数据进行Base64编码
        return Base64.encode(sign);
    }

    /**
     * 校验签名
     *
     * @param input          : 原文
     * @param algorithm      : 算法
     * @param publicKey      : 公钥
     * @param signaturedData : 签名
     * @return : 数据是否被篡改
     * @throws Exception
     */
    private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signaturedData) throws Exception {
        // 获取签名对象
        Signature signature = Signature.getInstance(algorithm);
        // 初始化签名
        signature.initVerify(publicKey);
        // 传入原文
        signature.update(input.getBytes());
        // 校验数据
        return signature.verify(Base64.decode(signaturedData));

    }

}

keytool工具使用

  • 常用命令:
    生成keypair
    keytool -genkeypair keystore xxx.jks
    keytool -genkeypair -alias lisi keystore xxx.jks(后面部分是为证书指定别名,否则采用默认的名称为mykey)

  • 看看keystore中有哪些项目:
    keytool -list xxx.jks 或keytool -list -v xxx.jks
    keytool -exportcert -alias lisi -file lisi.cer

  • 生成可打印的证书:
    keytool -exportcert -alias lisi -file lisi.cer –rfc

  • 显示数字证书文件中的证书信息:
    keytool -printcert -file lisi.cer
    直接双击lisi.cer,用window系统的内置程序打开lisi.cer

  • 导出公钥
    openssl是一个加解密工具包,这里使用openssl来导出公钥信息。
    安装 openssl:http://slproweb.com/products/Win32OpenSSL.html
    配置完环境变量之后,便可以使用openssl导出密钥的公钥了

    keytool -list -rfc --keystore xxx.jks | openssl x509 -inform pem -pubkey

你可能感兴趣的:(java,密码学)