一.对称加密和非对称加密
密码体制有两种: 对称密码体制( 又称为单钥密码体制) 和非对称密码体制( 又称为双钥密码体制或公钥密码体制) 。对称密码体制使用相同的密钥( 秘密密钥) 对消息进行加密/解密,系统的保密性主要由密钥的安全性决定,而与算法是否保密无关。
对称密码体制设计和实现的中心是: 用何种方法产生满足保密要求的密钥以及用何种方法将密钥安全又可靠地分配给通信双方。对称密码体制可以通过分组密码或流密码来实现,它既可以用于数据加密,又可以用于消息认证。非对称密码体制使用公钥加密消息,使用私钥来解密。使用非对称密码体制可增强通信的安全性。
在密码学体系中,对称加密、非对称加密、单向散列函数、消息认证码、数字签名和伪随机数生成器被统称为密码学家的工具箱。其中,对称加密和非对称加密主要是用来保证机密性;单向散列函数用来保证消息的完整性;消息认证码的功能主要是认证;数字签名保证消息的不可抵赖性。
二.对称加密
对称加密又称但密钥加密,整个加密过程中只使用一个密钥。所谓对称其实就是使用一把密钥加密,使用同一把密钥解密。对称加密由于加解和解密使用的是同一个密钥算法,故而在加解密的过程中速度比较快,适合于数据量比较大的加解密。
对称加密的主要有优点就是算法公开、计算量小、加密速度快、加密效率高;但是它也存在强大的缺点,缺点就是密钥协商过程中,一旦密钥泄露,别人可以获取到密钥,这样也能对密文进行解密。另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的独一密钥,这会使得收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。
常用的对称加密算法有 DES、3DES(TripleDES)、AES、TDEA、Blowfish、RC2、RC4 和 RC5 等。
三.几种对称加密在Java中的使用
1.DES加密算法(ECB、无填充):
package amigo.endecrypt;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import org.apache.commons.codec.binary.Base64;
public class DESUtil {
//算法名称
public static final String KEY_ALGORITHM = "DES";
//算法名称/加密模式/填充方式
//DES共有四种工作模式-->>ECB:电子密码本模式、CBC:加密分组链接模式、CFB:加密反馈模式、OFB:输出反馈模式
public static final String CIPHER_ALGORITHM = "DES/ECB/NoPadding";
/**
*
* 生成密钥key对象
* @param KeyStr 密钥字符串
* @return 密钥对象
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws Exception
*/
private static SecretKey keyGenerator(String keyStr) throws Exception {
byte input[] = HexString2Bytes(keyStr);
DESKeySpec desKey = new DESKeySpec(input);
//创建一个密匙工厂,然后用它把DESKeySpec转换成
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(desKey);
return securekey;
}
private static int parse(char c) {
if (c >= 'a') return (c - 'a' + 10) & 0x0f;
if (c >= 'A') return (c - 'A' + 10) & 0x0f;
return (c - '0') & 0x0f;
}
// 从十六进制字符串到字节数组转换
public static byte[] HexString2Bytes(String hexstr) {
byte[] b = new byte[hexstr.length() / 2];
int j = 0;
for (int i = 0; i < b.length; i++) {
char c0 = hexstr.charAt(j++);
char c1 = hexstr.charAt(j++);
b[i] = (byte) ((parse(c0) << 4) | parse(c1));
}
return b;
}
/**
* 加密数据
* @param data 待加密数据
* @param key 密钥
* @return 加密后的数据
*/
public static String encrypt(String data, String key) throws Exception {
Key deskey = keyGenerator(key);
// 实例化Cipher对象,它用于完成实际的加密操作
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
SecureRandom random = new SecureRandom();
// 初始化Cipher对象,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, deskey, random);
byte[] results = cipher.doFinal(data.getBytes());
// 该部分是为了与加解密在线测试网站(http://tripledes.online-domain-tools.com/)的十六进制结果进行核对
for (int i = 0; i < results.length; i++) {
System.out.print(results[i] + " ");
}
System.out.println();
// 执行加密操作。加密后的结果通常都会用Base64编码进行传输
return Base64.encodeBase64String(results);
}
/**
* 解密数据
* @param data 待解密数据
* @param key 密钥
* @return 解密后的数据
*/
public static String decrypt(String data, String key) throws Exception {
Key deskey = keyGenerator(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
//初始化Cipher对象,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, deskey);
// 执行解密操作
return new String(cipher.doFinal(Base64.decodeBase64(data)));
}
public static void main(String[] args) throws Exception {
String source = "amigoxie";
System.out.println("原文: " + source);
String key = "A1B2C3D4E5F60708";
String encryptData = encrypt(source, key);
System.out.println("加密后: " + encryptData);
String decryptData = decrypt(encryptData, key);
System.out.println("解密后: " + decryptData);
}
}
2.3DES加密算法:
3DES的在Java的实现与DES类似,如下代码为3DES加密算法、CBC模式、NoPadding填充方式的加密解密结果,参考代码如下所示:
package amigo.endecrypt;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class ThreeDESUtil {
// 算法名称
public static final String KEY_ALGORITHM = "desede";
// 算法名称/加密模式/填充方式
public static final String CIPHER_ALGORITHM = "desede/CBC/NoPadding";
/**
* CBC加密
* @param key 密钥
* @param keyiv IV
* @param data 明文
* @return Base64编码的密文
* @throws Exception
*/
public static byte[] des3EncodeCBC(byte[] key, byte[] keyiv, byte[] data) throws Exception {
Security.addProvider(new BouncyCastleProvider());
Key deskey = keyGenerator(new String(key));
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ips = new IvParameterSpec(keyiv);
cipher.init(Cipher.ENCRYPT_MODE, deskey, ips);
byte[] bOut = cipher.doFinal(data);
for (int k = 0; k < bOut.length; k++) {
System.out.print(bOut[k] + " ");
}
System.out.println("");
return bOut;
}
/**
*
* 生成密钥key对象
* @param KeyStr 密钥字符串
* @return 密钥对象
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws Exception
*/
private static Key keyGenerator(String keyStr) throws Exception {
byte input[] = HexString2Bytes(keyStr);
DESedeKeySpec KeySpec = new DESedeKeySpec(input);
SecretKeyFactory KeyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
return ((Key) (KeyFactory.generateSecret(((java.security.spec.KeySpec) (KeySpec)))));
}
private static int parse(char c) {
if (c >= 'a') return (c - 'a' + 10) & 0x0f;
if (c >= 'A') return (c - 'A' + 10) & 0x0f;
return (c - '0') & 0x0f;
}
// 从十六进制字符串到字节数组转换
public static byte[] HexString2Bytes(String hexstr) {
byte[] b = new byte[hexstr.length() / 2];
int j = 0;
for (int i = 0; i < b.length; i++) {
char c0 = hexstr.charAt(j++);
char c1 = hexstr.charAt(j++);
b[i] = (byte) ((parse(c0) << 4) | parse(c1));
}
return b;
}
/**
* CBC解密
* @param key 密钥
* @param keyiv IV
* @param data Base64编码的密文
* @return 明文
* @throws Exception
*/
public static byte[] des3DecodeCBC(byte[] key, byte[] keyiv, byte[] data) throws Exception {
Key deskey = keyGenerator(new String(key));
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ips = new IvParameterSpec(keyiv);
cipher.init(Cipher.DECRYPT_MODE, deskey, ips);
byte[] bOut = cipher.doFinal(data);
return bOut;
}
public static void main(String[] args) throws Exception {
byte[] key = "6C4E60E55552386C759569836DC0F83869836DC0F838C0F7".getBytes();
byte[] keyiv = { 1, 2, 3, 4, 5, 6, 7, 8 };
byte[] data = "amigoxie".getBytes("UTF-8");
System.out.println("data.length=" + data.length);
System.out.println("CBC加密解密");
byte[] str5 = des3EncodeCBC(key, keyiv, data);
System.out.println(new sun.misc.BASE64Encoder().encode(str5));
byte[] str6 = des3DecodeCBC(key, keyiv, str5);
System.out.println(new String(str6, "UTF-8"));
}
}
3.AES加密算法:
1).AES加解密
首先定义加密、解密工具类
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
/**
* AES加解密工具类
*/
public class AES {
/**
* AES加密
*
* @param key
* @param iv
* @throws GeneralSecurityException
* @throws IOException
*/
public static byte[] encryptAes(byte[] data, byte[] key, byte[] iv)
throws GeneralSecurityException, IOException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
return cipher.doFinal(data);
}
/**
* AES解密
*
* @param key
* @param iv
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static byte[] decryptAesToByteString(byte[] data, byte[] key, byte[] iv)
throws GeneralSecurityException, IOException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
return cipher.doFinal(data);
}
}
加密和解密的代码很像,唯一的不同点是,加密 Cipher.ENCRYPT_MODE
, 解密用的是 Cipher.DECRYPT_MODE
下面我们写一个测试代码:
try {
//加密密码
String key = "zhaoyanjunzhaoy1";
//偏移量
String iv = "1234567890123456";
String message = "今天是周二,我好开心";
//加密
byte[] encryResult = AES.encryptAes(message.getBytes(), key.getBytes(), iv.getBytes());
//解密
byte[] decryResult = AES.decryptAesToByteString(encryResult, key.getBytes(), iv.getBytes());
System.out.println("解密数据 = " + new String(decryResult));
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
输出结果:
解密数据 = 今天是周二,我好开心
2).AES默认实现类
不带模式和填充来获取AES算法的时候,其默认使用 AES/ECB/PKCS5Padding
(输入可以不是16字节,也不需要填充向量), 所以不需要偏移量参数
Cipher cipher = Cipher.getInstance("AES");
下面封装一个工具类:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
/**
* AES加解密工具类
*/
public class AES {
/**
* AES加密
*
* @param key
* @throws GeneralSecurityException
*/
public static byte[] encryptAes(byte[] data, byte[] key) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
return cipher.doFinal(data);
}
/**
* AES解密
*
* @param key
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static byte[] decryptAesToByteString(byte[] data, byte[] key)
throws GeneralSecurityException, IOException {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
return cipher.doFinal(data);
}
}
测试代码:
public class T2 {
public static void main(String[] args) {
try {
//加密密码
String key = "zhaoyanjunzhaoy1";
//加密正文
String message = "今天是周二,我好开心";
//加密
byte[] encryResult = AES.encryptAes(message.getBytes(), key.getBytes());
//解密
byte[] decryResult = AES.decryptAesToByteString(encryResult, key.getBytes());
System.out.println("解密数据 = " + new String(decryResult));
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
}
}
测试结果:
解密数据 = 今天是周二,我好开心
3).AES随机加密
在上面的例子中,我们在 AES 加密中,需要指定规定长度的密码,偏移量。在 Java 中还给我们提供了 KeyGenerator 类来随机生成一个密码和偏移量,解决了我们动脑想密码的问题。
我们来看看随机加密怎么用:
/**
* AES加密/解密
*
* @throws GeneralSecurityException
*/
public void encryptAes() throws GeneralSecurityException {
//原始数据
String message = "今天是周四,好开心哦";
byte[] data = message.getBytes();
//指定加密类型
KeyGenerator keygen = KeyGenerator.getInstance("AES");
//指定秘钥长度
keygen.init(256);
SecretKey secretKey = keygen.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
//获取秘钥
byte[] key = secretKey.getEncoded();
//获取偏移量
byte[] iv = cipher.getIV();
//解密数据
byte[] ciphertext = cipher.doFinal(data);
//解密数据
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
byte[] decryMessage = cipher.doFinal(ciphertext);
System.out.println("解密数据 = " + new String(decryMessage));
}
这种加密 key , iv 都是随机产生的,每次加密后的密文都不一样,适合一定的特殊场景。
随机是怎么发生的,我们就看:
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
在 init 方法的时候,JceSecurity.RANDOM
产生随机数,源码如下:
public final void init(int opmode, Key key) throws InvalidKeyException {
init(opmode, key, JceSecurity.RANDOM);
}
具体实现细节,感兴趣的话可以深入研究。