以下内容为《Java加密与解密的艺术》一书与慕课网“搞定Java加解密”的学习笔记,其中大部分程序来自网络。
Java加密与解密概要01
一些基础
- 科克霍夫原则:数据的安全基于密钥而不是算法的保密。即系统安全取决于密钥,算法公开,密钥保密。这也是现代密码学设计的基本原则;
- 分组密码:将要加密的内容分为固定长度的组,用同一密钥和算法对每一组加密;多用于网络加密;
- 流密码(序列密码):加密时每次加密一个字节,即逐位加密;
Base64
Base64原理
- Base64就是一种 基于64个可打印字符来表示二进制数据的表示方法;
字符选用了”A-Z、a-z、0-9、+、/“64个可打印字符。数值代表字符的索引,这个是标准Base64协议规定的,不能更改
Base64的实现:用4个Base64编码表示3个ASCII码字符,具体如下:
以上例子是基于内容可以被3整除的情况下,但如果内如无法被3整除,则需要补0;理论上Base64编码是4位1组,如果不够4位则以=补位(其实不补=也不影响解码,但=可以作为结束符号使用):
Base64在Java中的实现:Jdk、Commons Codec、Bouncy Castle;
- Base64的应用场景:email、中文或图片传输等;
Base64的JDK实现
package info.yuyublog.encryption;
import com.sun.org.apache.xml.internal.security.utils.Base64;
public class Base64Test {
public static void main(String[] args) throws Exception
{
String asB64 = Base64.encode("some string".getBytes("utf-8"));
System.out.println(asB64);
byte[] btarrB64 = Base64.decode(asB64);
String resB64 = new String(btarrB64);
System.out.println(resB64);
}
}
输出结果:
c29tZSBzdHJpbmc=
some string
消息摘要算法
- 特点:
- 无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。
- 一般地,只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。
- 只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息(不可逆性)。
- 分为MD(Message Digest),SHA(Secure Hash Algorithm),MAC(Message Authentication code)
MD算法(Message Digest)
- MD算法包含MD2、MD4、MD5等算法,MD5由MD4发展而来,MD4由MD2发展而来,现在常用的是MD5算法,不过由于密码学的发展,MD5的安全性也已大大下降;
- 有关MD5算法的原理与具体实现可以参考这篇博客。
- MD5一般用于传输文件的一致性验证(例如下载网站上附加的MD5)、数字证书、安全访问认证等(安全访问认证由于用户密码比较简单的话极易利用彩虹表破解,所以安全性较低,使用时最好使用加盐加密)。
MD5算法的JDK实现
package info.yuyublog.encryption;
import java.security.MessageDigest;
public class MD5Test {
public final static String MD5(String s) {
char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
try {
byte[] btInput = s.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
System.out.println(MD5Test.MD5("加密"));
}
}
输出结果:
56563EDF23B9D717DC63981B8836FC60
SHA算法(Secure Hash Algorithm)
- SHA算法严格意义上来说可以分为SHA-1、SHA-2、SHA-3三代算法,其中后缀的数字代表代数。SHA-1是第一代算法,目前谷歌已经宣告实现了对其的碰撞,安全性相对较低。SHA-2是第二代算法的统称,包含SHA-224、SHA-256、SHA-384、SHA-512四个算法。目前SHA-1与SHA-2这5个算法是比较常用的。
- SHA算法现在一般用于网站根证书(SHA-1即将淘汰但仍占有很大一部分,SHA-2是趋势)、加密数据传输等等。
SHA-256算法JDK实现
package info.yuyublog.encryption;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA256Test {
/**
* 利用java原生的摘要实现SHA256加密
* @param str 加密后的报文
* @return
*/
public static String getSHA256StrJava(String str){
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return encodeStr;
}
/**
* 将byte转为16进制
* @param bytes
* @return
*/
private static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i<bytes.length;i++){
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length()==1){
//1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
public static void main(String[] args) {
String src = new String("Hello world!");
String des = getSHA256StrJava(src);
System.out.println(des);
}
}
输出结果:
c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a
MAC算法(Message Authentication Code)
- 含有密钥散列函数算法,兼容了MD与SHA算分的特性,并在此基础上加入了密钥;
对称加密算法
加密密钥与解密密钥相同,加密算法是解密算法的逆运算。常用的有DES、AES与PBE.
DES算法(Data Encryption Standard)
DES算法的JDK实现
package info.yuyublog.encryption;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
public class DESTest {
public static byte[] desCrypto(byte[] datasource, String password) {
try {
SecureRandom random = new SecureRandom();
DESKeySpec desKey = new DESKeySpec(password.getBytes());
// 创建一个密匙工厂,然后用它把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);
// 获取数据并加密
return cipher.doFinal(datasource);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
private static byte[] decrypt(byte[] src, String password) throws Exception {
// DES算法要求有一个可信任的随机数源
SecureRandom random = new SecureRandom();
// 创建一个DESKeySpec对象
DESKeySpec desKey = new DESKeySpec(password.getBytes());
// 创建一个密匙工厂
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 cipher.doFinal(src);
}
private static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i<bytes.length;i++){
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length()==1){
//1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
public static void main(String[] args) throws Exception {
// 待加密内容
String str = "测试内容";
// 密码,长度要是8的倍数
String password = "12345678";
byte[] result = DESTest.desCrypto(str.getBytes(), password);
System.out.println("加密后内容为:" + byte2Hex(result));
// 直接将如上内容解密
try {
byte[] decryResult = DESTest.decrypt(result, password);
System.out.println("解密后内容为:" + new String(decryResult));
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
输出结果:
加密后内容为:a5d37c3d8502d503cc7b1eb62cef4b79
解密后内容为:测试内容
DESede
- DESede 即三重DES加密算法,也被称为3DES或者Triple DES。使用三(或两)个不同的密钥对数据块进行三次(或两次)DES加密(加密一次要比进行普通加密的三次要快)。三重DES的强度大约和112bit的密钥强度相当。通过迭代次数的提高了安全性,但同时也造成了加密效率低的问题。
- 到目前为止,还没有人给出攻击三重DES的有效方法。
- 三重DES有四种模型
- DES-EEE3,使用三个不同密钥,顺序进行三次加密变换。
- DES-EDE3,使用三个不同密钥,依次进行加密-解密-加密变换。
- DES-EEE2,其中密钥K1=K3,顺序进行三次加密变换。
- DES-EDE2, 其中密钥K1=K3,依次进行加密-解密-加密变换。
DESede JDK实现
package info.yuyublog.encryption;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
public class DESedeTest {
private static final String KEY_ALGORITHM = "DESede";
private static final String DEFAULT_CIPHER_ALGORITHM = "DESede/ECB/ISO10126Padding";
/**
* 初始化密钥
*
* @return byte[] 密钥
* @throws Exception
*/
public static byte[] initSecretKey() throws Exception{
//返回生成指定算法的秘密密钥的 KeyGenerator 对象
KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
//初始化此密钥生成器,使其具有确定的密钥大小
kg.init(168);
//生成一个密钥
SecretKey secretKey = kg.generateKey();
return secretKey.getEncoded();
}
/**
* 转换密钥
*
* @param key 二进制密钥
* @return Key 密钥
* @throws Exception
*/
private static Key toKey(byte[] key) throws Exception{
//实例化DES密钥规则
DESedeKeySpec dks = new DESedeKeySpec(key);
//实例化密钥工厂
SecretKeyFactory skf = SecretKeyFactory.getInstance(KEY_ALGORITHM);
//生成密钥
SecretKey secretKey = skf.generateSecret(dks);
return secretKey;
}
/**
* 加密
*
* @param data 待加密数据
* @param key 密钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encrypt(byte[] data,Key key) throws Exception{
return encrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
}
/**
* 加密
*
* @param data 待加密数据
* @param key 二进制密钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encrypt(byte[] data,byte[] key) throws Exception{
return encrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
}
/**
* 加密
*
* @param data 待加密数据
* @param key 二进制密钥
* @param cipherAlgorithm 加密算法/工作模式/填充方式
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encrypt(byte[] data,byte[] key,String cipherAlgorithm) throws Exception{
//还原密钥
Key k = toKey(key);
return encrypt(data, k, cipherAlgorithm);
}
/**
* 加密
*
* @param data 待加密数据
* @param key 密钥
* @param cipherAlgorithm 加密算法/工作模式/填充方式
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encrypt(byte[] data,Key key,String cipherAlgorithm) throws Exception{
//实例化
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
//使用密钥初始化,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, key);
//执行操作
return cipher.doFinal(data);
}
/**
* 解密
*
* @param data 待解密数据
* @param key 二进制密钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decrypt(byte[] data,byte[] key) throws Exception{
return decrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
}
/**
* 解密
*
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decrypt(byte[] data,Key key) throws Exception{
return decrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
}
/**
* 解密
*
* @param data 待解密数据
* @param key 二进制密钥
* @param cipherAlgorithm 加密算法/工作模式/填充方式
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decrypt(byte[] data,byte[] key,String cipherAlgorithm) throws Exception{
//还原密钥
Key k = toKey(key);
return decrypt(data, k, cipherAlgorithm);
}
/**
* 解密
*
* @param data 待解密数据
* @param key 密钥
* @param cipherAlgorithm 加密算法/工作模式/填充方式
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decrypt(byte[] data,Key key,String cipherAlgorithm) throws Exception{
//实例化
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, key);
//执行操作
return cipher.doFinal(data);
}
private static String showByteArray(byte[] data){
if(null == data){
return null;
}
StringBuilder sb = new StringBuilder("{");
for(byte b:data){
sb.append(b).append(",");
}
sb.deleteCharAt(sb.length()-1);
sb.append("}");
return sb.toString();
}
private static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i<bytes.length;i++){
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length()==1){
//1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
public static void main(String[] args) throws Exception {
byte[] key = initSecretKey();
System.out.println("key:"+ showByteArray(key));
Key k = toKey(key);
String data ="123456789";
System.out.println("加密前数据: string:"+data);
System.out.println("加密前数据: byte[]:"+showByteArray(data.getBytes()));
System.out.println();
byte[] encryptData = encrypt(data.getBytes(), k);
System.out.println("加密后数据: byte[]:"+showByteArray(encryptData));
System.out.println("加密后数据: hexStr:"+byte2Hex(encryptData));
System.out.println();
byte[] decryptData = decrypt(encryptData, k);
System.out.println("解密后数据: byte[]:"+showByteArray(decryptData));
System.out.println("解密后数据: string:"+new String(decryptData));
}
}
输出结果:
key:{50,-33,-50,35,26,-56,104,121,47,103,112,31,-88,69,-88,55,-22,50,-88,-110,-23,-101,82,-122}
加密前数据: string:123456789
加密前数据: byte[]:{49,50,51,52,53,54,55,56,57}
加密后数据: byte[]:{66,71,-7,21,-21,-34,72,55,67,75,-41,-122,117,92,96,-29}
加密后数据: hexStr:4247f915ebde4837434bd786755c60e3
解密后数据: byte[]:{49,50,51,52,53,54,55,56,57}
解密后数据: string:123456789
AES算法(Advanced Encryption Standard)
- 又称为Rijndael算法,由于DESede的低效与较慢的运算速度满足不了安全需求,AES应运而生。DES使用56位密钥,比较容易被破解,而AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据,所以安全性较高,目前已经变成目前对称加密中最流行算法之一;
- AES目前尚未有官方的破译报告;
- 原理参见这里;
- 现在常应用于Secure SHell等;
AES JDK实现
package info.yuyublog.encryption;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
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 AESTest {
/**
* 加密
*
* @param content 需要加密的内容
* @param key 加密密码
* @return
*/
public static byte[] encrypt(String content, String key) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new java.security.SecureRandom(key.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
byte[] byteContent = content.getBytes("UTF-8");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);// 初始化
byte[] result = cipher.doFinal(byteContent);
return result; // 加密
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
- 解密
-
- @param content 待解密内容
- @param key 解密密钥
- @return
*/
public static byte[] decrypt(byte[] content, String key) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new java.security.SecureRandom(key.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);// 初始化
byte[] result = cipher.doFinal(content);
return result; // 加密
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
- 字符串加密
-
- @param content 要加密的字符串
- @param key 加密的AES Key
- @return
*/
public static String encryptString(String content, String key) {
return parseByte2HexStr(encrypt(content, key));
}
/**
- 字符串解密
-
- @param content 要解密的字符串
- @param key 解密的AES Key
- @return
*/
public static String decryptString(String content, String key) {
byte[] decryptFrom = parseHexStr2Byte(content);
byte[] decryptResult = decrypt(decryptFrom, key);
return new String(decryptResult);
}
/**
- 将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;
}
/**
- 将二进制转换成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();
}
public static void main(String aregs[]) {
String content = "协议加密Test&123";
String key = "25d55ad283aa400af464c76d713c07ad";
try {
// 加密
System.out.println("加密前:" + content);
String encrypt = AESTest.encryptString(content, key);
System.out.println("加密后:" + encrypt);
// 解密
String decrypt = AESTest.decryptString(encrypt, key);
System.out.println("解密后:" + decrypt);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果:
加密前:协议加密Test&123
加密后:7D07155438880C51E50433C17D9AFF8DCB2A9596AF46B2ABA33C18918053E3E0
解密后:协议加密Test&123
PBE(Password Based Encryption)
- PBE算法是一种基于口令的加密算法,其特点在于口令是由用户自己掌握的,采用随机数杂凑多重加密等方法保证数据的安全性。
- PBE算法没有密钥的概念,密钥在其它对称加密算法中是经过算法计算得出来的,PBE算法则是使用口令替代了密钥。PBE算法并没有真正构建新的加密/解密算法,而是对我们已经知道的对称加密算法(如DES算法)做了包装。使用PBE算法对数据做加解密操作的时候,其实是使用了DES或者是AES等其它对称加密算法做了相应的操作。
- 由于仅仅使用口令十分不安全,所以PBE中引入了盐来提升安全性;
PBE JDK实现
package info.yuyublog.encryption;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class PBETest {
static final String KEY_ALGORITHM = "PBEWithMD5AndDES";
static byte[] salt = "hello123".getBytes(); // 盐:Salt must be 8 bytes long
static int iterationCount = 888; // 循环次数
static Cipher cipher;
public static void main(String[] args) throws Exception {
byte[] encrypt = encrypt("PBETest");
System.out.println("PBE加密后:" + Arrays.toString(encrypt));
System.out.println("PBE解密后:" + decrypt(encrypt));
}
/**
* 使用PBE 算法 加密
*
* @return 加密后的字符数组
* @throws Exception
*/
static byte[] encrypt(String str) throws Exception {
cipher = Cipher.getInstance(KEY_ALGORITHM);
// 使用SecretKeyFactory 生成key
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
PBEKeySpec keySpec = new PBEKeySpec("password".toCharArray());
SecretKey key = factory.generateSecret(keySpec);
System.out.println("key:" + Arrays.toString(key.getEncoded()));
cipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(salt, iterationCount));// 使用加密模式初始化
return cipher.doFinal(str.getBytes()); // 按单部分操作加密或解密数据,或者结束一个多部分操作。
}
/**
*
* @param encrypt
* @return
* @throws Exception
*/
static String decrypt(byte[] encrypt) throws Exception {
cipher = Cipher.getInstance(KEY_ALGORITHM);
// 使用SecretKeyFactory 生成key
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
PBEKeySpec keySpec = new PBEKeySpec("password".toCharArray());
SecretKey key = factory.generateSecret(keySpec);
System.out.println("key:" + Arrays.toString(key.getEncoded()));
cipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(salt, iterationCount));// 使用加密模式初始化
byte[] result = cipher.doFinal(encrypt); // 按单部分操作加密或解密数据,或者结束一个多部分操作。
return new String(result);
}
}
输出结果:
key:[112, 97, 115, 115, 119, 111, 114, 100]
PBE加密后:[-4, 57, 100, 25, 91, 55, 112, -76]
key:[112, 97, 115, 115, 119, 111, 114, 100]
PBE解密后:PBETest