一、对称加密技术
对称加密特点是加密和解密的过程使用相同的密钥(也叫做key),也就是说加密的时候使用的秘钥,在解密的时候也要用这个密钥,这种方法在密码学中叫做对称加密算法,对称加密算法使用起来简单快捷,密钥较短,且破译困难,除了数据加密标准(DES),另一个对称密钥加密系统是国际数据加密算法(IDEA),它比DES的加密性好,而且对计算机功能要求也没有那么高。
对称加密算法在电子商务交易过程中存在几个问题:
1、要求提供一条安全的渠道使通讯双方在首次通讯时协商一个共同的密钥。直接的面对面协商可能是不现实而且难于实施的,所以双方可能需要借助于邮件和电话等其它相对不够安全的手段来进行协商;
2、密钥的数目难于管理。因为对于每一个合作者都需要使用不同的密钥,很难适应开放社会中大量的信息交流;
3、对称加密算法一般不能提供信息完整性的鉴别。它无法验证发送者和接受者的身份;
4、对称密钥的管理和分发工作是一件具有潜在危险的和烦琐的过程。对称加密是基于共同保守秘密来实现的,采用对称加密技术的贸易双方必须保证采用的是相同的密钥,保证彼此密钥的交换是安全可靠的,同时还要设定防止密钥泄密和更改密钥的程序。
常见的对称加密算法有DES、3DES、Blowfish、IDEA、RC4、RC5、RC6和AES。小提示:des对称加密算法已经过时,被aes所取代。
举例:aes的实现和优化: java使用aes加密文件内容
aes的简化写法
二、非对称加密技术
与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
非对称加密算法实现机密信息交换的基本流程是这样的:甲生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。甲方只能用其专用密钥解密由其公用密钥加密后的任何信息。
非对称加密的典型应用是数字签名或者机密信息的网络传输。
常见的非对称加密算法有:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
举例rsa:
RSA简介
1977年,由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出了RSA非对称加密算法,当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。rsa是属于非对称加密的范畴,从那时直到现在,RSA算法一直是最广为使用的"非对称加密算法",我们在linux上实现免密登录的时候还记不记得就是使用的rsa吧。。
RSA流程
我们举个例子来说明这一切,在RSA中,最大值(称为max)由随机挑选的两个素数相乘而得。公钥和密钥在0和最大值之间挑选(称为pub和priv)。为了加密一个数字,让这个数字自己乘自己pub次,并确保当乘积大于最大值时能够回折(取模以确保乘积不大于最大值max)。解密时,再用这个加密得到的结果自己乘自己priv次,便能退回到原始的数字。这是一件很神奇的事情!但是它确实被实现了。
为了创建RSA密钥对,首先得随机的挑选两个素数来计算出max。然后从0到max中再挑选出公钥pub,只要知道两个素数,那么就能够从pub中算出私钥。这就是为什么因式分解与破解RSA有关—对max的因式分解,可以得到两个素数,这样你便能够通过pub计算出某人的私钥,从而来解密消息。
让我们举一个更加具体的例子吧。挑选素数13和7。他们积为91,也就是max。从0到91中挑选5作为pub来加密。利用算法Extended Euclidean,输入参数:7,13,5,得到私钥是29。
三个参数(max:91,pub:5,priv:29)定义了RSA加密算法。这时,可以取一个数字,通过乘自己5次来加密自己,然后取这个结果乘自己29次来回到原来的数字。
让我们来加密“CLOUD”。为了能够用数学来表示一个消息,我们必须将字母转变为数字。一个常用的转换方法是UTF-8。每个字母对应一个数字,如下图:
经过编码后的“CLOUD”是67,76,79,85,68。每个数字都小于最大值91,这是前提,所以我们能够各自对他们进行加密,为了简便,我们以第一个字母为例:
我们让67乘5次来加密自己:
67x67=4489,4489大于max91,需要取模4489%91=30(即上面的回折过程)
下面同理:
30x67=2010=8
8x67=536=81
81x67=5427=58
这个结果说明67加密后为58。
为每个字母重复这个过程,我们可以加密CLOUD。
为了解密,我们需要让58乘自己29次。
58x58=3364=88
88x58=5104=8
…
9x58=522=67
神奇的事情发生了!我们变回了67。
至于为什么会相等,为什么乘积大于max之后就要取模,你不需要去证明,因为这是数学上的问题,你作为一个开发人员只需要按着别人给的公式去实现它就行了,而别人给的所谓公式就是上面我说的这个计算流程。
理解几点:
RSA算法,单次加密明文最大长度117字节,解密要求密文最大长度为128字节,所以在加密和解密的过程中需要分块(分多次)进行。
rsa算法中,如果采用公钥加密,那么解密需要用对应的私钥;如果采用私钥加密,那么解密需要用对应的公钥。它们是相互对应的,并不是只有公钥可以用来加密。
下面是在java中使用rsa加密(使用公钥、私钥)、rsa解密(使用私钥、公钥)、以及rsa签名与签名的校验的代码。其中依赖的Base64类使用的是commons-codec-1.9.jar。
package d3;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
public class TestApp {
/**
* RSA单次最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
//算法
public static final String KEY_ALGORITHM = "RSA";
//RSA最大解密密文大小
private static final int MAX_DECRYPT_BLOCK = 128;
//签名算法
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
//公钥和秘钥
private static Map keyMap=new HashMap();
static{
try {
keyMap=genKeyPair();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
System.out.println("本次生成的公钥:"+getPublicKey(keyMap));
System.out.println("本次生成的私钥:"+getPrivateKey(keyMap));
test1();
test2();
}
//测试公钥加密之后,再使用私钥解密
public static void test1() throws Exception{
System.out.println("-----------测试公钥加密——私钥解密------------");
String source = "乐之者java";
System.out.println("加密前文字:\r\n" + source);
byte[] data = source.getBytes();
byte[] encodedData = TestApp.encryptByPublicKey(data, getPublicKey(keyMap));
System.out.println("加密后文字:\r\n" + new String(encodedData));
byte[] decodedData = TestApp.decryptByPrivateKey(encodedData, getPrivateKey(keyMap));
String target = new String(decodedData);
System.out.println("解密后文字: \r\n" + target);
System.out.println("\r");
}
//测试私钥加密之后,再使用公钥解密;以及签名、验签的测试
public static void test2() throws Exception{
System.out.println("-----------测试私钥加密——公钥解密-----------");
String source = "rsa加密算法";
System.out.println("加密前文字:\r\n" + source);
byte[] data = source.getBytes();
byte[] encodedData = TestApp.encryptByPrivateKey(data, getPrivateKey(keyMap));
System.out.println("加密后文字:\r\n" + new String(encodedData));
byte[] decodedData = TestApp.decryptByPublicKey(encodedData, getPublicKey(keyMap));
String target = new String(decodedData);
System.out.println("解密后文字: \r\n" + target);
System.out.println("\r");
System.out.println("-----------测试私钥签名——公钥验证签名-----------");
String sign = TestApp.sign(encodedData, getPrivateKey(keyMap));
System.out.println("签名:\r" + sign);
boolean status = TestApp.verify(encodedData, getPublicKey(keyMap), sign);
System.out.println("验证结果:\r" + status);
}
//生成密钥对(公钥和私钥)的对象
private static Map genKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map keyMap = new HashMap();
keyMap.put("RSAPublicKey", publicKey);
keyMap.put("RSAPrivateKey", privateKey);
return keyMap;
}
/**
* 获取私钥
*/
public static String getPrivateKey(Map keyMap)throws Exception {
Key key = (Key) keyMap.get("RSAPrivateKey");
String base64Str = Base64.encodeBase64String(key.getEncoded());
return base64Str;
}
/**
* 获取公钥
*/
public static String getPublicKey(Map keyMap)
throws Exception {
Key key = (Key) keyMap.get("RSAPublicKey");
String base64Str = Base64.encodeBase64String(key.getEncoded());
return base64Str;
}
/**
* 私钥加密
* @param data 源数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String privateKey)
throws Exception {
byte[] keyBytes = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateK);
int inputLen = data.length;//获得字节长度/字节大小
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
/**
* 公钥解密
* @param encryptedData 已加密数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] encryptedData,
String publicKey) throws Exception {
byte[] keyBytes = Base64.decodeBase64(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher
.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher
.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
/**
* 公钥加密
*
* @param data 源数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey)
throws Exception {
byte[] keyBytes = Base64.decodeBase64(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
/**
* 私钥解密
* @param encryptedData
* 已加密数据
* @param privateKey
* 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] encryptedData,
String privateKey) throws Exception {
byte[] keyBytes = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher
.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher
.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
/**
* 校验数字签名
* @param data 已加密数据
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
*
* @return
* @throws Exception
*
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
byte[] keyBytes = Base64.decodeBase64(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(Base64.decodeBase64(sign));
}
/**
* 对信息使用私钥生成数字签名
* @param data 已加密数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateK);
signature.update(data);
return Base64.encodeBase64String(signature.sign());
}
}
运行结果:
三、Hash算法(摘要算法)
Hash算法特别的地方在于它是一种单向算法,用户可以通过hash算法对目标信息生成一段特定长度的唯一hash值,却不能通过这个hash值重新获得目标信息。因此Hash算法常用在不可还原的密码存储、信息完整性校验等。
常见的Hash算法有MD2、MD4、MD5、HAVAL、SHA。
通过 Apache Commons Codec 实现Base64/MD5/SHA256等算法,代码如下:
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
public class App {
public static void main(String[] args) throws Exception{
String base64 = base64Encode("乐之者java");
System.out.println("base64 encode="+base64);
byte[] buf = base64Decode(base64);
System.out.println("base64 decode="+new String(buf));
String md5 = md5("乐之者java");
System.out.println("md5="+md5+"**len="+md5.length());
String sha256 = sha256Hex("乐之者java");
System.out.println("sha256="+sha256+"**len="+sha256.length());
}
public static String base64Encode(String data){
return Base64.encodeBase64String(data.getBytes());
}
public static byte[] base64Decode(String data){
return Base64.decodeBase64(data.getBytes());
}
public static String md5(String data) {
return DigestUtils.md5Hex(data);
}
public static String sha256Hex(String data) {
return DigestUtils.sha256Hex(data);
}
}
运行结果:
可以看到,直接使用Apache Commons Codec里面的DigestUtils.md5Hex(data);即可完成md5加密,当然你想指定字符串编码的话也可以使用其重载函数DigestUtils.md5Hex(data.getBytes("utf-8"));来指定字符串编码
四、其他
上面的base64和下面的url加密与解密可以认为是编码解码方式,不是加密解密。
java中URLEncoder加密与URLDecoder解密。
public static void main(String[] args) {
try {
// 将普通字符创转换成application/x-www-from-urlencoded字符串
String urlString = URLEncoder.encode("乐之者java", "utf-8");
System.out.println(urlString); //结果:%E4%B9%90%E4%B9%8B%E8%80%85java
// 将application/x-www-from-urlencoded字符串转换成普通字符串
String keyWord = URLDecoder.decode(urlString, "utf-8");
System.out.println(keyWord); //输出: 乐之者java
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
执行结果: