非对称加密的使用

最近给华为做一个项目,用到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());