RSA算法,加密/解密和签名/验签

1、概述

非对称加密算法

加密和解密用的密钥是不同的,这种加密方式是用数学上的难解问题构造的,通常加密解密的速度比较慢,适合偶尔发送数据的场合。优点是密钥传输方便。常见的非对称加密算法为RSA、ECC和EIGamal。

具体详细分析参考文章Java 实现 RSA 非对称加密算法

2、工具类

public class RSAUtils {

	private static final String PROVIDER_NAME = "BC";

	public final static String MAP_KEY_PUBLIC_KEY = "publicKey";
	public final static String MAP_KEY_PRIVATE_KEY = "privateKey";

	/** 数字签名算法:SHA1withRSA **/
	public final static String SIGNATURE_ALGORITHM_SHA1WITHRSA = "SHA1withRSA";
	/** 数字签名算法:MD5withRSA **/
	public final static String SIGNATURE_ALGORITHM_MD5WITHRSA = "MD5withRSA";

	/** 加密算法RSA **/
	public static final String KEY_ALGORITHM = "RSA";

	public static final String TRANSFORMATION_PKCS1PADDING = "RSA/ECB/PKCS1Padding";
	public static final String TRANSFORMATION_NOPADDING = "RSA/ECB/NOPADDING";

	/**
	 * 生成RSA公私钥对
	 * Map.keySet() 
	 */
	public static Map<String, String> generateRSAKeyPlain() {
		Map<String, String> map = new HashMap<String, String>();
		try {
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
			KeyPair keyPair = keyPairGenerator.generateKeyPair();
			map.put(MAP_KEY_PUBLIC_KEY, getPublicKeyPlain(keyPair.getPublic()));
			map.put(MAP_KEY_PRIVATE_KEY, getPrivateKeyPlain(keyPair.getPrivate()));
		} catch (NoSuchAlgorithmException e) {
			System.err.println("无此算法");
			e.printStackTrace();
		}
		return map;
	}

	/**
	 * RSA加密
	 * @param key  公钥
	 * @param data 数据
	 */
	public static byte[] encrypt(Key key, byte[] data) throws Exception {
		return encrypt(key, "RSA/ECB/PKCS1Padding", data);
	}

	/**
	 * RSA加密
	 * @param key  加密
	 * @param   算法
	 * @param data 数据
	 */
	public static byte[] encrypt(Key key, String transformation, byte[] data) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance(transformation, PROVIDER_NAME);
			cipher.init(Cipher.ENCRYPT_MODE, key);
			int blockSize = cipher.getBlockSize();
			int outputSize = cipher.getOutputSize(data.length);
			int leavedSize = data.length % blockSize;
			int blocksSize = leavedSize != 0 ? data.length / blockSize + 1
					: data.length / blockSize;
			byte[] raw = new byte[outputSize * blocksSize];
			int i = 0;
			while (data.length - i * blockSize > 0) {
				if (data.length - i * blockSize > blockSize) {
					cipher.doFinal(data, i * blockSize, blockSize, raw, i
							* outputSize);
				} else {
					cipher.doFinal(data, i * blockSize, data.length - i
							* blockSize, raw, i * outputSize);
				}
				i++;
			}
			return raw;
		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * RSA解密
	 */
	public static byte[] decrypt(Key key, byte[] data) throws Exception {
		return decrypt(key, "RSA/ECB/PKCS1Padding", data);
	}

	/**
	 * RSA解密
	 */
	public static byte[] decrypt(Key key, String transformation, byte[] data) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance(transformation, PROVIDER_NAME);
			cipher.init(Cipher.DECRYPT_MODE, key);
			int blockSize = cipher.getBlockSize(); // 密文数据分块
			if (data.length % blockSize != 0)
				throw new AppBizException("RSA descrypt:unexpected data length:" + data.length);
			int blocks = data.length / blockSize; // 获得分块数据
			int inputOffset = 0;
			int outputOffset = 0;
			byte[] buffer = new byte[blocks * blockSize];
			for (int i = 0; i < blocks; i++) {
				int hasDone = cipher.doFinal(data, inputOffset, blockSize,
						buffer, outputOffset);
				outputOffset += hasDone;
				inputOffset += blockSize;
			}
			return ArrayUtils.subarray(buffer, 0, outputOffset);
		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * 数据签名验签
	 * @param publicKey     公钥
	 * @param algorithm    数字签名算法 "MD5withRSA","SHA1withRSA" 等
	 * @param data   数据
	 * @param signData 验签数据
	 */
	public static boolean verify(PublicKey publicKey, String algorithm, byte[] data, byte[] signData) throws NoSuchAlgorithmException,
            InvalidKeyException, SignatureException {
		Signature signature = Signature.getInstance(algorithm);
		signature.initVerify(publicKey);
		signature.update(data);
		return signature.verify(signData);
	}

