非对称密钥加密算法有两个密钥:公钥(Public Key)和私钥(Private Key)。如果用公钥加密,只能用私钥解密还原,加密和解密使用的是不同密钥,因此叫非对称密钥加密算法。
最常用的非对称密钥加密算法是RSA,除此之外还有ECC(椭圆曲线算法)等。
1976年,美国计算机学家Whitfield Diffie和Martin Hellman发明了Diffie-Hellman密钥交换算法。
基于Diffie-Hellman密钥交换算法的原理,1977年,美国麻省理工的三名科学家Ron Rivest、Adi Shamir和Leonard Adleman共同提出了RSA算法。
RSA需要进行大整数运算,因此性能较低,一般不直接用于加密大量数据的场景。除此之外,RSA广泛运用于密钥交换、签名等领域。
RSA的一个应用领域就是密钥交换。我们知道,为了加密通信就需要交换密钥,然而明文传输密钥不安全,加密传输又需要已有一个密钥,因此这种情况下就陷入了死循环。
后来出现了Diffie-Hellman密钥交换算法巧妙的解决了这一问题。在其基础上发展出来的RSA也能够用于密钥交换,流程如下:
数字签名的作用和现实中“签名”类似,就是认证签署人的身份,其左右为两个方面,确认发送人的身份,以及防抵赖。流程如下:
一些软件、服务程序的配置中经常需要配置RSA密钥,RSA密钥在存储格式方面有很多标准,这里简单介绍一下。
X.509是ITU-T(国际电信联盟电信标准分局)和国际标准化组织(ISO)制定的公钥证书标准,标准包括证书公钥格式、身份信息和签名信息。我们日常使用的TLS/SSL使用的就是X.509证书。
PKCS(Public Key Cryptography Standards)即公钥密码标准,是由美国RSA数据安全公司制定的一组公钥密码学标准,其中包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。
PKCS#1标准定义了RSA 密码学规范,内容包括RSA密钥文件的格式和编码方式,以及加解密、签名、填充的基础算法。PKCS#8是后出的私钥格式标准,它除了RSA还支持其他算法私钥,PKCS#8支持基于PBE的密钥加密存储,因此我们能够对其设定密码口令。PKCS#12则定义了一种存档文件格式,用于实现存储加密对象在一个单独的文件中,可以用作密钥库。
对于RSA私钥,比较常用的就是PKCS#1和PKCS#8格式,后者因为可以设置PBE密码加密,因此安全性更高。
PEM和DER是两种比较常见的证书、密钥文件编码格式,两者的区别是PEM为BASE64编码的文本密钥文件,DER为二进制密钥文件,其内部数据格式相同的前提下可以直接用BASE64编解码转换。DER格式比较方便程序读取,而PEM格式是为了邮件等方式传输而出现的。PEM文本中会有标签头尾,例如:
-----BEGIN PUBLIC KEY-----
(此处为具体BASE64编码)
-----END PUBLIC KEY-----
一些常见的标签:
CERTIFICATE:X.509证书。
PUBLIC KEY:X.509主体公钥。
RSA PUBLIC KEY:PKCS#1 RSA公钥。
RSA PRIVATE KEY:PKCS#1 RSA私钥。
PRIVATE KEY:PKCS#8私钥。
ENCRYPTED PRIVATE KEY:加密的PKCS#8私钥。
注意:PEM和DER是编码格式文件,不是指里面具体内容,里面可以是密钥,可以是证书,或是可能的其他内容。
我们可以用openssl
命令行工具生成密钥对的PEM文件,生成PEM格式密钥对:
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -out public.pem -pubout
openssl默认会生成PEM格式的RSA密钥文件,公钥内部数据为X.509的公钥格式,私钥为PKCS#1格式。我们也可以将密钥对转为DER格式:
openssl rsa -in private.pem -out private.der -outform der
openssl rsa -in public.pem -out private.der -pubin -outform der
上述命令将密钥文件格式转为了DER格式,但内部数据依然保持原样,这里我们也可以将私钥转换为常用的PKCS#8格式:
openssl pkcs8 -topk8 -inform der -in private.der -outform der -nocrypt -out private_pkcs8.der
JDK默认公钥支持X.509格式,私钥支持PKCS#8格式。下面代码读取了之前用openssl
生成的密钥文件。
byte[] data = "Hello, world!".getBytes();
// 读取公钥
byte[] pubKeyBytes = FileUtils.readFileToByteArray(new File("E:/public.der"));
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKeyBytes);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);
// 读取私钥
byte[] priKeyBytes = FileUtils.readFileToByteArray(new File("E:/private_pkcs8.der"));
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(priKeyBytes);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(pkcs8EncodedKeySpec);
// 加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encData = cipher.doFinal(data);
// 解密
cipher.init(Cipher.DECRYPT_MODE, privateKey);
data = cipher.doFinal(encData);