第16章 Java安全
随着Internet的发展,安全性已经引起越来越大的关注。Java自其诞生起就将安全性作为主要考虑因素之一,随着Java的发展,更多的安全机制加入到Java中,在Java 2 SDK 1.4中更是集成了JCE、JSSE、JAAS等Java安全扩展平台。这些安全机制是开发基于企业级Java 2应用平台(J2EE)上安全的应用程序的基础。
实例149 一个简单的加密和解密程序—凯撒密码
凯撒密码是罗马扩张时期朱利斯·凯撒(Julius Caesar)创造的,用于加密通过信使传递的作战命令。加密的过程是将字母表中的字母移动一定位置,从而实现文本的加密。如果将字母表中的字母向右移动2位,则字母A将变为C,字母B将变为D,以此类推,一个明文字符串Hello就被加密成Jgnnq。之后解密,就会返回原字符串。这里,移动的位数2是加密和解密所用的密钥。
技术要点
凯撒密码的技术要点如下:
• 提取出要加密的字符串、密钥。
• 将字符串中每个字符都取出并进行移位。
实现步骤
(1)新建一个类名为Caesar.java。
(2)代码如下所示:
package chp16; import java.util.Scanner; public class Caesar { public static void main(String args[]) throws Exception { System.out.println("[A 加密][J 解密],Please Choose One"); Scanner c = new Scanner(System.in); //创建Scanner对象 String s1 = c.nextLine(); //获取本行的字符串 if (s1.equalsIgnoreCase("A")) { //判断变量s1与A是否相等,忽略大小 System.out.println("请输入明文:"); Scanner sc = new Scanner(System.in); String s = sc.nextLine(); System.out.println("请输入密钥:"); Scanner sc1 = new Scanner(System.in); int key = sc1.nextInt(); //将下一个输入项转换成int类型 Encryption(s, key); //调用Encryption方法 } else if (s1.equalsIgnoreCase("J")) { System.out.println("请输入密文:"); Scanner sc = new Scanner(System.in); String s = sc.nextLine(); System.out.println("请输入密钥:"); Scanner sc1 = new Scanner(System.in); int key = sc1.nextInt(); Decrypt(s, key); //调用Encryption方法 } } public static void Encryption(String str, int k) { //加密 String string = ""; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (c >= 'a' && c <= 'z') //如果字符串中的某个字符是小写字母 { c += k % 26; //移动key%26位 if (c < 'a') c += 26; //向左超界 if (c > 'z') c -= 26; //向右超界 } else if (c >= 'A' && c <= 'Z') //如果字符串中的某个字符是大写字母 { c += k % 26; if (c < 'A') c += 26; //同上 if (c > 'Z') c -= 26; //同上 } string += c; //将加密后的字符连成字符串 } System.out.println(str + " 加密后为:" + string); } public static void Decrypt(String str, int n) { //解密 int k = Integer.parseInt("-" + n); String string = ""; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (c >= 'a' && c <= 'z') //如果字符串中的某个字符是小写字母 { c += k % 26; //移动key%26位 if (c < 'a') c += 26; //向左超界 if (c > 'z') c -= 26; //向右超界 } else if (c >= 'A' && c <= 'Z') //如果字符串中的某个字符是大写字母 { c += k % 26; if (c < 'A') c += 26; //同上 if (c > 'Z') c -= 26; //同上 } string += c; //将解密后的字符连成字符串 } System.out.println(str + " 解密后为:" + string); } }
(3)运行程序可以看到加密的结果,如图16-1所示。解密的结果如图16-2所示。
图16-1 执行加密的结果
图16-2 执行解密结果
源程序解读
字母表中共有26个字符,在移位前先将移动的位数(key)和26取模。Java将字符加上一个正整数即代表在字母表中右移多少位。如果移动的位数是负值,则代表在字母表中左移多少位。
尽管在移动之前已经将移动的位数和26取了模,但通过这种方式实现右移或左移仍可能发生超界。如字母x右移4位应该是字母b,但将字母x增加4后超出26个字母的范围。因此移位后使用两个if语句判断一下,如果向左超界(c<'a')则增加26;如果向右超界(c>'z')则减去26。此外,由于大写字母和小写字母判断是否超界的依据不同,程序中将字符分为大写和小写分别处理。所以,对每个字符进行移位,可以通过下面的代码实现:
c+=key%26; if(c<'a') c+=26; if(c>'z') c-=26;
实例150 创建对称密钥
现代密码算法的过程非常复杂,加密和解密使用相同的密钥,称为对称密钥算法。Java中已经提供了常用的加密算法,我们不需要了解算法的细节就可以直接使用这些算法实现加密。每一种算法所用的密钥都有所不同,本实例演示如何运用Java中提供的方法创建对称密钥,并通过对象序列化方式保存在文件中。
技术要点
创建对称密钥的技术要点如下:
• 获取密钥生成器。
• 密钥的生成。
• 密钥保存。
实现步骤
(1)新建一个类名为Symmetric_key.java。
(2)代码如下所示:
package chp16; //创建对称密钥 import java.io.*; import javax.crypto.*; public class Symmetric_key { public static void main(String args[]) throws Exception { KeyGenerator kg = KeyGenerator.getInstance("DESede"); //获取密钥生成器 kg.init(168); //初始化密钥生成器 SecretKey sk = kg.generateKey(); //生成密钥 FileOutputStream fos = new FileOutputStream("key1.dat");//将密钥保存在key1.dat文件中 ObjectOutputStream b = new ObjectOutputStream(fos); b.writeObject(sk); } }
(3)运行结果如图16-3所示。
图16-3 生成的密钥文件
源程序解读
(1)Java的KeyGenerator类中提供了创建对称密钥的方法。KeyGenerator类预定义了一个静态方法getInstance(),通过它获得KeyGenerator类型的对象。DES是目前最常用的对称加密算法,但安全性较差。在本程序中用于获取密钥生成器的代码如下所示:
KeyGenerator kg = KeyGenerator.getInstance("DESede"); //获取密钥生成器
(2)初始化密钥生成器一般是指定密钥的长度。如果不进行初始化,系统会根据算法自动使用默认的密钥长度。在本程序中用于初始化密钥生成器的代码如下所示:
kg.init(168); //初始化密钥生成器
(3)通过KeyGenerator类型的对象中generateKey()方法可以获得密钥。其类型为SecretKey类型,可用于以后的加密和解密。在本程序中用于生成密钥的代码如下所示:
SecretKeysk=kg.generateKey( );
实例151 CBC方式的加密
CBC使用一个8个字节的随机数(称为初始向量,IV)来加密第一个分组,然后使用得到的密文加密第二个分组,加密第二个分组得到的密文再加密第三个分组,⋯⋯。这样,即使两个分组相同,得到的密文也不同。本实例演示了使用CBC加密方式以及初始向量进行加密的编程步骤。
技术要点
使用CBC方式对字符串进行加密的技术要点如下:
• 生成密钥。
• 生成初始向量。
• 获取密码器。
• 初始化密码器,并执行加密。
实现步骤
(1)新建一个类名为ENC_CBC.java。
(2)代码如下所示:
package chp16; import java.io.*; import java.util.*; import java.security.*; import javax.crypto.*; import javax.crypto.spec.*; public class ENC_CBC { public static void main(String args[]) throws Exception { String s = "你好Java你好Java你好Java你好Java"; String path = System.getProperty("user.dir") + "//key1.dat"; //得到密钥的路径 FileInputStream f1 = new FileInputStream(path); //获取密钥 ObjectInputStream b = new ObjectInputStream(f1); //创建对象输入流 Key k = (Key) b.readObject(); //从 ObjectInputStream 读取对象 //生成初始化向量 byte[] rand = new byte[8]; Random r = new Random(); //创建随机数生成器 r.nextBytes(rand); //生成随机字节并将其置于rand字节数组中 IvParameterSpec iv = new IvParameterSpec(rand); //使用 rand中的字节作为IV来初始化一个IvParameterSpec对象 //加密 Cipher cp = Cipher.getInstance("DESede/CBC/PKCS5Padding"); cp.init(Cipher.ENCRYPT_MODE, k, iv); byte ptext[] = s.getBytes("UTF8"); byte ctext[] = cp.doFinal(ptext); //打印加密结果 System.out.println("输出加密结果为:"); for (int i = 0; i < ctext.length; i++) { System.out.print(ctext[i] + ","); if ((i + 1) % 5 == 0) System.out.println(); } //保存加密结果 FileOutputStream f2 = new FileOutputStream("EncCBC.dat"); f2.write(rand); f2.write(ctext); } }
(3)运行结果如图16-4所示。
图16-4 加密的结果
源程序解读
(1)在本程序主要是通过下面的语句来获取密钥,与前面所讲的方法相同,在这里就不详细说明了。
FileInputStream f1 = new FileInputStream(path);//获取密钥 ObjectInputStream b = new ObjectInputStream(f1);//创建对象输入流 Key k = (Key) b.readObject();//从 ObjectInputStream 读取对象
(2)使用CBC方式首先要生成初始向量,然后在获取密码器对象时通过getInstance()方法的参数设定加密方式。在本程序中主要是通过下面的语句来生成初始向量的,该语句利用随机数生成的byte数组为初始值创建IvParameterSpec对象。
IvParameterSpec iv=new IvParameterSpec(rand);
(3)在获取密码器时,通过getInstance()方法的参数指定加密方式,该参数DESede/CBC/PKCS5Padding由3个参数组成。其中第1个参数DESede代表所用的加密算法;第2个参数CBC即加密模式,除了CBC外,还有NONE、ECB、CFB、OFB和PCBC等可以用;第3个参数为填充模式,对称加密常用的填充方式称为PKCS#5 padding,如果加密算法不进行填充,则填充方式为No padding。
(4)一切准备工作就绪,接下来,就需要执行加密操作了。与前面的程序相比,在其参数中增加了一项初始化向量,即第2步得到的iv。执行加密时同样使用doFinal()方法对字节数组进行加密。具体代码如下所示:
Cipher cp = Cipher.getInstance("DESede/CBC/PKCS5Padding"); cp.init(Cipher.ENCRYPT_MODE, k, iv); byte ptext[] = s.getBytes("UTF8"); byte ctext[] = cp.doFinal(ptext);
实例152 CBC方式的解密
在实例151中,利用CBC方式对字符串进行了加密操作。光有加密操作还不行,同样解密操作也是必不可少的。本实例演示了使用CBC解密方式以及初始向量进行解密的编程步骤。
技术要点
CBC方式的解密的技术要点如下:
• 取出向量。
• 密文和密钥的获取。
• 获取密码器。
实现步骤
(1)新建一个类名为DEC_CBC.java。
(2)代码如下所示:
package chp16; import java.io.*; import java.security.*; import javax.crypto.*; import javax.crypto.spec.*; public class DEC_CBC { public static void main(String args[]) throws Exception { String path = System.getProperty("user.dir") + "//key1.dat";//得到密钥的路径 FileInputStream in = new FileInputStream("EncCBC.dat"); //获取密文 byte[] rand = new byte[8]; in.read(rand); IvParameterSpec iv = new IvParameterSpec(rand); //获取初始向量 int num = in.available(); byte[] ctext = new byte[num]; in.read(ctext); FileInputStream in1 = new FileInputStream(path); //获取密钥 ObjectInputStream b = new ObjectInputStream(in1); Key k = (Key) b.readObject(); //获取密码器,执行解密 Cipher cp = Cipher.getInstance("DESede/CBC/PKCS5Padding"); cp.init(Cipher.DECRYPT_MODE, k, iv); byte[] ptext = cp.doFinal(ctext); String p = new String(ptext, "UTF8"); System.out.println(p); } }
(3)运行结果如图16-5所示。
图16-5 解密后的字符串
源程序解读
(1)在本程序中获取初始向量可以使用文件输入流的read()方法从文件EncCBC.dat中读取8个字节的对应初始向量的随机数,并用其创建IvParameterSpec对象。
(2)由于EncCBC.dat中剩余部分为加密结果,因此使用文件输入流的available()方法判断剩余字节的数量,并创建相应大小的字节数组,读入数据。密钥必须和加密时所用的密钥相同。
(3)进行解密的操作与加密的相同,只是在初始化密码器时使用Cipher.DECRYPT_MODE,表明进行解密。
实例153 计算消息摘要
消息摘要是对原始数据按照一定算法进行计算得到的计算结果,它主要用于检验原始数据是否被修改过。消息摘要和加密是不同的,从加密的结果可以得到原始数据,而从消息摘要中不可能得到原始数据。本实例演示了在简单的编程中,计算出指定字符串的消息摘要。
技术要点
计算消息摘要的技术要点如下:
• MessageDigest对象的使用。
• 字符串如何传入。
• 计算消息摘要。
• 计算结果如何处理。
实现步骤
(1)新建一个类名为MD5_Message.java。
(2)代码如下所示:
package chp16; import java.security.MessageDigest; public class MD5_Message { public static void main(String args[]) throws Exception { System.out.println("计算字符串的消息摘要为:"); String string = "车到山前必有路"; MessageDigest md = MessageDigest.getInstance("MD5");//生成MessageDigest对象 md.update(string.getBytes("GBK")); //传入需要计算的字符串 byte b[] = md.digest(); //计算消息摘要 String result = ""; //处理计算结果 for (int i = 0; i < b.length; i++) { result += Integer.toHexString((0x000000ff & b[i]) | 0xffffff00) .substring(6); } System.out.println(result); } }
图16-6 消息摘要的运行结果
(3)运行结果如图16-6所示。
源程序解读
(1)程序中的“MessageDigest md=MessageDigest.getInstance("MD5");”这条语句的主要作用是创建MessageDigest对象。MessageDigest类也是一个工厂类,其构造器是受保护的,不允许直接使用new MessageDigest()来创建对象,而必须通过其静态方法getInstance()生成MessageDigest对象。
(2)MessageDigest的update方法中,传入的参数是字节类型或字节类型数组,对于字符串,需要先使用getBytes()方法生成字符串数组。
(3)MessageDigest的digest方法起到了计算消息摘要的作用,计算的结果通过字节类型的数组返回。
实例154 使用消息摘要保存口令
程序中经常需要验证用户输入的口令是否正确,如果将正确的用户口令直接存放在程序、文件或数据库中,则很容易被黑客窃取到,这时可以只保存口令的消息摘要。本实例中将介绍如何在user_pass.txt文件中,账号以明文方式保存而口令以消息摘要保存,方便以后验证用。
技术要点
使用消息摘要保存口令的技术要点如下:
• 账号口令初始化。
• MessageDigest对象的应用。
• 计算消息摘要的内容。
• 在文件或数据库中保存账号和口令的消息摘要。
实现步骤
(1)新建一个类名为Sava_Mess.java。
(2)代码如下所示:
package chp16; import java.io.FileOutputStream; import java.io.PrintWriter; import java.security.MessageDigest; public class Sava_Mess { public static void main(String args[]) throws Exception { String name = "yjp"; //账号 String passwd = "nql"; //口令 MessageDigest m = MessageDigest.getInstance("MD5"); //创建MessageDigest对象 m.update(passwd.getBytes("UTF8")); //将口令传入需要计算的字节数组 byte s[] = m.digest(); //进行消息摘要的计算 String result = ""; for (int i = 0; i < s.length; i++) { result += Integer.toHexString((0x000000ff & s[i]) | 0xffffff00) .substring(6); } PrintWriter out = new PrintWriter(new FileOutputStream("user_pass.txt")); //将消息口令写入指定的文件中 out.println(name); out.println(result); out.close(); } }
图16-7 保存口令的运行结果
(3)运行结果如图16-7所示。
源程序解读
(1)执行MessageDigest类的静态方法getInstance()生成MessageDigest对象。其中传入的参数指定计算消息摘要所使用的算法。
(2)passwd为需要计算的口令,使用getBytes()方法生成字符串数组,传入MessageDigest对象的update()方法。
(3)MessageDigest的digest方法起到了计算消息摘要的作用,计算的结果通过字节类型的数组返回。
(4)语句“PrintWriter out= new PrintWriter(new FileOutputStream("user_pass.txt "));”将账号和口令消息摘要保存在user_pass.txt文件中,在user_pass.txt中的内容如图16-7所示。
实例155 使用消息摘要验证口令
实例154中已将口令的消息摘要保存在文件中,本实例将介绍如何使用所保存的消息摘要验证用户输入的口令是否正确。若账号和口令都与保存在user_pass.txt中的相同,则提示“账号和口令都正确”,否则提示“错误的账号或口令”。
技术要点
使用消息摘要验证口令的技术要点如下:
• 读取文件中对应的口令的消息摘要。
• 生成用户输入的口令的消息摘要。
• 将用户输入的口令的消息摘要和文件中保存的口令摘要进行对比。
实现步骤
(1)新建一个类名为Validate_Mess.java。
(2)代码如下所示:
package chp16; import java.io.BufferedReader; import java.io.FileReader; import java.security.MessageDigest; public class Validate_Mess { public static void main(String args[]) throws Exception { /* 读取保存的口令摘要 */ String user = ""; String password = ""; BufferedReader in = new BufferedReader(new FileReader("user_pass.txt")); while ((user = in.readLine()) != null) { password = in.readLine(); if (user.equals("yjp")) { break; } } /* 生成用户输入的口令摘要 */ MessageDigest md = MessageDigest.getInstance("MD5"); md.update("nql".getBytes("UTF8")); byte b[] = md.digest(); String result = ""; for (int i = 0; i < b.length; i++) { result += Integer.toHexString((0x000000ff & b[i]) | 0xffffff00) .substring(6); } /* 检验口令摘要是否匹配 */ if (user.equals("yjp") && result.equals(password)) { System.out.println("账号和口令都正确"); } else { System.out.println("错误的账号或口令"); } } }
图16-8 验证口令的运行结果
(3)运行结果如图16-8所示。
源程序解读
(1)因为在user_pass.txt中,第一行保存的是账号,第二行保存的是账号对应的口令的消息摘要,所以以行的形式来读取用户账号对应的口令的消息摘要。如下所示:
String user = ""; String password = ""; BufferedReader in = new BufferedReader(new FileReader("user_pass.txt")); while ((user = in.readLine()) != null) { password = in.readLine(); if (user.equals("yjp")) { break; } }
(2)计算用户输入的口令的消息摘要可以参考实例154。
(3)最后,比较用户输入的口令的消息摘要和文件中保存的口令摘要是否一致,当账号和口令摘要都与文件中保存的一致时,则验证通过。
实例156 攻击消息摘要保存的口令
通过消息摘要保存口令的学习,认为保存口令较为安全的原因是,攻击者即使通过攻击得到了口令文件,知道了账号的口令摘要,也难以通过该值反推出口令的值,因而无法登录系统。但是当口令的安全系数比较弱时,也就是说口令的位数很少时,攻击者很容易通过字典式攻击由口令的消息摘要反推出原有口令的值。本实例将演示如何攻击消息摘要保存的口令。
技术要点
攻击消息摘要保存的口令的技术要点如下:
• 生成字符串组合。
• 生成字典。
• 保存字典。
实现步骤
(1)新建一个类名为Attack_Mess.java。
(2)代码如下所示:
package chp16; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.PrintWriter; import java.security.MessageDigest; public class Attack_Mess { public static void main(String args[]) throws Exception { /** 获取用户的摘要信息 */ String user = ""; String password = ""; BufferedReader in = new BufferedReader(new FileReader("user_pass.txt")); while ((user = in.readLine()) != null) { password = in.readLine(); if (user.equals("yjp")) { System.out.println("获取用户的摘要信息为:"); System.out.println(password + "\n"); break; } } MessageDigest m = MessageDigest.getInstance("MD5"); PrintWriter out = new PrintWriter(new FileOutputStream("dictary.txt")); //指定攻击口令摘要的字典 /** 生成3位口令的字典 */ for (int i1 = 'a'; i1 < 'z'; i1++) { for (int i2 = 'a'; i2 < 'z'; i2++) for (int i3 = 'a'; i3 < 'z'; i3++) { char[] ch = { (char) i1, (char) i2, (char) i3 }; String passwd = new String(ch); m.update(passwd.getBytes("UTF8")); byte s[] = m.digest(); String result = ""; for (int i = 0; i < s.length; i++) { result += Integer.toHexString( (0x000000ff & s[i]) | 0xffffff00).substring(6); } out.print(passwd + ","); //写入字符和字符的消息摘要 out.println(result); } } out.close(); Dictionary(password); //破解传入摘要的口令 } public static void Dictionary(String pass) throws Exception { //查看生成的字典是否包含给定的消息摘要 String md, str; BufferedReader in = new BufferedReader(new FileReader("dictary.txt")); //读取生成的字典 while ((md = in.readLine()) != null) { if (md.indexOf("") != -1) { str = md.substring(md.lastIndexOf(",") + 1);//取出消息摘要 if (str.equals(pass)) { System.out.println("根据用户的摘要信息而破解的口令为:"); System.out.println(md.substring(0, md.lastIndexOf(","))); //输出口令 break; } } } in.close(); } }
图16-9 攻击消息摘要的运行结果
(3)运行结果如图16-9所示。
源程序解读
(1)在本程序中使用三重for循环生成三个字符的所有组合,为了能简单地演示功能效果,只考虑口令为小写字符a~z的情况。实际使用时,需考虑各种常用字符的各种组合。
(2)在计算消息摘要、保存字典等一切操作完毕后,接下来就该运用此字典来破解那些安全系数较弱的口令。在本程序中以保存在user_pass.txt文件中的口令为例,讲解了破解的过程。由此可以看出,对于位数少的密码还是容易被攻击者破获的,所以在日常生活中,尽量将密码设得复杂些,不给那些不道德的人留一点机会。
实例157 使用加盐技术防范字典式攻击
口令被轻松攻击的主要原因在于口令过短。如果口令很长,则计算所有组合的消息摘要可能要成百上千年,这将大大加大了生成字典的难度。同时口令过长也给用户带来了不便记忆的麻烦,因此用户使用的口令长度总是有限的。本实例将介绍如何使用加盐技术在有限的口令长度上防止字典式攻击。
技术要点
使用加盐技术防范字典式攻击的技术要点如下:
• 账号口令初始化。
• 生成随机数(盐)。
• MessageDigest对象的应用。
• 在文件或数据库中保存账号和口令的消息摘要。
实现步骤
(1)新建一个类名为Validate_Salt.java。
(2)代码如下所示:
package chp16; import java.io.FileOutputStream; import java.io.PrintWriter; import java.security.MessageDigest; import java.util.Random; public class Validate_Salt { public static void main(String args[]) throws Exception { //读入账号口令 String user = "yjp"; String passwd = "nql"; //生成盐 Random rand = new Random(); byte[] by = new byte[15]; rand.nextBytes(by); //计算消息摘要 MessageDigest md = MessageDigest.getInstance("MD5"); md.update(by); md.update(passwd.getBytes("UTF8")); byte b[] = md.digest(); String result = ""; for (int i = 0; i < b.length; i++) { result += Integer.toHexString((0x000000ff & b[i]) | 0xffffff00) .substring(6); } //保存账号、盐和消息摘要 PrintWriter out = new PrintWriter(new FileOutputStream( "saft_password.txt")); out.println(user); for (int i = 0; i < by.length; i++) { out.print(by[i] + " "); } out.println(""); out.println(result); out.close(); } }
(3)第一次运行的结果如图16-10所示。
(4)第二次运行的结果如图16-11所示。
图16-10 第一次运行的结果
图16-11 第二次运行的结果
源程序解读
由上面的结果可以看出每次使用的盐都会有不一样的消息摘要,即使攻击者得到saft_password.txt文件,如果还像以前那样生成字典,即使计算出1~20个字符长度的所有字符组合,也只能攻击8个字符长度的口令,如果用户口令长度超过8个,则字典将无效。而如果不预先生成字典进行攻击,则攻击者每次都必须先取出saft-passwdsalt.txt文件中口令的盐的值,然后重复进行“组串—生成字典—匹配”的计算,而不是只需要计算一次。这样,如果一次计算需要耗时半年,而用户不到半年如一个月就修改一次口令,则攻击者将无法得逞。
(1)执行MessageDigest类的静态方法getInstance()生成MessageDigest对象。其中传入的参数指定计算消息摘要所使用的算法。
(2)创建字节数组by。使用Java中Random类生成随机数(盐),执行Random类的nextBytes()方法,方法的参数为by,即可生成随机数并将随机数赋值给by。
(3)执行MessageDigest类的digest()方法进行计算,然后将账号、盐和口令消息摘要报存在saft_password.txt文件中。对于盐,这里将数组中各个byte值以数字保存在文件中,各个数字之间以空格隔开。
实例158 输入流的加密
实际编程中常针对流进行加密,如对整个文件进行加密/解密或对网络通信进行加密/解密等。尽管可以先从流中读出字节,然后进行加密/解密,但使用Java中针对流提供的专门的类更加方便。本实例以最简单的程序演示了针对输入流的加密和解密。
技术要点
输入流的加密的技术要点如下:
• 密钥生成。
• 初始化密码器Cipher。
• 创建输入流。
• 创建CipherInputStream对象。
• 读取输入流。
实现步骤
(1)新建一个类名为E_InStream.java。
(2)代码如下所示:
package chp16; import java.io.*; import java.security.*; import javax.crypto.*; public class E_InStream { public static void main(String args[]) throws Exception { //生成密钥 FileInputStream f = new FileInputStream("key1.dat"); ObjectInputStream ob = new ObjectInputStream(f); Key k = (Key) ob.readObject(); Cipher cp = Cipher.getInstance("DESede"); //创建密码器 cp.init(Cipher.ENCRYPT_MODE, k); //初始化密码器 FileInputStream in = new FileInputStream("file/file.doc"); //创建要加密的输入流 CipherInputStream cin = new CipherInputStream(in, cp);//创建CipherInputStream对象 int b = 0; int i = 1; FileOutputStream out = new FileOutputStream("E_InStream.dat"); //将密文保存到指定的文件中 System.out.println("对文件输入流加密的密文如下:"); while ((b = cin.read()) != -1) { //读取输入流 System.out.print((byte) b + ","); i++; if (i % 30 == 0) System.out.println(); //打印格式控制 } } }
(3)运行结果如图16-12所示。
图16-12 运行结果
源程序解读
(1)“FileInputStream f=new FileInputStream("key1.dat");”语句从文件中读取以前保存的密钥,这样保证了本实例所用的密钥和以前相同,以便于对比加密结果。
(2)“FileInputStream in=new FileInputStream("file|file.doc");”语句的作用是创建要加密的输入流,本程序是以加密文件为例,因此创建文件输入流,文件名为file.doc。
(3)“CipherInputStream cin=new CipherInputStream(in, cp);”语句的作用是创建CipherInputStream对象,根据前面所创建好的密码器和输入流为参数构造CipherInputStream对象。
(4)与java.io包中的基本的输入流一样使用read()方法从CipherInputStream流中读取数据,则在读取过程中会自动根据密码器中的设置进行加密。
实例159 输入流的解密
本实例演示了针对输入流的解密,将实例158中的加密文件进行解密,并把解密的结果输入指定的另外一个文件。
技术要点
输入流的解密的技术要点如下:
• 密钥生成。
• 初始化密码器:
cp.init(Cipher.DECRYPT_MODE, k);
• 获取解密的内容。
• 创建CipherInputStream对象。
• 读取输入流。
实现步骤
(1)新建一个类名为D_InStream.java。
(2)代码如下所示:
package chp16; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.security.Key; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; public class D_InStream { public static void main(String args[]) throws Exception { //生成密钥 FileInputStream f = new FileInputStream("key1.dat"); ObjectInputStream ob = new ObjectInputStream(f); Key k = (Key) ob.readObject(); Cipher cp = Cipher.getInstance("DESede"); //创建密码器 cp.init(Cipher.DECRYPT_MODE, k); //初始化密码器 FileInputStream in = new FileInputStream("E_InStream.dat");//获取要解密的文件 CipherInputStream cin = new CipherInputStream(in, cp);//创建CipherInputStream对象 int b = 0; System.out.println("对文件输入流解密的原文如下:"); while ((b = cin.read()) != -1) { //读取输入流 System.out.print((char) b ); } } }
(3)运行结果如图16-13所示。
图16-13 输入流解密的运行结果
源程序解读
(1)“FileInputStream f=new FileInput-Stream("key1.dat");”该语句从文件中读取加密时所用的密钥,这样保证了本实例所用的密钥和加密时的密钥相同,以便于对比解密后的结果。
(2)“FileInputStream in = new FileInputStream("E_InStream.dat");”该语句的作用是获取要解密的文件,本程序是以上一个实例加密创建的文件为例,因此创建文件输入流,文件名为E_InStream.dat。
(3)“CipherInputStream cin=new CipherInputStream(in, cp);”该语句的作用是创建CipherInputStream对象,根据前面所创建好的密码器和输入流为参数构造CipherInputStream对象。
(4)同java.io包中的基本的输入流一样使用read()方法从CipherInputStream流中读取数据,则在读取过程中会自动根据密码器中的设置进行解密。
实例160 输出流的加密
本实例演示了针对输出流的加密,将指定文件中的内容进行加密,并把加密的结果输入指定的另外一个文件。
技术要点
输出流的加密的技术要点如下:
• 密钥生成。
• 初始化密码器。
• 创建加密的输出流。
• 创建CipherOutputStream对象。
• 写输出流。
实现步骤
(1)新建一个类名为E_OutStream.java。
(2)代码如下所示:
package chp16; import java.io.*; import java.security.*; import javax.crypto.*; public class E_OutStream { public static void main(String args[]) throws Exception { FileInputStream fis = new FileInputStream("key1.dat"); //获取密钥 ObjectInputStream ois = new ObjectInputStream(fis); Key key = (Key) ois.readObject(); //生成密钥 Cipher cp = Cipher.getInstance("DESede"); //创建密码器 cp.init(Cipher.ENCRYPT_MODE, key); //初始化密码器 FileInputStream in = new FileInputStream("file/java.txt"); //获取需加密的文件 FileOutputStream out = new FileOutputStream("file/Out_java.dat"); //指定读出密文的文件 CipherOutputStreamcout = newCipherOutputStream(out,cp);//创建CipherOutputStream对象 int b = 0; int i = 1; System.out.println("对文件输出流加密的密文如下:"); while ((b = in.read()) != -1) { //写出流 cout.write(b); System.out.print((byte) b + " "); i++; if (i % 20 == 0) System.out.println(); //打印格式控制 } //关闭流 cout.close(); out.close(); in.close(); } }
图16-14 输出流加密的运行结果
(3)运行结果如图16-14所示。
源程序解读
(1)生成密钥、创建并初始化密码器、创建要加密的输出流这三个步骤与输入流的加密是一样的,可以参考输入流的加密。
(2)“FileInputStream in = new FileInputStream("file/java.txt");”语句的主要作用是获取要加密或解密的内容。要加密的内容可以是各种形式,只要可以转换为整型或字节数组形式即可。
(3)同java.io包中的基本的输出流一样使用write()方法向CipherOutputStream流中写数据(数据为需要加密的明文,本实例使用read()方法从文件中读取明文),则在写之前CipherOutputStream流会自动按照其参数中的密码器设置先进行加密或解密操作,然后再写入其参数的输出流中。
(4)加密的结果可以输出到各种输出流中,本实例将加密结果保存为文件,因此创建文件输出流,将其和前面创建好的密码器一起作为参数传递给CipherOutputStream对象。
实例161 输出流的解密
本实例演示了针对输出流的解密,将实例160中加密的文件内容进行解密,并把解密的结果输出到控制台上。
技术要点
输出流的解密的技术要点如下:
• 密钥生成。
• 初始化密码器,将初始密码器改为:
cp.init(Cipher.DECRYPT_MODE, k);
• 创建加密的输出流。
• 创建CipherOutputStream对象。
• 写输出流。
实现步骤
(1)新建一个类名为D_OutStream.java。
(2)代码如下所示:
package chp16; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.security.Key; import javax.crypto.Cipher; import javax.crypto.CipherOutputStream; public class D_OutStream { public static void main(String args[]) throws Exception { FileInputStream fis = new FileInputStream("key1.dat"); //获取密钥 ObjectInputStream ois = new ObjectInputStream(fis); Key key = (Key) ois.readObject(); //生成密钥 Cipher cp = Cipher.getInstance("DESede"); //创建密码器 cp.init(Cipher.DECRYPT_MODE, key); //初始化密码器 FileInputStream in = new FileInputStream("file/Out_java.dat"); //获取需解密的文件 FileOutputStream out = new FileOutputStream("file/Dec_java.txt"); //指定读出密文的文件 CipherOutputStream cout = new CipherOutputStream(out, cp); //创建CipherOutputStream对象 int b = 0; System.out.println("对文件输出流解密的密文如下:"); while ((b = in.read()) != -1) { //写出流 cout.write(b); } //关闭流 cout.close(); out.close(); in.close(); } }
图16-15 输出流解密的运行结果
(3)运行结果如图16-15所示。
源程序解读
(1)“FileInputStream in=new FileInput-Stream("file/Out_java.dat");”该语句的主要作用是获取要解密的内容。在这里是要获取对输出流进行加密的文件的内容。
(2)同java.io包中的基本的输出流一样使用write()方法向CipherOutputStream流中写数据,该数据为需要解密的密文,本实例从文件中使用read()方法从file/Out_java.dat文件中读取密文,则在写之前CipherOutputStream流会自动按照其参数中的密码器设置先进行解密操作,然后再写入其参数中的输出流中。
(3)最后将解密的结果打印在控制台上。
实例162 RSA算法进行加密
RSA算法是使用整数进行加密运算的,在RSA公钥中包含了两个信息:公钥对应的整数e和用于取模的整数n。对于明文数字m,计算密文的公式是:me mod n。
本实例以加密一串最简单的字符串“I am a student”为例,演示了如何使用生成的RSA公钥文件进行加密。
技术要点
使用RSA算法加密的技术要点如下:
• 用FileInputStream获取公钥。
• 用RSAPublicKey类中的方法获取公钥的参数(e, n)。
• 用“BigInteger m=new BigInteger(ptext);”来获取明文整数(m)。
• 执行计算。
实现步骤
(1)新建一个类名为Password_Test.java。
(2)代码如下所示:
package chp16; import java.security.*; import java.security.interfaces.*; import java.math.*; import java.io.*; public class Password_Test { public static void main(String[] args) { try { new Password_Test(); Encryption_RSA(); } catch (Exception e) { e.printStackTrace(); } } public Password_Test() throws Exception { //构造方法,创建公钥和私钥 KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); //生成实现RSA算法的KeyPairGenerator对象 kpg.initialize(1024); //初始化确定密钥的大小 KeyPair kp = kpg.genKeyPair(); //生成密钥对 PublicKey pbkey = kp.getPublic(); //创建公钥 PrivateKey prkey = kp.getPrivate(); //创建私钥 //保存公钥 FileOutputStream file1 = new FileOutputStream("D:/Skey_RSA_pub.dat"); ObjectOutputStream ob1 = new ObjectOutputStream(file1); //创建ObjectOutputStream对象 ob1.writeObject(pbkey); //将指定的对象写入 ObjectOutputStream //保存私钥 FileOutputStream file2 = new FileOutputStream("D:/Skey_RSA_priv.dat"); ObjectOutputStream ob2 = new ObjectOutputStream(file2); ob2.writeObject(prkey); } public static void Encryption_RSA() throws Exception { System.out.println("根据公钥生成密文:"+"\n"); String string = "I am a student"; //获取公钥及参数e,n FileInputStream f_in = new FileInputStream("D:/Skey_RSA_pub.dat"); ObjectInputStream o_in = new ObjectInputStream(f_in); RSAPublicKey pbk = (RSAPublicKey) o_in.readObject(); BigInteger e = pbk.getPublicExponent(); //返回此公钥的指数 BigInteger n = pbk.getModulus(); //返回此公钥的模 System.out.println("公钥的指数 e= " + e); System.out.println("公钥的模 n= " + n); //明文 bit byte bt[] = string.getBytes("UTF8"); BigInteger bit = new BigInteger(bt); //计算密文c,打印 BigInteger mi = bit.modPow(e, n); //生成密文 System.out.println("生成密文为:" + mi+"\n\n"); //打印密文 //保存密文 String save = mi.toString(); BufferedWriter out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream("D:/Enc_RSA.dat"))); out.write(save, 0, save.length()); out.close(); } }
图16-16 生成密文的运行结果
(3)运行程序生成密文的结果如图16-16所示。
图16-17 生成的密钥及密文
(4)密钥及密文生成了相应的文件,如图16-17所示。
源程序解读
(1)Password_Test()构造方法中主要介绍了RSA公钥和私钥文件的生成。
• 首先创建密钥对生成器即KeyPair-Generator类型的对象。KeyPairGenerator类是一个工厂类,它通过其中预定义的一个静态方法getInstance()获取KeyPairGenerator类型的对象。getInstance()方法的参数是一个字符串,指定非对称加密所使用的算法,常用的有RSA、DSA等。
• 初始化密钥生成器。关于密钥长度,对于RSA算法,这里指定的其实是RSA算法中所用的模的位数,可以在512~2048之间。
• 最后获取公钥和私钥,可以通过KeyPair类的getPublic()和getPrivate()方法获得公钥和私钥对象。
(2)Encryption_RSA()方法中主要介绍了如何使用生成好的RSA公钥文件对字符串进行加密。
• 首先获取公钥。从生成的公钥文件Skey_RSA_pub.dat中读取公钥,由于生成该文件时使用的是RSA算法,因此从文件读取公钥对象后强制转换为RSAPublicKey类型,以便后面读取RSA算法所需要的参数。
• 接着获取明文。明文是一个字符串,为了用整数表达这个字符串,先使用字符串的getBytes()方法将其转换为byte类型数组,它其实是字符串中各个字符的二进制表达方式,这一串二进制数转换为一个整数将非常大,因此仍旧使用BigInteger类将这个二进制串转换为整型。
• 最后执行加密计算。计算前面的公式:me mod n。BigInteger类中已经提供了方法modPow()来执行这个计算。底数m执行这个方法,方法modPow()的第一个参数即指数e,第二个参数即模n。方法返回的结果即密文。
实例163 RSA算法进行解密
RSA算法的解密和加密类似,私钥对应的整数d和用于取模的整数n。其中的n和加密时的n完全相同。对于密文数字c,计算明文的公式是:cd mod n。之所以加密是由公式me mod n得到的密文c通过这个公式计算一下就可以反过来得到原来的明文m。本实例利用实例162生成的私钥文件Skey_RSA_priv.dat和密文文件Enc_RSA.dat进行解密。
技术要点
使用RSA算法解密的技术要点如下:
• 利用FileInputStream读取密文Enc_RSA.dat。
• 利用RSAPrivateKey类的readObject()方法获取私钥。
• 利用RSAPrivateKey类获取私钥的BigInteger类型的参数(d, n)。
• 利用BigInteger的modPow()方法执行解密的公式计算。
• 解析出明文整型数对应的字符串。
实现步骤
(1)新建一个类名为Password_DEC.java。
(2)代码如下所示:
package chp16; import java.security.*; import java.security.interfaces.*; import java.math.*; import java.io.*; public class Password_DEC { public static void main(String[] args) throws Exception {//根据RSA私钥进行解密 System.out.println("根据私钥破解密文:" + "\n"); //读取密文 BufferedReader in = new BufferedReader(new InputStreamReader( new FileInputStream("Enc_RSA.dat"))); String ctext = in.readLine(); BigInteger mi = new BigInteger(ctext); //读取私钥 FileInputStream f = new FileInputStream("D:/Skey_RSA_priv.dat"); ObjectInputStream b = new ObjectInputStream(f); RSAPrivateKey prk = (RSAPrivateKey) b.readObject(); BigInteger d = prk.getPrivateExponent(); //返回此私钥的指数 BigInteger n = prk.getModulus(); //返回此私钥的模 System.out.println("私钥的指数 d= " + d); System.out.println("私钥的模 n= " + n); BigInteger jie = mi.modPow(d, n); //进行解密操作 System.out.println("m= " + jie); //显示解密结果 byte[] mt = jie.toByteArray(); System.out.println("解密后的文本内容为:"); for (int i = 0; i < mt.length; i++) { System.out.print((char) mt[i]); } } }
(3)运行结果如图16-18所示。
源程序解读
从显示了私钥中的参数以及解密的结果与实例162的结果相对比,可以发现,两者结果中的n的值完全相同,整型的明文解密后的字符串为“I am a student”。
图16-18 私钥解密的运行结果
(1)在获取私钥的参数(d, n)的时候可以使用RSAPrivateKey类的getPrivateExponent()和getModulus()方法分别获得公钥中d和n的值。
(2)从生成的私钥文件Skey_RSA_priv.dat中读取私钥,由于生成该私钥使用的算法是RSA算法,因此从文件读取公钥对象后强制转换为RSAPrivateKey类型,以便后面读取RSA算法所需要的参数。
(3)RSA算法解密的结果m是一个很大的整数,为了计算出其对应的字符串的值,先使用BigInteger类的toByteArray()方法得到代表该整型数的字节数组,然后将数组中每个元素转换为字符,组成字符串。
(4)从生成的密文文件Enc_RSA.dat中读取密文,由于实例162保存的只是一行字符串,因此只要一条readLine()语句即可。由于这一行字符串表示的是一个很大的整型数,因此使用BigInteger类来表示这个整型数。
(5)使用BigInteger的modPow()方法计算前面的公式:cd mod n。方法返回的结果即明文对应的整型数m。
实例164 创建DH共享密钥
DH算法是建立在DH公钥和私钥的基础上的,X需要和Y共享密钥时,X和Y各自生成DH公钥和私钥,公钥对外公布而私钥各自秘密保存。本实例将介绍在Java中如何创建并部署DH公钥和私钥,以及如何利用它创建共享密钥。
技术要点
使用DH算法产生密要对和创建共享密钥的技术要点如下:
• 利用DHParameterSpec类创建DH参数。
• 利用KeyPairGenerator类的getInstance方法创建密钥对生成器。
• 利用“kpg.initialize(DHP);”语句初始化密钥生成器。
• 利用KeyPairGenerator类中的相应方法生成密钥对及获取公钥和私钥。
• 利用FileInputStream和ObjectInputStream类读取自己的DH私钥和对方的DH公钥。
• 利用KeyAgreement类的getInstance方法创建密钥协定对象。
• 利用“ka.init(prk);”语句初始化密钥协定对象。
• 利用KeyAgreement类的doPhase方法执行密钥协定。
• 利用KeyAgreement类的generateSecret生成共享信息。
实现步骤
(1)新建一个类名为Password_DH.java。
(2)代码如下所示:
package chp16; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import javax.crypto.KeyAgreement; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Password_DH { //skip1024ModulusBytes是从j2sdk的安装目录下的docs\guide\security\jce\ JCERefGuide.html复制而来 private final byte Modulus_Bytes[] = { (byte) 0xF4, (byte) 0x88, (byte) 0xFD, (byte) 0x58, (byte) 0x4E, (byte) 0x49, (byte) 0xDB, (byte) 0xCD, (byte) 0x20, (byte) 0xB4, (byte) 0x9D, (byte) 0xE4, (byte) 0x91, (byte) 0x07, (byte) 0x36, (byte) 0x6B, (byte) 0x33, (byte) 0x6C, (byte) 0x38, (byte) 0x0D, (byte) 0x45, (byte) 0x1D, (byte) 0x0F, (byte) 0x7C, (byte) 0x88, (byte) 0xB3, (byte) 0x1C, (byte) 0x7C, (byte) 0x5B, (byte) 0x2D, (byte) 0x8E, (byte) 0xF6, (byte) 0xF3, (byte) 0xC9, (byte) 0x23, (byte) 0xC0, (byte) 0x43, (byte) 0xF0, (byte) 0xA5, (byte) 0x5B, (byte) 0x18, (byte) 0x8D, (byte) 0x8E, (byte) 0xBB, (byte) 0x55, (byte) 0x8C, (byte) 0xB8, (byte) 0x5D, (byte) 0x38, (byte) 0xD3, (byte) 0x34, (byte) 0xFD, (byte) 0x7C, (byte) 0x17, (byte) 0x57, (byte) 0x43, (byte) 0xA3, (byte) 0x1D, (byte) 0x18, (byte) 0x6C, (byte) 0xDE, (byte) 0x33, (byte) 0x21, (byte) 0x2C, (byte) 0xB5, (byte) 0x2A, (byte) 0xFF, (byte) 0x3C, (byte) 0xE1, (byte) 0xB1, (byte) 0x29, (byte) 0x40, (byte) 0x18, (byte) 0x11, (byte) 0x8D, (byte) 0x7C, (byte) 0x84, (byte) 0xA7, (byte) 0x0A, (byte) 0x72, (byte) 0xD6, (byte) 0x86, (byte) 0xC4, (byte) 0x03, (byte) 0x19, (byte) 0xC8, (byte) 0x07, (byte) 0x29, (byte) 0x7A, (byte) 0xCA, (byte) 0x95, (byte) 0x0C, (byte) 0xD9, (byte) 0x96, (byte) 0x9F, (byte) 0xAB, (byte) 0xD0, (byte) 0x0A, (byte) 0x50, (byte) 0x9B, (byte) 0x02, (byte) 0x46, (byte) 0xD3, (byte) 0x08, (byte) 0x3D, (byte) 0x66, (byte) 0xA4, (byte) 0x5D, (byte) 0x41, (byte) 0x9F, (byte) 0x9C, (byte) 0x7C, (byte) 0xBD, (byte) 0x89, (byte) 0x4B, (byte) 0x22, (byte) 0x19, (byte) 0x26, (byte) 0xBA, (byte) 0xAB, (byte) 0xA2, (byte) 0x5E, (byte) 0xC3, (byte) 0x55, (byte) 0xE9, (byte) 0x2F, (byte) 0x78, (byte) 0xC7 }; //创建质数模数sModulus private final BigInteger sModulus = new BigInteger(1, Modulus_Bytes); //创建基数生成器sBase private final BigInteger sBase = BigInteger.valueOf(3); private String address1 = "D:/temp/D_DH_Public_Key.dat"; private String address2 = "D:/temp/D_DH_Private_Key.dat"; private String address3 = "F:/temp/F_DH_Public_Key.dat"; private String address4 = "F:/temp/F_DH_Private_Key.dat"; File f; public static void main(String[] args) throws Exception { Password_DH dd = new Password_DH(); dd.Share_Password(); dd.Share_Password_1(); } public Password_DH() throws Exception { //利用构造方法创建DH公钥和私钥 DHParameterSpec DPS = new DHParameterSpec(sModulus, sBase); //使用质数模数sModulus和基数生成器sBase为Diffie-Hellman构造一个参数集 KeyPairGenerator KPG = KeyPairGenerator.getInstance("DH"); //生成实现DH算法的KeyPairGenerator对象KPG KPG.initialize(DPS); //初始化密钥对生成器 KeyPair kp = KPG.genKeyPair(); //生成密钥对 PublicKey puk = kp.getPublic(); //获取公钥 PrivateKey prk = kp.getPrivate(); //获取私钥 //保存公钥 FileOutputStream f1 = new FileOutputStream(address1); ObjectOutputStream b1 = new ObjectOutputStream(f1); b1.writeObject(puk); //保存私钥 FileOutputStream f2 = new FileOutputStream(address2); ObjectOutputStream b2 = new ObjectOutputStream(f2); b2.writeObject(prk); //保存公钥 FileOutputStream f3 = new FileOutputStream(address3); ObjectOutputStream b3 = new ObjectOutputStream(f3); b3.writeObject(puk); //保存私钥 FileOutputStream f4 = new FileOutputStream(address4); ObjectOutputStream b4 = new ObjectOutputStream(f4); b4.writeObject(prk); } public void Share_Password() throws Exception { //读取对方的DH公钥 FileInputStream f1 = new FileInputStream(address3); ObjectInputStream b1 = new ObjectInputStream(f1); PublicKey pbk = (PublicKey) b1.readObject(); //读取自己的DH私钥 FileInputStream f2 = new FileInputStream(address2); ObjectInputStream b2 = new ObjectInputStream(f2); PrivateKey prk = (PrivateKey) b2.readObject(); //执行密钥协定 KeyAgreement ka = KeyAgreement.getInstance("DH"); ka.init(prk); ka.doPhase(pbk, true); //生成共享信息 byte[] sb = ka.generateSecret(); SecretKeySpec k = new SecretKeySpec(sb, "D_DHSpec"); System.out.println("此密钥的算法名称为:" + k.getAlgorithm() + "\n"); System.out.println(k.getAlgorithm() + "密钥内容为:" + "\n"); byte be[] = k.getEncoded(); for (int i = 0; i < k.getEncoded().length; i++) { System.out.print(be[i] + ","); if ((i + 1) % 10 == 0) System.out.println(); } System.out.println("\n\n"); } public void Share_Password_1() throws Exception { //读取对方的DH公钥 FileInputStream f1 = new FileInputStream(address1); ObjectInputStream b1 = new ObjectInputStream(f1); PublicKey pbk = (PublicKey) b1.readObject(); //读取自己的DH私钥 FileInputStream f2 = new FileInputStream(address4); ObjectInputStream b2 = new ObjectInputStream(f2); PrivateKey prk = (PrivateKey) b2.readObject(); //执行密钥协定 KeyAgreement ka = KeyAgreement.getInstance("DH"); ka.init(prk); ka.doPhase(pbk, true); //生成共享信息 byte[] sb = ka.generateSecret(); SecretKeySpec k = new SecretKeySpec(sb, "F_DHSpec"); System.out.println("此密钥的算法名称为:" + k.getAlgorithm() + "\n"); System.out.println(k.getAlgorithm() + "密钥内容为:" + "\n"); byte be[] = k.getEncoded(); for (int i = 0; i < k.getEncoded().length; i++) { System.out.print(be[i] + ","); if ((i + 1) % 10 == 0) System.out.println(); } } }
图16-19 Eclipse输出结果
(3)运行结果的初始页面如图16-19所示。
(4)在F盘生成的密钥对文件如图16-20所示。
(5)在D盘生成的密钥对文件如图16-21所示。
源程序解读
由图16-19可以看出D盘和F盘上的文件运行后得到的字节数组内容完全相同,因此使用它创建的密钥也将相同。由于DH算法内在的数学规律,D盘和F盘上的文件在运行时只使用了对方可以公开的信息(公钥),而各自独立地得到了相同的密钥,完成了密钥分发工作。
(1)Password_DH()构造方法的主要功能是使用DH算法产生密钥对。
(2)Password()方法的主要功能是使有D盘的私钥和F盘的公钥产生共享密钥。Java中KeyAgreement类实现了密钥协定,它使用init()方法传入自己的私钥,使用doPhase()方法传入对方的公钥,进而可以使用generateSecret( )方法生成共享的信息。
图16-20 F盘生成的文件
图16-21 D盘生成的文件
• 若想产生共享密钥,首先需读取自己的DH私钥和对方的DH公钥。私钥和公钥是分别从公钥和私钥两个文件中读取。
• 密钥协定对象即KeyAgreement类型的对象,与KeyPairGenerator类似,KeyAgreement类是一个工厂类,通过其中预定义的一个静态方法getInstance()获取KeyAgreement类型的对象。getInstance()方法的参数指定为DH。
• 执行KeyAgreement对象的init()方法,传入获得的自己的私钥。
• 执行KeyAgreement对象的generateSecret()方法,返回字节类型的数组。A、B双方得到的该数组的内容完全相同,用它创建密钥也完全相同。如可使用SecretKeySpec k=new SecretKeySpec(sb,"DESede");创建密钥。
(3)Password_1()方法的主要功能是使有D盘的公钥和F盘的私钥产生共享密钥。具体分析可以参考Password()方法。
实例165 用公钥计算消息摘要的验证码
在计算机安全通信过程中,常使用消息摘要和消息验证码来保证传输的数据的安全性,并可以防止第三方的恶意修改。本实例将利用消息摘要和消息验证码(公钥)来验证传输的消息摘要是否正确。
技术要点
用公钥计算消息摘要的验证码的技术要点如下:
• 输入/输出流的使用。
• 消息摘要串的应用。
• 消息摘要转换成字符串。
实现步骤
(1)新建一个类名为MyMessage.java。
(2)代码如下所示:
package chp16; import java.io.FileInputStream; import java.security.DigestInputStream; import java.security.MessageDigest; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; //可利用共同的密钥来计算消息摘要的验证码 MyMessage.java public class MyMessage { public static void main(String[] args) throws Exception { message_1(); message_2(); message_3(); } public static void message_1() throws Exception { String str = "Beautiful my home in China"; //定义一个消息摘要串 //共同的密钥编码,这可以通过其他算法计算出来 byte[] kb = { 11, 105, -119, 50, 4, -105, 16, 38, -14, -111, 21, -95, 70, -15, 76, -74, 67, -88, 59, -71, 55, -125, 104, 42 }; SecretKeySpec k = new SecretKeySpec(kb, "HMACSHA1"); //获取共同的密钥 Mac m = Mac.getInstance("HmacMD5"); //获取Mac对象 m.init(k); //用给定的密钥初始化此Mac对象 m.update(str.getBytes("UTF-8")); byte[] by = m.doFinal(); //生成消息码 //下面把消息码转换为字符串 String result = ""; for (int i = 0; i < by.length; i++) { result += Integer.toHexString((0x000000ff & by[i]) | 0xffffff00) .substring(6); } System.out.println("用共同的密钥来计算消息摘要的验证码:" + result); } //计算一段字符串的消息摘要 public static void message_2() throws Exception { String str = "Nice to meet you,Tom"; MessageDigest md = MessageDigest.getInstance("MD5"); //常用的有MD5、SHA算法等 md.update(str.getBytes("UTF-8")); //传入原始字串 byte[] by = md.digest(); //计算消息摘要放入byte数组中 //下面把消息摘要转换为字符串 String result = ""; for (int i = 0; i < by.length; i++) { result += Integer.toHexString((0x000000ff & by[i]) | 0xffffff00) .substring(6); } System.out.println("一段字符串的消息摘要为:" + result); } //从输入(出)流中计算消息摘要 public static void message_3() throws Exception { String fileName = "D:/abc/test.txt"; MessageDigest md = MessageDigest.getInstance("MD5"); FileInputStream fin = new FileInputStream(fileName); DigestInputStream din = new DigestInputStream(fin, md); //构造输入流 int length; while ((length = din.read()) != -1) { //做一些对文件的处理 if(length=='$') din.on(true); //当遇到文件中的符号$时才开始计算 } byte[] by = md.digest(); //获得消息摘要 //下面把消息摘要转换为字符串 String result = ""; for (int i = 0; i < by.length; i++) { result += Integer.toHexString((0x000000ff & by[i]) | 0xffffff00) .substring(6); } System.out.println("从文本中计算消息摘要为:" + result); } }
(3)运行结果如图16-22所示。
图16-22 公钥计算消息摘要
源程序解读
(1)消息摘要主要是检测原始数据是否被修改过。消息摘要与加密不同,加密是从原始数据中获得一部分信息,因此,消息摘要可以被认做是原始数据的指纹。
(2)消息验证码在现实中的应用。例如,当X和Y通信时,X将数据传给Y,同时也将数据的消息摘要传给Y,Y收到后可以用该消息摘要验证X传来的消息是否正确。消息验证码可以解决这一问题。使用消息验证码的前提是X和Y双方有一个共同的密钥,这样X可以将数据计算出来的消息摘要加密后发给Y,以防消息摘要被改。由于使用了共同的密钥,所以称为“验证码”。
实例166 利用DES加密/解密
本实例主要实现对指定文件进行DES加/解密的过程。一段文字被加密后,输出的都是一些乱码。这样就很好地加密了文字,并且解密后的文字与原文字相同。
技术要点
利用DES加密/解密的技术要点如下:
• java.nio.channels.FileChannel的用法。
• 标识的应用。
• Des加密算法。
实现步骤
(1)新建一个类名为DES_File.java。
(2)代码如下所示:
//主文件:DES_File.java package chp16; import java.io.*; import java.nio.*; import java.nio.channels.FileChannel; public class DES_File { private static final boolean isenc = true; //加密 private static final boolean isdec = false; //解密 private String sourceFileName; //源文件的名称 private String targetFileName; //目标文件的名称 private String inKey; //密钥 private boolean encdes; //加密解密标识,true表示加密,false表示解密 private File sourceFile; //源文件 private File targetFile; //目标文件 private Des_SI des; //选择si的功能函数类:Des_SI private void get_FilePath() { //获取文件的路径 String pathname; int pos = sourceFileName.lastIndexOf("/"); pathname = sourceFileName.substring(0, pos); //取得文件路径 File dir = new File(pathname); //根据取得的路径创建文件对象 if (!dir.exists()) { //判断是否存在 System.out.println(pathname + " is not exist"); System.exit(1); } else if (!dir.isDirectory()) { //判断是否为目录 System.out.println(pathname + " is not a directory"); System.exit(1); } pos = targetFileName.lastIndexOf("/"); pathname = targetFileName.substring(0, pos); dir = new File(pathname); if (!dir.exists()) { //判断该目录是否存在 if (!dir.mkdirs()) { System.out.println("can not creat directory:" + pathname); System.exit(1); } } else if (!dir.isDirectory()) { System.out.println(pathname + " is not a directory"); System.exit(1); } } private static int Channel_Buf(FileChannel channel, ByteBuffer buf) throws IOException { long byteLeft = channel.size() - channel.position(); if (byteLeft == 0L) return -1; buf.position(0); //设置此文件通道的文件位置为0 buf.limit(buf.position() + (byteLeft < 8 ? (int) byteLeft : 8)); //设置缓冲区的限制 return channel.read(buf); //将字节序列从此通道读入给定的缓冲区 } private void Source_Target(boolean flag) { //根据flag标识进行相应的操作 if(flag) System.out.println("DES加密操作开始............"); else System.out.println("DES解密操作开始............"); des = new Des_SI(inKey); //将密钥转换成二进制 FileOutputStream outputFile = null; try { outputFile = new FileOutputStream(sourceFile, true); //创建文件输出流 } catch (java.io.FileNotFoundException e) { System.out.println(e.getStackTrace()); } FileChannel outChannel = outputFile.getChannel(); //创建文件通道 try { if (outChannel.size() % 2 != 0) { ByteBuffer bufTemp = ByteBuffer.allocate(1); bufTemp.put((byte) 32); bufTemp.flip(); outChannel.position(outChannel.size()); outChannel.write(bufTemp); bufTemp.clear(); } } catch (Exception ex) { System.out.println(ex.getStackTrace()); System.exit(1); } FileInputStream inFile = null; try { inFile = new FileInputStream(sourceFile); } catch (java.io.FileNotFoundException e) { System.out.println(e.getStackTrace()); } outputFile = null; try { outputFile = new FileOutputStream(targetFile, true); } catch (java.io.FileNotFoundException e) { System.out.println(e.getStackTrace()); } FileChannel inChannel = inFile.getChannel(); outChannel = outputFile.getChannel(); ByteBuffer inBuf = ByteBuffer.allocate(8); ByteBuffer outBuf = ByteBuffer.allocate(8); try { String sourceStr; String targetStr; while (true) { if (Channel_Buf(inChannel, inBuf) == -1) break; sourceStr = ((ByteBuffer) (inBuf.flip())).asCharBuffer() .toString(); inBuf.clear(); if (flag) { //若为true,则加密 targetStr = des.enc(sourceStr, sourceStr.length()); } else { //若为false,则解密 targetStr = des.dec(sourceStr, sourceStr.length()); } outBuf.clear(); if (targetStr.length() == 4) { for (int i = 0; i < 4; i++) { outBuf.putChar(targetStr.charAt(i)); } outBuf.flip(); } else { outBuf.position(0); outBuf.limit(2 * targetStr.length()); for (int i = 0; i < targetStr.length(); i++) { outBuf.putChar(targetStr.charAt(i)); } outBuf.flip(); } try { outChannel.write(outBuf); outBuf.clear(); } catch (java.io.IOException ex) { System.out.println(ex.getStackTrace()); } } System.out.println("输入流的文件通道的大小为:" + inChannel.size()); System.out.println("输出流的文件通道的大小为:" + outChannel.size()); System.out.println("EOF分析成功完成\n"); inFile.close(); outputFile.close(); } catch (java.io.IOException e) { System.out.println(e.getStackTrace()); System.exit(1); } } public DES_File(String sourceFileName, String targetFileName, String inKey, boolean encdes) { this.sourceFileName = sourceFileName; this.targetFileName = targetFileName; this.encdes = encdes; get_FilePath(); sourceFile = new File(sourceFileName); targetFile = new File(targetFileName); this.inKey = inKey; if (encdes == isenc) Source_Target(isenc); //执行加密操作 else Source_Target(isdec); //执行解密操作 } public static void main(String[] args) { String srcfile = System.getProperty("user.dir") + "\\file/file.doc"; //原始文件 String cypfile = System.getProperty("user.dir") + "\\file/encrypt.doc"; //加密后的密文文件 String trgfile = System.getProperty("user.dir") + "\\file/decrypt.doc"; //解密后的文件 String passWord = "ABCD4567"; //工作密匙为8个字符 new DES_File(srcfile, cypfile, passWord, true); new DES_File(cypfile, trgfile, passWord, false); } } //选择si的功能函数类文件:Des_SI.java package chp16; public class Des_SI { //选择si的功能函数 protected static byte[][] s1 = { //表s1 { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7 }, { 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8 }, { 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0 }, { 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 } }; protected static byte[][] s2 = { //表s2 { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10 }, { 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5 }, { 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15 }, { 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 } }; protected static byte[][] s3 = { //表s3 { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8 }, { 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1 }, { 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7 }, { 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 } }; protected static byte[][] s4 = { //表s4 { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15 }, { 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9 }, { 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4 }, { 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 } }; protected static byte[][] s5 = { //表s5 { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9 }, { 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6 }, { 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14 }, { 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 } }; protected static byte[][] s6 = { //表s6 { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11 }, { 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8 }, { 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6 }, { 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 } }; protected static byte[][] s7 = { //表s7 { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1 }, { 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6 }, { 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2 }, { 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 } }; protected static byte[][] s8 = { //表s8 { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7 }, { 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2 }, { 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8 }, { 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } }; protected static byte[] binary = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1 }; //用于 S盒查找以确定对应的4位二进制数据 protected byte[] tempData = new byte[64]; //存储将要加密的明文或将要解密的密文 protected String Srcdata; protected String Tagdata; protected byte[] bufout = new byte[64]; //用于数据变换中转 //最终生成的密文或解密所得的明文 protected byte[] output = new byte[64]; protected byte[] Ln = new byte[32]; protected byte[] Rn = new byte[32]; protected byte[] LR = new byte[32]; protected byte[] ER = new byte[48]; protected byte[] temp = new byte[32]; protected byte[] result = new byte[8]; protected byte[] ki; protected String key; protected int readLen; protected boolean encFlag; //1为加密,0为解密 protected GetSubKey subKey; protected void Char4Tobit64(String data) { for (int i = 0; i < 64; i++) { tempData[i] = 0; } //将4个字符的unicode码数据转换成64位数据 for (int i = 0; i < readLen; i++) { int j = data.charAt(i); tempData[16 * i + 0] = (byte) ((j / 32768) % 2); tempData[16 * i + 1] = (byte) ((j / 16384) % 2); tempData[16 * i + 2] = (byte) ((j / 8192) % 2); tempData[16 * i + 3] = (byte) ((j / 4096) % 2); tempData[16 * i + 4] = (byte) ((j / 2048) % 2); tempData[16 * i + 5] = (byte) ((j / 1024) % 2); tempData[16 * i + 6] = (byte) ((j / 512) % 2); tempData[16 * i + 7] = (byte) ((j / 256) % 2); tempData[16 * i + 8] = (byte) ((j / 128) % 2); tempData[16 * i + 9] = (byte) ((j / 64) % 2); tempData[16 * i + 10] = (byte) ((j / 32) % 2); tempData[16 * i + 11] = (byte) ((j / 16) % 2); tempData[16 * i + 12] = (byte) ((j / 8) % 2); tempData[16 * i + 13] = (byte) ((j / 4) % 2); tempData[16 * i + 14] = (byte) ((j / 2) % 2); tempData[16 * i + 15] = (byte) (j % 2); } } protected void Bit64To4Char() { int j; char ch; StringBuffer strbuf = new StringBuffer(); for (int i = 0; i < 4; i++) { j = 0; j = 32768 * output[16 * i + 0] + 16384 * output[16 * i + 1] + 8192 * output[16 * i + 2] + 4096 * output[16 * i + 3] + 2048 * output[16 * i + 4] + 1024 * output[16 * i + 5] + 512 * output[16 * i + 6] + 256 * output[16 * i + 7] + 128 * output[16 * i + 8] + 64 * output[16 * i + 9] + 32 * output[16 * i + 10] + 16 * output[16 * i + 11] + 8 * output[16 * i + 12] + 4 * output[16 * i + 13] + 2 * output[16 * i + 14] + output[16 * i + 15]; ch = (char) j; strbuf.append(ch); } Tagdata = strbuf.toString(); } protected void IP() { /* 初始数据置换 */ bufout[0] = tempData[57]; bufout[1] = tempData[49]; bufout[2] = tempData[41]; bufout[3] = tempData[33]; bufout[4] = tempData[25]; bufout[5] = tempData[17]; bufout[6] = tempData[9]; bufout[7] = tempData[1]; bufout[8] = tempData[59]; bufout[9] = tempData[51]; bufout[10] = tempData[43]; bufout[11] = tempData[35]; bufout[12] = tempData[27]; bufout[13] = tempData[19]; bufout[14] = tempData[11]; bufout[15] = tempData[3]; bufout[16] = tempData[61]; bufout[17] = tempData[53]; bufout[18] = tempData[45]; bufout[19] = tempData[37]; bufout[20] = tempData[29]; bufout[21] = tempData[21]; bufout[22] = tempData[13]; bufout[23] = tempData[5]; bufout[24] = tempData[63]; bufout[25] = tempData[55]; bufout[26] = tempData[47]; bufout[27] = tempData[39]; bufout[28] = tempData[31]; bufout[29] = tempData[23]; bufout[30] = tempData[15]; bufout[31] = tempData[7]; bufout[32] = tempData[56]; bufout[33] = tempData[48]; bufout[34] = tempData[40]; bufout[35] = tempData[32]; bufout[36] = tempData[24]; bufout[37] = tempData[16]; bufout[38] = tempData[8]; bufout[39] = tempData[0]; bufout[40] = tempData[58]; bufout[41] = tempData[50]; bufout[42] = tempData[42]; bufout[43] = tempData[34]; bufout[44] = tempData[26]; bufout[45] = tempData[18]; bufout[46] = tempData[10]; bufout[47] = tempData[2]; bufout[48] = tempData[60]; bufout[49] = tempData[52]; bufout[50] = tempData[44]; bufout[51] = tempData[36]; bufout[52] = tempData[28]; bufout[53] = tempData[20]; bufout[54] = tempData[12]; bufout[55] = tempData[4]; bufout[56] = tempData[62]; bufout[57] = tempData[54]; bufout[58] = tempData[46]; bufout[59] = tempData[38]; bufout[60] = tempData[30]; bufout[61] = tempData[22]; bufout[62] = tempData[14]; bufout[63] = tempData[6]; } protected void XOR(byte[] op1, byte[] op2) { int len = op1.length; for (int i = 0; i < len; i++) { op1[i] = (byte) (op1[i] ^ op2[i]); } } protected void expand32To48bit(byte[] op) { /* 字节交换*/ ER[0] = op[31]; ER[1] = op[0]; ER[2] = op[1]; ER[3] = op[2]; ER[4] = op[3]; ER[5] = op[4]; ER[6] = op[3]; ER[7] = op[4]; ER[8] = op[5]; ER[9] = op[6]; ER[10] = op[7]; ER[11] = op[8]; ER[12] = op[7]; ER[13] = op[8]; ER[14] = op[9]; ER[15] = op[10]; ER[16] = op[11]; ER[17] = op[12]; ER[18] = op[11]; ER[19] = op[12]; ER[20] = op[13]; ER[21] = op[14]; ER[22] = op[15]; ER[23] = op[16]; ER[24] = op[15]; ER[25] = op[16]; ER[26] = op[17]; ER[27] = op[18]; ER[28] = op[19]; ER[29] = op[20]; ER[30] = op[19]; ER[31] = op[20]; ER[32] = op[21]; ER[33] = op[22]; ER[34] = op[23]; ER[35] = op[24]; ER[36] = op[23]; ER[37] = op[24]; ER[38] = op[25]; ER[39] = op[26]; ER[40] = op[27]; ER[41] = op[28]; ER[42] = op[27]; ER[43] = op[28]; ER[44] = op[29]; ER[45] = op[30]; ER[46] = op[31]; ER[47] = op[0]; } protected void sBox() { int valindex; valindex = s1[2 * ER[0] + ER[5]][2 * (2 * (2 * ER[1] + ER[2]) + ER[3]) + ER[4]]; valindex = valindex * 4; temp[0] = (byte) binary[0 + valindex]; temp[1] = (byte) binary[1 + valindex]; temp[2] = (byte) binary[2 + valindex]; temp[3] = (byte) binary[3 + valindex]; valindex = s2[2 * ER[6] + ER[11]][2 * (2 * (2 * ER[7] + ER[8]) + ER[9]) + ER[10]]; valindex = valindex = valindex * 4; temp[4] = (byte) binary[0 + valindex]; temp[5] = (byte) binary[1 + valindex]; temp[6] = (byte) binary[2 + valindex]; temp[7] = (byte) binary[3 + valindex]; valindex = s3[2 * ER[12] + ER[17]][2 * (2 * (2 * ER[13] + ER[14]) + ER[15]) + ER[16]]; valindex = valindex = valindex * 4; temp[8] = (byte) binary[0 + valindex]; temp[9] = (byte) binary[1 + valindex]; temp[10] = (byte) binary[2 + valindex]; temp[11] = (byte) binary[3 + valindex]; valindex = s4[2 * ER[18] + ER[23]][2 * (2 * (2 * ER[19] + ER[20]) + ER[21]) + ER[22]]; valindex = valindex = valindex * 4; temp[12] = (byte) binary[0 + valindex]; temp[13] = (byte) binary[1 + valindex]; temp[14] = (byte) binary[2 + valindex]; temp[15] = (byte) binary[3 + valindex]; valindex = s5[2 * ER[24] + ER[29]][2 * (2 * (2 * ER[25] + ER[26]) + ER[27]) + ER[28]]; valindex = valindex = valindex * 4; temp[16] = (byte) binary[0 + valindex]; temp[17] = (byte) binary[1 + valindex]; temp[18] = (byte) binary[2 + valindex]; temp[19] = (byte) binary[3 + valindex]; valindex = s6[2 * ER[30] + ER[35]][2 * (2 * (2 * ER[31] + ER[32]) + ER[33]) + ER[34]]; valindex = valindex = valindex * 4; temp[20] = (byte) binary[0 + valindex]; temp[21] = (byte) binary[1 + valindex]; temp[22] = (byte) binary[2 + valindex]; temp[23] = (byte) binary[3 + valindex]; valindex = s7[2 * ER[36] + ER[41]][2 * (2 * (2 * ER[37] + ER[38]) + ER[39]) + ER[40]]; valindex = valindex = valindex * 4; temp[24] = (byte) binary[0 + valindex]; temp[25] = (byte) binary[1 + valindex]; temp[26] = (byte) binary[2 + valindex]; temp[27] = (byte) binary[3 + valindex]; valindex = s8[2 * ER[42] + ER[47]][2 * (2 * (2 * ER[43] + ER[44]) + ER[45]) + ER[46]]; valindex = valindex = valindex * 4; temp[28] = (byte) binary[0 + valindex]; temp[29] = (byte) binary[1 + valindex]; temp[30] = (byte) binary[2 + valindex]; temp[31] = (byte) binary[3 + valindex]; } protected void p() { /* Permute - P */ Rn[0] = temp[15]; Rn[1] = temp[6]; Rn[2] = temp[19]; Rn[3] = temp[20]; Rn[4] = temp[28]; Rn[5] = temp[11]; Rn[6] = temp[27]; Rn[7] = temp[16]; Rn[8] = temp[0]; Rn[9] = temp[14]; Rn[10] = temp[22]; Rn[11] = temp[25]; Rn[12] = temp[4]; Rn[13] = temp[17]; Rn[14] = temp[30]; Rn[15] = temp[9]; Rn[16] = temp[1]; Rn[17] = temp[7]; Rn[18] = temp[23]; Rn[19] = temp[13]; Rn[20] = temp[31]; Rn[21] = temp[26]; Rn[22] = temp[2]; Rn[23] = temp[8]; Rn[24] = temp[18]; Rn[25] = temp[12]; Rn[26] = temp[29]; Rn[27] = temp[5]; Rn[28] = temp[21]; Rn[29] = temp[10]; Rn[30] = temp[3]; Rn[31] = temp[24]; } protected void IIP() { /* 初始逆置换 */ output[0] = bufout[39]; output[1] = bufout[7]; output[2] = bufout[47]; output[3] = bufout[15]; output[4] = bufout[55]; output[5] = bufout[23]; output[6] = bufout[63]; output[7] = bufout[31]; output[8] = bufout[38]; output[9] = bufout[6]; output[10] = bufout[46]; output[11] = bufout[14]; output[12] = bufout[54]; output[13] = bufout[22]; output[14] = bufout[62]; output[15] = bufout[30]; output[16] = bufout[37]; output[17] = bufout[5]; output[18] = bufout[45]; output[19] = bufout[13]; output[20] = bufout[53]; output[21] = bufout[21]; output[22] = bufout[61]; output[23] = bufout[29]; output[24] = bufout[36]; output[25] = bufout[4]; output[26] = bufout[44]; output[27] = bufout[12]; output[28] = bufout[52]; output[29] = bufout[20]; output[30] = bufout[60]; output[31] = bufout[28]; output[32] = bufout[35]; output[33] = bufout[3]; output[34] = bufout[43]; output[35] = bufout[11]; output[36] = bufout[51]; output[37] = bufout[19]; output[38] = bufout[59]; output[39] = bufout[27]; output[40] = bufout[34]; output[41] = bufout[2]; output[42] = bufout[42]; output[43] = bufout[10]; output[44] = bufout[50]; output[45] = bufout[18]; output[46] = bufout[58]; output[47] = bufout[26]; output[48] = bufout[33]; output[49] = bufout[1]; output[50] = bufout[41]; output[51] = bufout[9]; output[52] = bufout[49]; output[53] = bufout[17]; output[54] = bufout[57]; output[55] = bufout[25]; output[56] = bufout[32]; output[57] = bufout[0]; output[58] = bufout[40]; output[59] = bufout[8]; output[60] = bufout[48]; output[61] = bufout[16]; output[62] = bufout[56]; output[63] = bufout[24]; } public Des_SI(String key) { this.key = key; subKey = new GetSubKey(key); } //解密 public String enc(String Srcdata, int readLen) { this.Srcdata = Srcdata; this.readLen = readLen; Char4Tobit64(Srcdata); IP(); for (int i = 0; i < 32; i++) { Ln[i] = bufout[i]; //L0 Rn[i] = bufout[32 + i]; //R0 } for (int iter = 1; iter < 17; iter++) { for (int i = 0; i < 32; i++) { LR[i] = Ln[i]; Ln[i] = Rn[i]; } expand32To48bit(Rn); //Rn-1 expand to 48 bit save to ER[] switch (iter) { case 1: ki = subKey.k1; break; case 2: ki = subKey.k2; break; case 3: ki = subKey.k3; break; case 4: ki = subKey.k4; break; case 5: ki = subKey.k5; break; case 6: ki = subKey.k6; break; case 7: ki = subKey.k7; break; case 8: ki = subKey.k8; break; case 9: ki = subKey.k9; break; case 10: ki = subKey.k10; break; case 11: ki = subKey.k11; break; case 12: ki = subKey.k12; break; case 13: ki = subKey.k13; break; case 14: ki = subKey.k14; break; case 15: ki = subKey.k15; break; case 16: ki = subKey.k16; break; } XOR(ER, ki); sBox(); p(); XOR(Rn, LR); } for (int i = 0; i < 32; i++) { bufout[i] = Rn[i]; bufout[32 + i] = Ln[i]; } IIP(); Bit64To4Char(); return Tagdata; } //加密 public String dec(String Srcdata, int readLen) { this.Srcdata = Srcdata; this.readLen = readLen; Char4Tobit64(Srcdata); IP(); for (int i = 0; i < 32; i++) { Ln[i] = bufout[i]; //L0 Rn[i] = bufout[32 + i]; //R0 } for (int iter = 1; iter < 17; iter++) { for (int i = 0; i < 32; i++) { LR[i] = Ln[i]; Ln[i] = Rn[i]; } expand32To48bit(Rn); //Rn-1 扩展到48bit并保存到ER[] switch (iter) { case 1: ki = subKey.k16; break; case 2: ki = subKey.k15; break; case 3: ki = subKey.k14; break; case 4: ki = subKey.k13; break; case 5: ki = subKey.k12; break; case 6: ki = subKey.k11; break; case 7: ki = subKey.k10; break; case 8: ki = subKey.k9; break; case 9: ki = subKey.k8; break; case 10: ki = subKey.k7; break; case 11: ki = subKey.k6; break; case 12: ki = subKey.k5; break; case 13: ki = subKey.k4; break; case 14: ki = subKey.k3; break; case 15: ki = subKey.k2; break; case 16: ki = subKey.k1; break; } XOR(ER, ki); sBox(); p(); XOR(Rn, LR); } for (int i = 0; i < 32; i++) { bufout[i] = Rn[i]; bufout[32 + i] = Ln[i]; } IIP(); Bit64To4Char(); return Tagdata; } } //密钥转换成二进制的类文件:GetSubKey.java package chp16; public class GetSubKey { private String inKey; private StringBuffer keyBuf; private byte[] key = new byte[64]; //保存密钥的64位二进制表示 private byte[] kwork = new byte[56]; private static byte[] shift = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; protected byte[] k1 = new byte[48]; protected byte[] k2 = new byte[48]; protected byte[] k3 = new byte[48]; protected byte[] k4 = new byte[48]; protected byte[] k5 = new byte[48]; protected byte[] k6 = new byte[48]; protected byte[] k7 = new byte[48]; protected byte[] k8 = new byte[48]; protected byte[] k9 = new byte[48]; protected byte[] k10 = new byte[48]; protected byte[] k11 = new byte[48]; protected byte[] k12 = new byte[48]; protected byte[] k13 = new byte[48]; protected byte[] k14 = new byte[48]; protected byte[] k15 = new byte[48]; protected byte[] k16 = new byte[48]; protected byte[] kn = new byte[48]; public GetSubKey(String inKey) { byte j; this.inKey = inKey; int len = inKey.length(); keyBuf = new StringBuffer(inKey); if (len < 8) { //密钥必须是8位,若小于8位,不够的补空格,若大于8位则取前8位 for (int i = 1; i <= 8 - len; i++) keyBuf.append("?"); } inKey = keyBuf.toString(); //将8个字符的密钥转换成64位二进制表示 for (int i = 0; i < 8; i++) { j = (byte) (inKey.charAt(i)); key[8 * i] = (byte) ((j / 128) % 2); key[8 * i + 1] = (byte) ((j / 64) % 2); key[8 * i + 2] = (byte) ((j / 32) % 2); key[8 * i + 3] = (byte) ((j / 16) % 2); key[8 * i + 4] = (byte) ((j / 8) % 2); key[8 * i + 5] = (byte) ((j / 4) % 2); key[8 * i + 6] = (byte) ((j / 2) % 2); key[8 * i + 7] = (byte) (j % 2); } //初始化键的排列顺序 kwork[0] = key[56]; kwork[1] = key[48]; kwork[2] = key[40]; kwork[3] = key[32]; kwork[4] = key[24]; kwork[5] = key[16]; kwork[6] = key[8]; kwork[7] = key[0]; kwork[8] = key[57]; kwork[9] = key[49]; kwork[10] = key[41]; kwork[11] = key[33]; kwork[12] = key[25]; kwork[13] = key[17]; kwork[14] = key[9]; kwork[15] = key[1]; kwork[16] = key[58]; kwork[17] = key[50]; kwork[18] = key[42]; kwork[19] = key[34]; kwork[20] = key[26]; kwork[21] = key[18]; kwork[22] = key[10]; kwork[23] = key[2]; kwork[24] = key[59]; kwork[25] = key[51]; kwork[26] = key[43]; kwork[27] = key[35]; kwork[28] = key[62]; kwork[29] = key[54]; kwork[30] = key[46]; kwork[31] = key[38]; kwork[32] = key[30]; kwork[33] = key[22]; kwork[34] = key[14]; kwork[35] = key[6]; kwork[36] = key[61]; kwork[37] = key[53]; kwork[38] = key[45]; kwork[39] = key[37]; kwork[40] = key[29]; kwork[41] = key[21]; kwork[42] = key[13]; kwork[43] = key[5]; kwork[44] = key[60]; kwork[45] = key[52]; kwork[46] = key[44]; kwork[47] = key[36]; kwork[48] = key[28]; kwork[49] = key[20]; kwork[50] = key[12]; kwork[51] = key[4]; kwork[52] = key[27]; kwork[53] = key[19]; kwork[54] = key[11]; kwork[55] = key[3]; /* 开始计算子键 */ byte nbrofshift; byte temp1, temp2; for (int iter = 0; iter < 16; iter++) { nbrofshift = shift[iter]; for (int i = 0; i < (int) nbrofshift; i++) { temp1 = kwork[0]; temp2 = kwork[28]; for (int k = 0; k < 27; k++) { kwork[k] = kwork[k + 1]; kwork[k + 28] = kwork[k + 29]; } kwork[27] = temp1; kwork[55] = temp2; } /* Permute kwork - PC2 */ kn[0] = kwork[13]; kn[1] = kwork[16]; kn[2] = kwork[10]; kn[3] = kwork[23]; kn[4] = kwork[0]; kn[5] = kwork[4]; kn[6] = kwork[2]; kn[7] = kwork[27]; kn[8] = kwork[14]; kn[9] = kwork[5]; kn[10] = kwork[20]; kn[11] = kwork[9]; kn[12] = kwork[22]; kn[13] = kwork[18]; kn[14] = kwork[11]; kn[15] = kwork[3]; kn[16] = kwork[25]; kn[17] = kwork[7]; kn[18] = kwork[15]; kn[19] = kwork[6]; kn[20] = kwork[26]; kn[21] = kwork[19]; kn[22] = kwork[12]; kn[23] = kwork[1]; kn[24] = kwork[40]; kn[25] = kwork[51]; kn[26] = kwork[30]; kn[27] = kwork[36]; kn[28] = kwork[46]; kn[29] = kwork[54]; kn[30] = kwork[29]; kn[31] = kwork[39]; kn[32] = kwork[50]; kn[33] = kwork[44]; kn[34] = kwork[32]; kn[35] = kwork[47]; kn[36] = kwork[43]; kn[37] = kwork[48]; kn[38] = kwork[38]; kn[39] = kwork[55]; kn[40] = kwork[33]; kn[41] = kwork[52]; kn[42] = kwork[45]; kn[43] = kwork[41]; kn[44] = kwork[49]; kn[45] = kwork[35]; kn[46] = kwork[28]; kn[47] = kwork[31]; switch (iter) { case 0: for (int k = 0; k < 48; k++) { k1[k] = kn[k]; } break; case 1: for (int k = 0; k < 48; k++) { k2[k] = kn[k]; } break; case 2: for (int k = 0; k < 48; k++) { k3[k] = kn[k]; } break; case 3: for (int k = 0; k < 48; k++) { k4[k] = kn[k]; } break; case 4: for (int k = 0; k < 48; k++) { k5[k] = kn[k]; } break; case 5: for (int k = 0; k < 48; k++) { k6[k] = kn[k]; } break; case 6: for (int k = 0; k < 48; k++) { k7[k] = kn[k]; } break; case 7: for (int k = 0; k < 48; k++) { k8[k] = kn[k]; } break; case 8: for (int k = 0; k < 48; k++) { k9[k] = kn[k]; } break; case 9: for (int k = 0; k < 48; k++) { k10[k] = kn[k]; } break; case 10: for (int k = 0; k < 48; k++) { k11[k] = kn[k]; } break; case 11: for (int k = 0; k < 48; k++) { k12[k] = kn[k]; } break; case 12: for (int k = 0; k < 48; k++) { k13[k] = kn[k]; } break; case 13: for (int k = 0; k < 48; k++) { k14[k] = kn[k]; } break; case 14: for (int k = 0; k < 48; k++) { k15[k] = kn[k]; } break; case 15: for (int k = 0; k < 48; k++) { k16[k] = kn[k]; } break; } } } }
(3)运行结果开始执行加密与解密,如图16-23所示。
(4)要加密的源文件如图16-24所示。
(5)加密后的文件如图16-25所示。
(6)解密后的文件如图16-26所示。
图16-23 加密与解密开始运行
图16-24 原始文件
图16-25 加密后的文件
图16-26 解密后的文件
源程序解读
(1)DES算法的入口参数包括Key、Data和Mode。其中,Key为8字节共64位,是DES算法的工作密钥;Data也为8字节共64位,是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密和解密。
(2)下面的一个构造方法主要是将文件放在缓存区内。这是一个静态的方法,可以对这个方法进行调用,找到文件的存放路径。
private static int Channel_Buf(FileChannel channel, ByteBuffer buf)
(3)DES算法的工作流程:如果Mode为加密,则用Key把数据Data进行加密,生成Data的密码形式(64位)作为DES的输出结果;如果Mode为解密,则用Key把密码形式的数据Data解密,还原为Data的明码形式(64位)作为DES的输出结果。