	/**
	 * 进行数字签名
	 * @param privateKey    私钥
	 * @param data     签名数据
	 * @param algorithm    数字签名算法 "MD5withRSA","SHA1withRSA" 等
	 */
	public static byte[] sign(PrivateKey privateKey, byte[] data,String algorithm) throws NoSuchAlgorithmException,
            InvalidKeyException, SignatureException {
		Signature signature = Signature.getInstance(algorithm);
		signature.initSign(privateKey);
		signature.update(data);
		return signature.sign();
	}

	/**
	 * 获取公钥Base64编码的字符串
	 */
	public static String getPublicKeyPlain(PublicKey publicKey) {
		byte[] pbk = publicKey.getEncoded();
		return Base64.encodeBase64String(pbk);
	}

	/**
	 * 获取私钥Base64编码的字符串
	 */
	public static String getPrivateKeyPlain(PrivateKey privateKey) {
		byte[] prk = privateKey.getEncoded();
		return Base64.encodeBase64String(prk);
	}

	/**
	 * Base64编码的字符串 转 公钥对象
	 */
	public static PublicKey loadPublicKey(String publicKeyStr) {
		byte[] buffer = Base64.decodeBase64(publicKeyStr);
		PublicKey publicKey = null;
		try {
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
			publicKey = keyFactory.generatePublic(keySpec);
		} catch (NoSuchAlgorithmException e) {
			System.err.println("无此算法");
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			System.err.println("非法公钥");
			e.printStackTrace();
		}
		return publicKey;
	}

	/**
	 * Base64编码的字符串 转 私钥对象
	 */
	public static PrivateKey loadPrivateKey(String privateKeyStr) {
		PrivateKey privateKey = null;
		try {
			byte[] buffer = Base64.decodeBase64(privateKeyStr);
			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			privateKey = keyFactory.generatePrivate(keySpec);
		} catch (NoSuchAlgorithmException e) {
			System.err.println("无此算法");
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			System.err.println("非法私钥");
			e.printStackTrace();
		}
		return privateKey;
	}
}

3、RSA 加密与解密数据

RSA 非对称加密在使用中通常公钥公开,私钥保密,使用公钥加密,私钥解密。例如 客户端 给 服务端 加密发送数据:

  • 客户端从服务端获取公钥;
  • 客户端用公钥先加密要发送的数据,加密后发送给服务端;
  • 服务端拿到加密后的数据,用私钥解密得到原文。

公钥加密后的数据,只有用私钥才能解,只有服务端才有对应的私钥,因此只有服务端能解密,中途就算数据被截获,没有私钥依然不知道数据的原文内容,因此达到数据安全传输的目的

  Map<String,String> keyMap = RSAUtils.generateRSAKeyPlain();
  keyMap.get(RSAUtils.MAP_KEY_PRIVATE_KEY)
  keyMap.get(RSAUtils.MAP_KEY_PUBLIC_KEY)
  
  String data = "你好, World!";
  // 客户端: 加密
  byte[] cipherData = RSAUtils.encrypt(loadPublicKe(keyMap.get(RSAUtils.MAP_KEY_PUBLIC_KEY)), data.getBytes());
 
  // 服务端: 解密
  byte[] plainData = RSAUtils.decrypt(loadPrivateKey(keyMap.get(RSAUtils.MAP_KEY_PUBLIC_KEY)), cipherData);

4、签名与验签

RSA 非对称加密算法,除了用来加密/解密数据外,还可以用于对数据(文件)的 签名 和 验签,可用于确认数据或文件的完整性与签名者(所有者)

RSA 密钥对在使用时通常:

  • 加密/解密:通常使用 公钥加密,私钥解密。
  • 签名/验签:通常使用 私钥签名,公钥验签。

Android 安装包 APK 文件的签名,是 RSA 签名验签的典型应用:Android 打包后,用私钥对 APK 文件进行签名,并把公钥和签名结果放到 APK 包中。下次客户端升级 APK 包时,根据新的 APK 包和包内的签名信息,用 APK 包内的公钥验签校验是否和本地已安装的 APK 包使用的是同一个私钥签名,如果是,则允许安装升级。

 Map<String,String> keyMap = RSAUtils.generateRSAKeyPlain();
 keyMap.get(RSAUtils.MAP_KEY_PRIVATE_KEY)
 keyMap.get(RSAUtils.MAP_KEY_PUBLIC_KEY)
 
 File file = new File("demo.jpg");

// 私钥签名(文件): 对文件进行签名, 返回签名结果
byte[] fileSignInfo = RSAUtils.sign(file.getBytes(), loadPrivateKey(keyMap.get(RSAUtils.MAP_KEY_PRIVATE_KEY)),"SHA1withRSA");
System.out.println("文件签名信息:" + new BASE64Encoder().encode(fileSignInfo));

// 公钥验签(文件): 用公钥校验文件的签名是否来自公钥对应的私钥
boolean fileVerify = RSAUtils.verify(loadPublicKey(keyMap.get(RSAUtils.MAP_KEY_PUBLIC_KEY)),"SHA1withRSA",file.getBytes(), fileSignInfo);
System.out.println("文件验签结果:" + fileVerify);

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值