最近给华为做一个项目,用到SAML单点登录。其中涉及到非对称加密和SSL相关的东西。学习了一下,记录下。
非对称加密意思就是这套算法有2个钥匙,一个叫密钥,一个叫公钥,用密钥加密的内容,只能用公钥解密,用公钥加密的内容,只能用密钥解密。公钥是公开的,密钥是保密的。这样的特性被利用在SSL上,极大提高了网络传输的安全性。(SSL协议其实是两边拥有相同的密钥,而且这个密钥是临时生成的,并非每次都不变的。在一段生成了密钥之后如何告诉对方才不会被网络中盗取?那就是通过非对称加密来做这个事情。)
现在说说如何生成这对密钥和公钥。
我使用的是java,java SDK提供了keytool.exe 在bin 下,可以直接使用。
D:>keytool -genkey -alias huawei(别名) -keyalg RSA(加密算法) -validity 36500(证书有效期) -keysize 1024(密钥长度) -keystore D:\huawei.keystore(密钥数据库文件存放的地方) -storepass 222222(密钥数据库访问的密码) -keypass 111111(密钥数据库中密钥获取要使用的密码) -dnam e "CN=JOB,OU=企业服务部,O=北京MM科技有限公司,L=北京市,ST=北京市,C=CN"(主体的信息)
上面这行命令中小括号内的内容是注释,运行时需要去掉的。
运行之后,就会在D盘下面生成huawei.keystore 这个数据库文件。这是个数据库文件,里面可以有多个密钥公钥对,每次生成都可以存储在这个文件中,每个密码对通过 上面指定的 alias 别名区分,故不可重复。
然后就是从这个数据库文件中导出公钥,导出成 .cer 后缀的文件,这个文件就叫证书。里面包含公钥和主体信息等。
导出的命令是:
D:>keytool -export -keystore D:\huawei.keystore -alias huawei -file D:\huawei.cer
回车后,会让输入数据库文件的访问密码,即上面的storepass(569832)回车,即可导出证书。
下面是 RSA 算法加密解密的辅助类,上面部分是公开方法,下面部分是私有的辅助方法,字符串转码默认使用 UTF-8 ,对byte可视化编码默认使用 BASE64编码。要使用下面这个辅助类加密解密先需要提供 公钥(PublicKey)和秘钥(PrivateKey)(见下面第二个测试类中)。
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
/**
* 加密解密
* @author tanyf
*
*/
public final class EncryptUtils {
/**
* 通过秘钥将数据加密
* @param data 原始数据
* @param privateKey 秘钥
* @return 加密后的数据
* @throws Exception TODO
*/
public static byte[] encrypt(byte[] data,String privateKey) throws Exception{
return encrypt(data, parsePrivateKey(privateKey));
}
/**
* 通过秘钥将数据加密
* @param data 原始数据
* @param privateKey 秘钥
* @return 加密过后的数据
* @throws Exception TODO
*/
public static byte[] encrypt(byte[] data,PrivateKey privateKey) throws Exception{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 通过公钥将数据解密
* @param data 加密后的数据
* @param publicKey 公钥
* @return 解密后的数据
* @throws Exception TODO
*/
public static byte[] decrypt(byte[] data,String publicKey)throws Exception{
return decrypt(data, parsePublicKye(publicKey));
}
/**
* 通过公钥将数据解密
* @param data 加密后的数据
* @param publicKey 公钥
* @return 解密后的数据
* @throws Exception TODO
*/
public static byte[] decrypt(byte[] data,PublicKey publicKey)throws Exception{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 通过秘钥对数据进行签名
* @param data 原始数据
* @param privateKey 密钥
* @return 用PrivateKey对数据进行的签名
* @throws Exception TODO
*/
public static String sign(byte[] data,String privateKey)throws Exception{
Signature s = Signature.getInstance("MD5withRSA");
s.initSign(parsePrivateKey(privateKey));
s.update(data);
return encodeBase64(s.sign());
}
/**
* 通过秘钥对数据进行签名
* @param data 原始数据
* @param privateKey 密钥
* @return 用PrivateKey对数据进行的签名
* @throws Exception TODO
*/
public static String sign(byte[] data,PrivateKey privateKey)throws Exception{
Signature s = Signature.getInstance("MD5withRSA");
s.initSign(privateKey);
s.update(data);
return encodeBase64(s.sign());
}
/**
* 通过公钥校验签名是否正确
* @param data 解密之后的数据
* @param sign 用privateKey 对数据进行的签名字符串
* @param publicKey 公钥
* @return 签名是否正确,这确保了数据没有被篡改过
* @throws Exception TODO
*/
public static boolean verifySign(byte[] data,String sign,String publicKey)throws Exception{
Signature s = Signature.getInstance("MD5withRSA");
s.initVerify(parsePublicKye(publicKey));
s.update(data);
return s.verify(decodeBase64(sign));
}
/**
* 通过公钥校验签名是否正确
* @param data 解密之后的数据
* @param sign 用privateKey 对数据进行的签名字符串
* @param publicKey 公钥
* @return 签名是否正确,这确保了数据没有被篡改过
* @throws Exception TODO
*/
public static boolean verifySign(byte[] data,String sign,PublicKey publicKey)throws Exception{
Signature s = Signature.getInstance("MD5withRSA");
s.initVerify(publicKey);
s.update(data);
return s.verify(decodeBase64(sign));
}
// --------- 下面是私有辅助方法 ------------
/**
* 将秘钥字符串加工秘钥
* @param privateKeyStr 秘钥字符串
* @return 秘钥
* @throws Exception TODO
*/
private static PrivateKey parsePrivateKey(String privateKeyStr)throws Exception{
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodeBase64(privateKeyStr));
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
/**
* 将公钥字符串加工成公钥
* @param publicKyeStr 公钥字符串
* @return 公钥
* @throws Exception TODO
*/
private static PublicKey parsePublicKye(String publicKyeStr)throws Exception{
X509EncodedKeySpec spec = new X509EncodedKeySpec(decodeBase64(publicKyeStr));
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
/**
* 用BASE64解码
* @param str 待解码的字符串
* @return 解码后的byte数据
* @throws Exception TODO
*/
private static byte[] decodeBase64(String str)throws Exception{
return Base64.decodeBase64(str.getBytes("UTF-8"));
}
/**
* 用BASE64编码
* @param bs byte数据
* @return 编码后的字符串
* @throws Exception TODO
*/
private static String encodeBase64(byte[] bs)throws Exception{
return new String(Base64.encodeBase64(bs),"UTF-8");
}
}
测试类如下:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import javassist.expr.NewArray;
import javax.crypto.Cipher;
/**
* TODO
* @author tanyf
*
*/
public class Main {
/**
* TODO
* @param args TODO
* @throws NoSuchAlgorithmException TODO
*/
public static void main(String[] args) throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
KeyPair pair = generator.generateKeyPair();
/*
byte[] privateKeyBs = pair.getPrivate().getEncoded();
String privateKey = new BASE64Encoder().encode(privateKeyBs);
byte[] publicKeyBs = pair.getPublic().getEncoded();
String publicKey = new BASE64Encoder().encode(publicKeyBs);
System.out.println("-----秘钥-------");
System.out.println(privateKey);
System.out.println("-----公钥-------");
System.out.println(publicKey);
*/
// 下面是使用秘钥加密,使用公钥解密
byte[] data = "ABC欧文刚".getBytes("UTF-8");
byte[] encData = EncryptUtils.encrypt(data, pair.getPrivate());
String sign = EncryptUtils.sign(data, pair.getPrivate());
System.out.println("签名字符串为:\n"+sign);
byte[] decData = EncryptUtils.decrypt(encData, pair.getPublic());
String msg = new String(decData,"UTF-8");
System.out.println(msg);
System.out.println("签名是否正确: "+EncryptUtils.verifySign(decData, sign, pair.getPublic()));
System.out.println("--- 秘钥加密,公钥解密 done ---");
// 下面是使用公钥加密,使用秘钥解密 (为了验证下反过来加解密是否成功,所以没有使用到 EncryptUtils辅助类,代码证明,反过来也是可以的)
byte[] origBs = "购物劲歌王".getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pair.getPublic());
byte[] encBs = cipher.doFinal(origBs);
Cipher cipher2 = Cipher.getInstance("RSA");
cipher2.init(cipher.DECRYPT_MODE, pair.getPrivate());
byte[] decBs = cipher2.doFinal(encBs);
System.out.println(new String(decBs,"UTF-8"));
System.out.println("---------- 公钥加密,秘钥解密 DONE -----------");
}
}
助记:
要将字符串加密,先需要获取 公钥秘钥对,获取方式 通过 KeyPairGenerator来获取,得到 KeyPair ,此对象里面包含了秘钥和公钥
通过 getPrivate 和 getPublic 方法获取。 获取到的对象分别是 PrivateKey 和 PublicKey 对象。两个对象即可用于加密解密。如果需要将这些钥匙保存下来,需要弄成字符串,那么调用它们的 getEncoded() 方法获取到 byte[] 的数据,然后可以通过 BASE64 编码转成字符串。这样就方便拷贝粘贴等。当然不是硬性要求使用 BASE64 的,不过大家都习惯用它罢了。在使用的时候,再用BASE64 把字符串解码成 byte[] ,不过 byte[] 依然不能直接使用。
byte[] 需要转换成 PublicKey 或者 PrivateKey 才可以方便使用。
byte[] 转换成 PublicKey 和 PrivateKey 的方式类似,具体可见类中。
代码中可见,加密解密都使用的是 Cipher 类,不过是使用的 模式不同而已 如:cipher2.init(cipher.DECRYPT_MODE, pair.getPrivate());