java安全--加密

【0】README
1)本文文字描述转自 core java volume 2,旨在学习 java安全——加密 的基础知识;
2)java 安全性的第二个重要方面是加密。
3)认证对于代码签名已足够了-没必要将代码隐藏起来。但是,当applet或者应用程序传输机密信息时,比如信用卡号码和其他个人数据等,就有必要进行加密了。(干货——加密的应用背景)

【1】对称密码(加密和解密都使用相同的密钥)
(干货——对称密码定义:加密和解密都使用相同的密钥)
1)Cipher类: 该类是所有加密算法的超类。
(cipher——
    - n. 密码;暗号;零
    - vi. 使用密码;计算;做算术
    - vt. 计算;做算术;将…译成密码

1.1)获取密码对象:通过调用下面的getInstance方法可以获得一个密码对象:
[java]  view plain  copy
  1. Cipher cipher = Cipher.getInstance(algorithName);  

1.2)获取密码对象:或者调用下面这个方法:

[java]  view plain  copy
  1. Cipher cipher = Cipher.getInstance(algorithName, providerName);  

2)SunJCE:JDK中是由名为"SunJCE"的提供商提供密码,如果没有指定其他提供商,则会默认该提供商。

3)算法名称:是一个字符串,比如"AES" 或者 "DES/CBC/PKCS5Padding"。
3.1)DES(data encoding standard): 即数据加密标准,是一个密钥长度为56位的古老的分组密码。
3.2)AES(advanced encoding standard): 更好的选择是采用它的后续版本,即高级加密标准——AES;(干货——高级加密标准——AES
4)获得一个密码对象后, 
step1)就可以通过设置模式和密钥来对它初始化。
 
   
[java]  view plain  copy
  1. int mode = . . .;  
  2. Key key = . . .;  
  3. cipher.init(mode, key);  
Attention)
A1)模式有以下几种:
 
    
[java]  view plain  copy
  1. Cipher.ENCRYPT_MODE  
  2. Cipher.DECRYPT_MODE  
  3. Cipher.WRAP_MODE  
  4. Cipher.UNWRAP_MODE  
A2)wrap 和 unwrap模式用一个密钥对另一个密钥进行加密;
step2)反复调用update方法来对数据块进行加密。

step3) 完成上述操作后,还必须调用一次doFinal方法。

step3.1)如果有最后一个输入数据块(其字节数小于blockSize),那么就要调用:

[java]  view plain  copy
  1. outBytes = cipher.doFinal(inBytes, 0, inLength);  

step3.2)如果所有的输入数据都已经加密,则用下面的方法调用来代替:

[java]  view plain  copy
  1. outBytes = cipher.doFinal();  
Attention) 对doFinal的调用是必要的,以便对最后的块进行"填充"。

【2】密钥生成(吧密钥看做一个数学函数)
1)生成密钥的steps
step1)  为加密算法获取KeyGenerator。

step2) 用随机源来初始化密钥发生器。如果密码块的长度是可变的,还需要指定期望的密码块长度。

step3) 调用generateKey方法。

2)看个荔枝:下面是如何生成AES密钥的方法

 
  
[java]  view plain  copy
  1. keyGenerator keygen = KeyGenerator.getInstance("AES");  
  2. SecureRandom random = new SecureRandom();  
  3. keygen.init(random);  
  4. Key key = keygen.generateKey();  
2.1)或者,可以从一组固定的原始数据(也许是由口令或者随机击键产生的)来生成一个密钥,这时可以使用如下的SecretKeyFactory: (干货——引入SecretKeyFactory
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("AES");
[java]  view plain  copy
  1. byte[] keyData = . . .; // 16 bytes for AES  
  2. SecretKeySpec keySpec = new SecretKeySpec(keyData, "AES");  
  3. Key key = keyFactory.generateSecret(keySpec);  

3)problem+solution:

