软件的加密与解密是一个迷人的研究领域,它几乎可以与任意一种计算机技术紧密结合——密码学、程序设计语言、操作系统、数据结构。而由于这样或者那样的原因,对于这一领域的关注程度一直还处于低温状态。
网络安全一般需要注意以下几个关键点:
完整性(Integrity):确保信息在传输过程中,没有被篡改。
私密性(Confidentiality):也就是通过加密,确保只有可信的实体可以看到这些信息。
源认证(Authenticity):确保是可信的源发送了这些信息,而不是伪装源发送的消息。
不可否认性(Nonrepudiation):不能事后否认发送过这条信息。
今天我们说的是数据传输的私密性。加密技术是最常用的安全保密手段,利用技术手段把重要的数据变为乱码(加密)传送,到达目的地后再用相同或不同的手段还原(解密)。
加密技术包括两个元素:算法和密钥。
一、使用异或加密解密
先来看一个简单的加密解密的实例。
package com.wuxiaolong.EncrypteDecrypt;
import org.apache.commons.codec.digest.DigestUtils;
/**
* Description:
*
* @author 诸葛小猿
* @date 2020-07-22
*/
public class Test1 {
public static void main(String[] args) {
String content = "我爱你";
Integer key = 1000;
// 用户A发消息前,通过某种方式加密
String encryptStr = xor(content,key);
System.out.println(encryptStr);
// 密文通过网络传输
// 用户B收到密文后使用相同的方式解密
String decryptStr = xor(encryptStr,key);
System.out.println(decryptStr);
}
/**
* 加密算法及秘钥
* @param content
* @return
*/
public static String xor(String content, Integer key){
char[] chars = content.toCharArray();
for(int i=0; i chars[i] = (char) (chars[i]^key) ; } return new String(chars); } } 这里自定义了一个加解密方法xor,这个方法接收两个参数,一个是原文content,一个秘钥key。这样有加密算法,有秘钥,就可以加密了。 运行的结果: 懹燙䲈 // 加密结果 我爱你 // 解密结果 二、异或加密解密原理讲解 在发消息前,需要使用算法+秘钥对传输的消息(明文)进行加密,加密后使用密文在网络传输,传输到目的地时,再使用相同的算法+秘钥将密文翻译成明文。 在这个加密和解密的过程中,使用了相同的算法+秘钥。 这里可以看出,一个明文连续使用相同的算法+秘钥做两次加密,就可以得到原来的明文了。 这和计算机中的异或运算(^)很像,异或英文为exclusive OR,缩写成xor,异或运行有两个特点: 两个二进制数字相同为0,不同为1。 一个数字两次异或后,得到的是原数字本身。 下面使用一个字符a为例,和数字三进行两次异或运算,可以看出最终得到的结果还是a。 由于是位运算,参与运算的参数必须要转化成二进制后才能参与运算。上面的算法public static String xor(String content, Integer key)中,首先要将content转化成一个字符数组,然后将数组的每一个字符,和整形的key做异或运算得到新的字符,最终,将新的字符数组转化成新的字符串。 为什么不能使用content字符串直接与key求异或运算?因为字符串不能直接与数字运算,但是字符可以与数字运算,所以字符串转换成字符数组。 下面介绍的加密解密方法的底层,使用的都是二进制,所以加密解密的参数最终都会转换成二进制字节数组的形式进行处理。 上面这种方法中,加密和解密使用的是相同的密钥key,我们通常将这种方式称为对称加密。如果加密和解密使用的是不同的秘钥,则称之为非对称加密。 三、对称加密 对于对称性加密,双方通讯之前,都要事先知道相同的密匙和算法,之后便是对数据进行加解密了。 这里介绍几种常见的对称加密算法:DES,AES。 3.1 DES DES(Data Encryption Standard) 算法是美国政府机关为了保护信息处理中的计算机数据而使用的一种加密方式,是一种常规密码体制的密码算法,目前已广泛使用。该算法输入的是64比特的明文,在64比特密钥的控制下产生64比特的密文;反之输入64比特的密文,输出64比特的明文。64比特 的密钥中含有8个比特的奇偶校验位,所以实际有效密钥长度为56比特。使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行"异或"运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 个循环,使用异或,置换,代换,移位操作四种基本运算。 下面是使用JDK进行加密的示例代码: package com.wuxiaolong.EncrypteDecrypt; import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.IvParameterSpec; import java.security.Key; import java.util.Base64; /** * Description: * * @author 诸葛小猿 * @date 2020-07-23 */ public class DESUtil { /** * 偏移变量,固定占8位字节 */ private final static String IV_PARAMETER = "12345678"; /** * 加密算法 */ private static final String ALGORITHM = "DES"; /** * 加密/解密算法-工作模式-填充模式 */ private static final String CIPHER_ALGORITHM = "DES/CBC/PKCS5Padding"; /** * 默认编码 */ private static final String CHARSET = "utf-8"; /** * 秘钥 */ private static final String KEY = "key12345678"; public static void main(String[] args) { String content = "我爱你"; String encptStr = encrypt(KEY,content); System.out.println(encptStr); String decptStr = decrypt(KEY,encptStr); System.out.println(decptStr); } /** * 生成key * * @param password * @return * @throws Exception */ private static Key generateKey(String password) throws Exception { DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET)); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM); return keyFactory.generateSecret(dks); } /** * DES加密字符串 * * @param password 加密密码,长度不能够小于8位 * @param data 待加密字符串 * @return 加密后内容 */ public static String encrypt(String password, String data) { if (password== null || password.length() < 8) { throw new RuntimeException("加密失败,key不能小于8位"); } if (data == null) return null; try { Key secretKey = generateKey(password); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET)); cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); // 加密 byte[] bytes = cipher.doFinal(data.getBytes(CHARSET)); // base64编码 JDK1.8及以上可直接使用Base64,JDK1.7及以下可以使用BASE64Encoder byte[] encode = Base64.getEncoder().encode(bytes); return new String(encode); } catch (Exception e) { e.printStackTrace(); return data; } } /** * DES解密字符串 * * @param password 解密密码,长度不能够小于8位 * @param data 待解密字符串 * @return 解密后内容 */ public static String decrypt(String password, String data) { if (password== null || password.length() < 8) { throw new RuntimeException("加密失败,key不能小于8位"); } if (data == null) return null; try { Key secretKey = generateKey(password); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET)); cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); // base64解码 byte[] decode = Base64.getDecoder().decode(data.getBytes(CHARSET)); // 解密 byte[] decrypt = cipher.doFinal(decode); return new String(decrypt, CHARSET); } catch (Exception e) { e.printStackTrace(); return data; } } } 上面代码运行的结果: Xii999DE7LPx5io0awfOFw== // 加密结果 我爱你 // 解密结果 DES是一种分组数据加密技术(先将数据分成固定长度的小数据块,之后进行加密),速度较快,适用于大量数据加密,比如文件加密。文件加密的代码,可以关注我的公众号,输入关键字“java-summary”获取。 1997 年RSA数据安全公司发起了一项“DES 挑战赛”的活动,志愿者四次分别用四个月、41天、56个小时和22个小时破解了其用56bit DES算法加密的密文。即DES加密算法在计算机速度提升后的今天被认为是不安全的。所以针对保密级别特别高的数据推荐使用非对称加密算法。 3.2 AES AES (Advanced Encryption Standard),高级加密标准,是一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。 DES使用56位密钥,比较容易被破解,而AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据,相对来说安全很多。完善的加密算法在理论上是无法破解的,除非使用穷尽法。使用穷尽法破解密钥长度在128位以上的加密数据是不现实的,仅存在理论上的可能性。统计显示,即使使用目前世界上运算速度最快的计算机,穷尽128位密钥也要花上几十亿年的时间,更不用说去破解采用256位密钥长度的AES算法了。 下面是AES的源码展示: package com.wuxiaolong.EncrypteDecrypt; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; /** * Description: * * @author 诸葛小猿 * @date 2020-07-23 */ public class AESUtil { /** * 加密算法 */ private static final String ALGORITHM = "AES"; /** * 秘钥长度 */ private static final Integer KEY_LENGTH = 128; /** * 默认编码 */ private static final String CHARSET = "utf-8"; /** * 秘钥 */ private static final String KEY = "key12345678"; public static void main(String[] args) throws Exception { String content = "我爱你"; String encrypt = encrypt(content, KEY); System.out.println(encrypt); String decrypt = decrypt(encrypt, KEY); System.out.println(decrypt); } /** * 生成key * * @param password * @return * @throws Exception */ private static SecretKeySpec generateKey(String password) throws Exception { // 创建AES的Key生产者 KeyGenerator kgen = KeyGenerator.getInstance(ALGORITHM); // 利用用户密码作为随机数初始化出 // SecureRandom是生成安全随机数序列,password.getBytes()是种子,只要种子相同,序列就一样,所以解密只要有password就行 kgen.init(KEY_LENGTH, new SecureRandom(password.getBytes())); // 根据用户密码,生成一个密钥 SecretKey secretKey = kgen.generateKey(); // 返回基本编码格式的密钥,如果此密钥不支持编码,则返回 byte[] enCodeFormat = secretKey.getEncoded(); // 转换为AES专用密钥 SecretKeySpec key = new SecretKeySpec(enCodeFormat, ALGORITHM); return key; } /** * AES加密字符串 * * @param content 需要被加密的字符串 * @param password 加密需要的密码 * @return 密文 */ public static String encrypt(String content, String password) { try { SecretKeySpec key = generateKey(password); // 创建密码器 Cipher cipher = Cipher.getInstance(ALGORITHM); byte[] byteContent = content.getBytes(CHARSET); // 初始化为加密模式的密码器 cipher.init(Cipher.ENCRYPT_MODE, key); // 加密 byte[] result = cipher.doFinal(byteContent); // 二进制转换成16进制字符串 String hexStr = parseByte2HexStr(result); return hexStr; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 解密AES加密过的字符串 * * @param content AES加密过过的内容 * @param password 加密时的密码 * @return 明文 */ public static String decrypt(String content, String password) { try { // 十六进制字符串转换成二进制字节数组 byte[] byteArr = parseHexStr2Byte(content); SecretKeySpec key = generateKey(password); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key); // 解密 byte[] result = cipher.doFinal(byteArr); return new String(result,CHARSET); } catch (Exception e) { e.printStackTrace(); } return null; } /**将二进制转换成16进制 * @param buf * @return */ public static String parseByte2HexStr(byte buf[]) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < buf.length; i++) { String hex = Integer.toHexString(buf[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } sb.append(hex.toUpperCase()); } return sb.toString(); } /**将16进制转换为二进制 * @param hexStr * @return */ public static byte[] parseHexStr2Byte(String hexStr) { if (hexStr.length() < 1) return null; byte[] result = new byte[hexStr.length()/2]; for (int i = 0;i< hexStr.length()/2; i++) { int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16); int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16); result[i] = (byte) (high * 16 + low); } return result; } } 上面代码运行的结果: 765B2080D288F81355CB2A235AD7938C // 加密结果 我爱你 // 解密结果 相较于DES而言,AES算法有着更高的速度和资源使用效率,安全级别也较之更高了,被称为下一代加密标准。目前世界上还有组织在研究如何攻破AES这堵坚厚的墙,但是因为破解时间太长,AES得到保障,但是所用的时间不断缩小。随着计算机计算速度的增快,新算法的出现,AES遭到的攻击只会越来越猛烈,不会停止的。 AES现在广泛用于金融财务、在线交易、无线通信、数字存储等领域,经受了最严格的考验,但说不定哪天就会步DES的后尘。 3.3 对称加密的特点 优点: 速度快。相对于非对称加密,对称加密的性能更好,加解密速度更快。 安全。只能说相对还是安全的。 紧凑。加密后内容的长度基本变化不大。 缺点: 如果双方通讯时,通过明文传输共享密钥,容易出现中途劫持和窃听的问题。 随着通讯的参与者数量的增加,密钥数量急剧膨胀((n×(n-1))/2)。 因为密钥数量过多,对密钥的管理和存储是一个很大的问题(后面我会专门开一期说秘钥管理及系统设计)。 不支持数字签名和不可否认性。 四、非对称加密 对于非对称算法,双方通讯之前,都需要事先生成一对密匙(公钥、私钥),然后双方交换公钥。通常非对称加密使用的就是RAS算法。 在使用非对称密钥技术之前,所有参与者,不管是用户还是路由器等网络设备,都需要预先使用非对称密钥算法(例如RSA)产生一对密钥,其中包括一个公钥和一个私钥。公钥可以放在一个服务器上共享给属于这个密钥系统的所有用户与设备,而私钥需要由持有者严格保护,确保只有持有者才能唯一拥有。 非对称加密有很多优点,安全、公钥可以网络传输等。但是加密速度很慢,不会用来加密上KB的数据。 关于RAS算法,可以说的内容太多太多,比如RSR的数学原理、公钥加密私钥解密、私钥签名公钥验签、公私钥生成、大文件如何加密、秘钥如何管理等问题。这些我都会在这个安全系列的其他文章中详细说明的。这一期以代码展示为主,就不过多在这里说了。 4.1 RSA 这里需要注意的是,加密使用的是对方的公钥,解密使用的是对应的私钥。 package com.wuxiaolong.EncrypteDecrypt; import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map; /** * Description: * * @author 诸葛小猿 * @date 2020-07-24 */ public class RSAEncrypt { //用于封装随机产生的公钥与私钥 private static Map public static void main(String[] args) throws Exception { //生成公钥和私钥 genKeyPair(); //加密字符串 String message = "我爱你"; System.out.println("随机生成的公钥为:" + keyMap.get(0)); System.out.println("随机生成的私钥为:" + keyMap.get(1)); String messageEn = encrypt(message,keyMap.get(0)); System.out.println(message + "\t加密后的字符串为:" + messageEn); String messageDe = decrypt(messageEn,keyMap.get(1)); System.out.println("还原后的字符串为:" + messageDe); } /** * 随机生成密钥对 * @throws Exception */ public static void genKeyPair() throws Exception { // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象 KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); // 初始化密钥对生成器,密钥大小为 keyPairGen.initialize(2048,new SecureRandom()); // 生成一个密钥对,保存在keyPair中 KeyPair keyPair = keyPairGen.generateKeyPair(); // 得到私钥、公钥 RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到私钥字符串 String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded())); String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded()))); // 将公钥和私钥保存到Map 0表示公钥 1表示私钥 keyMap.put(0,publicKeyString); keyMap.put(1,privateKeyString); } /** * RSA公钥加密 * @param str 加密字符串 * @param publicKey 公钥 * @return 密文 * @throws Exception 加密过程中的异常信息 */ public static String encrypt( String str, String publicKey ) throws Exception{ //base64编码的公钥 byte[] decoded = Base64.decodeBase64(publicKey); RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded)); //RSA加密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, pubKey); String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8"))); return outStr; } /** * RSA私钥解密 * @param str 加密字符串 * @param privateKey 私钥 * @return 明文 * @throws Exception 解密过程中的异常信息 */ public static String decrypt(String str, String privateKey) throws Exception{ //64位解码加密后的字符串 byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8")); //base64编码的私钥 byte[] decoded = Base64.decodeBase64(privateKey); RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded)); //RSA解密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, priKey); String outStr = new String(cipher.doFinal(inputByte)); return outStr; } } 代码运行的结果: 随机生成的公钥为: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgJW1vVhZWd8NDiTIrAJK5N4EipP5jWw8WIsWiX3SzZGpzRuz5duRjhubxS2kOoGP6GnOI8KMAHwcFCjtgQNLfvoufHG9OiRaKQEkPhypF1vsuEC0rzeOcWbzIAsWk7B6wboGd6Or4L2MAHsIrluISgICq6BU1cVb/XSPX9tbIOfrjRNsbX5DnNd39XZ4yUlqIDocCQtV3rmQlG4e4nlsJTw073/7/eNCkp7FMJVTch+rQspUPlks9V1ic9TmBhW7dszAGWz4BmIe1elar5bapivBtnVpRX+yEGZSQWkchTqSQTOeYfuh3GFDAYoWlGEx+OeUX8SsRK1V3zeiwv6lpQIDAQAB 随机生成的私钥为: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCAlbW9WFlZ3w0OJMisAkrk3gSKk/mNbDxYixaJfdLNkanNG7Pl25GOG5vFLaQ6gY/oac4jwowAfBwUKO2BA0t++i58cb06JFopASQ+HKkXW+y4QLSvN45xZvMgCxaTsHrBugZ3o6vgvYwAewiuW4hKAgKroFTVxVv9dI9f21sg5+uNE2xtfkOc13f1dnjJSWogOhwJC1XeuZCUbh7ieWwlPDTvf/v940KSnsUwlVNyH6tCylQ+WSz1XWJz1OYGFbt2zMAZbPgGYh7V6VqvltqmK8G2dWlFf7IQZlJBaRyFOpJBM55h+6HcYUMBihaUYTH455RfxKxErVXfN6LC/qWlAgMBAAECggEAe+FAFWpfsuDcsAqqNmWDCBoJoATOAP0M6nUdwlqxVBGI2K1e1Q2Dnrhki0Pcm+0k2tHMotEUlob7ekSwBIJLIssfLA9cMf7Byg1qgFiWY4XRevYD3WcV2ZVImE92cdUtfySchHjv53ZVwkTGaUyP8lUbg4PVF5qrdHTuiHhJxFmqLwB6DC7HM9X2jleYJaNSZ2UwiloqkHAMDhqAu9212qZ4ISjbOg/iHDeTRKG3wIX989rsxAVSsruQm3DMZ/jeXzx8MpNjiLaT448dDSOhjhb1tOycMI7CWhYsuT7fOSFmtCtb9E3L8AVarKD2eiOqMaXj28wiuoST7lxa26hHoQKBgQDSmLPYHLXgOfJPIjuYv3IrBv9wr6H9N5FnSKYfISo8gQZJj4YWVFsc9/9vQyOOk0VvtSd28z0dixo1P4axEFe84vwVUsbgo7797nFLZWFm6tZZLgDiSgcxLhz9/5cjnY7irZMdPqKWQKkcFMm4Km8JX8BipwKVwuDiZ74enE1viQKBgQCcTpi0IxOaQQXWaDjG50MBBUIU0F1vM69apsJKeIY8XgE9Vsz/4yqwW7enIZHY15yx4btmZ/1ieOTqRI3x6SwFcgRZQD8OiKf/6WpHp7hXmTE34QJd9CPq+c8MgImd9BAX4rsdKp9pOGsTjFcAvLkU1crk/JQdht+p6VErJUwCPQKBgHLcFjqobgn9kMrYQOjugbY1+tva7t7Mj+FlHjWcQQz+0g8M2HUVTRxfplForNv2NsjWZM/bmlmipIimPTAVWcULh7GZlB6xMFoO0nvsr1MSghXhoVnKRmHsZKOj4yrppCS9xp8MqmCIo7NNIzfu7OCP+L3VmPNVdpIFQwwu840BAoGAUJx8RpK8flZAc3YmqfYE6VbT4LYhKcOHEQJlu1BzMljs3LySVvnKP0/d5FT8yc9Q4bBgHI1O8WUV8ffPGM6/REOsGHd4zb8OnX28sR2/hXXdG0txFBFgIMQa0wDLeGmxjxAAnicoNXTCTD+Zcyjhbuxfij71CFFRsmhA0zaV/6UCgYEAua9INHaJQ5VIFEyAWMHhfwQOU00BynQP0MugNwPypH9qejEzzBJ2fq8j69fxfeG2EENIiDymZMPwHybpYcipKT1aksCPu95HF1+yR6lNrSFDzn/kQu06bgB3V61wsgsKeDuDh++z381m1BONfK3MS1+BlxR/pJ59ikBLyB15tiQ= 我爱你 加密后的字符串为: Qo2qkCPF27rfbfAV8k3uLH4ogVA8BlnhtKEvL77KH6wZQKmzrq3z794TuWU9PbJpkYLD2tALt6t0y+Rr9rpPHCqtAGXkzDddu7get/8eNs4N3i8yoOYW2ui7yny7v5cVd8ZsxSHcszmaSV+wb+5AJcMzQhOvWaRwx1WVEOkDp/qBENGFdfPJ8gecbL7YgnqzUtccaJQomQWIlXk88mAU8nAALHO/goEI2CK1HRtb+9qyQgjHU1zFhBsli5yA8NDcqiczFb/nV90yKLMhn5DpIm3gU/qquQltfnkflSk+KXe5BFThIVxcu2FBIovOaX+s82L4DJXtRZS+aCSrHOPfSg== 还原后的字符串为:我爱你 加密解密使用的秘钥和签名验签的秘钥使用不一样,参考这个系列的其他文章。 4.2 非对称加密的特点 工作特点: 用一个密钥(公钥)加密的数据,只能用另一个密钥(私钥)来解密。 一个密钥(私钥)用来签名,一个密钥(公钥)用来验签。 优点: 由于不必担心交换的公钥被劫持,所以非对称密钥的分发更安全。 密钥数目和参与者数目相同。 在交换公钥之前,不需要预先建立某种信任关系。 支持数字签名和不可否认性。 缺点: 加密速度很慢。要不用来加密上KB的数据,可以用来加密key,key的数据量较小。 加密后,密文会变长。