非对称加密的使用
最近给华为做一个项目,用到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());