有道无术,术尚可求,有术无道,止于术。
文章目录
- 前言
- 信息安全
- 加密机制
- 核心概念
- 对称加密
- 非对称加密
- JCE
- 对称加解密
- 1. 创建密钥
- 2. 加密
- 3. 解密
- 非对称加解密
- 1. 创建密钥
- 2. 公钥加密
- 3. 私钥解密
前言
支付
和金钱挂钩,支付安全
显得尤为重要,微信、支付宝等第三方支付公司,为了确保支付安全问题,都下足了不少功夫,其底层还是和信息安全
密切挂钩,所以在这之前,我们需要了解各种安全机制,才能更好地了解支付流程和安全性问题。
信息安全
信息安全
是一个很广泛的概念,涉及计算机和网络系统的各个方面。从总体上来讲,信息安全有5个基本要素∶
-
机密性∶确保信息不暴露给未授权的实体或进程。
-
完整性∶只有得到允许的人才能够修改数据,并能够判别数据是否已被篡改。
-
可用性∶得到授权的实体在需要时可访问数据。
-
可控性∶可以控制授权范围内的信息流向和行为方式。
-
可审查性∶对出现的安全问题提供调查的依据和手段。
加密机制
数据加密
即对明文(未经加密的数据)按照某种加密算法(数据的变换算法)进行处理,而形成难以理解的密文(经加密后的数据)。即使密文被截获,截获方也无法或难以解码,从而防止泄露信息。
按照加密密钥和解密密钥的异同,有两种密钥体制,分别是对称密码体制
和非对称密码体制
。
核心概念
1、明文
加密前的消息叫明文
(plain text)。比如小明给小红发送一封情书没有加密,只要被截取,那么会直接看到明文的内容信息。连篇的土味情话被看到,盗信者直呼辣眼睛😭😭😭
2、密文
加密后的文本叫密文
(cipher text)。为了防止别人看到,小明对情书内容进行加密,加密后内容变成了不可读的内容,就算被窃取,盗信者也无法看懂其内容。
3、密钥
拥有钥匙
的人,使用钥匙
将明文变为密文,使用钥匙
将密文解析为明文,这把钥匙
叫做秘钥
(key)。
4、加密
使用秘钥
,将明文变为密文的过程叫做加密
(encrypt)。
5、解密
使用秘钥
,将密文还原为明文的过程叫做解密
(decrypt)。
6、加密算法
加密算法是一种密码学算法,就是指将明文变成密文的加密技术。所有的加密算法都是公开的,而算法使用的密钥
则必须保密。
对称加密
对称加密
加密和解密采用相同的密钥。因为只使用一个密钥,密钥必须保密,一旦被窃取,消息会被破解。其优点是运算速度快。
它要求发送方和接收方在安全通信之前,商定一个密钥
。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。
工作流程:
- 小明使用某种加密算法生成一个秘钥,并将这个秘钥给小红。
- 小明使用秘钥将情书进行加密,变为密文,并发送给小红。
- 小红收到情书后,使用秘钥将密文还原为明文。
非对称加密
非对称加密
有公钥
和私钥
两个概念,私钥自己拥有,不能给别人,公钥公开。比对称加密安全,但是运算速度更慢。
工作流程:
- 小红使用某种加密算法生成一对密钥(公钥和私钥),并将这个公钥给小明。
- 得到公钥的小明使用该密钥对机密信息进行加密后再发送给小红。
- 小红用自己的专用私钥对加密后的信息进行解密。
在传输过程中,即使攻击者截获了传输的密文,并得到了公钥,也无法破解密文,因为只有小红的私钥才能解密密文。
JCE
JCE
是Java Cryptography Extension
三个单词的缩写,译为Java 加密扩展,是JDK 1.4 提供的一个用于加密、密钥生成算法等功能的扩展包。
接下来我们使用 JCE
实现对称、非对称加解密。
对称加解密
1. 创建密钥
创建密钥需要创建一个javax.crypto.KeyGenerator
密钥生成器对象,KeyGenerator
提供对称密钥生成器的功能,使用此类的 getInstance
方法获取实例对象。
KeyGenerator
对象可重复使用,也就是说,在生成密钥后,可以重复使用同一个KeyGenerator
对象来生成更多的密钥。
KeyGenerator.getInstance()
方法返回指定算法的密钥生成器对象,三个重载方法说明如下:
// 返回生成指定算法的秘密密钥的 KeyGenerator 对象。
// algorithm:密钥算法的标准名称
public static final KeyGenerator getInstance(String algorithm) throws NoSuchAlgorithmException
// 返回生成指定算法的秘密密钥的 KeyGenerator 对象。
// algorithm:密钥算法的标准名称
// provider:提供者的名称
public static final KeyGenerator getInstance(String algorithm,String provider)throws NoSuchAlgorithmException,NoSuchProviderException
// 返回生成指定算法的秘密密钥的 KeyGenerator 对象。
// algorithm:密钥算法的标准名称
// provider:提供者对象
public static final KeyGenerator getInstance(String algorithm,Provider provider)throws NoSuchAlgorithmException
该方法需要两个重要参数:算法名称、服务提供者。
加密服务提供者
简称CSP
,java.security.Provider
是所有安全服务提供程序的基类。在进行加解密运算时,需要指定一个CSP
。JDK
默认安装并配置了一个或多个提供程序,也可以注册第三方或自定义服务提供者(例如第三方加密机)。如果没有指定该参数,会从可用的提供者中选取第一个。
通过以下代码,可以获取当前JDK
环境所有提供者:
Provider[] providers = Security.getProviders();
for (Provider provider : providers) {
String name = provider.getName();
double version = provider.getVersion();
System.out.println(name + ": " + version);
}
// 输出结果
SUN: 1.8
SunRsaSign: 1.8
SunEC: 1.8
SunJSSE: 1.8
SunJCE: 1.8
SunJGSS: 1.8
SunSASL: 1.8
XMLDSig: 1.8
SunPCSC: 1.8
SunMSCAPI: 1.8
算法名称:密钥算法的标准名称。常用的对称算法有:AES、3DES、SM1(国密JDK不支持)、SM4(国密)
等。
算法名称 | 秘钥长度 | 加密强度 | 性能 | 版权 |
---|---|---|---|---|
DES | 56 | 弱 | 快 | 美国 |
3DES | 168 | 中 | 慢 | 美国 |
IDEA | 128 | 强 | 中 | 瑞士 |
AES | 128 192 256 | 强 | 快 | 美国 |
SM1 | 128 | 强 | 中国 | |
SM4 | 128 | 强 | 中国 |
创建了秘钥生成器后,还需要调用init
方法进行初始化。常用方法:
// 初始化此密钥生成器,使其具有确定的密钥大小。
// 参数:keysize 密钥长度。这是特定于算法的一种规格,是以位数为单位指定的。
public final void init(int keysize)
密钥长度
是一个重要的参数,每种算法都有其支持的长度,长度越长的情况下,暴力破解的时间就越长。长度单位为位(bit)
。
生成对称密钥代码如下:
// 获取密钥生成器,指定算法为AES
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
// 初始化
keyGenerator.init(128);
// 生成公钥
SecretKey secretKey = keyGenerator.generateKey();
// 打印秘钥:LDDoCClZOvP9ZncFM19jZg==
byte[] keyEncoded = secretKey.getEncoded();
System.out.println(Base64.getEncoder().encodeToString(keyEncoded));
2. 加密
在上一步骤中,我们生成了一个Base64
编码的秘钥字符串:LDDoCClZOvP9ZncFM19jZg==
,接下来我们使用该秘钥进行加密。
加解密时,需要使用JCE
中的javax.crypto.Cipher
对象。此类为加密和解密提供密码功能。它构成了Java Cryptographic Extension
(JCE) 框架的核心。
调用getInstance
方法获取该对象实例,该方法需要一个字符串参数。
// 返回实现指定转换的 Cipher 对象。此方法从首选 Provider 开始遍历已注册安全提供者列表。
// 参数:transformation - 转换的名称,例如 DES/CBC/PKCS5Padding。
public static final Cipher getInstance(String transformation)
transformation
翻译过来是转换的意思,可以理解为一个转换规则,由三部分组成算法/加密模式/填充规则
。
算法就是标准的算法名称,之间我们已经介绍过了。
加密模式
主要有以下两种:
- ECB(电码本模式),将明文分成若干小段, 然后对每小段进行加密
- CBC(密码分组链接模式),先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再使用密钥进行加密
填充规则
:在分组密码中,当数据长度不符合分组长度时,需要按一定的方式,将尾部明文分组进行填充,这种将尾部分组数据填满的方法称为填充(Padding)。
填充规则
主要有以下几种:
- NOPADDING:即不填充,要求明文的长度,必须是加密算法分组长度的整数倍。
- ANSIX9.23:在填充字节序列中,最后一个字节填充为需要填充的字节长度,其余字节填充0。
- PKCS5PADDING:在填充字节序列中,每个字节填充为需要填充的字节长度。
以下列举了几种算法对应的转换名称:
算法 | 转换名称 |
---|---|
AES | AES/CBC/NoPadding |
AES | AES/CBC/PKCS5Padding |
AES | AES/ECB/NoPadding |
AES | AES/ECB/PKCS5Padding |
DES | DES/CBC/NoPadding |
DES | DES/CBC/PKCS5Padding |
DES | DES/ECB/NoPadding |
DES | DES/ECB/PKCS5Padding |
通过以上知识,加密代码如下:
// 原文
String message = "爱老虎油~";
// Base64编码的秘钥字符串
String keyBase64 = "LDDoCClZOvP9ZncFM19jZg==";
// 获取Cipher 对象
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// 将秘钥字符串转为秘钥对象
SecretKey keySpec = new SecretKeySpec(Base64.getDecoder().decode(keyBase64), "AES");
// 初始化,设置为加密模式,并传入秘钥
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 加密运算
byte[] cipherData = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
// 打印密文:SJ+6fnHAWt9ui7Pq3AlFmg==
System.out.println("密文:" + Base64.getEncoder().encodeToString(cipherData));
3. 解密
解密
就比较简单了,使用秘钥,调用Cipher
对象进行解密即可,代码如下:
// 解密
// 密文
String enBase64Message="SJ+6fnHAWt9ui7Pq3AlFmg==";
// Base64编码的秘钥字符串
String enKeyBase64 = "LDDoCClZOvP9ZncFM19jZg==";
// 获取Cipher 对象
Cipher deCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// 将秘钥字符串转为秘钥对象
SecretKey deKeySpec = new SecretKeySpec(Base64.getDecoder().decode(enKeyBase64), "AES");
// 初始化,设置解密模式,并传入秘钥
deCipher.init(Cipher.DECRYPT_MODE, deKeySpec);
// 解密运算
byte[] bytes = deCipher.doFinal(Base64.getDecoder().decode(enBase64Message));
// 打印明文
System.out.println("明文:" + new String(bytes));
执行结果如下:
密文:SJ+6fnHAWt9ui7Pq3AlFmg==
明文:爱老虎油~
非对称加解密
1. 创建密钥
使用KeyPairGenerator.getInstance()
获取一个秘钥生成器,参数类型和对称秘钥一样,区别在于算法名称不一样。
常见的非对称加密算法有:
- RSA:性能比较快,如果想要较高的加密难度,需要很长的秘钥。
- ECC:基于椭圆曲线提出。是目前加密强度最高的非对称加密算法。
- SM2:同样基于椭圆曲线问题设计。最大优势就是国家认可和大力支持。
代码如下:
// 获取非对称密钥生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
// 初始化
keyPairGenerator.initialize(512);
// 生成秘钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 获取内部密钥对
PrivateKey privateKey = keyPair.getPrivate(); // 私钥
PublicKey publicKey = keyPair.getPublic(); // 公钥
// 打印秘钥:
System.out.println("公钥:" + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
System.out.println("私钥:" + Base64.getEncoder().encodeToString(privateKey.getEncoded()));
执行结果:
公钥:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL8SAEW1o3lMg4XVQi29n7XqQ02ei0voeKcXvj+bVH1Fu+8W9CzSLpnlHHQgL9bl8Kly/5/y0TgE+NBxsrdfZo8CAwEAAQ==
私钥:MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvxIARbWjeUyDhdVCLb2ftepDTZ6LS+h4pxe+P5tUfUW77xb0LNIumeUcdCAv1uXwqXL/n/LROAT40HGyt19mjwIDAQABAkEAm4TsYdERaSbFGsbVIePpPimadHAqkzN6GQ8zVBR7NaaEeeFjZ/XX/FmNTvrKp2IXN5p0d0LZDOUPq3UL/nzpQQIhAP3JnMQBa9yeb3NzmCguaDwty8175sEDsB/NJLrULuehAiEAwLxrkZItOaoFRV6c78qPsHQGKSTQQBAFS2vycehW4C8CIQDH6wwM8zmunzgYcFTKQlRmI4VKJ3JNVcRmKMnoSsFwQQIgTob2lPMn9gyt5RuteY3giZZcRDs5lkBwx9ANkheF/gUCIAwBGYCzZHNsEr6PIfImrwxRLs/MFPbbQAHpUQaLaWL9
2. 公钥加密
使用公钥对原文数据进行加密。
// 使用公钥加密
// 原文
String message = "爱老虎油~";
// Base64编码的公钥字符串
String publicKeyBase64 = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMEC2gOpKGTzCdJ4flb6Oj9QmbwVANmwLn0zBkZINepGEeWnvORxlteakSK03O2XgtHSGyW4r520KBtvKEcdFAECAwEAAQ==";
// 获取Cipher 对象
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// 将公钥字符串转为公钥对象
X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyBase64));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKeyByValue = keyFactory.generatePublic(bobPubKeySpec);
// 初始化,设置为加密模式,并传入公钥
cipher.init(Cipher.ENCRYPT_MODE, publicKeyByValue);
// 加密运算
byte[] cipherData = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
// 打印密文:mMzlg+65E1QKJi+zOVdyzbYrq526fOd7ir0Zlkbvo2q37iGvrMiq/y9P4teBtPe9PRJF7HCsmezZ3/M0aiQbyw==
System.out.println("密文:" + Base64.getEncoder().encodeToString(cipherData));
3. 私钥解密
使用私钥对密文数据进行解密。
// 使用私钥解密
// 密文
String enBase64Message = "mMzlg+65E1QKJi+zOVdyzbYrq526fOd7ir0Zlkbvo2q37iGvrMiq/y9P4teBtPe9PRJF7HCsmezZ3/M0aiQbyw==";
// Base64编码的私钥字符串
String privateKeyBase64 = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAwQLaA6koZPMJ0nh+Vvo6P1CZvBUA2bAufTMGRkg16kYR5ae85HGW15qRIrTc7ZeC0dIbJbivnbQoG28oRx0UAQIDAQABAkEAmQgjx6dFadTxQrlaoqe/qxfC7MdSZ0czdP0RyoPSi64k942Rj46in5zDw60SynysVvMmVinxPPFS+6rv5kIWaQIhAPMXt8l7jPGmF6pYTmE2cx9OjB4Q9q8uhfIKZc0E7T9HAiEAy0JkNwSVxK76I9mFZpoRSfH37xAqLyUKhdCRAn5fBncCIQDGw3Pg6Ia750SeYgnkbrL+vCjRRKmPX4jh+SJ32jlqbQIgIXe6FpELtAn3qAV+AKnnpNxRraxktcSMmgIAjn+OV/sCIFlpFzkKXkW2cTv6oVHbFkqgrDPhTAXbshsa/U301Oac";
// 获取Cipher 对象
Cipher deCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// 将私钥字符串转为私钥对象
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyBase64));
KeyFactory privateKeyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKeyByValue = privateKeyFactory.generatePrivate(privateKeySpec);
// 初始化,设置解密模式,并传入私钥
deCipher.init(Cipher.DECRYPT_MODE, privateKeyByValue);
// 解密运算
byte[] bytes = deCipher.doFinal(Base64.getDecoder().decode(enBase64Message));
// 打印明文: 明文:爱老虎油~
System.out.println("明文:" + new String(bytes));