从DH算法我们可以看到,公钥-私钥组成的密钥对是非常有用的加密方式,因为公钥是可以公开的,而私钥是完全保密的,由此奠定了非对称加密的基础。
    非对称加密就是加密和解密使用的不是相同的密钥:只有同一个公钥-私钥对才能正常加解密。
    因此,如果小明要加密一个文件发送给小红,他应该首先向小红索取她的公钥,然后,他用小红的公钥加密,把加密文件发送给小红,此文件只能由小红的私钥解开,因为小红的私钥在她自己手里,所以,除了小红,没有任何人能解开此文件。
    非对称加密的典型算法就是RSA算法,它是由Ron Rivest, Adi Shamir, Leonard Adleman 这三个哥们一起发明的,所以用他们仨的首字母缩写表示。
    非对称加密相比对称加密的显著优点在于,对称加密需要协商密钥,而非对称加密可以安全地公开各自的公钥,在N个人之间通信的时候:使用非对称加密只需要N个密钥对,每个人只管理自己的密钥对。 而使用对称加密则需要
    N*(n-1)/2个密钥,因此,每个人需要管理N-1个密钥,密钥管理难度大,而且非常容易泄漏。

    既然非对称加密这么好,那我们抛弃对称加密,完全使用非对称加密行不行?也不行,因为非对称加密的缺点就是运算速度非常慢,比对称加密要慢很多。
    所以,在实际应用的时候,非对称加密总是和对称加密一起使用。假设小明需要小红传输加密文件,他俩首先交换了各自的公钥,然后:

    1. 小明生成一个随机的AES口令,然后用小红的公钥通过RSA加密这个口令,并发给小红;
    2. 小红用自己的RSA私钥解密得到AES口令;
    3. 双方使用这个共享的AES口令,用AES 加密通信。

    可见非对称加密实际上应用在第一步,即加密“AES口令”,这也是我们在浏览器中常用的HTTPS协议的做法,即浏览器 和服务器先通过RSA交换AES口令,接下来双方通信实际上采用的是速度较快的AES对称加密,而不是缓慢的RSA非对称加密。
    Java标准库提供了RSA 算法的实现:

    1. public class Main {
    2. public static void main(String[] args) throws Exception{
    3. //明文
    4. byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
    5. //创建公钥/私钥对
    6. Person alice = new Person("Alice");
    7. //用Alice的公钥加密:
    8. byte[] pk = alice.getPublicKey();
    9. System.out.println(String.format("public key: %x",new BigInteger(1, pk));
    10. byte[] encrypted = alice.encrypt(plain);
    11. System.out.println(String.format("encrypted: %x",new BigInteger(1,encrypted));
    12. //用Alice的私钥解密:
    13. byte[] sk = alice.getPrivateKey();
    14. System.out.pritnln(String.format("private key: %x", new BigInteger(1,sk)));
    15. byte[] decrypted = alice.decrypt(encrypted);
    16. System.out.println(new String(decrypted,"UTF-8"));
    17. }
    18. }
    19. class Person {
    20. String name;
    21. //私钥
    22. PrivateKey sk;
    23. //公钥
    24. PublicKey pk;
    25. public Person(String name) throws Exception {
    26. this.name= name;
    27. //生成公钥/私钥对
    28. KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
    29. kpGen.initialize(1024);
    30. KeyPair kp = kpGen.generateKeyPair();
    31. this.sk = kp.getPrivate();
    32. this.pk = kp.getPublic();
    33. }
    34. //把私钥导出为字节
    35. public byte[] getPrivateKey(){
    36. return this.sk.getEncoded();
    37. }
    38. //把公钥导出为字节
    39. public byte[] getPublicKey(){
    40. return this.pk.getEncoded();
    41. }
    42. //用公钥加密
    43. public byte[] encrypt(byte[] message) throws GeneralSecurityException{
    44. Cipher cipher = Cipher.getInstance("RSA");
    45. cipher.init(Cipher.ENCRYPT_MODE, this.pk);
    46. return cipher.doFinal(message);
    47. }
    48. //用私钥解密
    49. public byte[] decrypt(byte[] input) throws GeneralSecurityException {
    50. Cipher cipher = Cipher.getInstance("RSA");
    51. cipher.init(Cipher.DECRYPT_MODE,this.sk);
    52. return cipher.doFinal(input);
    53. }
    54. }

    RSA的公钥和私钥都可以通过getEncoded()方法获得以byte[]表示的二进制数据,并根据需要保存到文件中。要从byte[]数组恢复公钥或私钥,可以这么写:

    1. byte[] pkData = new byte[] {};
    2. byte[] skData = new byte[] {};
    3. KeyFactory kf = KeyFactory.getInstance("RSA");
    4. //恢复公钥
    5. X509EncodedKeySpec pkSpec = new X509ENcodedKeySpec(pkData);
    6. PublicKey pk = kf.generatePublic(pkSpec);
    7. //恢复私钥
    8. PKCS8EncodedKeySpec skspec = new PKCS8EncodedKeySpec(skData);
    9. PrivateKey sk = kf.generatePrivate(skSpec);

    以RSA算法为例,它的密钥有256/512/1024/2048/4096等不同的长度。长度越长,密码强度越大,当然计算速度也越慢。
    如果修改待加密的byte[]数据的大小,可以发现,使用512bit的RSA加密时,明文长度不能超过53字节,使用1024bit的RSA加密时,明文长度不能超过177字节,这也是为什么使用RSA的时候,总是配合AES一起使用,即用AES 加密任意明文,用RSA加密AES口令。
    此外,只使用非对称加密算法不能防止中间人攻击。