3.1)problem: 如果要生成密钥,必须使用"真正的随机"数。例如,在Random类中的常规的随机数发生器,是根据当前的日期和时间来产生随机数的,因此它不够随机。例如,假设计算机时钟可以精确到1/10秒,那么,每天最多存在864,000个种子。如果攻击者知道发布密钥的日期(通常可以由截止日期推算出来),那么就可以很容易地生成那一天所有可能的种子。

3.2)solution: SecureRandom类产生的随机数,远比由Random类产生的那些数字安全得多。一旦你在字节数组中收集到随机位后,就可以将它传递给setSeed方法。(干货-SecureRandom类产生的随机数,远比由Random类产生的那些数字安全得多。) 

[java]  view plain  copy
  1. SecureRandom secrand = new SecureRandom();  
  2. byte[] b = new byte[20];  
  3. // fill with truly random bits  
  4. secrand.setSeed(b);  
Attention)  如果没有为随机数发生器提供种子,那么它将通过启动线程,使它们睡眠,然后测量它们被唤醒的准确时间,来计算自己的20个字节的种子。

4)代码列表:
[java]  view plain  copy
  1. package com.corejava.chapter9.cryption;  
  2.   
  3. import java.io.*;  
  4. import java.security.*;  
  5. import javax.crypto.*;  
  6.   
  7. public class AESTest  
  8. {  
  9.    public static void main(String[] args)   
  10.       throws IOException, GeneralSecurityException, ClassNotFoundException  
  11.    {  
  12.       if (args[0].equals("-genkey")) // 产生密钥  
  13.       {  
  14.           // 获取密钥生成器  
  15.          KeyGenerator keygen = KeyGenerator.getInstance("AES");  
  16.          // 创建随机源  
  17.          SecureRandom random = new SecureRandom();  
  18.          // 用随机源来初始化密钥发生器  
  19.          keygen.init(random);  
  20.          // 生成密钥  
  21.          SecretKey key = keygen.generateKey();  
  22.          try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(args[1])))  
  23.          {  
  24.             out.writeObject(key); // 写出密钥到文件  
  25.          }  
  26.       }  
  27.       else // 加密或者解密  
  28.       {  
  29.          int mode;  
  30.          // 加密(解密)模式  
  31.          if (args[0].equals("-encrypt")) mode = Cipher.ENCRYPT_MODE;  
  32.          else mode = Cipher.DECRYPT_MODE;  
  33.   
  34.          // 带资源的try 语句, args[3]==secret.key  
  35.          try (ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream(args[3]));  
  36.             InputStream in = new FileInputStream(args[1]);  
  37.             OutputStream out = new FileOutputStream(args[2]))  
  38.          {  
  39.             Key key = (Key) keyIn.readObject();  
  40.             Cipher cipher = Cipher.getInstance("AES");  
  41.             cipher.init(mode, key); // 设置模式和密钥对其初始化  
  42.             Util.crypt(in, out, cipher);  
  43.          }  
  44.       }  
  45.    }  
  46. }  
[java]  view plain  copy
  1. package com.corejava.chapter9.cryption;  
  2.   
  3. import java.io.*;  
  4. import java.security.*;  
  5. import javax.crypto.*;  
  6.   
  7. public class Util  
  8. {  
  9.    public static void crypt(InputStream in, OutputStream out, Cipher cipher) throws IOException,  
  10.          GeneralSecurityException  
  11.    {  
  12.       int blockSize = cipher.getBlockSize();  
  13.       int outputSize = cipher.getOutputSize(blockSize);  
  14.       byte[] inBytes = new byte[blockSize];  
  15.       byte[] outBytes = new byte[outputSize];  
  16.   
  17.       int inLength = 0;  
  18.       boolean more = true;  
  19.       while (more)  
  20.       {  
  21.           // inBytes 就是一个缓冲区  
  22.          inLength = in.read(inBytes);  
  23.          if (inLength == blockSize)  
  24.          {  
  25.             // 只要输入数据块具有全长度(长度能够被8整除): 就要调用update方法;   
  26.             int outLength = cipher.update(inBytes, 0, blockSize, outBytes);  
  27.             out.write(outBytes, 0, outLength);  
  28.          }  
  29.          else more = false;  
  30.       }  
  31.       // 而如果输入数据块不具有全长度(长度不能被8整除,此时需要填充): 就要调用 doFinal 方法;  
  32.       if (inLength > 0)   
  33.           outBytes = cipher.doFinal(inBytes, 0, inLength);  
  34.       else  
  35.           outBytes = cipher.doFinal();  
  36.       out.write(outBytes);  
  37.    }  
  38. }  
