只用一样东西,不明白它的道理,实在不高明
只知道How,不知道Why,出了一点小问题时就无能为力了。我们课上鼓励大家在Linux下学习编程,尽量在命令行中编辑/编译/调试程序,Git的使用,数据库的管理都先会命令方式下使用,这样在IDE中,在GUI界面中出了问题,我们有更好的方法查找。
结果是:
这个“会用,明理,扩展”方法论可以用到很多技术的学习上。明理之前要会用。
程序开发中,程序员有两种角色:
在很多课程中,同学们学习是一人分饰两种角色,但很多人不清楚这两种角色。
《ThinkinJava》中说:
开发一个程序时,最好的方法就是将对象想象为“服务提供者”,程序通过调用若干对象提供的服务来实现预期目标。
Java的一个好处是API极其丰富,想学习一门技术可以先用Java写点程序,再深入学习就能明理了。
我们每个人都有自己的秘密,所谓秘密就是不希望被别人知道的信息。
在现代社会中,很多信息都存储在计算机里,这让信息的使用变得非常方便,不过,也正是因为如此,在现代社会中要保护好自己的秘密信息已经变得非常困难。即便别人复制了你的秘密信息,你也不会有所察觉,因为你手上的信息并没有丢失;正是因为信息可以很容易地被修改,所以你的重要文件也存在被他人篡改的风险;此外,如果有人将你的秘密信息通过邮件发送给第三者或者是发布在网页上,也会给你带来大麻烦。为了解决这些问题,人们开发出形形色色的密码技术。
我们遇到的安全问题可以归结为安全的三个属性(CIA金三角):
其他安全技术目标:
密码学逐步发展成为一门学科,那么什么是密码学呢?
密码学:主要是研究保密通信和信息保密的学科,包括信息保密传输和信息加密存储等。
密码学包含密码编码学(Cryptography)和密码分析学(Cryptanalyst)两个分支。编码学与分析学相互促进,又相互制约。一方面,两者在加强密码分析的安全上相互促进;另一方面,两者在实施更为有效的攻击方面也相互影响。
让我们来看一下密码学常用术语,如下所示:
密码学并不是孤立存在的,它需要有一个环境——保密通信模型。密码学的目的在于确保信息的保密传送。通常这样理解这层含义:信息的发送者和接收者在不安全的信道上进行通信,而破译者不能理解他们通信的内容。用保密通信模型来诠释这种信息传送方式,如下图所示:
在保密通信模型中,密码技术在保证信息传输安全中发挥着重要的作用:
我们将上述六种技术统称为密码学家的工具箱。我们将信息安全所面临的威胁与用来应对这些威胁的密码技术之间的关系用一张图表来表示出来:
根据密钥的使用方法,可以将密码分为对称密码和公钥密码两种。
将对称密码和公钥密码结合起来的密码方式称为混合密码系统(hybridcryptosystem),这种系统结合了对称密码和公钥密码两者的优势。
我们在各种系统中输入的“密码”,我们应该叫口令(Password),主要用来身份验证的,加密过程中可以用PBE(PasswordBasedEncryption,基于口令加密)算法,使用口令产生密钥,而口令由用户自己掌管。
密码与信息安全常识
Java安全体系结构总共分为4个部分:
JCA和JCE是Java平台提供的用于安全和加密服务的两组API。它们并不执行任何算法,它们只是连接应用和实际算法实现程序的一组接口。软件开发商可以根据JCE接口(又称安全提供者接口)将各种算法实现后,打包成一个Provider(安全提供者),动态地加载到Java运行环境中。根据美国出口限制规定,JCA可出口,但JCE对部分国家是限制出口的。因此,要实现一个完整的安全结构,就需要一个或多个第三方厂商提供的JCE产品,称为安全提供者。BouncyCastleJCE就是其中的一个安全提供者。
安全提供者是承担特定安全机制实现的第三方。有些提供者是完全免费的,而另一些提供者则需要付费。提供安全提供者的公司有Sun、BouncyCastle等,Sun(Oracle)提供了如何开发安全提供者的细节。BouncyCastle提供了可以在J2ME/J2EE/J2SE平台得到支持的API,而且BouncyCastle的API是免费的。JDK1.4版本及其后续版本中包含了上述扩展包,无须进行配置。
密码学是在战争中发家的,即使最简单的凯撒密码也不例外。凯撒密码是罗马扩张时期朱利斯凯撒(JuliusCaesar)创造的,用于加密通过信使传递的作战命令。它将字母表中的字母移动一定位置而实现加密。
在密码学中存在着各种各样的置换方式,但所有不同的置换方式都包含2个相同的元素。密钥和协议(算法)。凯撒密码的密钥是3,算法是将普通字母表中的字母用密钥对应的字母替换。置换加密的优点就在于它易于实施却难于破解.发送方和接收方很容易事先商量好一个密钥,然后通过密钥从明文中生成密文,即是敌人若获取密文,通过密文直接猜测其代表的意义,在实践中是不可能的。
凯撒密码的加密算法极其简单。其加密过程如下:
在这里,我们做此约定:明文记为m,密文记为c,加密变换记为E(k1,m)(其中k1为密钥),解密变换记为D(k2,m)(k2为解密密钥)(在这里k1=k2,不妨记为k)。凯撒密码的加密过程可记为如下一个变换:
c≡m+kmodn(其中n为基本字符个数)
同样,解密过程可表示为:m≡c+kmodn(其中n为基本字符个数)
publicstaticvoidmain(Stringargs[])throwsException{Strings=args[0];intkey=Integer.parseInt(args[1]);Stringes="";for(inti=0;i
javaCaesar明文(要加密的字符串)密钥(移动的位数)
即可加密。在密钥前面加上负号,将运行
javaCaesar明文(要加密的字符串)-密钥(移动的位数)
即可解密。
如为了加密字符串“HelloWorld!”,可随意取一个密钥如4,运行:
javaCaesar"HelloWorld!"4
将输出“LippsAsvph!”。这里“HelloWorld!”是明文,“LippsAsvph!”是密文。
如果密钥大于26,程序中移位前会和26取模而将其调整到26以下。因此运行:
javaCaesar"HelloWorld!"30
同样将输出“LippsAsvph!”。
为了将密文“LippsAsvph!”解密,需要知道加密该密文所用的密钥4,这样,执行:
javaCaesar"LippsAsvph!"-4
将得到明文“HelloWorld!”。
如果密钥和加密时所用的不同,则解密时将得到无意义的输出,如运行
javaCaesar"LippsAsvph!"–3
程序将输出“IfmmpXpsme!”。这样,只有知道密钥才能得到原来的密文。
一般人见到"LippsAsvph!",能否猜出是“HelloWorld!”?我们可以通过暴力破解把0-25都试一遍,也可以通过统计手段进行破解。
本实例给出Java中创建对称密钥的步骤,并通过对象序列化方式保存在文件中。
(1)获取密钥生成器KeyGeneratorkg=KeyGenerator.getInstance("DESede");分析:Java中KeyGenerator类中提供了创建对称密钥的方法。Java中的类一般使用new操作符通过构造器创建对象,但KeyGenerator类不是这样,它预定义了一个静态方法getInstance(),通过它获得KeyGenerator类型的对象。这种类成为工厂类或工厂。方法getInstance()的参数为字符串类型,指定加密算法的名称。可以是“Blowfish”、“DES”、“DESede”、“HmacMD5”或“HmacSHA1”等。这些算法都可以实现加密,这里我们不关心这些算法的细节,只要知道其使用上的特点即可。其中“DES”是目前最常用的对称加密算法,但安全性较差。针对DES安全性的改进产生了能满足当前安全需要的TripleDES算法,即“DESede”。“Blowfish”的密钥长度可达448位,安全性很好。“AES”是一种替代DES算法的新算法,可提供很好的安全性。
(2)初始化密钥生成器kg.init(168);分析:该步骤一般指定密钥的长度。如果该步骤省略的话,会根据算法自动使用默认的密钥长度。指定长度时,若第一步密钥生成器使用的是“DES”算法,则密钥长度必须是56位;若是“DESede”,则可以是112或168位,其中112位有效;若是“AES”,可以是128,192或256位;若是“Blowfish”,则可以是32至448之间可以被8整除的数;“HmacMD5”和“HmacSHA1”默认的密钥长度都是64个字节。
(3)生成密钥SecretKeyk=kg.generateKey();分析:使用第一步获得的KeyGenerator类型的对象中generateKey()方法可以获得密钥。其类型为SecretKey类型,可用于以后的加密和解密。
(4)通过对象序列化方式将密钥保存在文件中FileOutputStreamf=newFileOutputStream("key1.dat");ObjectOutputStreamb=newObjectOutputStream(f);b.writeObject(k);分析:ObjectOutputStream类中提供的writeObject方法可以将对象序列化,以流的方式进行处理。这里将文件输出流作为参数传递给ObjectOutputStream类的构造器,这样创建好的密钥将保存在文件key1.data中。
importjava.io.*;importjavax.crypto.*;publicclassSkey_DES{publicstaticvoidmain(Stringargs[])throwsException{KeyGeneratorkg=KeyGenerator.getInstance("DESede");kg.init(168);SecretKeyk=kg.generateKey();FileOutputStreamf=newFileOutputStream("key1.dat"); ObjectOutputStreamb=newObjectOutputStream(f); b.writeObject(k);}}运行javaSkey_DES,在当前目录下将生成文件key1.dat,其中包含的密钥可以用于使用Triple-DES算法的加密和解密。
上面的示例将密钥通过对象序列化方式保存在文件中,在文件中保存的是对象,本实例以另一种方式保存在文件中,即以字节保存在文件中。
Java中所有的密钥类都有一个getEncoded()方法,通过它可以从密钥对象中获取主要编码格式,其返回值是字节数组。其主要步骤为:
(1)获取密钥
FileInputStreamf=newFileInputStream("key1.dat");ObjectInputStreamb=newObjectInputStream(f);Keyk=(Key)b.readObject();分析:首先创建文件输入流,然后将其作为参数传递给对象输入流,最后执行对象输入流的readObject()方法读取密钥对象。由于readObject()返回的是Object类型,因此需要强制转换成Key类型。
(2)获取主要编码格式
byte[]kb=k.getEncoded();
分析:执行SecretKey类型的对象k的getEncoded()方法,返回的编码放在byte类型的数组中。
(3)保存密钥编码格式
FileOutputStreamf2=newFileOutputStream("keykb1.dat");f2.write(kb);
分析:先创建文件输出流对象,在其参数中指定文件名,如keykb1.dat。然后执行文件输出流的write()方法将第2步中得到的字节数组中的内容写入文件。
importjava.io.*;importjava.security.*;publicclassSkey_kb{publicstaticvoidmain(Stringargs[])throwsException{FileInputStreamf=newFileInputStream("key1.dat");ObjectInputStreamb=newObjectInputStream(f);Keyk=(Key)b.readObject();byte[]kb=k.getEncoded();FileOutputStreamf2=newFileOutputStream("keykb1.dat"); f2.write(kb);//打印密钥编码中的内容for(inti=0;i 运行程序 输入javaSkey_kb运行程序,在程序的当前目录中将产生文件名为keykb1.dat的文件,屏幕输出如下: 11,-105,-119,50,4,-105,16,38,-14,-111,21,-95,70,-15,76,-74,67,-88,59,-71,55,-125,104,42, 此即程序中创建的密钥的编码内容,如果用文本编辑器打开keykb1.dat,看到的不是上面的数字而是类似下面的字符: 棄2&驊馤禖僪* 这是因为keykb1.dat是一个二进制文件,存放的是任意二进制数。(Linux下可以用od或xxd命令查看) 读者运行时肯定结果和上面会有所不同,每次运行时生成的密钥都不会相同,这就保证了密钥的唯一性。作为对称密钥,只要保证若加密某段文字用的是某个密钥,则解密这段密文时用同样的密钥即可。 首先要从文件中获取已经生成的密钥,然后考虑如何使用密钥进行加密。这涉及到各种算法。Java中已经提供了常用的加密算法,我们执行Java中Cipher类的各个方法就可以完成加密过程,其主要步骤为: (1)从文件中获取密钥FileInputStreamf=newFileInputStream("key1.dat");ObjectInputStreamb=newObjectInputStream(f);Keyk=(Key)b.readObject(); (2)创建密码对象(Cipher对象)Ciphercp=Cipher.getInstance("DESede"); 分析:和KeyGenerator类一样,Cipher类是一个工厂类,它不是通过new方法创建对象,而是通过其中预定义的一个静态方法getInstance()获取Cipher对象。 getInstance()方法的参数是一个字符串,该字符串给出Cipher对象应该执行哪些操作,因此把传入的字符串称为转换(transformation)。通常通过它指定加密算法或解密所用的算法的名字,如本例的"DESede"。此外还可以同时指定反馈模式及填充方案等,如"DESede/ECB/PKCS5Padding"。 (3)初始化密码对象cp.init(Cipher.ENCRYPT_MODE,k);分析:该步骤执行Cipher对象的init()方法对Cipher对象进行初始化。该方法包括两个参数,第一个参数指定密码器准备进行加密还是解密,若传入Cipher.ENCRYPT_MODE则进入加密模式。第二个参数则传入加密或解密所使用的密钥,即第1步从文件中读取的密钥对象k。 (4)获取等待加密的明文 Strings="HelloWorld!";byteptext[]=s.getBytes("UTF8");分析:Cipher对象所作的操作是针对字节数组的,因此需要将要加密的内容转换成字节数组。本例中要加密的是一个字符串s,可以使用字符串的getBytes()方法获得对应的字节数组。getBytes()方法中必须使用参数"UTF8"指定…,否则… (5)执行加密 bytectext[]=cp.doFinal(ptext);分析:执行Cipher对象的doFinal()方法,该方法的参数中传入待加密的明文,从而按照前面几步设置的算法及各种模式对所传入的明文进行加密操作,该方法返回加密的结果。 (6)处理加密结果 FileOutputStreamf2=newFileOutputStream("SEnc.dat");f2.write(ctext);分析:第5步得到的加密结果是字节数组,对其可作各种处理,如在网上传递、保存在文件中等。这里将其保存在文件Senc.dat中。 importjava.io.*;importjava.security.*;importjavax.crypto.*;publicclassSEnc{publicstaticvoidmain(Stringargs[])throwsException{Strings="HelloWorld!"; FileInputStreamf=newFileInputStream("key1.dat"); ObjectInputStreamb=newObjectInputStream(f); Keyk=(Key)b.readObject(); Ciphercp=Cipher.getInstance("DESede");cp.init(Cipher.ENCRYPT_MODE,k);byteptext[]=s.getBytes("UTF8");for(inti=0;i 当前目录下必须有前面生成的密钥文件key1.dat, 输入javaSEnc运行程序,在程序的当前目录中将产生文件名为SEnc.dat的文件,屏幕输出如下: 72,101,108,108,111,32,87,111,114,108,100,33,-57,119,0,-45,-9,23,37,-56,-60,-34,-99,105,99,113,-17,76,其中第一行为字符串"HelloWorld!"的字节数组编码方式,第二行为加密后的内容,第二行的内容会随着密钥的不同而不同。 第一行的内容没有加过密,任何人若得到第一行数据,只要将其用二进制方式写入文本文件,用文本编辑器打开文件就可以看到对应的字符串“HelloWorld!”。而第二行的内容由于是加密过的,没有密钥的人即使得到第二行的内容也无法知道其内容。 密文同时保存在SEnc.dat文件中,将其提供给需要的人时,需要同时提供加密时使用的密钥(key1.dat,或keykb1.dat),这样收到SEnc.dat中密文的人才能够解密文件中的内容。 前面加密后的密文SEnc.dat,以及加密时所使用的密钥key1.dat或keykb1.dat,本实例对SEnc.dat中的密文进行解密,得到明文。 首先要从文件中获取加密时使用的密钥,然后考虑如何使用密钥进行解密。其主要步骤为: (1)获取密文 FileInputStreamf=newFileInputStream("SEnc.dat");intnum=f.available();byte[]ctext=newbyte[num];f.read(ctext);分析:密文存放在文件SEnc.dat中,由于解密是针对字节数组进行操作的,因此要先将密文从文件中读入字节数组。首先创建文件输入流,然后使用文件输入流的available()方法判断密文将占用多少字节,从而创建相应大小的字节数组ctext,最后使用文件输入流的read()方法一次性读入数组ctext。 (2)获取密钥 FileInputStreamf2=newFileInputStream("keykb1.dat");intnum2=f2.available();byte[]keykb=newbyte[num2];f2.read(keykb);SecretKeySpeck=newSecretKeySpec(keykb,"DESede");SecretKeySpec类的构造器中第2个参数则指定加密算法。由于keykb1.dat中的密钥原来使用的是DESede算法,因此这里仍旧使用字符串“DESede”作为参数。 (3)创建密码对象(Cipher对象) Ciphercp=Cipher.getInstance("DESede"); (4)初始化密码对象cp.init(Cipher.DECRYPT_MODE,k); (5)执行解密 byte[]ptext=cp.doFinal(ctext); importjava.io.*;importjava.security.*;importjavax.crypto.*;importjavax.crypto.spec.*;publicclassSDec{publicstaticvoidmain(Stringargs[])throwsException{//获取密文FileInputStreamf=newFileInputStream("SEnc.dat");intnum=f.available();byte[]ctext=newbyte[num];f.read(ctext);//获取密钥FileInputStreamf2=newFileInputStream("keykb1.dat");intnum2=f2.available();byte[]keykb=newbyte[num2];f2.read(keykb);SecretKeySpeck=newSecretKeySpec(keykb,"DESede");//解密Ciphercp=Cipher.getInstance("DESede");cp.init(Cipher.DECRYPT_MODE,k);byte[]ptext=cp.doFinal(ctext);//显示明文Stringp=newString(ptext,"UTF8");System.out.println(p);}}程序中最后将明文生成字符串加以显示。 当前目录下必须有前面生成的密钥文件keykb1.dat,以及密文文件SEnc.dat。 输入javaSDec运行程序,将输出明文字符串“HelloWorld!”。 下面演示如何使用Java中定义好的类创建RSA公钥和私钥。 Java的KeyPairGenerator类提供了一些方法来创建密钥对以便用于非对称加密,密钥对创建好后封装在KeyPair类型的对象中,在KeyPair类中提供了获取公钥和私钥的方法。具体步骤如下: (1)创建密钥对生成器 KeyPairGeneratorkpg=KeyPairGenerator.getInstance("RSA"); 分析:密钥对生成器即KeyPairGenerator类型的对象,和2.2.1小节的第1步中介绍的KeyGenerator类一样,KeyPairGenerator类是一个工厂类,它通过其中预定义的一个静态方法getInstance()获取KeyPairGenerator类型的对象。getInstance()方法的参数是一个字符串,指定非对称加密所使用的算法,常用的有RSA,DSA等。 (2)初始化密钥生成器 kpg.initialize(1024); 分析:对于密钥长度。对于RSA算法,这里指定的其实是RSA算法中所用的模的位数。可以在512到2048之间。 (3)生成密钥对 KeyPairkp=kpg.genKeyPair(); 分析:使用KeyPairGenerator类的genKeyPair()方法生成密钥对,其中包含了一对公钥和私钥的信息。 (4)获取公钥和私钥 PublicKeypbkey=kp.getPublic();PrivateKeyprkey=kp.getPrivate(); 分析:使用KeyPair类的getPublic()和getPrivate()方法获得公钥和私钥对象。 importjava.io.*;importjava.security.*;importjavax.crypto.*;importjavax.crypto.spec.*;publicclassSkey_RSA{publicstaticvoidmain(Stringargs[])throwsException{KeyPairGeneratorkpg=KeyPairGenerator.getInstance("RSA");kpg.initialize(1024);KeyPairkp=kpg.genKeyPair();PublicKeypbkey=kp.getPublic();PrivateKeyprkey=kp.getPrivate();//保存公钥FileOutputStreamf1=newFileOutputStream("Skey_RSA_pub.dat");ObjectOutputStreamb1=newObjectOutputStream(f1);b1.writeObject(pbkey);//保存私钥FileOutputStreamf2=newFileOutputStream("Skey_RSA_priv.dat");ObjectOutputStreamb2=newObjectOutputStream(f2);b2.writeObject(prkey);}}分析:使用对象流将密钥保存在文件中,加密所用的公钥和解密所用的私钥分开保存。将公钥对外公布,供其他人加密使用,而把私钥秘密保存,在需要解密时使用。 输入javaSkey_RSA运行程序,当前目录下将生成两个文件:Skey_RSA_pub.dat和Skey_RSA_priv.dat,前者保存着公钥,后者保存着私钥。将文件Skey_RSA_pub.dat对外公布(如放在Web服务器上给大家下载,或者直接拷贝给所有需要的人),而Skey_RSA_priv.dat秘密保存。 以加密一串最简单的字符串“HelloWorld!”为例,演示了如何使用上面生成的RSA公钥文件Skey_RSA_pub.dat进行加密。 编程思路: 使用RSA公钥进行加密的代码和使用DESede进行加密其实没什么大的区别,只是Cipher类的getInstance()方法的参数中应该指定使用RSA。但由于J2SDK1.4中只实现了RSA密钥的创建,没有实现RSA算法,因此需要安装其他加密提供者软件才能直接使用Cipher类执行加密解密。其实有了RSA公钥和私钥后,自己编写程序从底层实现RSA算法也并不复杂。本实例给出简单的例子实现了RSA加密,使读者只使用J2SDK1.4便能直观地了解非对称加密算法。 RSA算法是使用整数进行加密运算的,在RSA公钥中包含了两个信息:公钥对应的整数e和用于取模的整数n。对于明文数字m,计算密文的公式是:memodn。因此,编程步骤如下: (1)获取公钥 FileInputStreamf=newFileInputStream("Skey_RSA_pub.dat");ObjectInputStreamb=newObjectInputStream(f);RSAPublicKeypbk=(RSAPublicKey)b.readObject();分析:从公钥文件Skey_RSA_pub.dat中读取公钥,由于生成使用的是RSA算法,因此从文件读取公钥对象后强制转换为RSAPublicKey类型,以便后面读取RSA算法所需要的参数。 (2)获取公钥的参数(e,n) BigIntegere=pbk.getPublicExponent();BigIntegern=pbk.getModulus();分析:使用RSAPublicKey类的getPublicExponent()和getModulus()方法可以分别获得公始中e和n的值。由于密钥很长,因此对应的整数值非常大,无法使用一般的整型来存储,Java中定义了BigInteger类来存储这类很大的整数并可进行各种运算。 (3)获取明文整数(m) Strings="HelloWorld!";byteptext[]=s.getBytes("UTF8");BigIntegerm=newBigInteger(ptext);分析:明文是一个字符串,为了用整数表达这个字符串,先使用字符串的getBytes()方法将其转换为byte类型数组,它其实是字符串中各个字符的二进制表达方式,这一串二进制数转换为一个整数将非常大,因此仍旧使用BigInteger类将这个二进制串转换为整型。 本实例中出于简化,将整个字符串转换为一个整数。实际使用中,应该对明文进行分组,因为RSA算法要求整型数m的值必须小于n。 (4)执行计算 BigIntegerc=m.modPow(e,n);分析:计算前面的公式:memodn。BigInteger类中已经提供了方法modPow()来执行这个计算。底数m执行这个方法,方法modPow()的第一个参数即指数e,第二个参数即模n。方法返回的结果即公式memodn的计算结果,即密文。 importjava.security.*;importjava.security.spec.*;importjavax.crypto.*;importjavax.crypto.spec.*;importjavax.crypto.interfaces.*;importjava.security.interfaces.*;importjava.math.*;importjava.io.*;publicclassEnc_RSA{publicstaticvoidmain(Stringargs[])throwsException{Strings="HelloWorld!";//获取公钥及参数e,nFileInputStreamf=newFileInputStream("Skey_RSA_pub.dat");ObjectInputStreamb=newObjectInputStream(f);RSAPublicKeypbk=(RSAPublicKey)b.readObject();BigIntegere=pbk.getPublicExponent();BigIntegern=pbk.getModulus();System.out.println("e="+e);System.out.println("n="+n);//明文mbyteptext[]=s.getBytes("UTF8");BigIntegerm=newBigInteger(ptext);//计算密文c,打印BigIntegerc=m.modPow(e,n);System.out.println("c="+c);//保存密文Stringcs=c.toString();BufferedWriterout=newBufferedWriter(newOutputStreamWriter(newFileOutputStream("Enc_RSA.dat")));out.write(cs,0,cs.length());out.close();}}程序最后将密文c打印出来,并以字符串形式保存在文件中。 输入javaEnc_RSA运行程序,得到如下结果: 其中显示了公钥中的参数以及加密的结果c,这些都是很大的整数,n和c多达上百位。程序运行后密文c以字符串形式保存在文件Enc_RSA.dat中。 下面实例使用私钥文件Skey_RSA_priv.dat,对密文文件Enc_RSA.dat进行解密。 使用RSA私钥进行解密的代码也可以在Cipher类的getInstance()方法的参数中指定使用RSA,使用解密模式进行解密。但需要安装其他加密提供者软件才能直接使用Cipher类执行加密解密。本实例给出简单的例子从底层实现RSA解密,以便只使用J2SDK1.4便能直观地了解非对称加密算法。 RSA算法的解密和加密类似,在RSA私钥中包含了两个信息:私钥对应的整数d和用于取模的整数n。其中的n和加密时的n完全相同。对于密文数字c,计算明文的公式是:cdmodn,之所以加密时由公式memodn得到的密文c通过这个公式计算一下就可以反过来得到原来的明文m,有其本身的数学规律决定。从编程角度只需要知道这个结果就行了。编程步骤如下: (1)读取密文 BufferedReaderin=newBufferedReader(newInputStreamReader(newFileInputStream("Enc_RSA.dat")));Stringctext=in.readLine();BigIntegerc=newBigInteger(ctext);分析:从密文文件Enc_RSA.dat中读取密文,由于保存的只是一行字符串,因此只要一条readLine()语句即可。由于这一行字符串表示的是一个很大的整型数,因此使用BigInteger类来表示这个整型数。 (2)获取私钥 FileInputStreamf=newFileInputStream("Skey_RSA_priv.dat");ObjectInputStreamb=newObjectInputStream(f);RSAPrivateKeyprk=(RSAPrivateKey)b.readObject();分析:从私钥文件Skey_RSA_priv.dat中读取私钥,由于使用的是RSA算法,因此从文件读取私钥对象后强制转换为RSAPrivateKey类型,以便后面读取RSA算法所需要的参数。 (3)获取私钥的参数(d,n) BigIntegerd=prk.getPrivateExponent();BigIntegern=prk.getModulus();分析:使用RSAPrivateKey类的getPrivateExponent()和getModulus()方法可以分别获得私钥中d和n的值。 BigIntegerm=c.modPow(d,n); 分析:使用BigInteger的modPow()方法计算前面的公式:cdmodn。方法返回的结果即公式cdmodn的计算结果,即明文对应的整型数m。 (5)计算明文整型数对应的字符串 byte[]mt=m.toByteArray();for(inti=0;i importjava.security.*;importjava.security.spec.*;importjavax.crypto.*;importjavax.crypto.spec.*;importjavax.crypto.interfaces.*;importjava.security.interfaces.*;importjava.math.*;importjava.io.*;publicclassDec_RSA{publicstaticvoidmain(Stringargs[])throwsException{//读取密文BufferedReaderin=newBufferedReader(newInputStreamReader(newFileInputStream("Enc_RSA.dat")));Stringctext=in.readLine();BigIntegerc=newBigInteger(ctext);//读取私钥FileInputStreamf=newFileInputStream("Skey_RSA_priv.dat");ObjectInputStreamb=newObjectInputStream(f);RSAPrivateKeyprk=(RSAPrivateKey)b.readObject();BigIntegerd=prk.getPrivateExponent();//获取私钥参数及解密BigIntegern=prk.getModulus();System.out.println("d="+d);System.out.println("n="+n);BigIntegerm=c.modPow(d,n);//显示解密结果System.out.println("m="+m);byte[]mt=m.toByteArray();System.out.println("PlainTextis");for(inti=0;i 其中显示了私钥中的参数以及解密的结果,其中整型的明文转换后显示出字符串“HelloWorld!”。 非对称加密解决了密钥分发的难题,但其计算量比对称密钥大,因此一般并不使用非对称加密加密大量数据。常见的做法是:主要数据通过对称密钥加密,而使用非对称加密来分发对称密钥,这样就将两者的优势结合了起来。 例如若A和B之间想秘密传送大量数据,一方(如A)先创建公私钥对,公钥对外公布,另一方(如B)创建对称密钥,然后使用A的公钥加密对称密钥,传递给A,A收到后用自己的私钥解密,得到对称密钥,以后A和B之间就可以使用对称密钥加密通信了。 除了这种方式以外,还可以使用密钥协定来交换对称密钥。执行密钥协定的标准算法是DH算法(Diffie-Hellman算法),本节介绍在Java中如何使用DH算法来交换共享密钥。 DH算法是建立在DH公钥和私钥的基础上的,A需要和B共享密钥时,A和B各自生成DH公钥和私钥,公钥对外公布而私钥各自秘密保存。本实例将介绍Java中如何创建并部署DH公钥和私钥,以便后面一小节利用它创建共享密钥。 程思路: 和上面使用KeyPairGenerator类创建RSA公钥和私钥类似,只是其参数中指定“DH”,此外在初始化时需要为DH指定特定的参数。 代码与分析: 运行程序: 建立两个目录A和B,模拟需要秘密通信的A、B双方,由于DH算法需要A和B各自生成DH公钥和私钥,因此在这两个目录下都拷贝编译后文件Key_DH。 首先由A创建自己的公钥和私钥,即在A目录下输入“javaKey_DHApub.datApri.dat”运行程序,这时在目录A下将产生文件Apub.dat和Apri.dat,前者保存着A的公钥,后者保存着A的私钥。然后由B创建自己的公钥和私钥,即在B目录下输入“javaKey_DHBpub.datBpri.dat”运行程序,这时在目录B下将产生文件Bpub.dat和Bpri.dat,前者保存着B的公钥,后者保存着B的私钥。最后发布公钥,A将Apub.dat拷贝到B目录,B将Bpub.dat拷贝到A的目录。这样,A、B双方的DH公钥和私钥已经创建并部署完毕。 DH算法中,A可以用自己的密钥和B的公钥按照一定方法生成一个密钥,B也可以用自己的密钥和A的公钥按照一定方法生成一个密钥,由于一些数学规律,这两个密钥完全相同。这样,A和B间就有了一个共同的密钥可以用于各种加密。本实例介绍Java中在上一小节的基础上如何利用DH公钥和私钥各自创建共享密钥。 Java中KeyAgreement类实现了密钥协定,它使用init()方法传入自己的私钥,使用doPhase()方法传入对方的公钥,进而可以使用generateSecret()方法生成共享的信息具体步骤如下: (1)读取自己的DH私钥和对方的DH公钥 FileInputStreamf1=newFileInputStream(args[0]);ObjectInputStreamb1=newObjectInputStream(f1);PublicKeypbk=(PublicKey)b1.readObject();FileInputStreamf2=newFileInputStream(args[1]);ObjectInputStreamb2=newObjectInputStream(f2);PrivateKeyprk=(PrivateKey)b2.readObject();分析:从文件中获取密钥。只是分为公钥和私钥两个文件,通过命令行参数传入公钥和私钥文件名,第一个命令行参数为对方的公钥文件名,第二个命令行参数为自己的私钥文件名。 (2)创建密钥协定对象 KeyAgreementka=KeyAgreement.getInstance("DH");分析:密钥协定对象即KeyAgreement类型的对象,和KeyPairGenerator类类似,KeyAgreement类是一个工厂类,通过其中预定义的一个静态方法getInstance()获取KeyAgreement类型的对象。getInstance()方法的参数指定为“DH”。 (3)初始化密钥协定对象 ka.init(prk);分析:执行密钥协定对象的init()方法,传入第1步获得的自己的私钥,它在第1步中通过第2个命令行参数提供。 (4)执行密钥协定 ka.doPhase(pbk,true);分析:执行密钥协定对象的doPhase()方法,其第一个参数中传入对方的公钥。在本实例中,只有A、B两方需要共享密钥,因此对方只有一个,因此第二个参数设置为true。如果有A、B、C三方需要共享密钥,则对方有两个,doPhase()方法要写两次,每次在第1个参数中传入一个公钥,第2个参数最初设置为false,最后一次设置为true。例如C方应该执行ka.doPhase(pbk_of_A,false);ka.doPhase(pbk_of_B,true);。一次类推,可以用密钥协定实现多方共享一个密钥。 (5)生成共享信息 byte[]sb=ka.generateSecret();分析:执行密钥协定对象的generateSecret()方法,返回字节类型的数组。A、B双方得到的该数组的内容完全相同,用它创建密钥也各方完全相同。如可使用SecretKeySpeck=newSecretKeySpec(sb,"DESede");创建密钥。 importjava.io.*;importjava.math.*;importjava.security.*;importjava.security.spec.*;importjavax.crypto.*;importjavax.crypto.spec.*;importjavax.crypto.interfaces.*;publicclassKeyAgree{publicstaticvoidmain(Stringargs[])throwsException{//读取对方的DH公钥FileInputStreamf1=newFileInputStream(args[0]);ObjectInputStreamb1=newObjectInputStream(f1);PublicKeypbk=(PublicKey)b1.readObject();//读取自己的DH私钥FileInputStreamf2=newFileInputStream(args[1]);ObjectInputStreamb2=newObjectInputStream(f2);PrivateKeyprk=(PrivateKey)b2.readObject();//执行密钥协定KeyAgreementka=KeyAgreement.getInstance("DH");ka.init(prk);ka.doPhase(pbk,true);//生成共享信息byte[]sb=ka.generateSecret();for(inti=0;i 将程序KeyAgree编译后分别拷贝在A和B两个目录,首先在A目录输入“javaKeyAgreeBpub.datApri.dat”运行程序,它使用文件Bpub.dat中对方的公钥和文件Apri.dat中自己的私钥创建了一段共享的字节数组。 使用Java计算指定字符串的消息摘要。java.security包中的MessageDigest类提供了计算消息摘要的方法, 首先生成对象,执行其update()方法可以将原始数据传递给该对象,然后执行其digest()方法即可得到消息摘要。具体步骤如下: (2)传入需要计算的字符串m.update(x.getBytes("UTF8"));分析:x为需要计算的字符串,update传入的参数是字节类型或字节类型数组,对于字符串,需要先使用getBytes()方法生成字符串数组。 (3)计算消息摘要bytes[]=m.digest();分析:执行MessageDigest对象的digest()方法完成计算,计算的结果通过字节类型的数组返回。 (4)处理计算结果必要的话可以使用如下代码将计算结果s转换为字符串。 Stringresult="";for(inti=0;i importjava.security.*;publicclassDigestPass{publicstaticvoidmain(Stringargs[])throwsException{Stringx=args[0];MessageDigestm=MessageDigest.getInstance("MD5");m.update(x.getBytes("UTF8"));bytes[]=m.digest();Stringresult="";for(inti=0;i 输入javaDigestCalcabc来运行程序,其中命令行参数abc是原始数据,屏幕输出计算后的消息摘要:900150983cd24fb0d6963f7d28e17f72。