## 引言
在现实世界中,每个人都有自己的密码。在各种系统中都有各类加密和解密的需求。 本文将详细介绍一下RSA的前身后世,应用场景和在Java中的实现,从理论到实践,一步到位,触手可用。
## 非对称加密与对称加密
### 对称加密(Symmetric Cryptography)
对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key),这种方法在密码学中叫做对称加密算法。对称加密有很多种算法,由于它效率很高,所以被广泛使用在很多加密协议的核心当中。
关于对称加密的概念非常容易理解,在现实实际中应用广泛,具体的加密算法都是依赖于秘钥的长度和加解密所需的资源和时间,这里需要一个平衡的。
比如DES, 3DES, AES等等都属于对称加密算法。
### 非对称加密(Asymmetric Cryptography)
1976年,美国学者Dime和Henman为解决信息公开传送和密钥管理问题,提出一种新的密钥交换协议,允许在不安全的媒体上的通讯双方交换信息,安全地达成一致的密钥,这就是“公开密钥系统”。相对于“对称加密算法”这种方法也叫做“非对称加密算法”。
非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥、公钥(public key)和私钥(private key)。私钥只能由一方安全保管,不能外泄。而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。比如,你向银行请求公钥,银行将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人--银行才能对你的消息解密。与对称加密不同的是,银行不需要将私钥通过网络发送出去,因此安全性大大提高。
公私钥体系很好地解决了陌生人通信中加密和解密的问题, 但是加密和解密速度比较慢,如果数据量比较大,则用其加解密不太现实,时间和资源所需都比较大。
非对称加密体系下的数据传输过程,可以参照下图:
在上图中,加密和解密分别使用不同的秘钥,从而很好地解决了秘钥传输的问题,不同的人可以使用不同的秘钥来加解密数据。 这里还有一个非常重要的概念是CA中心,Certificate Authority证书中心的概念。 由于公钥是可以被所有人任意获取的,公开使用的,证书之间也可以基于签名来建立一个信任的链条。从而针对证书建立起来一整套追踪和管理的体系。这里不再赘述这个内容,感兴趣的读者可以参照CA的证书体系获取更多信息。
在这幅图中,体现了CA的价值。CA主要是在商业系统中用以分发和管理证书的一套系统,默认情况下,也可以直接线下发送证书。这里仅仅是为大家普及一个CA概念。
## RSA
在非对称加密算法中,存在着RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)等诸多算法,相比而言RSA是其中应用最为广泛的一种加密算法。
RSA公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。1987年7月首次在美国公布,当时他们三人都在麻省理工学院工作实习。RSA就是他们三人姓氏开头字母拼在一起组成的。
RSA是目前最有影响力和最常用的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。今天只有短的RSA钥匙才可能被强力方式解破。到2008年为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被解破的。但在分布式计算和量子计算机理论日趋成熟的今天,RSA加密安全性受到了挑战和质疑。
RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
对于普通的开发者而言,对于这些信息仅需要了解即可,对于大家来说,了解其原理是为了更好地使用这些算法来服务于我们的系统。
## RSA的应用场景(通信,签名和完整性)
### 通信加解密
由于非对称加密方法中存在加解密速度慢的问题,对于大的数据由于时间和资源的约束无法商业使用,则采取了这种的方法进行加密:
**.** 使用对称秘钥进行内容加密,比如DES之类的算法,速度快
**.** 使用非对称算法针对秘钥进行加密, 确保秘钥的安全
**.** 一事一秘, 每次通信可以采取动态的秘钥,每次都动态生成秘钥
目前来说,主流的通信机制都是采用这种方式,这个是原理上的方式,但不是实际商业上的方式,实际上的商业方式,相比这种方式会更复杂,具体的一种方式如下:
- 通信双方A和B,各自持有一对公私钥对,且且持有对方的公钥
- 基于公钥信息,加密通信认证信息,建立可信任连接
- 在建立可信任连接之后,动态创建一套公私钥对C,在A和B之间共享
- 使用动态生产呢个的公私钥对,来进行实际的通信。
相比于原理上的非对称加密通信而言,实际中的通信机制往往会更复杂,考虑到强度和安全性的问题。
### 签名认证
除了加解密之外,还有在实际中广泛使用的签名认证。 签名证书的用途:加入有A用户向B用户发送了信息“A love you”,B接收到之后怎么确定就是A发送的呢(因为中间人攻击中,假如C截获了A发送的信息后,改成“C love you”,B是无法确定这两个信息的发送者,因他她们都同样用B的工要加密),于是产生了签名证书技术。
签名证书是由CA用自己的私钥对用户的信息进行加密,并把发给用户的,用户可以在发送信息的过程中附加上自己的签名证书,让接受者验证信息的来源。当接受者接受到发送者的签名证书后即可用ca的公钥解密验证发送者的身份信息。
## RSA在Java的实现示例
主要的Java实现如下:
```
public class RSAUtil {
public static final String RSA_ALGORITHM = "RSA";
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
public static final Charset UTF8 = Charset.forName("UTF-8");
/**
* 入口的publicKey是基于Base64编码过的,需要解码
* data为实际的数据, sign:摘要信息
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
// 解密由base64编码的公钥
byte[] keyBytes = base64Decode(publicKey);
// 构造X509EncodedKeySpec对象
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密算法
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
// 取公钥匙对象
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
// 验证签名是否正常
return signature.verify(base64Decode(sign));
}
/**
* 基于字符数组,获取公钥信息。
*/
public static PublicKey getPublicKey(byte[] publicKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(publicKeyBytes);
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(pubSpec);
return publicKey;
}
/**
* 基于字符数组获取privateKey私钥信息
*/
public static PrivateKey getPrivatekey(byte[] privateKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);
return privateKey;
}
/**
* 这里并未强制要求数据使用Base64进行编码或者解码操作,开发者自行决定。
*/
public static byte[] encrypt(PrivateKey privateKey, byte[] bytes) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(bytes);
}
/**
* 这里并未强制要求数据使用Base64进行编码或者解码操作,开发者自行决定。
*/
public static byte[] decrypt(PublicKey publicKey, byte[] encrypted) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(encrypted);
}
private static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
/**
* base64的编码
*/
public static String base64Encode(byte[] data) {
return new BASE64Encoder().encode(data);
}
/**
* base64的解码
*/
public static byte[] base64Decode(String data) throws IOException {
return new BASE64Decoder().decodeBuffer(data);
}
}
```
在上述的代码示例中,提供了加密和解密方法,但是都并未要求使用base64解码和编码实现,开发者可以自行决定是否使用,在外层方法嵌套或者将base64方法放入decrpt/encrpt方法之内实现。
在此实现中提供了verify方法用以验证sign是否正确,签名认证方法。
这里使用了JDK中rt.jar中内置的sun.misc中的Base64的编码和解码方法,如果不喜欢依赖于Oracle的JDK,可以选用Apache的codec包,在maven中配置如下依赖接口:
```
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
```
具体的使用方法,可以参考网上的相关资料。
## 总结
加解密的算法虽然其中涵盖了复杂的数据算法,但是使用起来还是非常简单方便的,只要了解其中的主要原理和应用场景,把握重要的算法关键点,即可满足我们的基本需求。
## 关于我自己
这些技巧和总结都是来自于实际的工作,欢迎大家反馈和提出自己的意见。
文章也会同步发在CSDN的个人博客上,木小鱼的笔记(http://blog.csdn.net/blueheart20)。