5)看个荔枝:

5.1)请按照如下 steps 使用上述程序:

step1)首先生成一个密钥,运行如下命令行:

[cpp]  view plain  copy
  1. java AESTest -genkey secret.key  

step2)密钥就被保存在secret.key文件中了。现在可以用如下命令进行加密:

[cpp]  view plain  copy
  1. java AESTest -encrypt plaintextFile encryptedFile secret.key  

step3)用如下命令进行解密:

[cpp]  view plain  copy
  1. java AESTest -decrypt encryptedFile decryptedFile secret.key  
[cpp]  view plain  copy
  1. E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>javac com/corejava/chapter9/cryption/AES  
  2. Test.java  
  3. E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java com.corejava.chapter9.cryption.AEST  
  4. est -genkey com/corejava/chapter9/cryption/secret.key  
  5. E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java com.corejava.chapter9.cryption.AEST  
  6. est -encrypt com/corejava/chapter9/cryption/input.txt com/corejava/chapter9/cryption/output.txt com/  
  7. corejava/chapter9/cryption/secret.key  
  8. E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java com.corejava.chapter9.cryption.AEST  
  9. est -encrypt com/corejava/chapter9/cryption/input.txt com/corejava/chapter9/cryption/encrypted.txt c  
  10. om/corejava/chapter9/cryption/secret.key  
  11. E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java com.corejava.chapter9.cryption.AEST  
  12. est -decrypt com/corejava/chapter9/cryption/encrypted.txt com/corejava/chapter9/cryption/decrypted.t  
  13. xt com/corejava/chapter9/cryption/secret.key  
Conclusion)
C1)该程序非常直观。使用-genkey选项将产生一个新的密钥,并且将其序列化到给定的文件中。
C2)该操作需要花费较长的时间,因为密钥随机生成器的初始化非常耗费时间。
C3) -encrypt 和 -decrypt选项都调用相同的crypt方法,而crypt方法则调用密码的update 和 doFinal方法。
Attention) 请注意update方法和doFinal方法是怎样被调用的?(干货——update方法和doFinal方法的被调用条件的区别
A1)只要输入数据块具有全长度(长度能够被8整除): 就要调用update方法;
A2)而如果输入数据块不具有全长度(长度不能被8整除,此时需要填充): 或者没有更多额外的数据(以便生成一个填充字节),那么就要调用doFinal方法;

【3】密码流
1) JCE(Java Cryptography Extension,java加密扩展)库: 提供了一组使用非常方便的数据类,用于对流进行自动加密或解密。
2)看荔枝:
2.1)下面是对文件数据进行加密的方法:
 
   
[java]  view plain  copy
  1. Cipher cipher = . . .;  
  2. cipher.init(Cipher.ENCRYPT_MODE, key);  
  3. CipherOutputStream out = new CipherOutputStream(new FileOutputStream(outputFileName), cipher);  
  4. byte[] bytes = new byte[BLOCKSIZE];  
  5. int inLength = getData(bytes); // get data from data source  
  6. while (inLength != -1)  
  7. {  
  8. out.write(bytes, 0, inLength);  
  9. inLength = getData(bytes); // get more data from data source  
  10. }  
  11. out.flush();  
