🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## 1. 加密技术 ### 1.1 对称加密 > * 加密和解密都是使用同一把秘钥,这种方法称之为对称加密,也成为单秘钥加密 > * 常见的对称加密算法有: > AES、DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK 等 DES > 全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1976 年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),随后在国际上广泛流传开来。 3DES > 也叫Triple DES,是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。 > 它相当于是对每个数据块应用三次DES 加密算法。由于计算机运算能力的增强,原版DES 密码的密钥长度变得容易被暴力破解;3DES 即是设计用来提供一种相对简单的方法,即通过增加DES 的密钥长度来避免类似的攻击,而不是设计一种全新的块密码算法。 AES > 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael 加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001 年11 月26 日发布于FIPS PUB 197,并在2002 年5 月26 日成为有效的标准。2006 年,高级加密标准已然成为对称密钥加密中最流行的算法之一。 #### 1.1.1 DES DES 加密原理(对比特位进行操作,交换位置,异或等等,无需详细了解) > 1. Bit是计算机传输的最小单位,以0或1表示(二进制),例如3的二进制为00000011 > 2. Byte与Bit的区别 > 数据存储以‘字节’为单位,数据传输大多是以‘位’(bit,又名‘比特’)为单位,每8 个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小一级的信息单位(完成的信息)。 > Byte的取值范围是-128到127,第一个高位是符号位1表示负数 ![](https://box.kancloud.cn/a1b36e7e744eda4bb2f34fadb4f0be57_424x220.png) 即10000000 到01111111 之间, 转换成十进制就是-128到127 #### 1.1.2 对称加密的具体应用方式 1. 生成秘钥并保存到硬盘上,以后读取该秘钥进行加密解密操作,实际开发中用得比较少 ~~~ //生成随机秘钥 SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey(); //序列化秘钥到磁盘上 FileOutputStream fos = new FileOutputStream(new File("heima.key")); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(secretKey); //从磁盘里读取秘钥 FileInputStream fis = new FileInputStream(new File("heima.key")); ObjectInputStream ois = new ObjectInputStream(fis); Key key = (Key) ois.readObject(); ~~~ 2、使用自定义秘钥(秘钥写在代码里) ~~~ //创建密钥写法1 KeySpec keySpec = new DESKeySpec(key.getBytes()); SecretKey secretKey = SecretKeyFactory.getInstance(ALGORITHM). generateSecret(keySpec); //创建密钥写法2 //SecretKey secretKey = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); //得到key 后,后续代码就是Cipher 的写法,此处省略... ~~~ 注意事项 把秘钥写在代码里有一定风险,当别人反编译代码的时候,可能会看到秘钥,android 开发里建议用JNI 把秘钥值写到C 代码里,甚至拆分成几份,最后再组合成真正的秘钥 ### 1.2 非对称加密 与对称加密算法不同,非对称加密算法需要两个密钥:公钥(publickey)和私钥(privatekey)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密;如果用私钥对数据进行加密,那么只有用对应的公钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。 RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)等,其中支付宝使用的就是RSA算法 ![](https://box.kancloud.cn/6c707746971969beae638b14a70ee9af_561x296.png) ## 2. 生成pfx证书 打开Microsoft .NET Framework 的SDK命令提示,按以下步骤操作: > (所有的工具可在C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\中找到)没有就安装,这里是我的百度网盘地址: > 链接:http://pan.baidu.com/s/1qYPwmTY 密码:ji03 1. 利用makecert.exe工具生成X.509证书(.cer包含公钥)和一个.pvk私钥文件 makecert.exe -r -n "CN=aixin" -e 12/31/2019 -sv aixin.pvk aixin.cer ![](https://box.kancloud.cn/cf369f900d2dbe8233a4a86df382be5c_643x60.png) -r: 自签名 -n :"cn=MyCA": 证书的subject name, -e :证书过期时间 -sv :生成.pvk文件 执行命令后会提示输入私钥密码,要求密码长度不能小于六 ![](https://box.kancloud.cn/eb6d9d4929b41bff7e1d1aa0d0083ff1_347x225.png) aixin.cer aixin.spc在当前目录下生成 2. 利用X.509证书(.cer)创建发行者证书 (.spc),使用工具cert2spc.exe cert2spc.exe aixin.cer aixin.spc ![](https://box.kancloud.cn/813ff15cbd8b6648f28982e737ade20e_459x45.png) ramkykey. spc在当前目录下生成 3. 导出pfx证书 执行: pvkimprt.exe -pfx aixin.spc aixin.pvk 输入私钥密码 ![](https://box.kancloud.cn/c8858b3c130809006483f53190ef68e1_317x210.png) ok,下一步 ![](https://box.kancloud.cn/e09d3d9a907a43e9f5d16b9ea02cb7ca_503x422.png) 下一步,选择导出私钥 ![](https://box.kancloud.cn/583cb630cd9c8b7770063d5b3e21890c_503x422.png) 下一步,选择个人信息交换 ![](https://box.kancloud.cn/7441f24ca64567b0a962d2dac53781f8_503x422.png) 下一步,输入证书保护密码 ![](https://box.kancloud.cn/eee660b5320d11b2f667527a4836cccf_503x422.png) ![](https://box.kancloud.cn/cd2a23f2a11531de6815aecd66a6f327_503x422.png) 完成 2. 验证签名 Isign(接口),签名 ~~~ package cn.com.bigssl.crypto; import java.security.PrivateKey; public interface ISign { public String sign(String data, long timestamp, PrivateKey key) throws Exception; public String sign(byte[] data, long timestamp, PrivateKey key) throws Exception; public String sign(String data, PrivateKey key) throws Exception; public String sign(byte[] data, PrivateKey key) throws Exception; } ~~~ sign(实现类),签名 ~~~ package cn.com.bigssl.crypto; import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin; import javax.crypto.Cipher; import java.security.MessageDigest; import java.security.PrivateKey; public class Sign implements ISign { public String sign(String data, long timestamp, PrivateKey key) throws Exception { return sign(data.getBytes("utf-8"), timestamp, key); } public String sign(String data, PrivateKey key) throws Exception{ return sign(data.getBytes("utf-8"), 0, key); } public String sign(byte [] data, PrivateKey key) throws Exception { return sign(data, 0, key); } public String sign(byte [] data, long timestamp, PrivateKey key) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256"); //1. 加密 md.update(data); if(timestamp > 0){ md.update(EncodeUtil.toBE(timestamp)); } byte[] hash = md.digest(); // 2. 获得加密后的byte Cipher cipher = Cipher.getInstance("RSA"); // 3. 获得加解密类Cipher(RSA非对称加密) cipher.init(Cipher.ENCRYPT_MODE, key); // 4. 初始化Cipher为加密类(用私钥加密) byte[] encrypted = cipher.doFinal(hash); // 5. 获得由Cipher加密后的byte[] return HexBin.encode(encrypted); //6. 返回字符串 } } ~~~ 验证签名类(接口) ~~~ package cn.com.bigssl.crypto; import java.security.cert.X509Certificate; public interface IVerify { public boolean verify(String data, long timestamp, String encodedEncryptedStr, X509Certificate userCert) throws Exception ; public boolean verify(String data, String encodedEncryptedStr, X509Certificate userCert) throws Exception; public boolean verify(byte[] data, String encodedEncryptedStr, X509Certificate userCert) throws Exception; public boolean verify(byte[] data, long timestamp, String encodedEncryptedStr, X509Certificate userCert) throws Exception; } ~~~ 验证签名类(实现类) ~~~ package cn.com.bigssl.crypto; import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin; import javax.crypto.Cipher; import java.security.MessageDigest; import java.security.cert.X509Certificate; import java.util.Arrays; public class Verify implements IVerify { public boolean verify(String data, long timestamp, String encodedEncryptedStr, X509Certificate userCert) throws Exception { return verify(data.getBytes("utf-8"), timestamp, encodedEncryptedStr, userCert); } public boolean verify(String data, String encodedEncryptedStr, X509Certificate userCert) throws Exception { return verify(data.getBytes("utf-8"), 0, encodedEncryptedStr, userCert); } public boolean verify(byte [] data, String encodedEncryptedStr, X509Certificate userCert) throws Exception{ return verify(data,0, encodedEncryptedStr, userCert); } public boolean verify(byte [] data, long timestamp, String encodedEncryptedStr, X509Certificate userCert) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256");// 1. SHA加密 md.update(data); if(timestamp > 0){ md.update(EncodeUtil.toBE(timestamp)); } byte[] hash = md.digest(); // 2. 获取SHA加密后的Byte数组 byte[] encryptedStr = HexBin.decode(encodedEncryptedStr); // 3. HexBin把十六进制字符串解析成Byte数组 Cipher cipher = Cipher.getInstance("RSA"); // 4. 建立解密类Cipher(类型RSA非对称加密) cipher.init(Cipher.DECRYPT_MODE, userCert); // 5. 使用解密模式,解密要是为公钥 byte[] plain = cipher.doFinal(encryptedStr); // 6. 获得解密后的Byte数组 boolean ok = Arrays.equals(hash, plain); // 7. 比较data(SHA加密的)byte数组,与传过来的用私钥加密的数组是否一样,判断对方是否有我的证书 return ok; } } ~~~ 签名测试类(私钥加密,公钥解密) ~~~ package cn.com.bigssl.crypto; import com.aexit.motordriver.commons.utils.URLParameter; import org.apache.log4j.Logger; import java.io.FileInputStream; import java.io.InputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Date; import java.util.Enumeration; public class TestSignAndVerify { private static Logger logger = Logger.getLogger(TestSignAndVerify.class); public static void main(String[] args) throws Exception { String password = "4rfv$RFV"; KeyStore keyStore = KeyStore.getInstance("PKCS12"); //导入证书 try (InputStream input = new FileInputStream("C:\\Users\\Administrator\\Desktop\\aixin.pfx")) { keyStore.load(input, password.toCharArray());//todo 这里加载较慢,建议加入单例 } // * 每个证书都有自己的别名,秘钥要通过【别名】+【私钥密码】获得,公钥通过【别名获得】 Enumeration<String> aliases = keyStore.aliases(); if (!aliases.hasMoreElements()) { throw new RuntimeException("no alias found"); } String alias = aliases.nextElement(); PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray()); X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias); String data = "{\"district\":\"220100\",\"licnum\":\"112233445566\",\"name\":\"爱信测试驾校\"}"; long timestamp = new Date().getTime(); ISign sign = new Sign(); String sign_hex = sign.sign(data, timestamp, privateKey); logger.debug("数据签名字符串:" + sign_hex); IVerify verify = new Verify(); boolean ok = verify.verify("gdfwfhkwl", timestamp, sign_hex, cert); logger.debug("数据签名校验成功:" + ok); String url = URLParameter.getFromTimingUrlParameter(data,timestamp); logger.debug("数据签名URL:" + url); } } ~~~