0


Java中的加密与安全(总结篇)

一、编码算法

    什么是编码?我们可以理解为我们日常使用的文字就是一种编码,比如我们把中文翻译成英文就是一种编码的过程,懂得魔方盲拧原理的同学把魔方的色相,位置转换成英文字母也是一种编码,当然,这些都是建立在特殊的编码规则里面的。那么在计算机中,同样存在编码,我们比较熟知的就有**ASCII编码、Unicode编码、UTF-8编码**等。

    ASCII编码中通过8位二进制数存储了大小写的英文字母,数字,特殊字符,但由于只有127个字符,不能对更多的文字进行编码,所以就用到了2个字节值的Unicode编码,和占用3个字节值的UTF-8编码,当然还有更多,这里暂时就不作以解释了。那么既然我们知道了编码,就应该了解几个编码算法了,目前比较常用有**URL编码和Base64编码**。

URL编码

    URL编码的目的是把任意文本数据编码为%前缀表示的文本,通常便于浏览器和服务器处理,举个例子,我们在浏览器上搜索“仙草不加料”,浏览器导航栏会显示“https://www.baidu.com/s?wd=%E4%BB%99%E8%8D%89%E4%B8%8D%E5%8A%A0%E6%96%99”这里15个类似“%x”的形式就代表的是“仙草不加料”这5个字符,而URL编码是这样规定的:如果字符是A~Z,a~z,0~9以及-、_、.、*,则保持不变, 如果是其他字符,先转换为UTF-8编码,然后对每个字节以%XX表示。这也就很好理解了,每个中文占三个字节,%E4%BB%99就表示的是仙,那么在Java中也给我们提供了两个类能够实现URL编码算法:URLEncoder和URLDecoder,分别实现**编码和解码**的功能。

编码时使用encode()方法,解码时使用decode()方法,并且都需要指定编码集

// 编码    
String result = URLEncoder.encode("仙草不加料", "utf-8");
System.out.println(result);        

// 解码
String param = URLDecoder.decode("%E4%BB%99%E8%8D%89%E4%B8%8D%E5%8A%A0%E6%96%99","utf-8");
System.out.println(param);

// 运行结果:
// %E4%BB%99%E8%8D%89%E4%B8%8D%E5%8A%A0%E6%96%99
// 仙草不加料

Base64编码

    Base64编码是对二进制数据进行编码,表示成文本格式,但是编码后数据量会增加1/3。它的原理是把每3字节的二进制数据按6bit一组,用4个int类型整数表示,然后查表,把int整数用索引对应到字符,得到编码后的字符串。

    表中是这样规定的:6位整数的范围总是0~63,所以,能用64个字符表示:字符A~Z对应索引0~25,字符a~z对应索引26~51,字符0~9对应索引52~61,最后两个索引62、63分别用字符+和/表示。Java也同样提供了Base64这个类实现Base64编码算法,使用getEncoder().encodeToString()进行编码,使用getDecoder().decode()进行解码。代码如下:
String str = "仙草不加料";
byte[] input = str.getBytes();
System.out.println(Arrays.toString(input));
// 编码
String b64encoded = Base64.getEncoder().encodeToString(input);
System.out.println(b64encoded);

// 解码
byte[] output = Base64.getDecoder().decode(b64encoded);
System.out.println(Arrays.toString(output));
System.out.println(new String(output));

// 运行结果
// [-49, -55, -78, -35, -78, -69, -68, -45, -63, -49]
// z8my3bK7vNPBzw==
// [-49, -55, -78, -35, -78, -69, -68, -45, -63, -49]
// 仙草不加料

二、哈希算法

    哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个**固定长度**的输出摘要。

    哈希算法的目的:为了验证原始数据是否被篡改。**注意**:此哈希非彼哈希,我们之前所了解到的hashcode()或HashCode哈希码,都**不是**哈希算法,hashcode()是Object类中的native本地方法,底层使用c/c++语言实现,用于获取该对象的HashCode哈希码,而HashCode哈希码是该对象的内存地址通过“哈希hash算法”计算出的整数值,代表该对象在哈希表中的位置。

    这两种都只能算是一种逻辑上计算算法,**安全性**较差,容易产生哈希冲突,并不是一种安全的哈希算法,而哈希算法最重要的**特点**就是:**相同的输入一定得到相同的输出;不同的输入大概率得到不同的输出**。常见的哈希算法有