2.2)可以使用CipherInputStream,对文件的数据进行读取和解密:
 
   
[java]  view plain  copy
  1. Cipher cipher = . . .;  
  2. cipher.init(Cipher.DECRYPT_MODE, key);  
  3. CipherInputStream in = new CipherInputStream(new FileInputStream(inputFileName), cipher);  
  4. byte[] bytes = new byte[BLOCKSIZE];  
  5. int inLength = in.read(bytes);  
  6. while (inLength != -1)  
  7. {  
  8. putData(bytes, inLength); // put data to destination  
  9. inLength = in.read(bytes);  
  10. }  
Attention)  密码流类能够透明地进行update 和 doFinal方法的调用,所以非常方便。

【4】公共密钥密码(SSH免密码登录的加密解密形式)
1)problem+solution:
1.1)problem: AES密码是一种对称密码,加密和解密都使用相同的密钥。对称密码的致命缺点在于密码的分发。如果Alice给Bob发送一个加密方法,那么Bob需要使用与Alice相同的密钥。如果Alice修改了密钥,那么她必须在给Bob发送信息的同时,还要通过安全信道发送新的密钥,但是也许她并不拥有到达Bob的安全信道,这就是为什么她必须首先对她发送给Bob的信息进行加密的原因。
1.2)solution: 公共密钥密码技术解决了这个问题。
2)公共密钥技术的idea: 在公共密钥密码中,Bob拥有一个密钥对,包括一个公共密钥和一个相匹配的私有密钥。Bob可以在任何地方公布公共密钥,但是他必须严格保守他的私有密钥。Alice只需要使用bob 的 公共密钥对她发送给Bob的信息进行加密即可。(干货——当Alice需要向Bob 发送加密文件时,那Alice就需要使用 Bob 的公共密钥对她发送给Bob的信息进行加密
3)solution+problem:
3.1)problem实际上,加密过程并没有那么简单。所有已知的公共密钥算法的操作速度都比对称密钥算法(比如DES或AES等)慢得多,使用公共密钥算法对大量的信息进行加密是不切实际的。
3.2)solution但是,如果像下面这样,将公共密钥密码与快速的对称密码结合起来,这个问题就可以得到解决:(干货——这里涉及到两次加密——对明文加密 + 对对称密钥加密)

s1 Alice生成一个随机对称加密密钥,她用该密钥对明文进行加密。(第一次加密:对明文加密)

s2 Alice用Bob的公共密钥给对称密钥进行加密。(第二次加密:对对称密钥加密)

s3Alice将加密后的对称密钥和加密后的明文同时发送给Bob。

s4 Bob用他的私有密钥给对称密钥解密。

s5) Bob用解密后的对称密钥给信息解密。

4)最普通的公共密钥算法: 是Rivest, Shamir, 和 Adleman发明的RSA算法。

5)如何使用RSA算法?

step1)如果要使用RSA算法,需要一对公共/私有密钥。你可以按如下方法使用KeyPairGenerator来获得:

 
   
[java]  view plain  copy
  1. KeyPairGenerator pairgen = KeyPairGenerator.getInstance("RSA");  
  2. SecureRandom random = new SecureRandom();  
  3. pairgen.initialize(KEYSIZE, random);  
  4. KeyPair keyPair = pairgen.generateKeyPair();  
  5. Key publicKey = keyPair.getPublic();  
  6. Key privateKey = keyPair.getPrivate();  
step2)程序有三个选项。 -genkey选项用于产生一个密钥对,-encrypt选项用于生成AES密钥,并且用公共密钥对其进行包装。
[java]  view plain  copy
  1. Key key = . . .; // an AES key  
  2. Key publicKey = . . .; // a public RSA key  
  3. Cipher cipher = Cipher.getInstance("RSA");  
  4. cipher.init(Cipher.WRAP_MODE, publicKey);  
  5. byte[] wrappedKey = cipher.wrap(key);  

