非对称加密算法与应用场景
· 阅读需 9 分钟
简介
非对称加密算法是密码学的一种算法,它需要两个密钥,一个是公开密钥(简称为:公钥),另一个是私有密钥(简称为:私钥);一个用作加密,另一个则用作解密。
使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。
由于加密和解密需要两个不同的密钥,故被称为非对称加密;不同于加密和解密都使用同一个密钥的对称加密。
虽然两个密钥在数学上相关,但如果知道了其中一个,并不能凭此计算出另一个;因此其中一个可以公开,称为公钥,任意向外发布;不公开的密钥称为私钥,必须由用户自行严格秘密保管,绝不透过任何途径向任何人提供,也不会透露给被信任的要通信的另一方。
应用场景
非对称密码算法,所谓非对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密。
非对称加密主要应用于两个场景:数据加密和签名
加密和签名的区别
加密和签名都是为了安全性考虑,但有所不同。加密是为了防止信息被泄露,签名是为了防止信息被篡改。
加密过程
- A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
- A传递自己的公钥给B,B使用A的公钥对消息进行加密。
- A接收到B加密的消息,利用A自己的私钥对消息进行解密。
整个过程中,只用A的私钥才能对消息进行解密,防止消息被泄露。
签名过程
- A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
- A用自己的私钥对消息进行加签,形成签名,并将签名和消息本身一起传递给B。
- B收到消息后,通过A的公钥进行验签,如果验签成功,则证明消息是A发送的。
整个过程,只有使用A私钥签名的消息才能被验签成功。即使知道了消息内容,也无法伪造签名,防止消息被篡改。
Java 代码示例
Rsa 加解密工具类
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* Rsa 非对称加密解密工具类
*/
public class RsaUtil {
/**
* 公钥加密
*
* @param plaintext 明文
* @param pubKey 公钥
*/
public static <R> R encrypt(String plaintext, PublicKey pubKey, Production<R> production) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); // 密文
return production.apply(encrypted);
}
/**
* 公钥加密
*
* @param pubKeyBase64 base64格式公钥
*/
public static <R> R encrypt(String plaintext, String pubKeyBase64, Production<R> production) throws Exception {
// 公钥证书
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodeBase64ToByte(pubKeyBase64));
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
return encrypt(plaintext, publicKey, production); // 加密
}
/**
* 公钥加密
*
* @param pubKeyBase64 base64格式公钥
*/
public static String encryptToBase64String(String plaintext, String pubKeyBase64) throws Exception {
// 公钥证书
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodeBase64ToByte(pubKeyBase64));
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
return encrypt(plaintext, publicKey, RsaUtil::toBase64String); // 加密
}
/**
* 私钥解密
*
* @param encrypted 密文
* @param priKey 私钥
*/
public static <R> R decrypt(String encrypted, PrivateKey priKey, Production<R> production) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
byte[] decrypted = cipher.doFinal(decodeBase64ToByte(encrypted));
return production.apply(decrypted);
}
public static String decryptToString(String encrypted, PrivateKey priKey) throws Exception {
return decrypt(encrypted, priKey, String::new);
}
/**
* 私钥解密
*
* @param priKeyBase64 base64格式私钥
*/
public static <R> R decrypt(String encryptedText, String priKeyBase64, Production<R> production) throws Exception {
// 私钥证书
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodeBase64ToByte(priKeyBase64));
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
return decrypt(encryptedText, privateKey, production);
}
public static String decryptToString(String encryptedText, String privateKeyBase64) throws Exception {
return decrypt(encryptedText, privateKeyBase64, String::new);
}
public static String toBase64String(byte[] encoded) {
return java.util.Base64.getEncoder().encodeToString(encoded);
}
public static byte[] decodeBase64ToByte(String decoded) {
return java.util.Base64.getDecoder().decode(decoded);
}
@FunctionalInterface
public interface Production<R> {
/**
* @return the function result
*/
R apply(byte[] data);
}
/**
* 生成密匙对
*
* @param keySize 密钥大小
*/
public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException {
// 初始化一个RSA算法的密钥对生成器
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(keySize);
return keyGen.generateKeyPair();
}
public static Map<String, String> generateBase64KeyPair(int keySize) throws Exception {
KeyPair keyPair = generateKeyPair(keySize);
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, String> keyMap = new HashMap<>(2);
keyMap.put("private", toBase64String(priKey.getEncoded()));
keyMap.put("public", toBase64String(pubKey.getEncoded()));
return keyMap;
}
}
Rsa 签名验签工具类
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.concurrent.Callable;
/**
* <p>该类提供常见的 HashWithRSA 组合算法,用于提供数据签名和验签。</p>
*
* <p>HashWithRSA 指的是使用 RSA 加密算法结合哈希函数进行数字签名或数据加密。
* RSA(Rivest–Shamir–Adleman)是一种非对称加密算法,而哈希函数则用于生成消息
* 摘要(即签名)。</p>
*
* <p>加密和签名的区别:
* 加密和签名都是为了安全性考虑,但有所不同。加密是为了防止信息被泄露,签名是为了防止信息被篡改。</p>
*
* <h3>加密过程:</h3>
*
* <ul>
* <li>A生成一对密钥(公钥和私钥)。私钥不公开,A自己保留。公钥为公开的,任何人可以获取。</li>
* <li>A传递自己的公钥给B,B使用A的<em>公钥对消息进行加密</em>。</li>
* <li>A接收到B加密的消息,利用A自己的<em>私钥对消息进行解密</em></li>
* </ul>
*
* <p>整个过程中,只用A的私钥才能对消息进行解密,防止消息被泄露。</p>
*
* <h3>签名过程:</h3>
*
* <ul>
* <li>A生成一对密钥(公钥和私钥)。私钥不公开,A自己保留。公钥为公开的,任何人可以获取。</li>
* <li>A用自己的私钥对消息进行加签,形成签名,并将签名和消息本身一起传递给B。</li>
* <li>B收到消息后,通过A的公钥进行验签。如果验签成功,则证明消息是A发送的。</li>
* </ul>
*
* <p>整个过程,只有使用A私钥签名的消息才能被验签成功。即使知道了消息内容,也无法伪造签名,防止消息被篡改。
*/
public enum HashWithRSA {
MD5withRSA("MD5withRSA"),
SHA1withRSA("SHA1withRSA"),
SHA256withRSA("SHA256withRSA"),
SHA384withRSA("SHA384withRSA"),
SHA512withRSA("SHA512withRSA"),
;
private final String algorithm;
HashWithRSA(String algorithm) {
this.algorithm = algorithm;
}
/**
* 私钥签名
*
* @param priKey 私钥
* @param plaintext 原文
*/
public byte[] signature(byte[] priKey, byte[] plaintext) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
// 私钥证书
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(priKey);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
// 进行签名
Signature spi = Signature.getInstance(algorithm);
spi.initSign(privateKey);
spi.update(plaintext);
return spi.sign();
}
public <R> R signature(byte[] priKey, byte[] plaintext, Production<R> production) throws Exception {
return production.apply(signature(priKey, plaintext));
}
public byte[] signature(Callable<byte[]> priKey, Callable<byte[]> plaintext) throws Exception {
return signature(priKey.call(), plaintext.call());
}
public <R> R signature(Callable<byte[]> priKey, Callable<byte[]> plaintext, Production<R> production) throws Exception {
return production.apply(signature(priKey, plaintext));
}
public byte[] signature(String priKeyBase64, String plaintext) throws Exception {
return signature(decodeBase64ToByte(priKeyBase64), plaintext.getBytes(StandardCharsets.UTF_8));
}
public <R> R signature(String priKeyBase64, String plaintext, Production<R> production) throws Exception {
return production.apply(signature(priKeyBase64, plaintext));
}
/**
* 验签
*
* @param pubKey 公钥
* @param plaintext 原文
* @param signature 私钥签名{@link #signature(byte[], byte[])}
*/
public boolean verify(byte[] pubKey, byte[] plaintext, byte[] signature) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException {
// 公钥证书
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKey);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
Signature spi = Signature.getInstance(algorithm);
spi.initVerify(publicKey);
spi.update(plaintext);
// 验签
return spi.verify(signature);
}
public boolean verify(String pubKeyBase64, byte[] plaintext, byte[] signature) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException {
return verify(decodeBase64ToByte(pubKeyBase64), plaintext, signature);
}
public boolean verify(String pubKeyBase64, String plaintext, byte[] signature) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException {
return verify(decodeBase64ToByte(pubKeyBase64), plaintext.getBytes(StandardCharsets.UTF_8), signature);
}
public boolean verify(String pubKeyBase64, String plaintext, String signature) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException {
return verify(decodeBase64ToByte(pubKeyBase64), plaintext.getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8));
}
public static String toBase64String(byte[] encoded) {
return java.util.Base64.getEncoder().encodeToString(encoded);
}
public static byte[] decodeBase64ToByte(String decoded) {
return java.util.Base64.getDecoder().decode(decoded);
}
@FunctionalInterface
public interface Production<R> {
/**
* @return the function result
*/
R apply(byte[] data);
}
}
HMAC 工具类
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* <p>HMAC(Hash-based Message Authentication Code)是一种使用哈希函数
* 和秘密密钥来生成消息认证码的算法.</p>
*
* <p>HMAC通过将密钥混入哈希运算中,提供了一种更强大的消息认证方法,防范了一
* 些针对普通哈希的攻击.</p>
*
* <p>用于验证消息的完整性和认证消息发送者的身份.</p>
*/
public enum Hmac {
MD5("HmacMD5"),
SHA1("HmacSHA1"),
SHA256("HmacSHA256"),
SHA384("HmacSHA384"),
SHA512("HmacSHA512"),
;
private final String algorithm;
Hmac(String algorithm) {
this.algorithm = algorithm;
}
public byte[] hmac(byte[] secret, byte[] plaintext) throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance(this.name());
SecretKeySpec keySpec = new SecretKeySpec(secret, algorithm);
mac.init(keySpec);
return mac.doFinal(plaintext);
}
public <R> R hmac(byte[] secret, byte[] plaintext, Production<R> production) throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance(this.name());
SecretKeySpec keySpec = new SecretKeySpec(secret, algorithm);
mac.init(keySpec);
return production.apply(mac.doFinal(plaintext));
}
public String hmacHex(String secret, String plaintext) throws NoSuchAlgorithmException, InvalidKeyException {
return hmac(secret.getBytes(StandardCharsets.UTF_8), plaintext.getBytes(StandardCharsets.UTF_8), Hex::encodeHexString);
}
public String hmacBase64(String secret, String plaintext) throws NoSuchAlgorithmException, InvalidKeyException {
return hmac(secret.getBytes(StandardCharsets.UTF_8), plaintext.getBytes(StandardCharsets.UTF_8), Base64::encodeBase64String);
}
@FunctionalInterface
public interface Production<R> {
/**
* @return the function result
*/
R apply(byte[] data);
}
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
String secret = "f$s1f@9.";
String plaintext = "hello,world";
System.out.println(SHA1.hmacHex(secret, plaintext));
System.out.println(SHA1.hmacBase64(secret, plaintext));
}
}