算法输出长度(位)输出长度(字节)MD5128 bits16 bytesSHA-1160 bits20 bytesRipeMD-160160 bits20 bytesSHA-256256 bits32 bytesSHA-512512 bits64 bytes
哈希算法主要用于检验用户文件存储用户密码。

** **首先说检验用户文件,我们在网站上下载文件时,经常会看到下载页上显示MD5哈希值,这个哈希值就是提供给我们检验文件是否被篡改的的标准,相同的输入总是会得到相同的输出,如果输入不同,那么输出也就不同,当我们下载文件到本地后,通过文件的字节计算出文件的哈希值,如果与官网相同,说明文件正常,如果不同,说明文件被篡改,已经不安全了。

    第二个用途:存储用户密码,如果我们在存储密码的时候,以明文的形式存储的话,会产生较大的风险,比如:数据库管理员能直接看到用户的密码;数据库泄露时,明文密码也随之泄露。因此使用加密算法能有效保持密码的安全性,但是还有一点,如果密码比较常用的话,黑客会很容易创建一张彩虹表,这张表里会存放常用口令和对应的MD5哈希值,黑客只需要暴力穷举就能有一定概率成功,所以我们可以采取随机数的方式抵御彩虹表的攻击,这种方式成为“加盐”,由于加了随机数,哈希算法处理之后的密码就更加随机了,安全性得到了很大的的提高。

MD5算法

    以MD5(SHA算法同理,获取加密工具对象时,更换对应算法名称即可)为例,使用代码实现如何使用哈希算法进行加密:
package com.fulian.demo02;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.UUID;