step2.1)然后它便生成一个包含下列内容的文件(files):

f1)包装过的密钥的长度;

f2)包装过的密钥字节;

f3)用AES密钥加密的明文;

Attention)-decrypt选项:  用于对这样的文件进行解密。

6)代码列表

[java]  view plain  copy
  1. package com.corejava.chapter9.cryption;  
  2.   
  3. import java.io.DataInputStream;  
  4. import java.io.DataOutputStream;  
  5. import java.io.FileInputStream;  
  6. import java.io.FileOutputStream;  
  7. import java.io.IOException;  
  8. import java.io.InputStream;  
  9. import java.io.ObjectInputStream;  
  10. import java.io.ObjectOutputStream;  
  11. import java.io.OutputStream;  
  12. import java.security.GeneralSecurityException;  
  13. import java.security.Key;  
  14. import java.security.KeyPair;  
  15. import java.security.KeyPairGenerator;  
  16. import java.security.SecureRandom;  
  17.   
  18. import javax.crypto.Cipher;  
  19. import javax.crypto.KeyGenerator;  
  20. import javax.crypto.SecretKey;  
  21.   
  22. public class RSATest  
  23. {  
  24.     private static final int KEYSIZE = 512;  
  25.     public static void main(String[] args) throws GeneralSecurityException, IOException, ClassNotFoundException   
  26.     {  
  27.         if(args[0].equals("-genkey")) // 生成密钥对(公钥+私钥)  
  28.         {  
  29.             // 密钥对生成器  
  30.             KeyPairGenerator pairgen = KeyPairGenerator.getInstance("RSA");  
  31.             SecureRandom sr = new SecureRandom();  
  32.             pairgen.initialize(KEYSIZE, sr); // 密钥对生成器初始化  
  33.             KeyPair pair = pairgen.generateKeyPair(); // 生成密钥对(公钥+私钥)  
  34.               
  35.             try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(args[1])))  
  36.             {  
  37.                 out.writeObject(pair.getPublic()); // 写入公钥到文件  
  38.             }  
  39.             try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(args[2])))  
  40.             {  
  41.                 out.writeObject(pair.getPrivate()); // 写入私钥到文件  
  42.             }  
  43.         }  
  44.         else if(args[0].equals("-encrypt")) // 加密模块  
  45.         {  
  46.             // 生成密钥  
  47.             KeyGenerator keygen = KeyGenerator.getInstance("AES");  
  48.             SecureRandom sr = new SecureRandom();  
  49.             keygen.init(sr);  
  50.             SecretKey key = keygen.generateKey();  
  51.               
  52.             // wrap with RSA public key  
  53.             // args[3]==public.key,args[2]==encryptedFile,args[1]=inputFile  
  54.             try(ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream(args[3]));  
  55.                 DataOutputStream dataOut = new DataOutputStream(new FileOutputStream(args[2]));  
  56.                     InputStream in = new FileInputStream(args[1]))  
  57.             {  
  58.                 Key publicKey = (Key)keyIn.readObject();// 读入公钥  
  59.                 Cipher cipher = Cipher.getInstance("RSA");// RSA密码对象  
  60.                 cipher.init(Cipher.WRAP_MODE, publicKey); // 通过设置打包模式和公钥 来对RSA密码对象进行初始化  
  61.                   
  62.                 byte[] wrappedKey = cipher.wrap(key);// 通过带有公钥的RSA算法对象给密钥加密  
  63.                 dataOut.writeInt(wrappedKey.length); // 将加密后的密钥写入到输出流  dataOut  
  64.                 dataOut.write(wrappedKey);  
  65.                   
  66.                 cipher = Cipher.getInstance("AES"); // AES 密码对象  
  67.                 cipher.init(Cipher.ENCRYPT_MODE, key); // 通过设置加密模式和密钥 来对 AES 密码对象进行初始化  
  68.                 Util.crypt(in, dataOut, cipher); // 利用AES密码对象对inFile 进行加密并写入到输出流 dataOut  
  69.             }   
  70.         }  
  71.         else // 解密模块  
  72.         {  
  73.             //args[1]==encryptedFile,args[3]=private.key,args[2]=decryptedFile;  
  74.             try(DataInputStream dataIn = new DataInputStream(new FileInputStream(args[1]));  
  75.                     ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream(args[3]));  
  76.                         OutputStream out = new FileOutputStream(args[2]))  
  77.             {   
  78.                 int length = dataIn.readInt();  
  79.                 byte[] wrappedKey = new byte[length];  
  80.                 dataIn.read(wrappedKey, 0, length); // 读入加密后的文件(经过公钥加密后的密钥 和 经过密钥加密后的文件内容)  
  81.                   
  82.                 // unwrap with RSA private key  
  83.                 Key privateKey = (Key)keyIn.readObject(); // 读入private.key 到 wrappedKey  
  84.                   
  85.                 Cipher cipher = Cipher.getInstance("RSA");  
  86.                 cipher.init(Cipher.UNWRAP_MODE, privateKey); // 通过设置解包模式和私钥 来对RSA密码对象进行初始化  
  87.                 // 通过带有私钥的RSA算法对象给密钥解密  
  88.                 Key key = cipher.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY);  
  89.                   
  90.                 cipher = Cipher.getInstance("AES"); // AES 密码对象  
  91.                 cipher.init(Cipher.DECRYPT_MODE, key);   // 通过设置解密模式和密钥 来对 AES 密码对象进行初始化                  
  92.                 Util.crypt(dataIn, out, cipher); // 通过使用解密后的密钥 对 加密后的文件内容 进行解密并写入到输出流 out  
  93.             }   
  94.         }  
  95.     }  
  96. }<strong>  
  97. </strong>  

