本文共 19384 字,大约阅读时间需要 64 分钟。
采用单钥密码的加密方法,同一个密钥可以同时用来加密和解密,这种加密方法称为对称加密,也称为单密钥加密。常用的对称加密算法:
算法特征:
将任意长度的信息转换为较短的固定长度的值,通常其长度要比信息小得多,且算法不可逆。常见的哈希算法有:
算法特征:
① 输入一样,输出必然相同;
② 雪崩效应,输入的微小改变,将会引起结果的巨大变化; ③ 定长输出,无论原始数据多大,结果大小都是相同的; ④ 不可逆,无法根据特征码还原原来的数据;非对称密钥加密也称为公钥加密,由一对公钥和私钥组成。公钥是从私钥提取出来的。可以用公钥加密,再用私钥解密,这种情形一般用于公钥加密;也可以用私钥加密,用公钥解密,常用于数字签名,因此非对称加密的主要功能就是加密和数字签名。
特征:
秘钥对,公钥(public key)和私钥(secret key)
主要功能:加密和签名常用的非对称加密算法:
公钥加密和数字签名的简单图解
1.
鲍勃有两把钥匙,一把是公钥,另一把是私钥。
2.
鲍勃把公钥送给他的朋友们----帕蒂、道格、苏珊----每人一把。
3.
苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密,就可以达到保密的效果。
4.
鲍勃收信后,用私钥解密,就看到了信件内容。这里要强调的是,只要鲍勃的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。
5.
鲍勃给苏珊回信,决定采用"数字签名"。他写完后先用Hash函数,生成信件的摘要(digest)。
6.
然后,鲍勃使用私钥,对这个摘要加密,生成"数字签名"(signature)。
7.
鲍勃将这个签名,附在信件下面,一起发给苏珊。
8.
苏珊收信后,取下数字签名,用鲍勃的公钥解密,得到信件的摘要。由此证明,这封信确实是鲍勃发出的。
9.
苏珊再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。
10.
复杂的情况出现了。道格想欺骗苏珊,他偷偷使用了苏珊的电脑,用自己的公钥换走了鲍勃的公钥。此时,苏珊实际拥有的是道格的公钥,但是还以为这是鲍勃的公钥。因此,道格就可以冒充鲍勃,用自己的私钥做成"数字签名",写信给苏珊,让苏珊用假的鲍勃公钥进行解密。
11.
后来,苏珊感觉不对劲,发现自己无法确定公钥是否真的属于鲍勃。她想到了一个办法,要求鲍勃去找"证书中心"(certificate authority,简称CA),为公钥做认证。证书中心用自己的私钥,对鲍勃的公钥和一些相关信息一起加密,生成"数字证书"(Digital Certificate)。
12.
鲍勃拿到数字证书以后,就可以放心了。以后再给苏珊写信,只要在签名的同时,再附上数字证书就行了。
13.
苏珊收信后,用CA的公钥解开数字证书,就可以拿到鲍勃真实的公钥了,然后就能证明"数字签名"是否真的是鲍勃签的。
14.
下面,我们看一个应用"数字证书"的实例:https协议。这个协议主要用于网页加密。
15.
首先,客户端向服务器发出加密请求。
16.
服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。
17.
客户端(浏览器)的"证书管理器",有"受信任的根证书颁发机构"列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。
18.
如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。
19.
如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告。
20.
如果数字证书是可靠的,客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。
再详细的Https协议的原理,将在后续的博客中进行介绍。
AESUtil
package com.yj.encrypt.util;import java.io.UnsupportedEncodingException;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.KeyGenerator;import javax.crypto.NoSuchPaddingException;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;public class AESUtil { /** * 加密 * * @param content * 需要加密的内容 * @param password * 加密密码 * @return */ public static String encrypt(String content, String password) { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128, new SecureRandom(password.getBytes())); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance("AES");// 创建密码器 byte[] byteContent = content.getBytes("utf-8"); cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化 byte[] result = cipher.doFinal(byteContent); String encryptResultStr = parseByte2HexStr(result); return encryptResultStr; // 加密 } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return null; } /** * 解密 * * @param content * 待解密内容 * @param password * 解密密钥 * @return */ public static String decrypt(String content, String password) { try { byte[] decryptFrom = parseHexStr2Byte(content); KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128, new SecureRandom(password.getBytes())); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance("AES");// 创建密码器 cipher.init(Cipher.DECRYPT_MODE, key);// 初始化 byte[] result = cipher.doFinal(decryptFrom); return new String(result); // 加密 } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException 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; }}
DESUtil
package com.yj.encrypt.util;import java.io.UnsupportedEncodingException;import java.security.SecureRandom;import java.util.Base64;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;public class DESUtil { /** * 加密 * * @param content * 需要加密的内容 * @param secretKey * 加密密码 * @return */ public static String encrypt(String content, String secretKey) { try { byte[] encryptionBytes = content.getBytes("UTF-8"); SecureRandom random = new SecureRandom(); DESKeySpec desKey = new DESKeySpec(buildDesKey(secretKey)); // 创建一个密钥工厂,然后用它把DESKeySpec转换成 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey securekey = keyFactory.generateSecret(desKey); // Cipher对象实际完成加密操作 Cipher cipher = Cipher.getInstance("DES"); // 用密钥初始化Cipher对象 cipher.init(Cipher.ENCRYPT_MODE, securekey, random); // 执行加密操作 byte[] encryptionBase64Bytes = Base64.getEncoder().encode(cipher.doFinal(encryptionBytes)); // 转换为字符串返回 return new String(encryptionBase64Bytes); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 解密 * * @param content * 需要解密的内容 * @param secretKey * 解密密码 * @return */ public static String decrypt(String content, String secretKey) { try { byte[] decryptionbytes = Base64.getDecoder().decode(content); // DES算法要求有一个可信任的随机数源 SecureRandom random = new SecureRandom(); // 创建一个DESKeySpec对象 DESKeySpec desKey = new DESKeySpec(buildDesKey(secretKey)); // 创建一个密钥工厂 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); // 将DESKeySpec对象转换成SecretKey对象 SecretKey securekey = keyFactory.generateSecret(desKey); // Cipher对象实际完成解密操作 Cipher cipher = Cipher.getInstance("DES"); // 用密钥初始化Cipher对象 cipher.init(Cipher.DECRYPT_MODE, securekey, random); // 开始解密操作 return new String(cipher.doFinal(decryptionbytes), "UTF-8"); } catch (Exception e) { e.printStackTrace(); } return null; } /* * 根据字符串生成密钥字节数组 * * @param keyStr 密钥字符串 * * @return * * @throws UnsupportedEncodingException */ public static byte[] buildDesKey(String keyStr) throws UnsupportedEncodingException { byte[] key = new byte[24]; // 声明一个24位的字节数组,默认里面都是0 byte[] temp = keyStr.getBytes("UTF-8"); // 将字符串转成字节数组 /* * 执行数组拷贝 System.arraycopy(源数组,从源数组哪里开始拷贝,目标数组,拷贝多少位) */ if (key.length > temp.length) { // 如果temp不够24位,则拷贝temp数组整个长度的内容到key数组中 System.arraycopy(temp, 0, key, 0, temp.length); } else { // 如果temp大于24位,则拷贝temp数组24个长度的内容到key数组中 System.arraycopy(temp, 0, key, 0, key.length); } return key; }}
DES3Util(3DES加解密工具类)
package com.yj.encrypt.util;import java.io.UnsupportedEncodingException;import java.util.Base64;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;public class DES3Util { // 定义加密算法,有DES、DESede(即3DES)、Blowfish private static final String Algorithm = "DESede"; /** * 加密 * * @param content * 需要加密的内容 * @param secretKey * 加密密码 * @return */ public static String encrypt(String content, String secretKey) { try { byte[] encryptionBytes = content.getBytes("UTF-8"); SecretKey deskey = new SecretKeySpec(build3DesKey(secretKey), Algorithm); // 生成密钥 Cipher c1 = Cipher.getInstance(Algorithm); // 实例化负责加密/解密的Cipher工具类 c1.init(Cipher.ENCRYPT_MODE, deskey); // 初始化为加密模式 return new String(Base64.getEncoder().encode(c1.doFinal(encryptionBytes))); } catch (java.security.NoSuchAlgorithmException e1) { e1.printStackTrace(); } catch (javax.crypto.NoSuchPaddingException e2) { e2.printStackTrace(); } catch (java.lang.Exception e3) { e3.printStackTrace(); } return null; } /** * 解密 * * @param content * 需要解密的内容 * @param secretKey * 解密密码 * @return */ public static String decrypt(String content, String secretKey) { try { byte[] decryptionbytes = Base64.getDecoder().decode(content); SecretKey deskey = new SecretKeySpec(build3DesKey(secretKey), Algorithm); Cipher c1 = Cipher.getInstance(Algorithm); c1.init(Cipher.DECRYPT_MODE, deskey); // 初始化为解密模式 return new String(c1.doFinal(decryptionbytes)); } catch (java.security.NoSuchAlgorithmException e1) { e1.printStackTrace(); } catch (javax.crypto.NoSuchPaddingException e2) { e2.printStackTrace(); } catch (java.lang.Exception e3) { e3.printStackTrace(); } return null; } /* * 根据字符串生成密钥字节数组 * * @param keyStr 密钥字符串 * * @return * * @throws UnsupportedEncodingException */ public static byte[] build3DesKey(String keyStr) throws UnsupportedEncodingException { byte[] key = new byte[24]; // 声明一个24位的字节数组,默认里面都是0 byte[] temp = keyStr.getBytes("UTF-8"); // 将字符串转成字节数组 /* * 执行数组拷贝 System.arraycopy(源数组,从源数组哪里开始拷贝,目标数组,拷贝多少位) */ if (key.length > temp.length) { // 如果temp不够24位,则拷贝temp数组整个长度的内容到key数组中 System.arraycopy(temp, 0, key, 0, temp.length); } else { // 如果temp大于24位,则拷贝temp数组24个长度的内容到key数组中 System.arraycopy(temp, 0, key, 0, key.length); } return key; }}
对称加密Test文件
package com.yj.encrypt;import org.junit.Test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.yj.encrypt.util.AESUtil;import com.yj.encrypt.util.DES3Util;import com.yj.encrypt.util.DESUtil;//对称加密public class SymmetricEncrypt { private Logger log = LoggerFactory.getLogger(this.getClass()); public static String content = "我的加密内容"; public static String secretKey = "mykey"; @Test public void DES() throws Exception { String result = DESUtil.encrypt(content, secretKey); log.info("DES加密结果:" + result); result = DESUtil.decrypt(result, secretKey); log.info("DES解密结果:" + result); } @Test public void DES3() throws Exception { String result = DES3Util.encrypt(content, secretKey); log.info("3DES加密结果:" + result); result = DES3Util.decrypt(result, secretKey); log.info("3DES解密结果:" + result); } @Test public void AES() throws Exception { String result = AESUtil.encrypt(content, secretKey); log.info("AES加密后:" + result); result = AESUtil.decrypt(result,secretKey); log.info("AES解密后:" + result); } // base不属于对称加密,暂时将它放在这里 // jdk实现,sun.misc包是Sun公司提供给内部使用的专用API,在java API文档中我们看不到任何有关BASE64影子,不建议使用 // 此处我们采用apache实现 @Test public void BASE64() throws Exception { String sourceStr = "123456"; org.apache.tomcat.util.codec.binary.Base64 base = new org.apache.tomcat.util.codec.binary.Base64(); String result = base.encodeAsString(sourceStr.getBytes("UTF-8")); log.info("BASE64加密结果:" + result); byte[] decodeByte = base.decode(result); result = new String(decodeByte); log.info("BASE64解密结果:" + result); }}
非对称加密Test文件
package com.yj.encrypt;import java.security.AlgorithmParameterGenerator;import java.security.AlgorithmParameters;import java.security.KeyFactory;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.PrivateKey;import java.security.PublicKey;import java.security.SecureRandom;import java.security.Security;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import javax.crypto.Cipher;import javax.crypto.spec.DHParameterSpec;import org.apache.tomcat.util.codec.binary.Base64;import org.bouncycastle.jce.provider.BouncyCastleProvider;import org.junit.Before;import org.junit.Test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;//非对称加密public class AsymmetricEncrypt { private Logger log = LoggerFactory.getLogger(this.getClass()); public static String content = "我的加密内容"; RSAPublicKey rsaPublicKey; RSAPrivateKey rsaPrivateKey; PublicKey elGamalPublicKey; PrivateKey elGamalPrivateKey; @Before public void init() throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(512);// 密钥长度为64的整数倍,最大是65536 KeyPair keyPair = keyPairGenerator.generateKeyPair(); rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); log.info("RSA公钥:" + Base64.encodeBase64String(rsaPublicKey.getEncoded())); log.info("RSA私钥:" + Base64.encodeBase64String(rsaPrivateKey.getEncoded())); log.info("==RSA结束==ELGamal开始=="); //JAVA7不支持,pom文件需引入BouncyCastle Security.addProvider(new BouncyCastleProvider()); AlgorithmParameterGenerator apg = AlgorithmParameterGenerator.getInstance("ElGamal"); apg.init(256); AlgorithmParameters params = apg.generateParameters(); DHParameterSpec dhParams = params.getParameterSpec(DHParameterSpec.class); KeyPairGenerator keyPairGene = KeyPairGenerator.getInstance("ElGamal"); keyPairGene.initialize(dhParams, new SecureRandom()); KeyPair ELGamalKeyPair = keyPairGene.generateKeyPair(); elGamalPublicKey = ELGamalKeyPair.getPublic(); elGamalPrivateKey = ELGamalKeyPair.getPrivate(); log.info("ELGamal公钥:" + Base64.encodeBase64String(elGamalPublicKey.getEncoded())); log.info("ELGamal私钥:" + Base64.encodeBase64String(elGamalPrivateKey.getEncoded())); } @Test public void RSA1() throws Exception { log.info("私钥加密,公钥解密"); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded()); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] result = cipher.doFinal(content.getBytes()); log.info("RSA私钥加密:" + Base64.encodeBase64String(result)); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded()); keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); cipher.init(Cipher.DECRYPT_MODE, publicKey); result = cipher.doFinal(result); log.info("RSA公钥解密:" + new String(result)); } @Test public void RSA2() throws Exception { log.info("公钥加密,私钥解密"); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded()); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] result = cipher.doFinal(content.getBytes()); log.info("RSA公钥加密:" + Base64.encodeBase64String(result)); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded()); keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); cipher.init(Cipher.DECRYPT_MODE, privateKey); result = cipher.doFinal(result); log.info("RSA私钥解密:" + new String(result)); } /* * 只有公钥加密,私钥解密 * */ @Test public void Elgamal() throws Exception { log.info("公钥加密,私钥解密"); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(elGamalPublicKey.getEncoded()); KeyFactory keyFactory = KeyFactory.getInstance("ELGamal"); PublicKey elGamalPublicKey = (PublicKey) keyFactory.generatePublic(x509EncodedKeySpec); Cipher cipher = Cipher.getInstance("ELGamal", "BC"); cipher.init(Cipher.ENCRYPT_MODE, elGamalPublicKey); byte[] result = cipher.doFinal(content.getBytes()); log.info("ELGamal公钥加密:" + Base64.encodeBase64String(result)); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(elGamalPrivateKey.getEncoded()); keyFactory = KeyFactory.getInstance("ELGamal"); PrivateKey elGamalPrivateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); cipher.init(Cipher.DECRYPT_MODE, elGamalPrivateKey); result = cipher.doFinal(result); log.info("ELGamal私钥解密:" + new String(result)); }}
Hash加密Test文件
package com.yj.encrypt;import java.security.MessageDigest;import org.junit.Test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;//HASH加密public class HashEncrypt { private Logger log = LoggerFactory.getLogger(this.getClass()); public static String content = "123456"; @Test public void MD5() throws Exception { String sourceStr = "123456"; String result; // 定义一个16进制char数组,取每一个byte前后四位对应char的十六进制数值 char HEX[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; byte[] sourceByte = sourceStr.getBytes("UTF-8"); // 获得MD5摘要算法的 MessageDigest 对象 MessageDigest messageDigest = MessageDigest.getInstance("MD5"); // 使用指定的字节更新摘要 messageDigest.update(sourceByte); // 获得密文 byte[] md = messageDigest.digest(); // 把密文转换成十六进制的字符串形式 int j = md.length; char buf[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; buf[k++] = HEX[byte0 >>> 4 & 0xf]; buf[k++] = HEX[byte0 & 0xf]; } result = new String(buf); log.info("MD5加密结果:" + result); } @Test public void SHA() throws Exception { String sourceStr = "123456"; String result; char HEX[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; MessageDigest messageDigest = MessageDigest.getInstance("SHA1"); messageDigest.update(sourceStr.getBytes("UTF-8")); byte[] md = messageDigest.digest(); int j = md.length; char buf[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; buf[k++] = HEX[byte0 >>> 4 & 0xf]; buf[k++] = HEX[byte0 & 0xf]; } result = new String(buf); log.info("SHA加密结果:" + result); }}
pom文件
4.0.0 com.yj Encrypt 0.0.1-SNAPSHOT jar Encrypt http://maven.apache.org UTF-8 org.springframework.boot spring-boot-starter-parent 1.5.17.RELEASE org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.bouncycastle bcprov-jdk15on 1.60