1、对称加密和非对称加密
对称加密指的就是加密和解密使用同一个秘钥。对称加密只有一个秘钥,作为私钥。
常见的对称加密算法:DES,AES,3DES等等。
非对称加密指的是加密和解密使用不同的秘钥,一把作为公开的公钥,另一把作为私钥。公钥加密的信息,只有私钥才能解密。私钥加密的信息,只有公钥才能解密。
常见的非对称加密算法:RSA,ECC
对称加密和非对称加密不能说谁好谁不好,主要是要看应用场景,如果可以用对称加密解决的问题,那么就没有必要用非对称进行加密。因为非对称加密的开销一般比较大,例如RSA 1024的安全性,与AES128的安全性是相当的。
2、对称加密算法AES
对称算法以DES和AES为代表性,其底层原理也有相似之处,都有一个S盒子(可不可以叫做黑盒子),然后通过交换和替换等复杂变换,达到加密的效果。对称加密算法有个非常重要的特性,就是加密和解密可以互逆。总之呢,算法还是挺复杂的,不过,作为程序员的我们,可以不去关心这些,我们只要知道,我们可以用他们来做加密。加密的安全性取决于密钥的长度,密钥越长,越安全如密钥长为128的AES,我们称之为AES128,密钥为256的AES,我们称之为AES256。当前计算机的计算能力下,128的AES基本是安全的,256完全可以放心了。
好了,用java代码来实现AES加解密吧,这个才是我们关心的。
加密方法:
public static final String VIPARA = "20179TELIGRR1234";//初始化向量 16位
private static final String DEFAULT_ENCODING = "utf-8";//编码方式
/**
* AES加密
* @param content 待加密的内容
* @param encryptKey 加密密钥
* @return 加密后的byte[]
* @throws Exception
*/
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
IvParameterSpec zeroIv = new IvParameterSpec(VIPARA.getBytes(DEFAULT_ENCODING));
SecretKeySpec keySpec = new SecretKeySpec(encryptKey.getBytes(DEFAULT_ENCODING),"AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE,keySpec,zeroIv);
return cipher.doFinal(content.getBytes(DEFAULT_ENCODING));
}
这个加密方法传入参数是文本内容以及密钥,注意密钥长度要为16byte(与上述的初始化向量是一样的)。
解密方法类似:
/**
* AES解密
* @param encryptBytes 待解密的byte[]
* @param decryptKey 解密密钥
* @return 解密后的String
* @throws Exception
*/
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
IvParameterSpec zeroIv = new IvParameterSpec(VIPARA.getBytes(DEFAULT_ENCODING));
SecretKeySpec keySpec = new SecretKeySpec(decryptKey.getBytes(DEFAULT_ENCODING),"AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE,keySpec,zeroIv);
byte[] decryptBytes = cipher.doFinal(encryptBytes);
return new String(decryptBytes);
}
上诉加密和解密方法,还是有挺多与密码学相关的知识的,比如初始化向量,以及加密的CBC模式,不过其实,作为我们应用层的,可以不去关心这些底层的实现,使用就可以完成我们的目标了。不过,使用旧了,自然而且,我们会好奇,它到底怎么实现的?它为什么就是安全的?到时,我们就可以深入去学习了,希望大家这种好奇来的越早越好。
有了上诉的加密和解密方法,我们就可以来进行加解密了,示例如下:
byte[] data1 = aesEncryptToBytes("DW1234567fddddddddddfffffffffff8","aaaaaaaaaaaaaaaa");
String data1Str = bytesToHexString(data1);
System.out.println("密文:"+data1Str);
String result1 = aesDecryptByBytes(hexStringToBytes(data1Str),"aaaaaaaaaaaaaaaa");
System.out.println("明文:"+result1);
这上面应用到两个函数bytesToHexString与hexStringToBytes,这个其实不是加解密的环节,而是编解码的环节了,它们的作用就是把字节转成16进制数,以及它的逆操作,具体函数如下:
/**
* 字节数组转十六进制数
*
* @param b
* @return
*/
public static String bytesToHexString(byte[] b) {
if(b == null || b.length <= 0){
return null;
}
StringBuilder sb = new StringBuilder(b.length * 2);
for (int i = 0; i < b.length; i++) {
sb.append(HEXCHAR[(b[i] & 0xf0) >>> 4]);
sb.append(HEXCHAR[b[i] & 0x0f]);
}
return sb.toString();
}
/**
* 16进制串字符转字节
* 和 bytesToHexString 互逆
* @param hexString
* @return
*/
public static byte[] hexStringToBytes(String hexString){
if(hexString == null || hexString.length() <= 0){
return null;
}
byte[] data = new byte[hexString.length()/2];
for(int i=0;i< hexString.length();){
char high = hexString.charAt(i);
i++;
char low = hexString.charAt(i);
data[i/2] = hexToByte(high,low);
i++;
}
return data;
}
3、非对称加密算法 RSA
非对称加密算法都有一个数学依据的,比如RSA的数学依据总结起来可以说:两个质数相乘得到一个合数是很容易的,而两个大质数相乘得到的大数难以被因式分解。
对于非对称加密,你首先先要一对密钥,一个公钥,用来发布出去的,一个私钥,只能自己拥有的。基于java的RSA密钥生成方法如下:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);// 秘钥长度为1024,可以改成2048等
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();//公钥
PrivateKey privateKey = keyPair.getPrivate();//私钥
现在公钥和私钥都有了,首先我们需要把它们保存起来,可以直接保存为文件,或者生成字符串等等,下面我把他们保存为文件
byte[] publicKeyByte = publicKey.getEncoded();
byte[] privateKeyByte = privateKey.getEncoded();
BufferedOutputStream pukout = new BufferedOutputStream(
new FileOutputStream("src/com/lh/test/publicKey.key"));
BufferedOutputStream prkout = new BufferedOutputStream(
new FileOutputStream("src/com/lh/test/privateKey.key"));
pukout.write(publicKeyByte);
prkout.write(privateKeyByte);
pukout.flush();
prkout.flush();
保存的文件读出如下:
BufferedInputStream pukIn = new BufferedInputStream(
new FileInputStream("src/com/lh/test/publicKey.key"));
BufferedInputStream prkIn = new BufferedInputStream(
new FileInputStream("src/com/lh/test/privateKey.key"));
byte[] puKeyByte = new byte[1024];
byte[] prKeyByte = new byte[1024];
pukIn.read(puKeyByte, 0, 1024);
prkIn.read(prKeyByte, 0, 1024);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
puKeyByte);// 公钥
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
prKeyByte);// 私钥
PrivateKey privateKey = keyFactory
.generatePrivate(pkcs8EncodedKeySpec);
现在我们通过文件读取(或其它途径)拿到了公钥和私钥,我们就可以用来使用了,如果公钥加密,那么需要私钥进行解密,反之亦然。
公钥加密:
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] resultbytes = cipher.doFinal(plainBytes);
//plainBytes 要加密的字节数组
私钥解密:
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] deBytes = cipher.doFinal(ciperBytes);
//ciperBytes要解密的字节数组
有了byte[],就可以根据上面的hexStringToBytes,bytesToHexString方法,进行字节数组与字符串的转换,便于传输。
4、安全哈希函数MD5
实际开发中,经常可以听到我们密码字段是用MD5加密的,其实这种说法是不对的,MD5(安全哈希函数)只是一串数据指纹,只能用来做数据验证。即,只能单向认证,只能从明文单向计算出MD5值,而不能从MD5值计算出明文。
安全哈希函数常用于验证信息完整性以及验证信息等,比如协议的完整性校验、验证密码。
安全哈希函数有MD5/SHA1/SHA2/SHA3,现在推荐使用SHA2/SHA3
Java实现MD5如下
private static final String md5Key = "MD5";
private static final String defaultEncoding = "utf-8";
/**
* 以字符串为输入输出的Md5
* @param dataStr
* @return
*/
public static String md5Encrypt(String dataStr){
try {
MessageDigest md5 = MessageDigest.getInstance(md5Key);
byte [] data = md5.digest(dataStr.getBytes(defaultEncoding));
return bytesToHexString(data);
} catch (Exception e) {
return null;
}
}