非对称加密算法
加密和解密用的密钥是不同的,这种加密方式是用数学上的难解问题构造的,通常加密解密的速度比较慢,适合偶尔发送数据的场合。优点是密钥传输方便。常见的非对称加密算法为RSA、ECC和EIGamal。
具体详细分析参考文章Java 实现 RSA 非对称加密算法
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;
}
}
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);
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);