使用对称密码时一定会遇到密钥配送问题,解决密钥配送问题的方法有以下几种:
- 通过事先共享密钥来解决:有时候现实无法满足
- 通过密钥分配中心来解决:密钥分配中心的安全和负载都是潜在危险因素
- 通过Diffie-Hellman密钥交换算法来解决:示例
- 通过公钥密钥来解决
1976年Whitfield Diffie和Martin Hellman提出了关于公钥密码设计的思想,尽管他们没有提出具体的公钥密码算法,但他们提出了应该将加密密钥和解密密钥分开的想法,而且还描述了公钥密码应该具备的性质。
1977年Ralph Merkle和Martin Hellman共同设计了一种具体的公钥密码算法--- Knapsack。该算法申请了专利,但后来被发现并不安全。
1978年美国MIT的Ron Rivest、Adi Shamir和Leonard Adleman共同发表了一种公钥密码算法---RSA(三人姓氏首字母缩写)。RSA可以说是现在公钥密码的事实标准。
公钥密码也叫非对称密码,密钥分为加密密钥和解密密钥。发送者用加密密钥对消息进行加密,接收者用解密密钥对密文进行解密。也就是说解密密钥从一开始就是由接收者自己保管的,因此只要将加密密钥发给发送者就可以解决密钥配送问题了,而根本不需要配送解密密钥。
非对称密码的加密密钥一般称为公钥,解密密钥一般称为私钥,公钥和私钥合称密钥对。公钥是公开的,可以通过不安全的网络传输;私钥则是保密的。公钥加密,私钥解密。当Alice想与Bob安全通信时,Alice先生成密钥对,接着将公钥发送给Bob,Bob用拿到的公钥加密要通信的内容然后将密文发送给Alice,Alice则用手中的私钥进行解密。
在非对称加密算法中,几乎所有的算法都是基于数学问题而建立的。RSA算法基于大数因子分解数学难题,其他的非对称加密算法如Rabin利用了mod N下求平方根的困难,EIGamal算法和ECC算法(椭圆曲线密码,Elliptic Curve Cryptography)基于离散对数难题。
RSA算法的公钥长度<<私钥长度。公钥密码加密速度比对称密码加密速度慢大约几百倍。
package com.fulcrum.encrypt;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class RSADemo {
private static final String RSA_ALGORITHM = "RSA";
private static final String PUBLIC_KEY = "PUBLIC_KEY";
private static final String PRIVATE_KEY = "PRIVATE_KEY";
public static void main(String[] args) throws Exception{
Map<String, byte[]> skMap = initKey();
byte[] bytesPublicKey = skMap.get(PUBLIC_KEY);
byte[] bytesPrivateKey = skMap.get(PRIVATE_KEY);
String str = "滑坡谬误的逻辑错误";
System.out.println("原文:"+str);
byte[] cipherText = encryptByPublicKey(str.getBytes(StandardCharsets.UTF_8), bytesPublicKey);
System.out.println("密文:"+Base64.encodeBase64String(cipherText));
System.out.println("解密:"+decryptByPrivateKey(cipherText, bytesPrivateKey));
}
public static String decryptByPrivateKey(byte[] data, byte[] bytesPrivateKey) throws Exception{
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(bytesPrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(data));
}
public static byte[] encryptByPublicKey(byte[] data, byte[] bytesPublicKey) throws Exception{
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(bytesPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
public static Map<String, byte[]> initKey() throws Exception{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
byte[] bytesPublicKey = keyPair.getPublic().getEncoded();
byte[] bytesPrivateKey = keyPair.getPrivate().getEncoded();
System.out.println("公钥字节数:"+bytesPublicKey.length);
System.out.println("私钥字节数:"+bytesPrivateKey.length);
Map<String, byte[]> skMap = new HashMap<>();
skMap.put(PUBLIC_KEY, bytesPublicKey);
skMap.put(PRIVATE_KEY, bytesPrivateKey);
return skMap;
}
}
公钥密码解决了密钥配送问题,但无法判断得到的公钥是否正确合法,因为它解决不了中间人攻击问题:
当Alice与Bob利用公钥密码进行通信时,Bob生成一对密钥并将公钥发送给Alice,Alice拿到公钥然后加密信息,接着将加密后的信息发送给Bob,Bob利用自己的私钥解密得到明文。看起来一切都很美好,但是这一旦遭遇中间人攻击,通信就完全失效了。
Bob生成一对密钥并将公钥发送给Alice,这时网络中的一个叫Hacker的中间人截获了这个公钥并保存下来,接着Hacker自己生成一对密钥并将其中的公钥发送给Alice,Alice拿到公钥利用公钥加密信息然后发送给Bob,此时Hacker再次截获信息并利用自己的私钥解密了信息。Bob可以修改这个信息或者干脆重新生成一个替代信息,然后利用截获到的Bob发送的公钥加密信息并发送给Bob,Bob利用自己的私钥解密得到明文,于是Alice和Bob的通信内容完全被篡改了但是两人却浑然不知,这就是中间人攻击。仅靠公钥密码是无法防御中间人攻击的。
我们好不容易用公钥密码解决了密钥配送问题,又遇到了中间人攻击,真是一波未平一波又起。要防御中间人攻击,还需要一种手段来确认所收到的公钥确实属于Bob,这种手段称为认证。这种情况下,我们使用公钥的证书来解决。