一、现代密码学
1、散列函数
散列函数,也见杂凑函数、摘要函数或哈希函数,可将任意长度的消息经过运算,变成固定长度数值,常见的有MD5
、SHA-1
、SHA256
,多应用在文件校验,数字签名中。
MD5 可以将任意长度的原文生成一个 128 位(16 字节)的哈希值
SHA-1 可以将任意长度的原文生成一个 160 位(20 字节)的哈希值
2、对称密码
对称密码应用了相同的加密密钥和解密密钥。对称密码分为:序列密码(流密码),分组密码(块密码)两种。流密码是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密,块密码是先对信息流分块,再对每一块分别加密。
例如原文为 1234567890,流加密即先对 1 进行加密,再对 2 进行加密,再对 3 进行加密……最后拼接成密文;块加密先分成不同的块,如 1234 成块,5678 成块,90XX(XX 为补位数字)成块,再分别对不同块进行加密,最后拼接成密文。前文提到的古典密码学加密方法,都属于流加密。
3、非对称密码
对称密码的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。
在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。
非对称密码有两支密钥,公钥(publickey)和私钥(privatekey),加密和解密运算使用的密钥不同。用公钥对原文进行加密后,需要由私钥进行解密;用私钥对原文进行加密后(此时一般称为签名),需要由公钥进行解密(此时一般称为验签)。公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文。
二、常见加密方式
对称加密
- 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
- 示例
- 我们现在有一个原文 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、DES
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| import com.sun.org.apache.xml.internal.security.utils.Base64;
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec;
public class DesAesDemo { public static void main(String[] args) throws Exception { String input = "hello world"; String key = "12345678"; String algorithm = "DES"; String transformation = "DES";
String encryptDES = getEncryptDES(input, key, algorithm, transformation); System.out.println("加密:" + encryptDES); String decryptDES = getDecryptDES(key, algorithm, transformation, encryptDES); System.out.println("解密:" + decryptDES);
}
private static String getEncryptDES(String input, String key, String algorithm, String transformation) throws Exception { Cipher cipher = Cipher.getInstance(transformation); SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm); cipher.init(Cipher.ENCRYPT_MODE, sks); byte[] bytes = cipher.doFinal(input.getBytes()); String encode = Base64.encode(bytes); return encode; }
private static String getDecryptDES(String key, String algorithm, String transformation, String encryptDES) throws Exception { Cipher cipher = Cipher.getInstance(transformation); SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm); cipher.init(Cipher.DECRYPT_MODE, sks); byte[] bytes = cipher.doFinal(Base64.decode(encryptDES)); return new String(bytes); } }
|
toString()与 new String ()用法区别
Base64 加解密是一种转换编码格式的原理
str.toString 是调用了这个 object 对象的类的 toString 方法。一般是返回这么一个 String:[class name]@[hashCode]
new String(str)是根据 parameter 是一个字节数组,使用 java 虚拟机默认的编码格式,将这个字节数组 decode 为对应的字符。若虚拟机默认的编码格式是 ISO-8859-1,按照 ascii 编码表即可得到字节对应的字符。
什么时候用
new String()一般使用字符转码的时候,byte[]数组的时候
toString()对象打印的时候使用
2、AES
与上面的 DES 不同的是 key 要求 16 位,算法和加密类型为 AES,其它的相同
1 2 3 4 5 6
| String key = "1234567812345678";
String algorithm = "AES";
String transformation = "AES";
|
3、Base64
Base64 是网络上最常见的用于传输 8Bit 字节码的可读性编码算法之一
可读性编码算法不是为了保护数据的安全性,而是为了可读性
可读性编码不改变信息内容,只改变信息内容的表现形式
所谓 Base64,即是说在编码过程中使用了 64 种字符:大写 A 到 Z、小写 a 到 z、数字 0 到 9、“+”和“/”
Base58 是 Bitcoin(比特币)中使用的一种编码方式,主要用于产生 Bitcoin 的钱包地址
相比 Base64,Base58 不使用数字”0”,字母大写”O”,字母大写”I”,和字母小写”i”,以及”+”和”/“符号
三、加密模式
1、ECB
ECB : Electronic codebook,电子密码本。需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密。
- 优点 : 可以并行处理数据
- 缺点 : 同样的原文生成同样的密文, 不能很好的保护数据
- 同时加密,原文是一样的,加密出来的密文也是一样的
2、CBC
CBC : Cipher-block chaining,密码块链接。每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。
- 优点 : 同样的原文生成的密文不一样
- 缺点 : 串行处理数据
四、填充模式
当需要按块处理的数据, 数据长度不符合块处理需求时, 按照一定的方法填充满块长的规则
1、NoPadding
- 不填充.
- 在 DES 加密算法下, 要求原文长度必须是 8byte 的整数倍
- 在 AES 加密算法下, 要求原文长度必须是 16byte 的整数倍
2、PKCS5Padding
数据块的大小为 8 位, 不够就补足
3、Tips
Cipher 算法,如果没有指定加密模式和填充模式,ECB/PKCS5Padding 就是默认值
如果使用 CBC 模式, 在初始化 Cipher 对象时, 需要增加参数
初始化向量 IV :IvParameterSpec iv = new IvParameterSpec(key.getBytes());
加密模式和填充模式(括号内数字代表多少位加密)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| AES/CBC/NoPadding (128) AES/CBC/PKCS5Padding (128) AES/ECB/NoPadding (128) AES/ECB/PKCS5Padding (128) DES/CBC/NoPadding (56) DES/CBC/PKCS5Padding (56) DES/ECB/NoPadding (56) DES/ECB/PKCS5Padding (56) DESede/CBC/NoPadding (168) DESede/CBC/PKCS5Padding (168) DESede/ECB/NoPadding (168) DESede/ECB/PKCS5Padding (168) RSA/ECB/PKCS1Padding (1024, 2048) RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048) RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
|
DES 使用NoPadding
这种填充模式,加密的原文必须是 8 个字节的整倍数
AES 使用NoPadding
这种填充模式,加密的原文必须是 16 个字节的整倍数
4、演示
使用DES/CBC/PKCS5Padding
模式,基于上面的 DES 加密进行修改
设置加密类型为DES/CBC/PKCS5Padding
模式
1 2
| String transformation = "DES/CBC/PKCS5Padding";
|
加密方法getEncryptDES
中添加向量,初始化时增加参数
1 2 3 4
| IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, sks, iv);
|
解密方法getDecryptDES
中添加向量,初始化时增加参数
1 2 3 4
| IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.DECRYPT_MODE, sks, iv);
|
如果使用DES/CBC/NoPadding
模式,需要修改原文为 8 个字节的倍数
1 2
| String input = "hello wo";
|
五、消息摘要
- 消息摘要(Message Digest)又称为数字摘要(Digital Digest)
- 它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向 Hash 加密函数对消息进行作用而产生
- 使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全
1、特点
无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用 MD5 算法摘要的消息有 128 个比特位,用 SHA-1 算法摘要的消息最终有 160 比特位的输出
只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出
消息摘要是单向、不可逆的
常见算法 :
MD5
SHA1
SHA256
SHA512
2、获取字符串消息摘要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import java.security.MessageDigest;
public class DigestDemo { public static void main(String[] args) throws Exception { String input = "hello world";
String md5 = getDigest(input, "MD5"); System.out.println("md5 = " + md5); System.out.println("---------------------------");
String sha1 = getDigest(input, "SHA-1"); System.out.println("sha1 = " + sha1); System.out.println("---------------------------");
String sha256 = getDigest(input, "SHA-256"); System.out.println("sha256 = " + sha256); System.out.println("---------------------------");
String sha512 = getDigest(input, "SHA-512"); System.out.println("sha512 = " + sha512); }
private static String toHex(byte[] digest) throws Exception {
StringBuilder sb = new StringBuilder();
for (byte b : digest) { String s = Integer.toHexString(b & 0xff); if (s.length() == 1) { 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(algorithm + ":密文的字节长度:" + digest.length);
return toHex(digest); } }
|
3、获取文件消息摘要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.security.MessageDigest;
public class DigestDemo { public static void main(String[] args) throws Exception {
String sha1 = getDigestFile("D:\\003.jpg", "SHA-1"); System.out.println("sha1 = " + sha1); System.out.println("---------------------------");
String sha512 = getDigestFile("D:\\003.jpg", "SHA-512"); System.out.println("sha512 = " + 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(algorithm + ":密文的字节长度:" + digest.length); return toHex(digest); }
private static String toHex(byte[] digest) { StringBuilder sb = new StringBuilder(); for (byte b : digest) { String s = Integer.toHexString(b & 0xff); if (s.length() == 1) { s = "0" + s; } sb.append(s); } System.out.println("16进制数据的长度:" + sb.toString().getBytes().length); return sb.toString(); } }
|
4、总结
- MD5 算法 : 摘要结果 16 个字节, 转 16 进制后 32 个字节
- SHA1 算法 : 摘要结果 20 个字节, 转 16 进制后 40 个字节
- SHA256 算法 : 摘要结果 32 个字节, 转 16 进制后 64 个字节
- SHA512 算法 : 摘要结果 64 个字节, 转 16 进制后 128 个字节
六、非对称加密
简介:
- 非对称加密算法又称
现代加密算法
- 非对称加密是计算机通信安全的基石,保证了加密数据
不会被破解
- 与对称加密算法不同,非对称加密算法需要两个密钥:
公开密钥(publickey)
和私有密(privatekey)
- 公开密钥和私有密钥是
一对
- 如果用
公开密钥
对数据进行加密
,只有用对应的私有密钥
才能解密
- 如果用
私有密钥
对数据进行加密
,只有用对应的公开密钥
才能解密
- 因为加密和解密使用的是两个
不同
的密钥,所以这种算法叫作非对称加密算法
- 示例
- 首先生成密钥对, 公钥为(5,14), 私钥为(11,14)
- 现在 A 希望将原文 2 发送给 B
- A 使用公钥加密数据. 2 的 5 次方 mod 14 = 4 , 将密文 4 发送给 B(mod:取余运算)
- B 使用私钥解密数据. 4 的 11 次方 mod 14 = 2, 得到原文 2
- 特点
- 加密和解密使用不同的密钥
- 如果使用私钥加密, 只能使用公钥解密
- 如果使用公钥加密, 只能使用私钥解密
- 处理数据的速度较慢, 因为安全级别高
- 常见算法
1、生成公钥和私钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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 RSAdemo { 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(); String privateKeyString = Base64.encode(privateKeyEncoded); String publicKeyString = Base64.encode(publicKeyEncoded); System.out.println(privateKeyString); System.out.println("-----------------私钥、公钥分割线------------------"); System.out.println(publicKeyString); } }
|
2、私钥加密公钥解密
在上面RSAdemo
的基础上加入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| String input = "hello world";
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE,privateKey);
byte[] bytes = cipher.doFinal(input.getBytes()); System.out.println(Base64.encode(bytes));
cipher.init(Cipher.DECRYPT_MODE,publicKey);
byte[] bytes1 = cipher.doFinal(bytes); System.out.println(new String(bytes1));
|
公钥加密、私钥解密只需要在初始化加密时公钥私钥换一下就可以了
3、保存公钥和私钥
前面代码每次都会生成 加密和解密,现在把加密和解密的方法全部到项目的根目录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import com.sun.org.apache.xml.internal.security.utils.Base64; import org.apache.commons.io.FileUtils; import javax.crypto.Cipher; import java.io.File; import java.nio.charset.Charset; import java.security.*;
public class RSAdemo1 { public static void main(String[] args) throws Exception { String algorithm = "RSA"; generateKeyToFile(algorithm, "./public.rsa", "./private.rsa"); }
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[] publicKeyEncoded = publicKey.getEncoded(); byte[] privateKeyEncoded = privateKey.getEncoded(); 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")); }
|
4、读取私钥加密数据
加入读取公钥私钥方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static PrivateKey getPrivateKey(String priPath, String algorithm) throws Exception { String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset()); KeyFactory keyFactory = KeyFactory.getInstance(algorithm); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString)); return keyFactory.generatePrivate(spec); }
public static PublicKey getPublicKey(String pulickPath, String algorithm) throws Exception { String publicKeyString = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset()); KeyFactory keyFactory = KeyFactory.getInstance(algorithm); X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyString)); return keyFactory.generatePublic(spec); }
|
全部代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| import com.sun.org.apache.xml.internal.security.utils.Base64; import org.apache.commons.io.FileUtils;
import javax.crypto.Cipher; import java.io.File; import java.nio.charset.Charset; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
public class RSAdemo2 { public static void main(String[] args) throws Exception { String input = "hello world"; String algorithm = "RSA"; PrivateKey privateKey = getPrivateKey("./private.rsa", algorithm); PublicKey publicKey = getPublicKey("./public.rsa", algorithm); String s = encryptRSA(algorithm, privateKey, input); String s1 = decryptRSA(algorithm, publicKey, s); System.out.println("私钥加密后的数据:" + s); System.out.println("--------------------------------------------------"); System.out.println("公钥解密后的数据:" + s1); }
public static PrivateKey getPrivateKey(String priPath, String algorithm) throws Exception { String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset()); KeyFactory keyFactory = KeyFactory.getInstance(algorithm); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString)); return keyFactory.generatePrivate(spec); }
public static PublicKey getPublicKey(String pulickPath, String algorithm) throws Exception { String publicKeyString = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset()); KeyFactory keyFactory = KeyFactory.getInstance(algorithm); X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyString)); return keyFactory.generatePublic(spec); }
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()); System.out.println(Base64.encode(bytes)); return Base64.encode(bytes); }
private static String decryptRSA(String algorithm, PublicKey publicKey, String s) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, publicKey); byte[] decode = Base64.decode(s); byte[] bytes1 = cipher.doFinal(decode); return new String(bytes1); } }
|
七、数字签名
数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名是非对称密钥加密技术与数字摘要技术的应用。
1、简单认识
数字签名的含义:在网络中传输数据时候,给数据添加一个数字签名,表示是谁发的数据,而且还能证明数据没有被篡改。
数字签名的主要作用就是保证了数据的有效性(验证是谁发的)和完整性(证明信息没有被篡改)。
2、原理
用私钥对消息进行加密,得到密文,就是一个签名的过程。而公钥是公开的,其他人可以很容易地使用公钥去尝试解密生成的密文,然后就可以知道这个签名是不是我签的。当然,在数字签名的过程中,对原文的保密性没有要求,所以加密、解密这样的名词在这个场景中并不准确,用签名和解签会更合适。
实际应用中,由于直接对原消息进行签名有安全性问题,而且原消息往往比较长,直接使用 RSA 算法进行签名速度会比较慢,所以我们一般对消息计算其摘要(比如 SHA-256 等),然后对摘要进行签名。只要使用的摘要算法是安全的(MD5、SHA-1 已经不安全了),那么这种方式的数字签名就是安全的。
一个具体的签名过程如下:
发送者对外发布公钥,并声明对应的私钥在自己手上
发送者对消息 M 计算其消息摘要(比如通过 sha256 ),得到消息摘要 D
发送者使用私钥对消息摘要 D 进行签名,得到签名 A
将需要发送的消息 M 和签名 A 一起发送出去
验证过程如下:
接收者首先对收到的消息 M 计算(即获取文件消息摘要,比如通过 sha256 ),得到接收者自己生成的消息摘要 d
接着使用发送者发布在网络的公钥对 A 进行解签,得到消息摘要 D
如果消息摘要 D 和 d 相同,那么证明消息 M 确实是发送者发出的,没有被篡改过
3、数字证书
上面提到我们对签名进行验证时,需要用到公钥。如果公钥是伪造的,那我们无法验证数字签名了,也就根本不可能从数字签名确定对方的合法性了。这时候证书就闪亮登场了。
数字证书这一名词并非是我国原有,而是来自于英文 digital certificate 的翻译。数字证书从本质上来说是一种电子文档,是由电子商务认证中心(以下简称为 CA 中心)所颁发的一种较为权威与公正的证书,
CA 中心采用的是以数字加密技术为核心的数字证书认证技术,通过数字证书,CA 中心可以对互联网上所传输的各种信息进行加密、解密、数字签名与签名认证等各种处理,同时也能保障在数字传输的过程中不被不法分子所侵入,或者即使受到侵入也无法查看其中的内容。
数字证书就相当于社会中的身份证,用户在进行电子商务活动时可以通过数字证书来证明自己的身份,并识别对方的身份,
4、网页加密
应用“数字证书”的实例:https 协议。这个协议主要用于网页加密
首先,客户端向服务器发出加密请求。
服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。
客户端(即浏览器)的“证书管理器”,有“受信任的根证书颁发机构”列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。
如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。
如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告。
如果数字证书是可靠的,客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。
5、代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| import java.io.File; import java.nio.charset.Charset; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import com.sun.org.apache.xml.internal.security.utils.Base64; import org.apache.commons.io.FileUtils;
public class SignatureDemo { public static void main(String[] args) throws Exception { String m = "hello world";
PublicKey publicKey = getPublicKey("./public.rsa", "RSA"); PrivateKey privateKey = getPrivateKey("./private.rsa", "RSA");
String signaturedData = getSignature(m, "sha256withrsa", privateKey);
boolean b = verifySignature(m, "sha256withrsa", publicKey, signaturedData); System.out.println(b);
}
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(); return Base64.encode(sign); }
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)); }
public static PrivateKey getPrivateKey(String priPath, String algorithm) throws Exception { String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset()); KeyFactory keyFactory = KeyFactory.getInstance(algorithm); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString)); return keyFactory.generatePrivate(spec); }
public static PublicKey getPublicKey(String pulickPath, String algorithm) throws Exception { String publicKeyString = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset()); KeyFactory keyFactory = KeyFactory.getInstance(algorithm); X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyString)); return keyFactory.generatePublic(spec); } }
|