7)运行该程序的steps:

step1)首先生成RSA密钥:

java RSATest -genkey public.key private.key

step2)然后对一个文件进行加密:

java RSATest -encrypt plaintextFile encryptedFile public.key

step3)最后,对文件进行解密,并且检验解密后的文件是否与明文相匹配。

java RSATest -decrypt encryptedFile decryptedFile private.key
[java]  view plain  copy
  1. 最后的执行结果:  
  2. E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java com.corejava.chapter9.cryption.RSAT  
  3. est -genkey com/corejava/chapter9/cryption/public.key com/corejava/chapter9/cryption/private.key  
  4. E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java com.corejava.chapter9.cryption.RSAT  
  5. est -encrypt com/corejava/chapter9/cryption/rsa_input.txt com/corejava/chapter9/cryption/rsa_encrypt  
  6. ed.txt com/corejava/chapter9/cryption/public.key  
  7. E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java com.corejava.chapter9.cryption.RSAT  
  8. est -decrypt com/corejava/chapter9/cryption/rsa_encrypted.txt com/corejava/chapter9/cryption/rsa_dec  
  9. rypted.txt com/corejava/chapter9/cryption/private.key  
Conclusion)
你现在已经看到了Java安全模型是如何允许我们去控制代码的执行的,这是Java平台的一个独一无二且越来越重要的方面。你已经看到了Java类库提供的认证和加密服务。

Attention)但是我们没有涉及许多高级和专有的话题,比如有:

A1)提供了对Kerberos协议进行支持的"通用安全服务"的GSS-API(原则上同样支持其他安全信息交换协议)。

A2)对SASL的支持,SASL即简单认证和安全层,可以为LDAP和IMAP协议所使用。

A3)对SSL的支持,SSL即安全套接层。在HTTP上使用SSL对应用程序的编程人员是透明的,只需要直接使用以https开头的URL即可

猜你喜欢

转载自blog.csdn.net/qq_18377515/article/details/79851753