对称加密算法
对称加密指的就是加密和解密使用同一个秘钥,所以叫做对称加密。对称加密只有一个秘钥,作为私钥。具体的算法有:DES、3DES、TDEA、Blowfish,RC5,IDEA。但是我们常见的有:DES、AES等等。
那么对称加密的优点是什么呢?
算法公开、计算量小、加密速度快、加密效率高。缺点
就是秘钥的管理和分发是非常困难的,不够安全。在数据传送前,发送方和接收方必须商定好秘钥,然后双方都必须要保存好秘钥,如果一方的秘钥被泄露了,那么加密的信息也就不安全了。另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的唯一秘钥,这会使得收、发双方所拥有的的钥匙数量巨大,秘钥管理也会成为双方的负担。
简单理解
加密:原文+秘钥 = 密文
解密:密文-秘钥 = 原文
可以看到两次过程使用的都是一个秘钥
》》》 DES
DES 加密算法是一种分组密码,以 64 位为分组对数据加密,它的密钥长度是 56 位,加密解密用同一算法。DES 加密算法是对密钥进行保密,而公开算法,包括加密和解密算法。这样,只有掌握了和发送方相同密钥的人才能解读由 DES 加密算法加密的密文数据。因此,破译 DES 加密算法实际上就是搜索密钥的编码。对于 56 位长度的密钥来说,如果用穷举法来进行搜索的话,其运算次数为 2 的 56 次方。接下来用 Java 实现 DES 加密:
private final static String DES = "DES";
public static void main(String[] args) throws Exception {
String data = "123 456";
String key = "wang!@#$";
System.err.println(encrypt(data, key));
System.err.println(decrypt(encrypt(data, key), key));
}
/**
* BASE64Encoder 加密
*
* @param data
* 要加密的数据
* @return 加密后的字符串
*/
public static String encryptBASE64(byte[] data) {
// BASE64Encoder encoder = new BASE64Encoder();
// String encode = encoder.encode(data);
// 从JKD 9开始rt.jar包已废除,从JDK 1.8开始使用java.util.Base64.Encoder
Base64.Encoder encoder = Base64.getEncoder();
String encode = encoder.encodeToString(data);
return encode;
}
/**
* BASE64Decoder 解密
*
* @param data
* 要解密的字符串
* @return 解密后的byte[]
* @throws Exception
*/
public static byte[] decryptBASE64(String data) throws Exception {
// BASE64Decoder decoder = new BASE64Decoder();
// byte[] buffer = decoder.decodeBuffer(data);
// 从JKD 9开始rt.jar包已废除,从JDK 1.8开始使用java.util.Base64.Decoder
Base64.Decoder decoder = Base64.getDecoder();
byte[] buffer = decoder.decode(data);
return buffer;
}
/**
* Description 根据键值进行加密
* @param data
* @param key 加密键byte数组
* @return
* @throws Exception
*/
public static String encrypt(String data, String key) throws Exception {
byte[] bt = encrypt(data.getBytes(), key.getBytes());
String strs = encryptBASE64(bt);
return strs;
}
/**
* Description 根据键值进行解密
* @param data
* @param key 加密键byte数组
* @return
* @throws IOException
* @throws Exception
*/
public static String decrypt(String data, String key) throws IOException, Exception {
if (data == null)
return null;
byte[] buf = decryptBASE64(data);
byte[] bt = decrypt(buf,key.getBytes());
return new String(bt);
}
/**
* Description 根据键值进行加密
* @param data
* @param key 加密键byte数组
* @return
* @throws Exception
*/
private static byte[] encrypt(byte[] data, byte[] key) throws Exception {
// 生成一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 从原始密钥数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance(DES);
// 用密钥初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
return cipher.doFinal(data);
}
/**
* Description 根据键值进行解密
* @param data
* @param key 加密键byte数组
* @return
* @throws Exception
*/
private static byte[] decrypt(byte[] data, byte[] key) throws Exception {
// 生成一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 从原始密钥数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance(DES);
// 用密钥初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
return cipher.doFinal(data);
}
在 Java 中用 DES 加密有一个特殊的地方:
- 秘钥设置的长度必须大于等于 8
- 秘钥设置的长度如果大于 8 的话,那么只会取前 8 个字节作为秘钥
为什么呢,我们可以看到在初始化 DESKeySpec 类的时候有下面一段,其中 var1 是我们传的秘钥。可以看到他进行了截取。只截取前八个字节。
》》》AES
AES 加密算法是密码学中的高级加密标准,该加密算法采用对称分组密码体制,密钥长度的最少支持为 128、192、256,分组长度 128 位,算法应易于各种硬件和软件实现。这种加密算法是美国联邦政府采用的区块加密标准,AES 标准用来替代原先的 DES,已经被多方分析且广为全世界所使用。
JCE,Java Cryptography Extension,在早期 JDK 版本中,由于受美国的密码出口条例约束,Java 中涉及加解密功能的 API 被限制出口,所以 Java 中安全组件被分成了两部分: 不含加密功能的 JCA(Java Cryptography Architecture )和含加密功能的 JCE(Java Cryptography Extension)。
JCE 的 API 都在 javax.crypto 包下,核心功能包括:加解密、密钥生成(对称)、MAC生成、密钥协商。
加解密功能由 Cipher 组件提供,其也是 JCE 中最核心的组件。在设置 Cipher 类的时候有几个注意点
-
Cipher 在使用时需以参数方式指定 transformation
-
transformation 的格式为 algorithm/mode/padding,其中algorithm 为必输项,如:AES/DES/CBC/PKCS5Padding,具体有哪些可看下表
-
缺省的 mode 为 ECB,缺省的 padding 为 PKCS5Padding
-
在 block 算法与流加密模式组合时,需在 mode 后面指定每次处理的 bit 数,如 DES/CFB8/NoPadding,如未指定则使用缺省值,SunJCE 缺省值为 64bits
-
Cipher 有 4 种操作模式:ENCRYPT_MODE(加密)、DECRYPT_MODE(解密)、WRAP_MODE(导出Key)、UNWRAP_MODE(导入Key),初始化时需指定某种操作模式
image.png
秘钥的可以由我们自己定义,也可以是由 AES 自己生成,当自己定义是需要是要注意:
-
根据 AES 规范,可以是 16 字节、24 字节和32 字节长,分别对应 128 位、192 位和 256 位;
-
为便于传输,一般对加密后的数据进行 base64 编码;
public static void main(String[] args) throws Exception {
/*
* 此处使用AES-128-ECB加密模式,key需要为16位。
*/
String cKey = "1234567890123456";
// 需要加密的字串
String cSrc = "buxuewushu";
System.out.println(cSrc);
// 加密
String enString = Encrypt(cSrc, cKey);
System.out.println("加密后的字串是:" + enString);
// 解密
String DeString = Decrypt(enString, cKey);
System.out.println("解密后的字串是:" + DeString);
}
// 加密
public static String Encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
return encryptBASE64(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}
/**
* BASE64Encoder 加密
*
* @param data
* 要加密的数据
* @return 加密后的字符串
*/
public static String encryptBASE64(byte[] data) {
// BASE64Encoder encoder = new BASE64Encoder();
// String encode = encoder.encode(data);
// 从JKD 9开始rt.jar包已废除,从JDK 1.8开始使用java.util.Base64.Encoder
Base64.Encoder encoder = Base64.getEncoder();
String encode = encoder.encodeToString(data);
return encode;
}
/**
* BASE64Decoder 解密
*
* @param data
* 要解密的字符串
* @return 解密后的byte[]
* @throws Exception
*/
public static byte[] decryptBASE64(String data) throws Exception {
// BASE64Decoder decoder = new BASE64Decoder();
// byte[] buffer = decoder.decodeBuffer(data);
// 从JKD 9开始rt.jar包已废除,从JDK 1.8开始使用java.util.Base64.Decoder
Base64.Decoder decoder = Base64.getDecoder();
byte[] buffer = decoder.decode(data);
return buffer;
}
// 解密
public static String Decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted1 = decryptBASE64(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original,"utf-8");
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
非对称加密算法
非对称加密算法中加密和解密用的不是同一个秘钥,所以叫作非对称加密算法。在非对称加密算法每个用户都有两把钥匙,一把公钥一把私钥。公钥是对外发布的,所有人都看的到所有人的公钥,私钥是自己保存,每个人都只知道自己的私钥而不知道别人的。而也正是在非对称加密算法中有加密和解密、加签和验签的概念。接下来我们解释一下这几个概念是什么意思。
加密和解密
用该用户的公钥加密后只能该用户的私钥才能解密。这种情况下,公钥是用来加密信息的,确保只有特定的人(用谁的公钥就是谁)才能解密该信息。所以这种我们称之为加密和解密
首先小明发了一条信息给A银行“我要存500元”。这条信息小明会根据 A 银行的对外发布的公钥把这条信息加密了,加密之后,变成“XXXXXXX”发给 A 银行。中间被第三者截获,由于没有 A 银行的私钥无法解密,不能知道信息的含义,也无法按正确的方式篡改。所以拿这条加密信息是没办法的。最后被 A 银行接受,A 银行用自己的私钥去解密这条信息,解密成功,读取内容,执行操作。然后得知消息是小明发来的,便去拿小明的公钥,把“操作成功(或失败)”这条信息用小明的公钥加密,发给小明。同理最后小明用自己的私钥解开,得知发来的信息内容。其他人截获因为没有小明的私钥所以也没有用
加签和验签
还有第二种情况,公钥是用来解密信息的,确保让别人知道这条信息是真的由我发布的,是完整正确的。接收者由此可知这条信息确实来自于拥有私钥的某人,这被称作数字签名,公钥的形式就是数字证书。所以这种我们称之为加签和验签。
银行 A 发布了一个银行客户端的补丁供所有用户更新,那为了确保人家下载的是正确完整的客户端,银行 A 会为这个程序打上一个数字签名(就是用银行 A 的私钥对这个程序加密然后发布),你需要在你的电脑里装上银行A的数字证书(就是银行对外发布的公钥),然后下载好这个程序,数字证书会去解密这个程序的数字签名,解密成功,补丁得以使用。同时你能知道这个补丁确实是来自这个银行 A,是由他发布的,而不是其他人发布的。
》》》 RSA
我们在开发过程中经常使用的非对称加密算法就是 RSA 算法。接下来我们使用 Java 实现 RSA 算法。
生成密钥
首先是生成 key 的部分,生成 key 有好多种做法
- 命令行:可以使用 openssl 进行生成公钥和私钥
openssl genrsa -out key.pem 1024
-out 指定生成文件,此文件包含公钥和私钥两部分,所以即可以加密,也可以解密
1024 生成密钥的长度
- 使用网站
- 代码:可以指定生成密钥的长度,最低是512
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
加密
有了密钥,就可以进行加密的操作了,接下来就介绍关于 RSA 的加密操作,非常简单只要传进来公钥和需要加密的数据即可
public static byte[] encrypt(PublicKey publicKey, String message) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(message.getBytes(UTF8));
}
解密
public static byte[] decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encrypted);
}
加签
/**
* 使用RSA签名
*/
private static String signWithRSA(String content, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(privateKey);
signature.update(content.getBytes("utf-8"));
byte[] signed = signature.sign();
return base64Encode(signed);
}
验签
/**
* 使用RSA验签
*/
private static boolean checkSignWithRSA(String content, PublicKey publicKey,String sign) throws Exception {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes("utf-8"));
return signature.verify(base64Decode(sign));
}
在加签验签的时候需要传入一个数字签名标准,我们这里填的是 SHA1WithRSA,它的意思是用 SHA 算法进行签名,用 RSA 算法进行加密。
算法说明:在对进行 SHA1 算法进行摘要计算后,要求对计算出的摘要进行处理,而不是直接进行 RSA 算法进行加密。要求把 SHA1 摘要的数据进行压缩到 20 个字节。在前面插入 15 个字节标示数据。
最后进行 RSA 加密。所以我们填写的 XXXWithRSA,这个 XXX 代表的就是使用什么摘要算法进行加签,至于摘要算法是什么,随后会有详细的说明。调用实验一下:
public static void main(String[] args) throws Exception {
KeyPair keyPair = buildKeyPair();
byte[] encryptData = encrypt(keyPair.getPublic(), "不学无数");
System.out.println(String.format("加密后的数据:%s",base64Encode(encryptData)));
System.out.println(String.format("解密后的数据:%s",new String(decrypt(keyPair.getPrivate(),encryptData),UTF8)));
String context = "加签的字符串";
String sign = signWithRSA(context, keyPair.getPrivate());
System.out.println(String.format("生成的签名:%s",sign));
Boolean checkSignWithRSA = checkSignWithRSA(context, keyPair.getPublic(), sign);
System.out.println(String.format("校验的结果:%s",checkSignWithRSA.toString()));
}
串起来
public class SecurityRSA {
public static final String RSA_ALGORITHM = "RSA";
public static final String UTF8 = "UTF-8";
public static void main(String[] args) throws Exception {
KeyPair keyPair = buildKeyPair();
byte[] encryptData = encrypt(keyPair.getPublic(), "你学废了吗");
System.out.println(String.format("加密后的数据:%s",encryptBASE64(encryptData)));
System.out.println(String.format("解密后的数据:%s",new String(decrypt(keyPair.getPrivate(),encryptData),UTF8)));
String context = "加签的字符串";
String sign = signWithRSA(context, keyPair.getPrivate());
System.out.println(String.format("生成的签名:%s",sign));
Boolean checkSignWithRSA = checkSignWithRSA(context, keyPair.getPublic(), sign);
System.out.println(String.format("校验的结果:%s",checkSignWithRSA.toString()));
}
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
public static byte[] encrypt(PublicKey publicKey, String message) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(message.getBytes(UTF8));
}
public static byte[] decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encrypted);
}
/**
* 使用RSA签名
*/
private static String signWithRSA(String content, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(privateKey);
signature.update(content.getBytes("utf-8"));
byte[] signed = signature.sign();
return encryptBASE64(signed);
}
/**
* BASE64Encoder 加密
*
* @param data
* 要加密的数据
* @return 加密后的字符串
*/
public static String encryptBASE64(byte[] data) {
// BASE64Encoder encoder = new BASE64Encoder();
// String encode = encoder.encode(data);
// 从JKD 9开始rt.jar包已废除,从JDK 1.8开始使用java.util.Base64.Encoder
Base64.Encoder encoder = Base64.getEncoder();
String encode = encoder.encodeToString(data);
return encode;
}
/**
* BASE64Decoder 解密
*
* @param data
* 要解密的字符串
* @return 解密后的byte[]
* @throws Exception
*/
public static byte[] decryptBASE64(String data) throws Exception {
// BASE64Decoder decoder = new BASE64Decoder();
// byte[] buffer = decoder.decodeBuffer(data);
// 从JKD 9开始rt.jar包已废除,从JDK 1.8开始使用java.util.Base64.Decoder
Base64.Decoder decoder = Base64.getDecoder();
byte[] buffer = decoder.decode(data);
return buffer;
}
/**
* 使用RSA验签
*/
private static boolean checkSignWithRSA(String content, PublicKey publicKey,String sign) throws Exception {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes("utf-8"));
return signature.verify(decryptBASE64(sign));
}
}
摘要算法
数据摘要算法是密码学算法中非常重要的一个分支,它通过对所有数据提取指纹信息以实现数据签名、数据完整性校验等功能,由于其不可逆性,有时候会被用做敏感信息的加密。数据摘要算法也被称为哈希(Hash)算法或散列算法。
消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。(摘要可以比方为指纹,消息摘要算法就是要得到文件的唯一职位)
特点
无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。一般地,只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息(不可逆性)。
好的摘要算法,没有人能从中找到“碰撞”或者说极度难找到,虽然“碰撞”是肯定存在的(碰撞即不同的内容产生相同的摘要)。
应用
一般地,把对一个信息的摘要称为该消息的指纹或数字签名。数字签名是保证信息的完整性和不可否认性的方法。数据的完整性是指信宿接收到的消息一定是信源发送的信息,而中间绝无任何更改;信息的不可否认性是指信源不能否认曾经发送过的信息。其实,通过数字签名还能实现对信源的身份识别(认证),即确定“信源”是否是信宿意定的通信伙伴。 数字签名应该具有唯一性,即不同的消息的签名是不一样的;同时还应具有不可伪造性,即不可能找到另一个消息,使其签名与已有的消息的签名一样;还应具有不可逆性,即无法根据签名还原被签名的消息的任何信息。这些特征恰恰都是消息摘要算法的特征,所以消息摘要算法适合作为数字签名算法。
有哪些具体的消息摘要算法?
CRC8、CRC16、CRC32:CRC(Cyclic Redundancy Check,循环冗余校验)算法出现时间较长,应用也十分广泛,尤其是通讯领域,现在应用最多的就是 CRC32 算法,它产生一个4字节(32位)的校验值,一般是以8位十六进制数,如FA 12 CD 45等。CRC算法的优点在于简便、速度快,严格的来说,CRC更应该被称为数据校验算法,但其功能与数据摘要算法类似,因此也作为测试的可选算法。
MD2 、MD4、MD5:这是应用非常广泛的一个算法家族,尤其是 MD5(Message-Digest Algorithm 5,消息摘要算法版本5),它由MD2、MD3、MD4发展而来,由Ron Rivest(RSA公司)在1992年提出,目前被广泛应用于数据完整性校验、数据(消息)摘要、数据加密等。MD2、MD4、MD5 都产生16字节(128位)的校验值,一般用32位十六进制数表示。MD2的算法较慢但相对安全,MD4速度很快,但安全性下降,MD5比MD4更安全、速度更快。 SHA1、SHA256、SHA384、SHA512:SHA(Secure Hash Algorithm)是由美国专门制定密码算法的标准机构——美国国家标准技术研究院(NIST)制定的,SHA系列算法的摘要长度分别为:SHA为20字节(160位)、SHA256为32字节(256位)、 SHA384为48字节(384位)、SHA512为64字节(512位),由于它产生的数据摘要的长度更长,因此更难以发生碰撞,因此也更为安全,它是未来数据摘要算法的发展方向。由于SHA系列算法的数据摘要长度较长,因此其运算速度与MD5相比,也相对较慢。
RIPEMD、PANAMA、TIGER、ADLER32 等:RIPEMD是Hans Dobbertin等3人在对MD4,MD5缺陷分析基础上,于1996年提出来的,有4个标准128、160、256和320,其对应输出长度分别为16字节、20字节、32字节和40字节。TIGER由Ross在1995年提出。Tiger号称是最快的Hash算法,专门为64位机器做了优化。
实例
在单独的使用摘要算法时我们通常使用的 MD5 算法,所以我们这里就单独说明使用 Java 实现 MD5 算法。
public class SecurityMD5 {
public static void main(String[] args) throws Exception {
String pass = "你学废了吗,原来如此";
String result = getMD5Str(pass);
System.out.println(result);
}
public static String getMD5Str(String str) throws Exception {
try {
// 生成一个MD5加密计算摘要
MessageDigest md = MessageDigest.getInstance("MD5");
// 计算md5函数
md.update(str.getBytes());
// digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
// BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
return new BigInteger(1, md .digest()).toString(16);
} catch (Exception e) {
throw new Exception("MD5加密出现错误,"+e.toString());
}
}
}
网友评论