public class Work02 {
    public static void main(String[] args) {
        // 原始密码
        String psw = "xiancaobujialiao";
        
        // UUID随机数,实现加盐
        String uuid = UUID.randomUUID().toString().substring(0, 5);
        System.out.println(uuid);
        
        try {
            // 根据当前算法,获取加密工具对象(摘要)
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            
            // 更新原始数据
            md5.update(psw.getBytes());
            md5.update(uuid.getBytes());
            
            // 加密
            byte[] md5ByteArray = md5.digest();
            System.out.println(Arrays.toString(md5ByteArray));
            System.out.println(md5ByteArray.length);
            
            // 加密后的字节数组,转换字符串
            StringBuilder md5Builder = new StringBuilder();
            for(byte b : md5ByteArray) {
                md5Builder.append(String.format("%02x", b));
            }
            
            System.out.println(md5Builder);
            System.out.println(md5Builder.length());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}

// 运行结果
b0f53
[-45, 41, 93, -57, 8, -35, -23, 10, 22, 6, 63, 71, -65, 81, 77, -29]
16
d3295dc708dde90a16063f47bf514de3
32
    使用MessageDigest时,我们首先根据哈希算法获取一个MessageDigest实例,然后,反复调用update(byte[])输入数据。当输入结束后,调用digest()方法获得byte[]数组表示的摘要,最后,把它转换为十六进制的字符串,就是我们常见的哈希码了。

RipeMd160

    但是,如果我们要用的某种算法,Java库没有提供怎么办?这是我们就需要使用第三方库BouncyCastle,它是提供了很多哈希算法和加密算法的第三方开源库,比如,RipeMD160算法,它是一种基于Merkle—Damard结构的加密哈希函数,是比特币的标准之一。那么,要实现RipeMd160算法,首先要导入BouncyCastle提供的bcprov-jdk15on-1.70.jar包,与MD5算法不同的是,使用Ripe160算法,需要先注册BouncyCastle。代码如下:

package com.fulian.demo03;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Arrays;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

// 使用第三方类库BouncyCastle提供RipeMD160算法进行加密
public class Main01 {
    public static void main(String[] args) {
        try {
            // 注册BouncyCastle
            Security.addProvider(new BouncyCastleProvider());
            
            // 获取RipeMD160算法的“消息摘要对象”(加密对象)
            MessageDigest md = MessageDigest.getInstance("RipeMD160");
            
            // 更新原始数据
            md.update("HelloWorld".getBytes());
            
            // 获取消息摘要(加密)
            byte[] result = md.digest();
            
            // 消息摘要的字节长度和内容
            System.out.println(result.length); // 160位 = 20字节
            System.out.println(Arrays.toString(result));
            
            // 16进制内容字符串
            String hex = new BigInteger(1, result).toString(16);
            System.out.println(hex.length()); // 20字节 = 40个字符
            System.out.println(hex);
            
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}

Hmac算法

Hmac 算法就是一种基于密钥的消息认证码算法,它的全称是 Hash-based Mes sage Authenticat ion code,是一种更安全的消息摘要算法。Hmac 算法总是和某种哈希算法配合起来用的。例如,我们使用 MD5 算法,对应的就是 Hmac MD5 算法,相比于我们手动使用随机数加盐,它通过KeyGenerator提供给我们更为安全的随机的key,因此,HmacMD5 可以看作带有一个安全的key 的MD5。而且HmacMD5 使用的 key 长度是64字节,更安全;Hmac 是标准算法,同样适用于 SHA-1 等其他哈希算法;Hmac 输出和原有的哈希算法长度一致。

和MD5相比,使用HmacMD5的步骤是:

  1. 通过名称HmaCMD5 获取 KeyGenerator 实例;
  2. 通过 KevGenerator 创建一个Secretkey 实例;
  3. 通过名称 HmaCMD5 获取Mac 实例;
  4. 用 Secretkey 初始化Mac实例;

5.对Mac 实例反复调用 update(byte [])输入数据;
6.调用 Mac 实例的 doFinal()获取最终的哈希值。

代码如下:

package com.fulian.demo02;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;

public class Test05 {
    public static void main(String[] args) {
        String psw = "jinwanzaodianshui";
        try {
            // 1.生成秘钥
            // 秘钥生成器
            KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
            
            // 生成秘钥key
            SecretKey key = keyGen.generateKey();
            
            // 获取秘钥key的字节数组(64)
            byte[] keyByteArray = key.getEncoded();
            System.out.println("秘钥内容:" + Arrays.toString(keyByteArray));
            System.out.println("秘钥长度:" + keyByteArray.length + "字节");
            
            StringBuilder keyStr = new StringBuilder();
            for(byte b : keyByteArray) {
                keyStr.append(String.format("%02x", b));
            }
            System.out.println("秘钥内容:" + keyStr);
            System.out.println("秘钥内容长度:" + keyStr.length());
            
            // 2.使用秘钥,进行加密
            // 获取算法对象
            Mac mac = Mac.getInstance("HmacMD5");
            
            // 初始化秘钥
            mac.init(key);

            // 更新原始内容
            mac.update(psw.getBytes());
            
            // 加密
            byte[] resultByteArray = mac.doFinal();
            
            System.out.println("加密结果:" + resultByteArray.length + "字节");
            
            StringBuilder resultStr = new StringBuilder();
            for(byte b : resultByteArray) {
                resultStr.append(String.format("%02x", b));
            }
            System.out.println("加密结果:" + resultStr);
            System.out.println("加密结果长度:" + resultStr.length());
            
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

如果我们想要使用同一个key加密多个消息时,上面的逻辑就行不通了,这时我们只需要第一次创建一个Secretkey,并以数组的形式保存下来,然后后续需要的话,调用new SecretKey方法,进行密钥的恢复,其他步骤均相同。代码如下:

// 通过秘钥字节数组,恢复秘钥
byte[] keyByteArray = {-107, -51, -82, 93, -72, 56, 77, 7, 85, -114, -54, -9, 114, -101, -58, -110, -27, 104, -46, -79, 42, 33, -22, -101, -27, -53, 41, -66, 48, -61, -77, -30, -9, 48, 71, -55, -35, 70, 83, 114, 116, 67, -112, -58, -65, -115, -128, -60, -72, -74, 53, -85, -111, -85, 28, 59, 103, -119, 32, 18, 58, 45, 117, -70};

SecretKey key = new SecretKeySpec(keyByteArray, "HmacMD5");
            

三、加密算法

对称加密算法

    对称加密算法就是用一个秘钥进行加密和解密操作,常用的对称加密算法主要有:

算法

密钥长度

工作模式

填充模式

DES

56/64

ECB/CBC/PCBC/CTR/...

NoPadding/PKCS5Padding/...

AES

128/192/256

ECB/CBC/PCBC/CTR/...

NoPadding/PKCS5Padding/PKCS7Padding/...

IDEA

128

ECB

PKCS5Padding/PKCS7Padding/...

密钥长度直接决定加密强度,而工作模式和填充模式可以看成是对称加密算法的参数和格式选择。
DES算法由于密钥过短,可以在短时间内被暴力破解,所以现在已经不安全了。AES算法目前光广泛的被使用,主要有ECB和CBC两种工作模式。

AES算法

ECB模式

    ECB模式是最简单的AES加密模式,它只需要一个固定长度的秘钥,固定的明文会产生固定的密码,主要步骤如下:

1.根据算法名称/工作模式/填充模式获取Cipher实例;

// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

2根据算法名称初始化一个SecretKey实例,密钥必须是指定长度;

// 根据key的字节内容,“恢复”秘钥对象
SecretKey keySpec = new SecretKeySpec(key, "AES");

3使用SerectKey初始化Cipher实例,并设置加密或解密模式;

// 初始化秘钥 : 设置加密模式
cipher.init(Cipher.ENCRYPT_MODE, keySpec);

// 初始化秘钥 : 设置解密模式
cipher.init(Cipher.DECRYPT_MODE, keySpec);

4传入明文或密文,获得密文或明文。

// 根据原始内容(字节),进行加密/解密
return cipher.doFinal(input);

CBC模式

    CBC模式相比于ECB这种一对一的加密方式,安全性会有很高的提升,它需要一个随机数作为IV参数,这样对于同一份明文,每次生成的密文都不同:

    在原有代码基础上,加密时,CBC模式需要生成一个16bytes的initalization vector:这里先简称iv,必须由SecureRandom生成,生成的16位随机数再封装成IvParameterSpec参数,并传入Cipher的初始化秘钥方法,最后,调用doFinal()方法,加密后的结果就保存了iv随机数和密文。解密时,由于传入的密文是iv随机数和密文两部分,先分割原密文并创建两个数组用来保存这两部分,然后根据数组内容恢复秘钥,把iv数组封装成IvParameterSpec对象,并初始化秘钥和ivIvParameterSpec对象,最后调用doFinal()方法,完成解密。
package com.fulian.demo03;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

// AES对称加密
// CBC工作模式
public class Main03 {
    public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        // 原文:
        String message = "Hello, world!";
        System.out.println("Message(原始信息):" + message);

        // 256位秘钥 = 32 bytes key:
        byte[] key = "1234567890abcdef1234567890abcdef".getBytes();

        // 加密
        byte[] data = message.getBytes();
        byte[] encrypted = encrypt(key, data);
        System.out.println("Encrypted(加密内容): " + Base64.getEncoder().encodeToString(encrypted));

        // 解密
        byte[] decrypted = decrypt(key, encrypted);
        System.out.println("Decrypted(解密内容): " + new String(decrypted));

    }

    // 加密
    public static byte[] encrypt(byte[] key, byte[] input) throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        // 设置算法/工作模式/填充
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        // 恢复秘钥对象
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");

        // CBC模式需要生成一个16bytes的initalization vector:
        SecureRandom sr = SecureRandom.getInstanceStrong();
        byte[] iv = sr.generateSeed(16); // 生成16个字节的随机数
        System.out.println(Arrays.toString(iv));
        IvParameterSpec ivps = new IvParameterSpec(iv); // 随机数封装成IvParameterSpec参数

        // 初始化秘钥:操作模式、秘钥、IV参数
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);

        // 加密
        byte[] data = cipher.doFinal(input);

        // IV不需要保密,把IV和密文一起返回:
        return join(iv, data);
    }

    // 解密
    public static byte[] decrypt(byte[] key,byte[] input) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
        // 把input分割成IV和密文
        byte[] iv = new byte[16];
        byte[] data = new byte[input.length - 16];
        
        System.arraycopy(input, 0, iv, 0, 16); // IV
        System.arraycopy(input, 16, data, 0, data.length); // 密文
        System.out.println(Arrays.toString(iv));
        // 解密
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey keySpec = new SecretKeySpec(key, "AES");
        IvParameterSpec ivps = new IvParameterSpec(iv);
        
        // 初始化秘钥:操作模式、秘钥、IV参数
        cipher.init(Cipher.DECRYPT_MODE, keySpec,ivps);
        
        // 根据原始内容(字节),进行解密
        return cipher.doFinal(data);
    }
    public static byte[] join(byte[] bs1, byte[] bs2) {
        byte[] r = new byte[bs1.length + bs2.length];
        System.arraycopy(bs1, 0, r, 0, bs1.length);
        System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
        return r;
    }
}

秘钥交换算法

    密钥交换算法:DH算法(Diffie-Hellman算法)。DH算法解决了密钥在双方不直接传递密钥的情况下完成密钥交换。假设有两个用户,用户双方都各自拥有一对密钥对,在DH算法下,双方通过交换各自的公钥,计算出一个共享密钥,在后续的数据传输中,双方通过这个共享秘钥进行加密和解密。代码如下:
package com.fulian.demo03;

import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.KeyAgreement;

public class Main04 {
    public static void main(String[] args) {
        // Bob和Alice:
        Person bob = new Person("Bob");
        Person alice = new Person("Alice");
        
        // 各自生成KeyPair: 公钥+私钥
        bob.generateKeyPair();
        alice.generateKeyPair();

        // 双方交换各自的PublicKey(公钥):
        // Bob根据Alice的PublicKey生成自己的本地密钥(共享公钥):
        bob.generateSecretKey(alice.publicKey.getEncoded());
        
        // Alice根据Bob的PublicKey生成自己的本地密钥(共享公钥):
        alice.generateSecretKey(bob.publicKey.getEncoded());

        // 检查双方的本地密钥是否相同:
        bob.printKeys();
        alice.printKeys();
        
        // 双方的SecretKey相同,后续通信将使用SecretKey作为密钥进行AES加解密...
    }
}

// 用户类
class Person {
    public final String name; // 姓名
    
    // 密钥
    public PublicKey publicKey; // 公钥
    private PrivateKey privateKey; // 私钥
    private byte[] secretKey; // 本地秘钥(共享密钥)

    // 构造方法
    public Person(String name) {
        this.name = name;
    }

    // 生成本地KeyPair:(公钥+私钥)
    public void generateKeyPair() {
        try {
            // 创建DH算法的“秘钥对”生成器
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
            kpGen.initialize(512);
            
            // 生成一个"密钥对"
            KeyPair kp = kpGen.generateKeyPair();
            this.privateKey = kp.getPrivate(); // 私钥
            this.publicKey = kp.getPublic(); // 公钥
            
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }
    
    // 按照 "对方的公钥" => 生成"共享密钥"
    public void generateSecretKey(byte[] receivedPubKeyBytes) {
        try {
            // 从byte[]恢复PublicKey:
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(receivedPubKeyBytes);
            
            // 根据DH算法获取KeyFactory
            KeyFactory kf = KeyFactory.getInstance("DH");
            // 通过KeyFactory创建公钥
            PublicKey receivedPublicKey = kf.generatePublic(keySpec);
            
            // 生成本地密钥(共享公钥)
            KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
            keyAgreement.init(this.privateKey); // 初始化"自己的PrivateKey"
            keyAgreement.doPhase(receivedPublicKey, true); // 根据"对方的PublicKey"
            
            // 生成SecretKey本地密钥(共享公钥)
            this.secretKey = keyAgreement.generateSecret();
            
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    public void printKeys() {
        System.out.printf("Name: %s\n", this.name);
        System.out.printf("Private key: %x\n", new BigInteger(1, this.privateKey.getEncoded()));
        System.out.printf("Public key: %x\n", new BigInteger(1, this.publicKey.getEncoded()));
        System.out.printf("Secret key: %x\n", new BigInteger(1, this.secretKey));
    }
}

// 运行结果
Name: Bob
Private key: 3081d202010030819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4020201800433023100d9a9a4ac2bbfeb81c0c9f2cfd4c386cd69838e9cd943940836247420053ba521bae5d4338148f89df09845e31c58119c
Public key: 3081df30819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4020201800343000240433210e34b78c88b61cceb54763468dbce08b44150a1f2dd431fdb604ff7a20fe3c57a26f135eb6871ce78c1b59212a653ac3b128e20f223bf1d8c70aadd49bd
Secret key: 9c8157f14a348f089b64e6dd34a4ff859a10a8109bb0449379478ca68da82a4bfa6f2b44e11cd17d09361ffb97a941c2db8abcd12991b549d813100dd20bf6e7
Name: Alice
Private key: 3081d202010030819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca40202018004330231009ddc784056bf34a3f5ef66d283a42bdb6a8fbbeca32769949fefb0d968a2a9a4ed49dd97dbb1dfccd7322e8c6a6ab914
Public key: 3081df30819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca40202018003430002400dfd12782e5332bb5e3a7a05446c6d5212cfc60c7bb8f5575af859151f12d07bc8c60148066b74d7570b18a85f7d4c915b5f9a215145315bfd7cdbb0f025765a
Secret key: 9c8157f14a348f089b64e6dd34a4ff859a10a8109bb0449379478ca68da82a4bfa6f2b44e11cd17d09361ffb97a941c2db8abcd12991b549d813100dd20bf6e7

非对称加密算法

    非对称加密算法:加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密。公钥是公开的,而私钥是私有的,那么在加密时用公钥进行加密,解密时用私钥进行解密,常见的非对称加密算法是RSA算法。代码如下:
package com.fulian.demo03;

import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;

import javax.crypto.Cipher;

// RSA
public class Main05 {
    public static void main(String[] args) throws Exception {
        // 明文:
        byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
        
        // 创建公钥/私钥对:
        Human alice = new Human("Alice");
        
        // 用Alice的公钥加密:
        // 获取Alice的公钥,并输出
        byte[] pk = alice.getPublicKey();
        System.out.println(String.format("public key(公钥): %x", new BigInteger(1, pk)));
        
        // 使用公钥加密
        byte[] encrypted = alice.encrypt(plain);
        System.out.println(String.format("encrypted: %x", new BigInteger(1, encrypted)));
       
        // 用Alice的私钥解密:
        // 获取Alice的私钥,并输出
        byte[] sk = alice.getPrivateKey();
        System.out.println(String.format("private key: %x", new BigInteger(1, sk)));
        
        // 使用私钥解密
        byte[] decrypted = alice.decrypt(encrypted);
        System.out.println(new String(decrypted, "UTF-8"));
    }
}

// 用户类
class Human {
    // 姓名
    String name;
    
    // 私钥:
    PrivateKey sk;
    
    // 公钥:
    PublicKey pk;

    // 构造方法
    public Human(String name) throws GeneralSecurityException {
        // 初始化姓名
        this.name = name;
        
        // 生成公钥/私钥对:
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
        kpGen.initialize(1024);
        KeyPair kp = kpGen.generateKeyPair();
        
        this.sk = kp.getPrivate();
        this.pk = kp.getPublic();
    }

    // 把私钥导出为字节
    public byte[] getPrivateKey() {
        return this.sk.getEncoded();
    }

    // 把公钥导出为字节
    public byte[] getPublicKey() {
        return this.pk.getEncoded();
    }

    // 用公钥加密:
    public byte[] encrypt(byte[] message) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, this.pk); // 使用公钥进行初始化
        return cipher.doFinal(message);
    }

    // 用私钥解密:
    public byte[] decrypt(byte[] input) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, this.sk); // 使用私钥进行初始化
        return cipher.doFinal(input);
    }
}

对称加密算法与非对称加密算法的区别:

1.对称加密算法: 加密和解密使用相同的秘钥,而非对称算法加密和解密采用不同的秘钥,但是通过秘钥对关联。
2.对称加密算法 :算法公开、计算量小、速度快、加密效率高,而非对称算法更加安全但是效率低下。
3.对称加密算法 :收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。非对称算法: 公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。

标签: java jvm 开发语言

本文转载自: https://blog.csdn.net/qq_45958440/article/details/125918876
版权归原作者 仙草不加料 所有, 如有侵权,请联系我们删除。

“Java中的加密与安全(总结篇)”的评论:

还没有评论