非对称加密和对称加密算法相比,多了一把秘钥,为双秘钥模式,一个公开称为公钥,一个保密称为私钥。遵循公钥加密私钥解密,或者私钥加密公钥解密。非对称加密算法源于DH算法,后又有基于椭圆曲线加密算法的密钥交换算法ECDH,不过目前最为流行的非对称加密算法是RSA,本文简单记录下RSA的使用。
RSA算法
RSA算法是最为典型的非对称加密算法,该算法由美国麻省理工学院(MIT)的Ron Rivest、Adi Shamir和Leonard Adleman三位学者提出,并以这三位学者的姓氏开头字母命名,称为RSA算法。
RSA算法的数据交换过程分为如下几步:
- A构建RSA秘钥对;
- A向B发布公钥;
- A用私钥加密数据发给B;
- B用公钥解密数据;
- B用公钥加密数据发给A;
- A用私钥解密数据。
JDK8支持RSA算法:
算法 | 秘钥长度 | 加密模式 | 填充模式 |
---|---|---|---|
RSA | 512~16384位,64倍数 | ECB | NoPadding PKCS1Padding OAEPWithMD5AndMGF1Padding OAEPWithSHA1AndMGF1Padding OAEPWithSHA-1AndMGF1Padding OAEPWithSHA-224AndMGF1Padding OAEPWithSHA-256AndMGF1Padding OAEPWithSHA-384AndMGF1Padding OAEPWithSHA-512AndMGF1Padding OAEPWithSHA-512/224AndMGF1Padding OAEPWithSHA-512/2256ndMGF1Padding |
代码例子:
1 | import org.junit.Test; |
程序输出如下:
1 | RSA公钥: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKBvz9cma+hXNiv2yXg6e1PyZhHVZm3bJXDvTJP2LyXo4vs9grH36Q9kNgr6quHtuU6fEoUxUu2zbEB8dkEWB9UCAwEAAQ== |
可以看到,公钥加密私钥解密和私钥加密公钥解密的模式都可行。
公私钥获取
假如现在有RSA公钥:
1 | MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKBvz9cma+hXNiv2yXg6e1PyZhHVZm3bJXDvTJP2LyXo4vs9grH36Q9kNgr6quHtuU6fEoUxUu2zbEB8dkEWB9UCAwEAAQ== |
RSA私钥:
1 | MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAoG/P1yZr6Fc2K/bJeDp7U/JmEdVmbdslcO9Mk/YvJeji+z2CsffpD2Q2Cvqq4e25Tp8ShTFS7bNsQHx2QRYH1QIDAQABAkEAjemZXORdesz52/WVzEVepai6ZHfw/Kdl/PmPMSoIFmz7mk55rprl2Akn2V0odSiHSnMWvDmOUIAvHaHF4Re4wQIhAN5GxVeF7ndyoWasxqIOVb6baNkUrapBM0nacPS4WA8JAiEAuMcvNM2Z1rW74JagoGlSIfRkNUqa+3LTCN/fK7VR2W0CICs/+gYduVjkpSMlW0ENKQH9m1kh/Oiz5xbnujLj676BAiBVGif7wdXgtcLaJYXFW7ygNtcQVFQdCz13EOTQVKpl4QIgY2YyH3vUYI2J68qCGtYjj5iNHUEwwze+Za1R7y0V43k= |
需要将它们还原为PublicKey和PrivateKey对象,可以参考如下代码:
1 | import org.junit.Test; |
程序输出如下:
1 | RSA公钥加密数据: PdSr+WRUWIxbA7stmZ03GCwDBnE3CyFL43bTskJmBilY+9lL63Jt0KxN0S2A4ombxvngbiB8PVZiqj1oSkgWpA== |
分段加解密
RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题。明文长度需要小于密钥长度,而密文长度则等于密钥长度。因此当加密内容长度大于密钥长度时,有效的RSA加解密就需要对内容进行分段。
这是因为,RSA算法本身要求加密内容也就是明文长度m必须满足0<m<密钥长度n
。如果小于这个长度就需要进行padding,因为如果没有padding,就无法确定解密后内容的真实长度,字符串之类的内容问题还不大,以0作为结束符,但对二进制数据就很难,因为不确定后面的0是内容还是内容结束符。而只要用到padding,那么就要占用实际的明文长度,于是实际明文长度需要减去padding字节长度。我们一般使用的padding标准有NoPPadding、OAEPPadding、PKCS1Padding等,其中PKCS#1建议的padding就占用了11个字节。
以秘钥长度为1024bits为例:
1 | import org.junit.Test; |
程序会抛出如下异常:
1 | 待加密内容长度: 120 |
对于1024长度的密钥。128字节(1024bits/8)减去PKCS#1建议的padding就占用了11个字节正好是117字节。所以加密的明文长度120字节大于117字节,程序抛出了异常。
要解决这个问题,可以采用分段加密的手段。编写一个分段加解密的工具类:
1 | import javax.crypto.Cipher; |
测试:
1 | import org.junit.Test; |
程序输出如下:
1 | 待加密内容长度: 120 |
建议
公钥是通过A发送给B的,其在传递过程中很有可能被截获,也就是说窃听者很有可能获得公钥。如果窃听者获得了公钥,向A发送数据,A是无法辨别消息的真伪的。因此,虽然可以使用公钥对数据加密,但这种方式还是会有存在一定的安全隐患。如果要建立更安全的加密消息传递模型,就需要AB双方构建两套非对称加密算法密钥,仅遵循“私钥加密,公钥解密”的方式进行加密消息传递;
RSA不适合加密过长的数据,虽然可以通过分段加密手段解决,但过长的数据加解密耗时较长,在响应速度要求较高的情况下慎用。一般推荐使用非对称加密算法传输对称加密秘钥,双方数据加密用对称加密算法加解密。