什么是编码?我们可以理解为我们日常使用的文字就是一种编码,比如我们把中文翻译成英文就是一种编码的过程,懂得魔方盲拧原理的同学把魔方的色相,位置转换成英文字母也是一种编码,当然,这些都是建立在特殊的编码规则里面的。那么在计算机中,同样存在编码,我们比较熟知的就有ASCII编码、Unicode编码、UTF-8编码等。
ASCII编码中通过8位二进制数存储了大小写的英文字母,数字,特殊字符,但由于只有127个字符,不能对更多的文字进行编码,所以就用到了2个字节值的Unicode编码,和占用3个字节值的UTF-8编码,当然还有更多,这里暂时就不作以解释了。那么既然我们知道了编码,就应该了解几个编码算法了,目前比较常用有URL编码和Base64编码。
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编码是对二进制数据进行编码,表示成文本格式,但是编码后数据量会增加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算法”计算出的整数值,代表该对象在哈希表中的位置。
这两种都只能算是一种逻辑上计算算法,安全性较差,容易产生哈希冲突,并不是一种安全的哈希算法,而哈希算法最重要的特点就是:相同的输入一定得到相同的输出;不同的输入大概率得到不同的输出。常见的哈希算法有
算法 | 输出长度(位) | 输出长度(字节) |
MD5 | 128 bits | 16 bytes |
SHA-1 | 160 bits | 20 bytes |
RipeMD-160 | 160 bits | 20 bytes |
SHA-256 | 256 bits | 32 bytes |
SHA-512 | 512 bits | 64 bytes |
哈希算法主要用于检验用户文件和存储用户密码。
首先说检验用户文件,我们在网站上下载文件时,经常会看到下载页上显示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[]数组表示的摘要,最后,把它转换为十六进制的字符串,就是我们常见的哈希码了。
但是,如果我们要用的某种算法,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 算法就是一种基于密钥的消息认证码算法,它的全称是 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两种工作模式。
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模式相比于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.对称加密算法 :收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。非对称算法: 公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。