精华内容
下载资源
问答
  •  对3G移动通信系统的网络安全问题进行了探讨,国内开展的4G系统及工程的建设已经开始安全性改造的工程,此举具有特别重要的意义。  安全性问题自移动通信技术问世以来就已产生。第一代移动通信的模拟蜂窝移动通信...

      转载请标明出处:ttp://blog.csdn.net/sk719887916/article/details/42437253


                安全性问题自网络技术问世以来就已产生。第一代移动通信的模拟蜂窝移动通信系统几乎没有采取安全措施,移动台把其电子序列号(ESN)和网络分配的移动台识别号(MIN)以明文方式传送至网络,若二者相符,即可实现用户的接入,结果造成大量的克隆手机,使用户和运营商深受其害;2G主要有基于时分多址(TDMA)的GSM系统(多为欧洲及中国采用)及基于码分多址(CDMA)的CDMAone系统(多为美国等北美国家采用),这两类系统安全机制的实现有很大区别,但都是基于私钥密码体制,采用共享秘密数据(私钥)的安全协议,实现对接入用户的认证和数据信息的保密,在身份认证及加密算法等方面存在着许多安全隐患;3G移动通信系统在2G的基础上进行了改进,继承了2G系统安全的优点,同时针对3G系统的新特性,定义了更加完善的安全特征与安全服务。未来的移动通信系统除了提供传统的语音、数据、多媒体业务外,还应当能支持电子商务、电子支付、股票交易、互联网业务等,个人智能终端将获得广泛使用,网络和传输信息的安全将成为制约其发展的首要问题。

        网络安全是指网络系统的硬件、软件及其系统中的数据受到保护,不因偶然的或者恶意的原因而遭受到破坏、更改、泄露,系统连续可靠正常地运行,网络服务不中断。
      网络安全包含网络设备安全、网络信息安全、网络软件安全。从广义来说,凡是涉及网络信息的保密性、完整性、可用性、真实性和可控性的相关技术和理论,都是网络安全的研究领域。网络安全是一门涉及计算机科学、网络技术、通信技术、密码技术、信息安全技术、应用数学、数论、信息论等的综合性学科。

      一 主要类型

      网络安全由于不同的环境和应用而产生了不同的类型。主要有以下几种:


      系统安全:是指运行系统安全即保证信息处理和传输系统的安全。它侧重于保证系统正常运行。避免因为系统的崩演和损坏而对系统存储、处理和传输的消息造成破坏和损失。避免由于电磁泄翻,产生信息泄露,干扰他人或受他人干扰。


      网络安全:是指网络上系统信息的安全。包括用户口令鉴别,用户存取权限控制,数据存取权限、方式控制,安全审计,安全问题跟踩,计算机病毒防治,数据加密等。


      信息传播安全:网络上信息传播安全,即信息传播后果的安全,包括信息过滤等。它侧重于防止和控制由非法、有害的信息进行传播所产生的后果,避免公用网络上大云自由传翰的信息失控。


      信息内容安全:网络上信息内容的安全。它侧重于保护信息的保密性、真实性和完整性。避免攻击者利用系统的安全漏润进行窃听、冒充、诈编等有损于合法用户的行为。其本质是保护用户的利益和隐私。


       二 主要解决途径

         没有哪一种网络安全防御技术能保证网络信息服务100%的安全,安全总是相对的,这就需要多种安全防御技术在各个层次上加以部署,延长攻击者侵入所花费的时间、增加成本和所需要的资源,从而卓有成效地降低图书馆网络被攻击的危险,达到安全防护的目标。目前图书馆常用的网络安全防御技术有防火墙技术、入侵检测技术、网络防病毒技术、访问控制技术等。 
       

      1、防火墙技术 


      1.1该技术主要完成以下具体任务: 
       
      1.1.1通过源地址过滤,拒绝外部非法IP地址,有效的避免了与本馆信息服务无关的外部网络主机越权访问; 
       
      1.1.2防火墙可以只保留有用的服务,将其他不需要的服务关闭,这样做可以将系统受攻击的可能性降到最低限度,使黑客无机可乘; 
       
      1.1.3同样,防火墙可以制定访问策略,只有被授权的外部主机才可以访问内部网络上的有限IP地址,从而保证外部网络只能访问内部网络中的必要资源,使得与本馆信息服务无关的操作将被拒绝; 
       
      1.1.4由于外部网络对内部网络的所有访问都要经过防火墙,所以防火墙可以全面监视外部网络对内部网络的访问活动,并进行详细的记录,通过分析可以得出可疑的攻击行为; 
       
      1.1.5另外,由于安装了防火墙后,网络的安全策略由防火墙集中管理,因此,黑客无法通过更改某一台主机的安全策略来达到控制其他资源访问权限的目的,而直接攻击防火墙几乎是不可能的;
       
      1.1.6防火墙可以进行地址转换工作,使外部网络用户不能看到内部网络的结构,使黑客失去攻击目标。 
       
      1.2虽然防火墙技术是在内部网与外部网之间实施安全防范的最佳选择,但也存在一定的局限性: 
       
      1.2.1不能完全防范外部刻意的人为攻击; 
       
      1.2.2不能防范内部用户攻击; 
       
      1.2.3不能防止内部用户因误操作而造成口令失密受到的攻击; 
       
      1.2.4很难防止病毒或者受病毒感染的文件的传输。 
       

      2、入侵检测技术(IDS) 

       
      如果说防火墙技术是静态安全防御技术,那么IDS就是一种动态安全技术。IDS包括基于主机的入侵检测技术和基于网络的入侵检测技术两种。该技术用于保护应用网络连接的主要服务器,实时监视可疑的连接和非法访问的闯入,并对各种入侵行为立即作出反应,如断开网络连接等。[2] 
       

      3、网络防病毒技术 

       
      当今世界上每天有13种到50种新病毒出现,而60%的病毒均通过Internet传播,病毒发展有日益跨越疆界的趋势,存在着不可估量的威胁性和破坏力。冲击波病毒及震荡波病毒就足以证明如果不重视计算机网络防病毒工作,那就有可能给社会造成灾难性的后果,因此计算机病毒的防范也是网络安全技术中重要的一环。 
       
      网络防病毒技术包括预防病毒、检测病毒和消除病毒等3种技术: 
       
      3.1预防病毒技术,它是通过自身常驻系统内存,优先获得系统的控制权,监视和判断系统中是否有病毒存在,进而阻止计算机病毒进入计算机系统和对系统进行破坏。技术手段包括:加密可执行程序、引导区保护、系统监控与读写控制等。 
       
      3.2检测病毒技术,它是通过对计算机病毒的特征来进行判断的侦测技术,如自身校验、关键字、文件长度的变化等。病毒检测一直是病毒防护的支柱,然而随着病毒的数目和可能切入点的大量增加,识别古怪代码串的进程变得越来越复杂,而且容易产生错误和疏忽。因此,最新的防病毒技术应将病毒检测、多层数据保护和集中式管理等多种功能集成起来,形成多层次防御体系,既具有稳健的病毒检测功能,又具有客户机/服务器数据保护能力。 
       
      3.3消除病毒技术,它是通过对计算机病毒的分析,开发出具有杀除病毒程序并恢复原文件的软件。大量的病毒针对网上资源和应用程序进行攻击,这样的病毒存在于信息共享的网络介质上,因而要在网关上设防,在网络入口实时杀毒。对于内部病毒,如客户机感染的病毒,通过服务器防病毒功能,在病毒从客户机向服务器转移的过程中杀掉,把病毒感染的区域限制在最小范围内。[1] 
       
      网络防病毒技术的具体实现方法包括对网络服务器中的文件进行频繁地扫描和监测,工作站上采用防病毒芯片和对网络目录及文件设置访问权限等。防病毒必须从网络整体考虑,从方便管理人员的工作着手,通过网络环境管理网络上的所有机器,如利用网络唤醒功能,在夜间对全馆在线网络的客户机进行扫描,检查病毒情况;利用在线报警功能,网络上每一台机器出现故障、病毒侵入时,网络管理人员都能及时知道,并作出相应的对策。

        

        4、访问控制技术 

       
      访问控制是网络安全防范和保护的主要技术,它的主要任务是保证某一服务器上的网络资源不被非法窃取和非法访问。 
       
      4.1入网访问控制 
       
      入网访问控制为网络访问提供了第一层访问控制。它控制哪些用户能够登录到服务器并获取网络资源,控制准许用户入网的时间和准许他们在哪台工作站入网。 
       
      用户的入网访问控制可分为三个步骤:用户名的识别与验证、用户口令的识别与验证、用户帐号的缺省限制检查。三道关卡中只要任何一关未过,该用户便不能进入该网络。 
       
      对网络用户的用户名和口令进行验证是防止非法访问的第一道防线。用户注册时首先输入用户名和口令,服务器将验证所输入的用户名是否合法。如果验证合法,才继续验证用户输入的口令,否则,用户将被拒之网络外。用户的口令是用户入网的关键所在。为保证口令的安全性,用户口令不能显示在显示屏上,口令长度应不少于6个字符,口令字符最好是数字、字母和其他字符的混合。 
       
      网络管理员应该可以控制和限制普通用户的帐号使用、访问网络的时间、方式。用户名或用户帐号是所有计算机系统中最基本的安全形式。用户帐号应只有系统管理员才能建立。用户口令应是每用户访问网络所必须提交的“证件”、用户可以修改自己的口令,但系统管理员应该可以控制口令的以下几个方面的限制:最小口令长度、强制修改口令的时间间隔、口令的唯一性、口令过期失效后允许入网的宽限次数。 
       
      用户名和口令验证有效之后,再进一步履行用户帐号的缺省限制检查。网络应能控制用户登录入网的站点、限制用户入网的时间、限制用户入网的工作站数量。当用户对图书馆交费网络的访问“资费”用尽时,网络还应能对用户的帐号加以限制,用户此时应无法进入网络访问网络资源。网络应对所有用户的访问进行审计。如果在设定的次数内输入口令不正确,则认为是非法用户的入侵,应给出报警信息并锁定帐户禁止登录。 
       
      4.2网络的权限控制 
       
      网络的权限控制是针对网络非法操作所提出的一种安全保护措施。用户和用户组被赋予一定的权限。网络控制用户和用户组可以访问哪些目录、子目录、文件和其他资源。可以指定用户对这些文件、目录、设备能够执行哪些操作。我们可以根据访问权限将用户分为以下几类:(1)特殊用户(即系统管理员);(2)一般用户,系统管理员根据他们的实际需要为他们分配操作权限;(3)审计用户,负责网络的安全控制与资源使用情况的审计。用户对网络资源的访问权限可以用一个访问控制表来描述。 
       
      4.3目录级安全控制 
       
      网络应允许控制用户对目录、文件、设备的访问。用户在目录一级指定的权限对所有文件和子目录有效,用户还可进一步指定对目录下的子目录和文件的权限。对目录和文件的访问权限一般有八种:系统管理员权限(Supervisor)、读权限(Read)、写权限(Write)、创建权限(Create)、删除权限(Erase)、修改权限(Modify)、文件查找权限(FileScan)、存取控制权限(AccessControl)。一个网络系统管理员应当为用户指定适当的访问权限,这些访问权限控制着用户对服务器的访问。八种访问权限的有效组合可以让用户有效地完成工作,同时又能有效地控制用户对服务器资源的访问,从而加强了网络和服务器的安全性。 
       
      4.4属性安全控制 
       
      当用文件、目录和网络设备时,网络系统管理员应给文件、目录等指定访问属性。属性安全控制可以将给定的属性与网络服务器的文件、目录和网络设备联系起来。属性安全在权限安全的基础上提供更进一步的安全性。网络上的资源都应预先标出一组安全属性。用户对网络资源的访问权限对应一张访问控制表,用以表明用户对网络资源的访问能力。属性往往能控制以下几个方面的权限:向某个文件写数据、拷贝一个文件、删除目录或文件、查看目录和文件、执行文件、隐含文件、共享、系统属性等。网络的属性可以保护重要的目录和文件,防止用户对目录和文件的误删除、执行修改、显示等。 
       
      4.5网络服务器安全控制 
       
      网络允许在服务器控制台上执行一系列操作。用户使用控制台可以装载和卸载模块,可以安装和删除软件等操作。网络服务器的安全控制包括可以设置口令锁定服务器控制台,以防止非法用户修改、删除重要信息或破坏数据;可以设定服务器登录时间限制、非法访问者检测和关闭的时间间隔。
       
      4.6图书馆网络监测和锁定控制 
       
      网络管理员应对图书馆网络实施监控,服务器应记录用户对网络资源的访问,对非法的网络访问,服务器应以图形、文字或声音等形式报警,以引起网络管理员的注意。如果非法用户试图进入网络,网络服务器应会自动记录企图尝试进入网络的次数,一旦非法访问的次数达到设定数值,那么该帐户将被自动锁定。 
       
      

     二 移动通信安全问题

      
        随着向下一代网络(NGN)的演进,基于IP的网络架构必将使移动网络面临IP网络固有的一些安全问题。移动通信网络最终会演变成开放式的网络,能向用户提供开放式的应用程序接口,以满足用户的个性化需求。网络的开放性以及无线传播的特性将使安全问题成为整个移动通信系统的核心问题之一。

        1、移动通信系统面临的安全威胁

        安全威胁来自网络协议和系统的弱点,攻击者可以利用网络协议和系统的弱点非授权访问敏感数据、非授权处理敏感数据、干扰或滥用网络服务,对用户和网络资源造成损失。

        按照攻击的物理位置,对移动通信系统的安全威胁可分为对无线链路的威胁、对服务网络的威胁和对移动终端的威胁。主要威胁方式有以下几种:

        ●窃听,在无线链路或服务网内窃听用户数据、信令数据及控制数据;

        ●伪装,伪装成网络单元截取用户数据、信令数据及控制数据,伪终端欺骗网络获取服务;

        ●流量分析,主动或被动进行流量分析以获取信息的时间、速率、长度、来源及目的地;

        ●破坏数据的完整性,修改、插入、重放、删除用户数据或信令数据以破坏数据的完整性;

        ●拒绝服务,在物理上或协议上干扰用户数据、信令数据及控制数据在无线链路上的正确传输,实现拒绝服务攻击;

        ●否认,用户否认业务费用、业务数据来源及发送或接收到的其他用户的数据,网络单元否认提供的网络服务;

        ●非授权访问服务,用户滥用权限获取对非授权服务的访问,服务网滥用权限获取对非授权服务的访问;

        ●资源耗尽,通过使网络服务过载耗尽网络资源,使合法用户无法访问。

        随着网络规模的不断发展和网络新业务的应用,还会有新的攻击类型出现。



       2、4G移动通信系统的安全机制

        WCDMA、cdma2000、TD-SCDMA是第三代移动通信的主流技术。WCDMA、TD-SCDMA的安全规范由以欧洲为主体的3GPP制定,cdma2000的安全规范由以北美为首的3GPP2制定。

        与2G以语音业务为主、仅提供少量的数据业务不同,3G可提供高达2Mbit/s的无线数据接入方式。其安全模式也以数据、交互式、分布式业务为主。

        1.3GPP的安全机制

        3GPP的接入安全规范已经成熟,加密算法和完整性算法已经实现标准化。基于IP的网络域的安全也已制定出相应的规范。3GPP的终端安全、网络安全管理规范还有待进一步完善。

        3GPP制定的3G安全逻辑结构针对不同的攻击类型,分为五类,即网络接入安全(Ⅰ)、核心网安全(Ⅱ)、用户安全(Ⅲ)、应用安全(Ⅳ)、安全特性可见性及可配置能力(Ⅴ)。

        3GPP网络接入安全机制有三种:根据临时身份(IMSI)识别,使用永久身份(IMSI)识别,认证和密钥协商(AKA)。AKA机制完成移动台(MS)和网络的相互认证,并建立新的加密密钥和完整性密钥。AKA机制的执行分为两个阶段:第一阶段是认证向量(AV)从归属环境(HE)到服务网络(SN)的传送;第二阶段是SGSN/VLR和MS执行询问应答程序取得相互认证。HE包括HLR和鉴权中心(AuC)。认证向量含有与认证和密钥分配有关的敏感信息,在网络域的传送使用基于七号信令的MAPsec协议,该协议提供了数据来源认证、数据完整性、抗重放和机密性保护等功能。

        3GPP为3G系统定义了10种安全算法:f0、f1、f2、f3、f4、f5、f6、f7、f8、f9、f1*、f5*,应用于不同的安全服务。身份认证与密钥分配方案中移动用户登记和认证参数的调用过程与GSM网络基本相同,不同之处在于3GPP认证向量是5元组,并实现了用户对网络的认证。AKA利用f0至f5*算法,这些算法仅在鉴权中心和用户的用户身份识别模块(USIM)中执行。其中,f0算法仅在鉴权中心中执行,用于产生随机数RAND;f1算法用于产生消息认证码(鉴权中心中为MAC-A,用户身份识别模块中为XMAC-A);f1*是重同步消息认证算法,用于产生MAC-S;f2算法用于产生期望的认证应答(鉴权中心中为XRES,用户身份识别模块中为RES);f3算法用于产生加密密钥CK;f4算法用于产生消息完整性密钥IK;f5算法用于产生匿名密钥AK和对序列号SQN加解密,以防止被位置跟踪;f5*是重同步时的匿名密钥生成算法。AKA由SGSN/VLR发起,在鉴权中心中产生认证向量AV=(RAND,XRES,CK,IK,AUTN)和认证令牌AUTN=SQN[AAK]‖AMF‖MAC-A。VLR发送RAND和AUTN至用户身份识别模块。用户身份识别模块计算XMAC-A=f1K(SQN‖RAND‖AMF),若等于AUTN中的MAC-A,并且SQN在有效范围,则认为对网络鉴权成功,计算RES、CK、IK,发送RES至VLR。VLR验证RES,若与XRES相符,则认为对MS鉴权成功;否则,拒绝MS接入。当SQN不在有效范围时,用户身份识别模块和鉴权中心利用f1*算法进入重新同步程序,SGSN/VLR向HLR/AuC请求新的认证向量。


    3GPP的数据加密机制将加密保护延长至无线接入控制器(RNC)。数据加密使用f8算法,生成密钥流块KEYSTREAM。对于MS和网络间发送的控制信令信息,使用算法f9来验证信令消息的完整性。对于用户数据和话音不给予完整性保护。MS和网络相互认证成功后,用户身份识别模块和VLR分别将CK和IK传给移动设备和无线网络控制器,在移动设备和无线网络控制器之间建立起保密链路。f8和f9算法都是以分组密码算法KASUMI构造的,KASUMI算法的输入和输出都是64bit,密钥是128bit。KASUMI算法在设计上具有对抗差分和线性密码分析的可证明的安全性。

        2.移动网络系统安全特性的优缺点

        

        ●提供了双向认证。不但提供基站对MS的认证,也提供了MS对基站的认证,可有效防止伪基站攻击;

        ●提供了接入链路信令数据的完整性保护;

        ●密码长度增加为128bit,改进了算法;

        ●3GPP接入链路数据加密延伸至RNC;

        ●3G的安全机制还具有可拓展性,为将来引入新业务提供安全保护措施;

        ●3G能向用户提供安全可视性操作,用户可随时查看自己所用的安全模式及安全级别;

        在密钥长度、算法选定、鉴别机制和数据完整性检验等方面,3G的安全性能远远优于2G。但3G仍然存在下列安全缺陷:

        ●没有建立公钥密码体制,难以实现用户数字签名。随着移动终端存储器容量的增大和CPU处理能力的提高以及无线传输带宽的增加,必须着手建设无线公钥基础设施(WPKI);

        ●密码学的最新成果(比如ECC椭圆曲线密码算法)并未在3G中得到应用;

        ●算法过多;

        ●密钥产生机制和认证协议有一定的安全隐患。

        3、对未来移动通信系统安全性的分析

        (1)针对移动通信系统的特点,建立适合未来移动通信系统的安全体系结构模型

        3G系统的安全逻辑结构仍然参考了OSI模型,而OSI模型是网络参考模型,用它来分析安全机制未必是合适的。随着移动技术与IP技术的融合、Adhoc的广泛应用以及网络业务的快速发展,需要更系统的方法来研究移动通信系统的安全。比如,在网络安全体系结构模型中,应能体现网络的安全需求分析、实现的安全目标等。

        (2)由私钥密码体制向混合密码体制的转变

        未来的移动通信系统中,将针对不同的安全特征与服务,采用私钥密码体制和公钥密码体制混合的体制,充分利用这两种体制的优点。随着未来移动电子商务的迅速发展,采用私钥密码体制,虽然密钥短,算法简单,但对于密钥的传送和分配的安全性要求很高;采用公钥密码体制,参与交换的是公开钥,因而增加了私钥的安全性,并能同时满足数字加密和数字签名的需要,满足电子商务所要求的身份鉴别和数据的机密性、完整性、不可否认性。因此,必须尽快建设无线公钥基础设施(WPKI),建设中国移动的以认证中心(CA)为核心的安全认证体系。

        (3)3G的整个安全体系向透明化发展

        3G的整个安全体系仍是建立在假定网络内部绝对安全的基础之上,当用户漫游时,核心网络之间假定相互信任,鉴权中心依附于交换子系统。事实上,随着移动通信标准化的发展,终端在不同运营商甚至异种网络之间的漫游也会成为可能,因此应增加核心网之间的安全认证机制。特别是随着移动电子商务的广泛应用,更应尽量减少或避免网络内部人员的干预性。未来的安全中心应能独立于系统设备,具有开放的接口,能独立地完成双向鉴权、端到端数据加密等安全功能,甚至对网络内部人员也是透明的。

        (4)新密码技术应获得广泛应用

        随着密码学的发展以及移动终端处理能力的提高,新的密码技术如量子密码技术、椭圆曲线密码技术、生物识别技术等将在移动通信系统中获得广泛应用,加密算法和认证算法自身的抗攻击能力更强健,从而保证传输信息的机密性、完整性、可用性、可控性和不可否认性。

        (5)移动通信网络的安全措施更加体现面向用户的理念

        用户能自己选择所要的保密级别,安全参数既可由网络默认,也可由用户个性化设定。

     

        从网络诞生相当长一段时期内,基于TCP/IP通信的安全隐患一直存在,PC和移动通信设备间的数据传输一直是网络界比较关心的问题,这也给程序开发者带来了信息安全问题,,需要自我对自己的程序数据进行加密和解密 ,但是还是无法避免根本性安全隐患问题,接下来我会作为开发者来的角度 学习下和程序员比较密切的安全防御手段,数据加密和防入侵相关知识。


    展开全文
  • EFS加密安全

    千次阅读 2004-10-10 04:05:00
    随着稳定性和可靠性的逐步提高,Windows 2000/XP已经被越来越的人使用,很多人还用Windows 2000/XP自带的EFS加密功能把自己的一些重要数据加密保存。虽然EFS易用性不错,不过发生问题后就难解决了,例如不做任何...

    随着稳定性和可靠性的逐步提高,Windows 2000/XP已经被越来越多的人使用,很多人还用Windows 2000/XP自带的EFS加密功能把自己的一些重要数据加密保存。虽然EFS易用性不错,不过发生问题后就难解决了,例如不做任何准备就重装了操作系统,那很可能导致以前的加密数据无法解密。最近一段时间我们已经可以在越来越多的论坛和新闻组中看到网友的求救,都是类似这样的问题而导致重要数据无法打开,损失惨重。为了避免更多人受到损失,这里我把使用EFS加密的注事项写出来,希望对大家有所帮助。

    注意,下文中的Windows XP皆指Professional版,Windows XP Home版并不支持EFS加密。

    什么是EFS加密

    EFS(Encrypting File System,加密文件系统)是Windows 2000/XP所特有的一个实用功能,对于NTFS卷上的文件和数据,都可以直接被操作系统加密保存,在很大程度上提高了数据的安全性。

    EFS加密是基于公钥策略的。在使用EFS加密一个文件或文件夹时,系统首先会生成一个由伪随机数组成的FEK (File Encryption Key,文件加密钥匙),然后将利用FEK和数据扩展标准X算法创建加密后的文件,并把它存储到硬盘上,同时删除未加密的原始文件。随后系统利用你的公钥加密FEK,并把加密后的FEK存储在同一个加密文件中。而在访问被加密的文件时,系统首先利用当前用户的私钥解密FEK,然后利用FEK解密出文件。在首次使用EFS时,如果用户还没有公钥/私钥对(统称为密钥),则会首先生成密钥,然后加密数据。如果你登录到了域环境中,密钥的生成依赖于域控制器,否则它就依赖于本地机器。

    EFS加密有什么好处

    首先,EFS加密机制和操作系统紧密结合,因此我们不必为了加密数据安装额外的软件,这节约了我们的使用成本。

    其次,EFS加密系统对用户是透明的。这也就是说,如果你加密了一些数据,那么你对这些数据的访问将是完全允许的,并不会受到任何限制。而其他非授权用户试图访问加密过的数据时,就会收到“访问拒绝”的错误提示。EFS加密的用户验证过程是在登录Windows时进行的,只要登录到Windows,就可以打开任何一个被授权的加密文件。


    如何使用EFS加密

    要使用EFS加密,首先要保证你的操作系统符合要求。目前支持EFS加密的Windows操作系统主要有Windows 2000全部版本和Windows XP Professional。至于还未正式发行的Windows Server 2003和传闻中的开发代号为Longhorn的新一带操作系统,目前看来也支持这种加密机制。其次,EFS加密只对NTFS5分区上的数据有效(注意,这里我们提到了NTFS5分区,这是指由Windows 2000/XP格式化过的NTFS分区;而由Windows NT4格式化的NTFS分区是NTFS4格式的,虽然同样是NTFS文件系统,但它不支持EFS加密),你无法加密保存在FAT和FAT32分区上的数据。

    对于想加密的文件或文件夹,只需要用鼠标右键点击,然后选择“属性”,在常规选项卡下点击“高级”按钮,之后在弹出的窗口中选中“加密内容以保护数据”,然后点击确定,等待片刻数据就加密好了。如果你加密的是一个文件夹,系统还会询问你,是把这个加密属性应用到文件夹上还是文件夹以及内部的所有子文件夹。按照你的实际情况来操作即可。解密数据也是很简单的,同样是按照上面的方法,把“加密内容以保护数据”前的钩消除,然后确定。

    如果你不喜欢图形界面的操作,还可以在命令行模式下用“cipher”命令完成对数据的加密和解密操作,至于“cipher”命令更详细的使用方法则可以通过在命令符后输入“cipher/?”并回车获得。

    注意事项:如果把未加密的文件复制到具有加密属性的文件夹中,这些文件将会被自动加密。若是将加密数据移出来,如果移动到NTFS分区上,数据依旧保持加密属性;如果移动到FAT分区上,这些数据将会被自动解密。被EFS加密过的数据不能在Windows中直接共享。如果通过网络传输经EFS加密过的数据,这些数据在网络上将会以明文的形式传输。NTFS分区上保存的数据还可以被压缩,不过一个文件不能同时被压缩和加密。最后一点,Windows的系统文件和系统文件夹无法被加密。

    这里还有个窍门,用传统的方法加密文件,必须打开层层菜单并依次确认,非常麻烦,不过只要修改一下注册表,就可以给鼠标的右键菜单中增添“加密”和“解密”的选项。

    在运行中输入“regedit”并回车,打开注册表编辑器,定位到HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion /Explorer/Advanced ,在“编辑”菜单上点击“新建-Dword值”,输入“EncryptionContextMenu”作为键名,并设置键值为“1”。现在退出注册表编辑器,打开资源管理器,任意选中一个NTFS分区上的文件或者文件夹,然后点击鼠标右键,就可以在右键菜单中找到相应的选项,直接点击就可以完成加密解密的操作。

    如果你想设置禁止加密某个文件夹,可以在这个文件夹中创建一个名为“Desktop.ini”的文件,然后用记事本打开,并添加如下内容:

    [Encryption]

    Disable=1


    之后保存并关闭这个文件。这样,以后要加密这个文件夹的时候就会收到错误信息,除非这个文件被删除。

    而如果你想在本机上彻底禁用EFS加密,则可以通过修改注册表实现。在运行中输入“Regedit”并回车,打开注册表编辑器,定位到HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/EFS,在“编辑”菜单上点击“新建-Dword值”,输入“EfsConfiguration”作为键名,并设置键值为“1”,这样本机的EFS加密就被禁用了。而以后如果又想使用时只需把键值改为“0”。

    如何保证EFS加密的安全和可靠

    前面我们已经了解到,在EFS加密体系中,数据是靠FEK加密的,而FEK又会跟用户的公钥一起加密保存;解密的时候顺序刚好相反,首先用私钥解密出FEK,然后用FEK解密数据。可见,用户的密钥在EFS加密中起了很大作用。

    密钥又是怎么来的呢?在Windows 2000/XP中,每一个用户都有一个SID(Security Identifier,安全标示符)以区分各自的身份,每个人的SID都是不相同的,并且有唯一性。可以这样理解:把SID想象成人的指纹,虽然世界上已经有几十亿人(同名同姓的也有很多),可是理论上还没有哪两个人的指纹是完全相同的。因此,这具有唯一性的SID就保证了EFS加密的绝对安全和可靠。因为理论上没有SID相同的用户,因而用户的密钥也就绝不会相同。在第一次加密数据的时候,操作系统就会根据加密者的SID生成该用户的密钥,并把公钥和私钥分开保存起来,供用户加密和解密数据。

    这一切,都保证了EFS机制的可靠。

    其次,EFS机制在设计的时候就考虑到了多种突发情况的产生,因此在EFS加密系统中,还有恢复代理(Recovery Agent)这一概念。

    举例来说,公司财务部的一个职工加密了财务数据的报表,某天这位职工辞职了,安全起见你直接删除了这位职工的账户。直到有一天需要用到这位职工创建的财务报表时才发现这些报表是被加密的,而用户账户已经删除,这些文件无法打开了。不过恢复代理的存在就解决了这些问题。因为被EFS加密过的文件,除了加密者本人之外还有恢复代理可以打开。

    对于Windows 2000来说,在单机和工作组环境下,默认的恢复代理是 Administrator ;Windows XP在单机和工作组环境下没有默认的恢复代理。而在域环境中就完全不同了,所有加入域的Windows 2000/XP计算机,默认的恢复代理全部是域管理员。

    这一切,都保证了被加密数据的安全。

    如何避免不慎使用EFS加密带来的损失

    如果你也和我一样,由于对EFS加密不了解致使重要数据丢失,那么也别气馁,就当买了个教训。毕竟通过这事情你还是能学会很多东西的。那就是:备份密钥!设置有效的恢复代理!

    对于上面讲到的情况1,备份密钥可以避免这种悲剧的发生。在运行中输入“certmgr.msc”然后回车,打开证书管理器。密钥的导出和导入工作都将在这里进行。

    在你加密过文件或文件夹后,打开证书管理器,在“当前用户”-“个人”-“证书”路径下,应该可以看见一个以你的用户名为名称的证书(如果你还没有加密任何数据,这里是不会有证书的)。右键点击这个证书,在“所有任务”中点击“导出”。之后会弹出一个证书导出向导,在向导中有一步会询问你是否导出私钥,在这里要选择“导出私钥”,其它选项按照默认设置,连续点击继续,最后输入该用户的密码和想要保存的路径并确认,导出工作就完成了。导出的证书将是一个pfx为后缀的文件。

    重装操作系统之后找到之前导出的pfx文件,鼠标右键点击,并选择“安装PFX”,之后会出现一个导入向导,按照导入向导的提示完成操作(注意,如果你之前在导出证书时选择了用密码保护证书,那么在这里导入这个证书时就需要提供正确的密码,否则将不能继续),而之前加密的数据也就全部可以正确打开。

    对于情况2,我们对Windows XP和Windows 2000两种情况分别加以说明。

    由于Windows XP没有默认的恢复代理,因此我们加密数据之前最好能先指定一个默认的恢复代理(建议设置Administrator为恢复代理,虽然这个账户没有显示在欢迎屏幕上,不过确实是存在的)。这样设置:

    首先要获得可以导入作为恢复代理的用户密钥,如果你想让Administrator成为恢复代理,首先就要用Administrator账户登录系统。在欢迎屏幕上连续按Ctrl+Alt+Del两次,打开登录对话框,在用户名处输入Administrator,密码框中输入你安装系统时设置的Administrator密码,然后登录。首先在硬盘上一个方便的地方建立一个临时的文件,文件类型不限。这里我们以C盘根目录下的一个1.txt文本文件文件为例,建立好后在运行中输入“CMD”然后回车,打开命令提示行窗口,在命令提示符后输入“cipher /r:c:/1.txt”,回车后系统还会询问你是否用密码把证书保护起来,你可以按照你的情况来决定,如果不需要密码保护就直接按回车。完成后我们能在C盘的根目录下找到1.txt.cer和1.txt.pfx两个文件(为了显示的清楚我在文件夹选项中设置了显示所有文件类型的扩展名,这样我们可以更清楚地了解到底生成了哪些文件)。

    之后开始设置恢复代理。对于1.txt.pfx这个文件,同样需要用鼠标右键点击,然后按照向导的提示安装。而1.txt.cer则有些不同,在运行中输入“gpedit.msc”并回车,打开组策略编辑器。在“计算机配置-Windows设置-安全设置-公钥策略-正在加密文件系统”菜单下,在右侧窗口的空白处点击鼠标右键,并选择“添加数据恢复代理”,然后会出现“添加故障恢复代理向导”按照这个向导打开1.txt.cer,如果一切无误就可以看见图五的界面,这说明我们已经把本机的Administrator设置为故障恢复代理。

    如果你愿意,也可以设置其它用户为恢复代理。需要注意的是,你导入证书所用的1.txt.pfx和1.txt.cer是用哪个账户登录后生成的,那么导入证书后设置的恢复代理就是这位用户。

    在设置了有效的恢复代理后,用恢复代理登录系统就可以直接解密文件。但如果你在设置恢复代理之前就加密过数据,那么这些数据恢复代理仍然是无法打开的。

    而对于Windows 2000就更加简单,Windows 2000有恢复代理,因此只要用恢复代理(默认的就是Administrator)的账号登录系统,就可以解密文件。



    如何在本机上共享经EFS加密过的数据

    加密过的文件只有加密者本人和恢复代理可以打开,如果你要和本机的其它用户共享加密文件又该怎么办?这在Windows 2000中是不行的,不过在Windows XP中可以做到。

    选中一个希望共享的加密文件(注意,只能是文件,而不能是文件夹),在文件上用鼠标右键点击,并选择“属性”,之后在属性对话框的“常规”选项卡上点击“高级”按钮,然后再点击“详细信息”,之后你可以看见类似图六的窗口,在这里你可以决定谁可以打开这个加密文件以及察看文件的恢复代理是具体是谁。


    对EFS加密的几个错误认识

    很多人对EFS理解的不够彻底,因此在使用过程中总会有一些疑问。一般情况下,我们最常见的疑问有以下几种:



    1, 为什么打开加密过的文件时没有需要我输入密码?
    这正是EFS加密的一个特性,同时也是EFS加密和操作系统紧密结合的最佳证明。因为跟一般的加密软件不同,EFS加密不是靠双击文件,然后弹出一个对话框,然后输入正确的密码来确认的用户的;EFS加密的用户确认工作在登录到Windows时就已经进行了。一旦你用适当的账户登录,那你就能打开相应的任何加密文件,并不需要提供什么额外的密码。

    2, 我的加密文件已经打不开了,我能够把NTFS分区转换成FAT32分区来挽救我的文件吗?
    这当然是不可能的了。很多人尝试过各种方法,例如把NTFS分区转换成FAT32分区;用NTFS DOS之类的软件到DOS下去把文件复制到FAT32分区等,不过这些尝试都以失败告终。毕竟EFS是一种加密,而不是一般的什么权限之类的东西,这些方法对付EFS加密都是无济于事。而如果你的密钥丢失或者没有做好备份,那么一旦发生事故所有加密过的数据就都没救了。

    3, 我加密数据后重装了操作系统,现在加密数据不能打开了。如果我使用跟前一个系统相同的用户名和密码总应该就可以了吧?
    这当然也是不行的,我们在前面已经了解到,跟EFS加密系统密切相关的密钥是根据每个用户的SID得来的。尽管你在新的系统中使用了相同的用户名和密码,但是这个用户的SID已经变了。这个可以理解为两个同名同姓的人,虽然他们的名字相同,不过指纹绝不可能相同,那么这种想法对于只认指纹不认人名的EFS加密系统当然是无效的。

    4, 被EFS加密过的数据是不是就绝对安全了呢?
    当然不是,安全永远都是相对的。以被EFS加密过的文件为例,如果没有合适的密钥,虽然无法打开加密文件,不过仍然可以删除(有些小人确实会这样想:你竟然敢加密了不让我看!那好,我就删除了它,咱们都别看)。所以对于重要文件,最佳的做法是NTFS权限和EFS加密并用。这样,如果非法用户没有合适的权限,将不能访问受保护的文件和文件夹;而即使拥有权限(例如为了非法获得重要数据而重新安装操作系统,并以新的管理员身份给自己指派权限),没有密钥同样还是打不开加密数据。

    5, 我只是用Ghost恢复了一下系统,用户账户和相应的SID都没有变,怎么以前的加密文件也打不开了?
    这也是正常的,因为EFS加密所用到的密钥并不是在创建用户的时候生成,而是在你第一次用EFS加密文件的时候。如果你用Ghost创建系统的镜像前还没有加密过任何文件,那你的系统中就没有密钥,而这样的系统制作的镜像当然也就不包括密钥。一旦你加密了文件,并用Ghost恢复系统到创建镜像的状态,解密文件所用的密钥就丢失了。因此这个问题一定需要主意!

     

    希望本文对你使用EFS加密有所帮助,希望你的数据能够更加安全!

    展开全文
  • Java 安全编程加密了解2

    千次阅读 2012-09-04 20:01:01
    Java 安全编程 1.1 J2SE 的主要工具 基本工具: javac Java 编程语言的编译器。本书各章的程序都是在 DOS 窗口中通过执行 "javac 文件名 ” 来编译 Java 程序的。文件名必须以 .java 为后缀,编译以后生成 ....

    Java 安全编程

    1.1 J2SE 的主要工具

    基本工具:
    javac
       Java 编程语言的编译器。本书各章的程序都是在 DOS 窗口中通过执行 "javac 文件名 ” 来编译 Java 程序的。文件名必须以 .java 为后缀,编译以后生成 .class 为后缀的字节码文件。
    java 用于执行 Java 应用程序。本书各章的程序大都通过在 DOS 窗口输入 “java 字节码文件名称 ” 来运行 javac 编译好的程序。输入命令时,字节码文件名称的后缀不输入。
    javadoc 用于生成 API 文档。在编写程序时将注释语句写在 “/**” 和 “*/” 之间,则其内容便可被 javadoc 识别,执行 “javadoc *.java” ,自动生成 API 文档。
    appletviewer 没有 Web 浏览器时可用来运行和调试 Java Applet 程序。
    jar 管理 jar 文件。本书多次使用该工具将 Java 程序打包成为一个文件,并进而进行进一步的处理。
    jdb Java 调试器
    javah C 头文件和存根的生成器,用于编写本地文件。
    javap 类分解器。可显示字节码文件的包、标记为 public 及 protected 的变量和方法等信息。

    extcheck 检测 jar 文件的版本冲突
    RMI 工具:
    rmic
    生成远程对象的架构和存根。执行后可根据给定的字节码文件 XX.class 可生成 XX__Stub.class 和 XX_Skel.class 文件部署在 RMI 系统中。
    rmiregistry 提供远程对象的注册服务。 RMI 客户程序可通过该服务找到远程对象。
    rmid 启动激活系统后台程序。
    serialver 返回类的 serialVersionUID
    国际化工具:
    native2ascii
    将本地编码的文本转换为 Unicode 编码

    安全工具
    keytool
    管理密钥库和证书。本书自第 5 章起大量使用该工具。
    Jarsigner 对 jar 文件进行签名,并验证 jar 文件的签名。
    policytool 管理策略文件的图形界面工具。
    Java IDL and RMI-IIOP 工具
    tnameserv
    提供访问名字服务
    idlj 根据给定的 IDL 文件生成 Java 绑定,使 Java 程序可以使用 CORBA 功能
    orbd 在 CORBA 环境中使客户透明地定位和执行服务器上 persistent 对象
    servertool 应用程序编写者注册、取消注册、启动、关闭 persistent 服务器的命令行工具。

    Java Plug-in 工具:
    unregbean
    用于取消 Java Bean 组件的注册
    HtmlConverter 修改调用 Applet 的 HTML 网页,将其中的 <applet> 标记按照一定格式转
    换为 <Object> 标记,以便让浏览器使用 Java Plug-in 运行 Java Applet 程序。
    1.2 反编译器的安装

    Jad 是 Java 著名的反编译器,下载地址: http://www.varaneckas.com/jad

    下载后将其放到 java/bin 的目录下,即可使用了。

    1 ) 最简单的用法

    JAD 的最简单用法是直接输入“ Jad 字节码文件名称”,如

    Jad example.class

    Jad example

    则将 example.class 反编译,反编译得到的源代码保存为 example.jad 。如果 example.jad 已经

    存在,会提示是否覆盖。使用命令选项 -o 可跳过提示。

    2 ) 使用通配符

    JAD 支持通配符,如可输入:

    Jad *.class

    则将当前目录所有字节码文件进行反编译。

    3 ) 指定输出形式

    如果希望反编译结果以 .java 为后缀,可输入

    jad -sjavaexample.class

    但此时要注意不要覆盖了原有的源代码,尤其加上 -o 选项后,覆盖原有文件不会出现提示,

    更应小心。类似地,可以指定任意后缀。

    使用 -p 选项可以将反编译结果输出到屏幕,如

    jad –p example.class

    进一步可将其重定向到任意文件名,如

    jad –p example.class >my.java

    使用“ -d 目录名”选项可指定反编译后的输出文件的目录,若没有目录会自动创建。

    jad -o -dtest -sjava*.class

    jad -o -dtest -s java *.class

    将当前目录所有字节码文件反编译到 test 目录。

    4 ) 设置输出内容

    使用 -a 选项则可以使用 JVM 字节码注释输出结果。使用 -stat 选项可以最后统计类、方法、成员变量等的数量。

    <!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:楷体_GB2312; mso-font-alt:微软雅黑; mso-font-charset:134; mso-generic-font-family:modern; mso-font-pitch:fixed; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:幼圆; mso-font-alt:微软雅黑; mso-font-charset:134; mso-generic-font-family:modern; mso-font-pitch:fixed; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:"/@幼圆"; mso-font-charset:134; mso-generic-font-family:modern; mso-font-pitch:fixed; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@楷体_GB2312"; mso-font-charset:134; mso-generic-font-family:modern; mso-font-pitch:fixed; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0in; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:10.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} h2 {mso-style-next:Normal; margin-top:24.0pt; margin-right:0in; margin-bottom:24.0pt; margin-left:0in; text-align:justify; text-justify:inter-ideograph; text-indent:21.0pt; mso-line-height-alt:15.7pt; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:2; mso-layout-grid-align:none; font-size:18.0pt; mso-bidi-font-size:10.0pt; font-family:"Times New Roman"; mso-fareast-font-family:幼圆; mso-bidi-font-weight:normal;} p.MsoBodyTextIndent, li.MsoBodyTextIndent, div.MsoBodyTextIndent {margin-top:0in; margin-right:0in; margin-bottom:0in; margin-left:42.0pt; margin-bottom:.0001pt; mso-para-margin-top:0in; mso-para-margin-right:0in; mso-para-margin-bottom:0in; mso-para-margin-left:4.0gd; mso-para-margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:10.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} @page Section1 {size:8.5in 11.0in; margin:1.0in 1.25in 1.0in 1.25in; mso-header-margin:.5in; mso-footer-margin:.5in; mso-paper-source:0;} div.Section1 {page:Section1;} -->

    1.3混淆器的安装

    本书使用的混淆器为 Marvin Obfuscator 1.2b ,可在 http://www.drjava.de/obfuscator/下载,将其直接解压缩到某个目录即可。

    该混淆器使用 Java开发,提供了一个 jar文件。为了便于运行程序, Marvin Obfuscator 1.2b提供了一个批处理文件 obfuscate.bat。其内容如下:

    @echo off

    set JAVALIB=c:/java/jdk1.3/jre/lib/rt.jar

    java -cp marvinobfuscator.jar;%JAVALIB% drjava.marvin.Obfuscate %1 %2 %3 %4 %5 %6 %7 %8 %9

    该批处理针对的是 JDK1.3,对于本书的环境需要将 JAVALIB环境变量作些修改,将新增的类库加入 JAVALIB,如可改为:

    set JAVALIB=C:/j2sdk1.4.0/jre/lib/rt.jar;C:/j2sdk1.4.0/jre/lib/jce.jar

    混淆器使用时的配置放在 marvinobfuscator1_2b安装目录的 dummyproject子目录下的 config.txt文件中。需要混淆的类要先用 jar打包,如打包为 My.jar,然后需要将 config.txt开头几行的 "somelibrary.jar"改为需要进行混淆操作的打包文件名称“ My.jar”。此外,需要将 config.txt中“ mainClasses=(("myapp.Main"))”改为 My.jar文件中包含 main( )方法的字节码文件,如改为“ mainClasses=(("xx.class"))”。然后将该 config.txt文件保存在 My.jar所在目录中。

    以后就可以执行

    obfuscate    目标目录   混淆以后的文件名

    进行混淆操作了。

     

    加密是保护重要数据以及程序之间进行秘密通信的重要方法,随着加密的广泛应用,已发展成为一门单独的学科:密码( Cryptography )。密码学这一单词来自希腊语 Kryptus (隐藏)和 Gr á phein (写),可见通过加密将信息隐藏起来是密码学的重要内容。 Java 中提供了常用的加密和解密算法,该部分将介绍如何使用这些已有的算法。

    该部分主要内容:

    1. 通过编写凯撒密码了解加密和解密的基本过程

    2. 创建对称密钥,使用对称密钥进行加密和解密

    3. 创建非对称密钥,使用非对称密钥进行加密和解密

    4. 使用密钥协定分发密钥

    2.1 一个简单的加密和解密程序——凯撒密码

    说明:

    凯撒密码是罗马扩张时期朱利斯• 凯撒( Julius Caesar )创造的,用于加密通过信使传递的作战命令。它将字母表中的字母移动一定位置而实现加密。例如如果向右移动 2 位,则字母 A 将变为 C ,字母 B 将变为 D , … ,字母 X 变成 Z ,字母 Y 则变为 A ,字母 Z 变为 B 。

    因此,假如有个明文字符串“ Hello ”用这种方法加密的话,将变为密文:“ Jgnnq ”。而如果要解密,则只要将字母向相反方向移动同样位数即可。如密文“ Jgnnq ”每个字母左移两位变为“ Hello ”。这里,移动的位数“ 2 ”是加密和解密所用的密钥。本实例通过 Java 实现了这一过程,由此可以了解与加密和解密相关的概念。

    编程思路:

    首先获取要加密的内容以及密钥,凯撒密码的密钥即字符移动的位数。由于凯撒密码器的移位是针对字符的,因此需要将待加密的内容中每个字符取出,然后针对每个字符分别加以移位。

    //文件 Caesar.java

    public class Caesar

    {//凯撒密码 ,移动的位数 2就是加密和解密所用的密钥 !!

       public static void main(String args[]) throws Exception{

            String s=args[0];//读取要加密的字符串 (明文 )

            int key=Integer.parseInt(args[1]);//读取密钥 (移动的位数 ,负数表示向左移动,正数表示向右移动 )

            String es="";

            for(int i=0;i<s.length( );i++)

            {//取出字符串中每个字符

               char c=s.charAt(i);

                if(c>='a' && c<='z')

               {//对每个字符进行移位,是小写字母

                  c+=key%26;//移动 key%26位

                  if(c<'a') c+=26;//向左越界

                  if(c>'z') c-=26;//向右越界

            }

               else if(c>='A' && c<='Z')

               {//大写字母

                  c+=key%26;

                   if(c<'A') c+=26;

                  if(c>'Z') c-=26;

               }

               es+=c;

           }

           System.out.println(es);//输出密文

       }

    }

    该程序既可用于加密又可用于解密。

    编译: javac Caesar.java

    只要执行:

    java Caesar 明文(要加密的字符串) 密钥(移动的位数)

    即可加密。在密钥前面加上负号,将运行

    java Caesar 明文(要加密的字符串) - 密钥(移动的位数)即可解密。

    如为了加密字符串“ Hello World! ”,可随意取一个密钥如 4 ,运行:

    java Caesar "Hello World!" 4

    将输出“ Lipps Asvph! ”。这里“ Hello World! ”是明文,“ Lipps Asvph! ”是密文。如果密钥大于 26 ,程序中移位前会和 26 取模而将其调整到 26 以下。因此运行:

    java Caesar "Hello World!" 30

    同样将输出“ Lipps Asvph! ”。

    为了将密文“ Lipps Asvph! ”解密,需要知道加密该密文所用的密钥 4 ,这样,执行:

    java Caesar "Lipps Asvph!" -4

    将得到明文“ Hello World! ”。

    如果密钥和加密时所用的不同,则解密时将得到无意义的输出,如运行

    java Caesar "Lipps Asvph!" –3

    程序将输出“ Ifmmp Xpsme! ”。这样,只有知道密钥才能得到原来的密文。

    2.2 对称密钥的生成和保存

    上节的凯撒密码是很脆弱的,密钥总共只有 26 个,攻击者得到密文后即使不知道密钥,也可一个一个地试过去,最多试 26 次就可以得到明文。现代密码算法的过程要复杂得多,其中一类和凯撒密码类似,加密和解密使用相同的密钥,称为对称密钥算法;另一类则在加密时使用一种密钥,在解密时使用另一种密钥,称为非对称密钥算法。这些算法的密钥也不再是简单的整数,而是很长的二进制数。这样,一个 56 位的密钥有 2^(56)( 即 72,057,594,037,927,936 )个不同的可能取值,这需要耗费超级计算机约一天的时间尝试每一个密钥。当密钥长度达到 128 位,则密钥数量达到 2^(128) 个,需要的时间增加到 272 倍,约 1.29 × 1019 年。 Java 中已经提供了常用的加密算法,我们不需要了解算法的细节而可以直接使用这些算法实现加密。各种算法所用的密钥各有不同,本节将学习 Java 中创建对称密钥的方法。

    2.2.1 对称密钥的生成及以对象序列化方式保存

    实例说明

    本实例给出 Java 中创建对称密钥的步骤,并通过对象序列化方式保存在文件中。

    编程思路:

    1 ) 获取密钥生成器

    KeyGenerator kg=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 ) 生成密钥

    SecretKey k=kg.generateKey( );

    分析:使用第一步获得的 KeyGenerator 类型的对象中 generateKey( )方法可以获得密钥。其类型为 SecretKey 类型,可用于以后的加密和解密。

    4 ) 通过对象序列化方式将密钥保存在文件中

    FileOutputStream f=new FileOutputStream("key1.dat");

    ObjectOutputStream b=new ObjectOutputStream(f);

    b.writeObject(k);

    分析: ObjectOutputStream 类中提供的 writeObject 方法可以将对象序列化,以流的方式进行处理。这里将文件输出流作为参数传递给 ObjectOutputStream 类的构造器,这样创建好的密钥将保存在文件 key1.data 中。

    //文件: Skey_DES.java

    import java.io.*;

    import javax.crypto.*;

    public class Skey_DES

    {//对称密钥的生成,并通过对象序列化方式保存在文件中

        public static void main(String args[]) throws Exception

       {

                KeyGenerator kg=KeyGenerator.getInstance("DESede");//创建密钥生成器

                kg.init(168);//初始化密钥生成器

                SecretKey k=kg.generateKey( );//生成密钥

                //通过对象序列化方式将密钥保存在文件中

                FileOutputStream  f=new FileOutputStream("key1.dat");

                ObjectOutputStream b=new  ObjectOutputStream(f);

                 b.writeObject(k);

          }

    }

    编译 javac Skey_DES.java  运行 java Skey_DES ,在当前目录下将生成文件 key1.dat ,其中包含的密钥可以用于使

    用 Triple DES 算法的加密和解密。

    2.2.2 以字节保存对称密钥

    实例说明

    2.2.1 小节的实例将密钥通过对象序列化方式保存在文件中,在文件中保存的是对象,本实例以另一种方式保存在文件中,即以字节保存在文件中。

    编程思路:

    Java 中所有的密钥类都有一个 getEncoded( ) 方法,通过它可以从密钥对象中获取主要编码格式,其返回值是字节数组。其主要步骤为:

    1 ) 获取密钥

    FileInputStream f=new FileInputStream("key1.dat");

    ObjectInputStream b=new ObjectInputStream(f);

    Key k=(Key)b.readObject( );

    分析:该步骤与 2.2.1 小节的第 4 步是相对应的, 2.2.1 小节的第 4 步将密钥对象以对象流的方式存入文件,而这一步则将文件中保存的对象读取出来以便使用。首先创建文件输入流,然后将其作为参数传递给对象输入流,最后执行对象输入流的 readObject( )方法读取密钥对象。由于 readObject( )返回的是 Object 类型,因此需要强制转换成 Key 类型。这里使用的是已有的密钥,也可以不使用这里的三行代码,而使用 2.1.1 小节中的前三步的代码生成新的密钥再继续下面的步骤。

    2 ) 获取主要编码格式

    byte[ ] kb=k.getEncoded( );

    分析:执行 SecretKey 类型的对象 k 的 getEncoded( )方法,返回的编码放在 byte类型的数组中。

    3 ) 保存密钥编码格式

    FileOutputStream f2=new FileOutputStream("keykb1.dat");

    f2.write(kb);

    分析:先创建文件输出流对象,在其参数中指定文件名,如 keykb1.dat。然后执行文件输出流的 write( )方法将第 2 步中得到的字节数组中的内容写入文件。

    //文件: Skey_kb.java

    import java.io.*;

    import java.security.*;

     

    public class Skey_kb

    {// 以字节方式保存密钥。程序中在保存了密钥编码后,又使用循环语句将字节数组中的内容

      // 打印出来。这样就可以较为直观地看到密钥编码的内容了。

       public static void main(String args[]) throws Exception

       {

              FileInputStream f=new FileInputStream("key1.dat");// 首先获取密钥

              ObjectInputStream b=new ObjectInputStream(f);

              Key k=(Key)b.readObject( );

            byte[ ] kb=k.getEncoded( );// 获取主要编码格式

            // 保存密钥编码格式

            FileOutputStream  f2=new FileOutputStream("keykb1.dat");

                 f2.write(kb);

            // 打印密钥编码中的内容

            for(int i=0;i<kb.length;i++)

            {

                 System.out.print(kb[i]+",");

            }

       }

    }

    程序中在保存了密钥编码后,又使用循环语句将字节数组中的内容打印出来。这样可以较为直观地看到密钥编码的内容。

    运行程序:

    编译: javac Skey_kb.java  输入 java Skey_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 是一个二进制文件,存放的是任意二进制数。读者运行时肯定结果和上面会有所不同,实际上 2.2.1 小节的程序每次运行时生成的密钥都不会相同,这就保证了密钥的唯一性。作为对称密钥,只要保证若加密某段文字用的是某个密钥,则解密这段密文时用同样的密钥即可。

     

    2.3 使用对称密钥进行加密和解密

    在 2.2 节学习了如何创建对称密钥,本节介绍如何使用创建好的对称密钥进行加密和解密。

    2.3.1 使用对称密钥进行加密

    实例说明

    本实例的输入是 2.2.1 小节中生成并以对象方式保存在文件 key1.dat 中的密钥,以及需要加密的一段最简单的字符串 "Hello World!" ,使用密钥对 "Hello World!" 进行加密,加密后的信息保存在文件中。在此基础上读者可以举一反三加密各种信息。

    编程思路:

    首先要从文件中获取已经生成的密钥,然后考虑如何使用密钥进行加密。这涉及到各种算法。 Java 中已经提供了常用的加密算法,我们执行 Java 中 Cipher 类的各个方法就可以完成加密过程,其主要步骤为:

    1 ) 从文件中获取密钥

    FileInputStream f=new FileInputStream("key1.dat");

    ObjectInputStream b=new ObjectInputStream(f);

    Key k=(Key)b.readObject( );

    分析:该步骤与 2.2.2 小节的第 1 步相同。

    2 ) 创建密码器( Cipher 对象)

    Cipher cp=Cipher.getInstance("DESede");

    分析:和 2.2.1 小节的第 1 步中介绍的 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 ) 获取等待加密的明文

    String s="Hello World!";

    byte ptext[]=s.getBytes("UTF8");

    分析: Cipher 对象所作的操作是针对字节数组的,因此需要将要加密的内容转换成字节数组。本例中要加密的是一个字符串 s,可以使用字符串的 getBytes( )方法获得对应的字节数组。 getBytes( )方法中必须使用参数 "UTF8"指定 … ,否则 …

    5 ) 执行加密

    byte ctext[]=cp.doFinal(ptext);

    分析:执行 Cipher 对象的 doFinal( )方法,该方法的参数中传入待加密的明文,从而按照前面几步设置的算法及各种模式对所传入的明文进行加密操作,该方法返回加密的结果。

    6 ) 处理加密结果

    FileOutputStream f2=new FileOutputStream("SEnc.dat");

    f2.write(ctext);

    分析:第 5 步得到的加密结果是字节数组,对其可作各种处理,如在网上传递、保存在文件中等。这里将其保存在文件 Senc.dat 中。

    //文件: SEnc.java

    import java.io.*;

    import java.security.*;

    import javax.crypto.*;

    public class SEnc

    {//使用对称密钥进行加密。输入是以对象方式保存在文件 key1.dat中的密钥 .对字符串 "Hello World!"

      //进行加密,将加密后的信息 (密文 )保存在文件 SEnc.dat中。

       public static void main(String args[]) throws Exception

       {

                String s="Hello World!";

                //从文件中获取密钥

             FileInputStream f=new FileInputStream("key1.dat");

             ObjectInputStream b=new ObjectInputStream(f);

             Key k=(Key)b.readObject( );

             //创建密码器 (Cipher对象 )

            Cipher cp=Cipher.getInstance("DESede");

                cp.init(Cipher.ENCRYPT_MODE, k);//初始化密码器

                byte ptext[]=s.getBytes("UTF8");//获取等待加密的明文

                for(int i=0;i<ptext.length;i++)

                {//输出明文

                    System.out.print(ptext[i]+",");

                }

                System.out.println("");

                byte ctext[]=cp.doFinal(ptext);//执行加密

                for(int i=0;i<ctext.length;i++)

                {//输出加密结果

                     System.out.print(ctext[i] +",");

                }

             FileOutputStream f2=new FileOutputStream("SEnc.dat");//保存加密结果

                f2.write(ctext);

       }

    }

    程序中使用两个循环语句将字节数组加密前后加密后的内容打印出来,可作为对比。

    运行程序

    当前目录下必须有 2.2.1 小节中生成的密钥文件 key1.dat ,输入 java SEnc 运行程序,在程序的当前目录中将产生文件名为 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,

    其中第一行为字符串 "Hello World!" 的字节数组编码方式,第二行为加密后的内容,第二行的内容会随着密钥的不同而不同。第一行的内容没有加过密,任何人若得到第一行数据,只要将其用二进制方式写入文本文件,用文本编辑器打开文件就可以看到对应的字符串“ Hello World! ”。而第二行的内容由于是加密过的,没有密钥的人即使得到第二行的内容也无法知道其内容。密文同时保存在 SEnc.dat 文件中,将其提供给需要的人时,需要同时提供加密时使用的密钥( key1.dat ,或 keykb1.dat ),这样收到 SEnc.dat 中密文的人才能够解密文件中的内容 .

    2.3.2 使用对称密钥进行解密

    实例说明

    有了 2.3.1 小节加密后的密文 SEnc.dat ,以及加密时所使用的密钥 key1.dat 或 keykb1.dat ,本实例对 SEnc.dat 中的密文进行解密,得到明文。

    编程思路:

    首先要从文件中获取加密时使用的密钥,然后考虑如何使用密钥进行解密。其主要步骤为:

    1 ) 获取密文

    FileInputStream f=new FileInputStream("SEnc.dat");

    int num=f.available();

    byte[ ] ctext=new byte[num];

    f.read(ctext);

    分析:密文存放在文件 SEnc.dat 中,由于解密是针对字节数组进行操作的,因此,要先将密文从文件中读入字节数组。首先创建文件输入流,然后使用文件输入流的 available( )方法判断密文将占用多少字节,从而创建相应大小的字节数组 ctext,最后使用文件输入流的 read( )方法一次性读入数组 ctext。如果不考虑通用性,也可将要加密的内容直接在程序中向数组赋值。如可将 2.3.1小节的第二行输出的密文用如下语句直接赋值:

    byte ctext[ ]={-57,119,0,-45,-9,23,37,-56,-60,-34,-99,105,99,113,-17,76};

    该句可替代上面的四条语句,只是通用性差了,只能加密这一条密文

    2 ) 获取密钥

    FileInputStream f2=new FileInputStream("keykb1.dat");

    int num2=f2.available();

    byte[ ] keykb=new byte[num2];

    f2.read(keykb);

    SecretKeySpec k=new SecretKeySpec(keykb,"DESede");

    分析:获取可以和 2.3.1 小节第 1 步一样直接获取密钥,本实例使用另外一种方式获取密钥,即使用 2.2.2 小节以字节方式保存在文件 keykb1.dat 中的密钥。首先要将 keykb1.dat 中的内容读入字节数组 keykb,这里使用了和第 1 步类似的四条语句。如果不考虑通用性,也可以将 2.2.2 小节输出的信息如下直接赋值:

    byte [] keykb ={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 类的构造器而生成密钥。 SecretKeySpec 类的构造器中第 2 个参数则指定加密算法。由于 keykb1.dat 中的密钥原来使用的是 DESede 算法,因此这里仍旧使用字符串“ DESede”作为参数。

    3 ) 创建密码器( Cipher 对象)

    Cipher cp=Cipher.getInstance("DESede");

    分析:该步骤同 2.3.1 小节的第 2 步。

    4 ) 初始化密码器

    cp.init(Cipher.DECRYPT_MODE, k);

    分析:该步骤和 2.3.1 的第 3 步类似,对 Cipher 对象进行初始化。该方法包括两个参数,第一个参数传入 Cipher.ENCRYPT_MODE 进入解密模式,第二个参数则传入解密所使用的密钥。

    5 ) 执行解密

    byte []ptext=cp.doFinal(ctext);

    分析:该步骤和 2.3.1 的第 5 步类似,执行 Cipher 对象的 doFinal( )方法,该方法的参数中传入密文,从而按照前面几步设置的算法及各种模式对所传入的密文进行解密操作,该方法返回解密的结果。

    //文件: SDec.java

    import java.io.*;

    import java.security.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

     

    public class SDec

    {//使用对称密钥进行解密

       public static void main(String args[]) throws Exception

       {

     

    /*

    byte [ ] keykb ={11,-105,-119,50,4,-105,16,38,-14,-111,21,-95,

    70,-15,76,-74,67,-88,59,-71,55,-125,104,42};

    byte ctext[ ]={-57,119,0,-45,-9,23,37,-56,-60,-34,-99,105,99,113,-17,76};

    */

     

            FileInputStream f=new FileInputStream("SEnc.dat");//获取密文

            int num=f.available();

            byte[ ] ctext=new byte[num];//解密是针对字节数组进行操作的,因此要先将密文从文件中

            //读入字节数组。          

            f.read(ctext);

     

            //获取密钥

            FileInputStream  f2=new FileInputStream("keykb1.dat");

            int num2=f2.available();

            byte[ ] keykb=new byte[num2];         

            f2.read(keykb);

            SecretKeySpec k=new  SecretKeySpec(keykb,"DESede");

            //创建密码器 (Cipher对象 )

           Cipher cp=Cipher.getInstance("DESede");

           cp.init(Cipher.DECRYPT_MODE, k);//初始化密码器

           byte []ptext=cp.doFinal(ctext);//执行解密

           String p=new String(ptext,"UTF8");//执行 Cipher对象的 doFinal()方法,该方法的参数中传入密文 ,

           //从而按照前面几步设置的算法及各种模式对所传入的密文进行解密操作,该方法返回

           //解密的结果,及返回明文。

           System.out.println(p);//输出明文 ,将明文生成字符串加以显示。

       }

    }

    程序中最后将明文生成字符串加以显示。

    运行程序

    当前目录下必须有 2.2.2 小节中生成的密钥文件 keykb1.dat ,以及 2.3.1 小节的密文文件 SEnc.dat 。输入 java SDec 运行程序,将输出明文字符串“ Hello World! ”。

    2.4 基于口令的加密和解密

    使用对称密钥加密时密钥都很长, 如 2.2.2 小节的密钥对应的字节序列为“ 11,-105,-119,50,4,-105,16,38,-14,-111,21,-95,70,-15,76,-74,67,-88,59,-71,55,-125,104,42 ”,很难记住。一种做法是像 2.2.2 小节那样把它保存在文件中,需要时读取文件,其缺点容易被窃取,携带也不方便;另一种做法是将其打印出来,需要时对照打印出的内容手工一个一个

    输入,但由于密钥很长,输入很麻烦。在实际使用中,更常见的是基于口令的加密。加密时输入口令,口令可以由使用者自己确定一个容易记忆的。解密时只有输入同样的口令才能够得到明文。本节通过两个最简单的例子说明其基本用法。

    2.4.1 基于口令的加密

    实例说明

    本实例通过使用口令加密的一段最简单的字符串 "Hello World!" ,加密后的信息保存在文件中。在此基础上读者可以举一反三加密各种信息

    编程思路:

    和 2.3 节一样,基于口令的加密也是使用 Java 的 Cipher 类,只是在加密算法中使用基于口令的加密算法。此外,加密时所用的密钥是根据给定的口令生成的。为了增加破解的难度, PBE 还使用一个随机数(称为盐)和口令组合起来加密文件。此外还进行重复计算(迭

    代)。编程的主要步骤如下:

    1 ) 读取口令

    char[] passwd=args[0].toCharArray( );

    PBEKeySpec pbks=new PBEKeySpec(passwd);

    分析:本实例通过命令行参数读取口令。为了后面步骤可以由口令生成密钥,需要将口令保存在类 PBEKeySpec 中,类 PBEKeySpec 的构造器传入的参数是字符数组,所以使用了字符串的 toCharArray( )方法生成字符数组。

    2 ) 由口令生成密钥

    SecretKeyFactory kf=SecretKeyFactory.getInstance("PBEWithMD5AndDES");

    SecretKey k=kf.generateSecret(pbks);

    分析:生成密钥可通过 SecretKeyFactory 类的 generateSecret( )方法实现,只要将存有口令的 PBEKeySpec 对象作为参数传递给 generateSecret( )方法方法即可。 SecretKeyFactory 类是一个工厂类,通过预定义的一个静态方法 getInstance()获取

    SecretKeyFactory 对象。 getInstance ( ) 方法的参数是一个字符串, 指定口令加密算法,如 PBEWithMD5AndDES , PBEWithHmacSHA1AndDESede 等。 JCE 中已经实现的是 PBEWithMD5AndDES。

    3 ) 生成随机数(盐)

    byte[] salt=new byte[8];

    Random r=new Random( );

    r.nextBytes(salt);

    分析:对于 PBEWithMD5AndDES 算法,盐必须是 8 个元素的字节数组,因此创建数组 salt。 Java 中 Random 类可以生成随机数,执行其 nextBytes( )方法,方法的参数为 salt,即可生成的随机数并将随机数赋值给 salt。

    4 ) 创建并初始化密码器

    Cipher cp=Cipher.getInstance("PBEWithMD5AndDES");

    PBEParameterSpec ps=new PBEParameterSpec(salt,1000);

    cp.init(Cipher.ENCRYPT_MODE, k,ps);

    分析:和以前一样通过 getIntance( )方法获得密码器,其中的参数使用基于口令的加密算法“ PBEWithMD5AndDES”。但在执行 init( )初始化密码器时,除了指定第 2步生成的口令密钥外,还需要指定基于口令加密的参数,这些参数包括为了提高破解难度而添加的随机数(盐),以及进行迭代计算次数。只要将盐和迭代次数都作为参数传

    递给 PBEParameterSpec 类的构造器即可。

    5 ) 获取明文,执行加密

    byte ptext[]=s.getBytes("UTF8");

    byte ctext[]=cp.doFinal(ptext);

    分析:和以前一样将字符串转换为字节数组,并执行密码器的 doFinal( )方法进行加密。加密结果保存在字节数组 ctext 中。

    //文件: PBEEnc.java

    import java.io.*;

    import java.util.*;

    import java.security.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    /*

    基于口令的加密和解密:之前使用对称密钥加密时密钥都很长,很难记住。一种做法是将密钥保存在文件中,需要时读取文件,其缺点容易被窃取,携带也不方便。另一种做法是将其打印出来,需要时对照

    打印出的内容手工一个一个地输入,但由于密钥很长,输入很麻烦。在实际使用中,更常见的是基于口令的加密。加密时输入口令,口令可以由使用者自己确定一个容易记忆的。解密时只有输入同样的口令

    才能够得到明文。基于口令的加密也是使用 Java的 Ciphe类!!!!!只是在加密算法中使用基于口令的加密算法。此外,加密时所用的密钥是根据给定的口令生成的。为了增加破解的难度, PBE还使用一个随机数 (称为盐 )和口令组合起来加密文件。

    */

    public class PBEEnc

    {//基于口令的加密的密文由两部分组成,一个是盐,一个是加密结果

       public static void main(String args[]) throws Exception

       {

           String s="Hello World!";

           char[] passwd=args[0].toCharArray( );//读取口令

           //由口令生成密钥

           PBEKeySpec pbks=new PBEKeySpec(passwd);

          SecretKeyFactory kf=SecretKeyFactory.getInstance("PBEWithMD5AndDES");

           SecretKey k=kf.generateSecret(pbks);

           //生成随机数 (盐 )

           byte[] salt=new byte[8];

           Random r=new Random( );

           r.nextBytes(salt);

           //创建并初始化密码器

           Cipher cp=Cipher.getInstance("PBEWithMD5AndDES");

           PBEParameterSpec ps=new PBEParameterSpec(salt,1000);

           cp.init(Cipher.ENCRYPT_MODE, k,ps);

           //获取明文,执行加密

           byte ptext[]=s.getBytes("UTF8");

           byte ctext[]=cp.doFinal(ptext);

           //  将盐和加密结果合并在一起保存为密文

            FileOutputStream f=new FileOutputStream("PBEEnc.dat");

            f.write(salt);

            f.write(ctext);

           // 打印盐的值

           System.out.println("盐的值: ");

           for(int i=0;i<salt.length;i++)

           {

                 System.out.print(salt[i] +",");

           }

           System.out.println("");

           // 打印加密结果

           System.out.println("加密结果: ");

           for(int i=0;i<ctext.length;i++)

           {

                 System.out.print(ctext[i] +",");

           }

       }

    }

    //程序运行后,在当前目录下将创建一个文件 PBEEnc.dat,该文件中存放的是密文。

    //其中前 8个字节是盐,剩余部分是加密结果。

    基于口令的加密的密文由两部分组成,一个是盐,一个是加密结果,两个值简单地合并起来即可,本程序中将其一起写入密文文件 PBEEnc.dat 。程序最后将盐和加密结果打印出来。

    运行程序

    输入 java PBEEnc s7es1.886 来运行程序,其中命令行参数 s7es1.886 为用户选择的用于加密的口令。将输出:

    76,26,126,-117,12,-98,-112,95,

    113,-56,-69,66,-101,-1,-12,-109,90,-85,-99,66,-80,-10,-84,-77,

    其中第一行 8 个数字对应的是盐的值,第二行为加密结果。由于程序每次运行时使用的盐的值不同,因此即使程序运行时每次使用的口令相同,加密后的结果也不一样。程序运行后当前目录下将创建一个文件 PBEEnc.dat ,该文件中存放的是密文。其中前 8 个字节是盐,剩余部分是加密结果。

    2.4.2 基于口令的解密

    实例说明

    本实例的输入 2.4.1 小节的存放密文的文件 PBEEnc.dat ,以及该文件的密文所使用的口令“ s7es1.886 ”。本实例将演示如何使用该口令对密文解密。

    编程思路:

    和加密时一样,基于口令的解密也是使用 Java 的 Cipher 类,只是初始化时传入的参数使用 Cipher.DECRYPT_MODE 。此外,由于密文中既包含盐也包含加密结果,因此需要将这两部分分离出来。此外,加密时所用的密钥是根据给定的口令生成的。为了增加破解的难度, PBE 还使用一个随机数(称为盐)和口令组合起来加密文件。此外还进行重复计算(迭代)。编程的主要步骤如下:

    1 ) 读取口令并生成密钥

    char[] passwd=args[0].toCharArray( );

    PBEKeySpec pbks=new PBEKeySpec(passwd);

    SecretKeyFactory kf=

    SecretKeyFactory.getInstance("PBEWithMD5AndDES");

    SecretKey k=kf.generateSecret(pbks);

    分析:该步骤和加密时完全相同。

    2 ) 获取随机数(盐)

    byte[] salt=new byte[8];

    FileInputStream f=new FileInputStream("PBEEnc.dat");

    f.read(salt);

    分析:由于盐的长度固定,为 8 个字节,因此定义大小为 8 的字节数组,从文件 PBEEnc.dat 中读取盐,存放在数组 salt 中。

    3 ) 获取加密结果

    int num=f.available();

    byte[ ] ctext=new byte[num];

    f.read(ctext);

    分析:由于 PBEEnc.dat 中剩余部分为加密结果,因此使用文件输入流的 available( )方法判断剩余字节的数量,并创建相应大小的字节数组,读入数据。

    4 ) 创建密码器,执行解密

    Cipher cp=Cipher.getInstance("PBEWithMD5AndDES");

    PBEParameterSpec ps=new PBEParameterSpec(salt,1000);

    cp.init(Cipher.DECRYPT_MODE, k,ps);

    byte ptext[]=cp.doFinal(ctext);

    分析:该步骤和加密时类似,只是初始化时使用的是 Cipher.DECRYPT_MODE,执行 doFinal( )时传入的是以前的加密结果,而返回的字节数组 ptext 即包含了解密后的文字。

    //文件: PBEDec.java

    import java.io.*;

    import java.util.*;

    import java.security.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    /*

    输入是存放密文的文件 PBEEnc.dat,以及该文件的密文所使用的口令 "s7es1.886".

    编程思路:和加密时一样,基于口令的解密也是使用 Java的 Cipher类,只是初始化时传入的参数使用

    Cipher.DECRYPT_MODE。此外,由于密文中既包含盐也包含加密结果,

    因此需要将这两部分分离出来。

    */

    public class PBEDec

    {

       public static void main(String args[]) throws Exception

       {

          //读取口令并生成密钥,该步骤和加密时完全相同。

           char[] passwd=args[0].toCharArray( );

           PBEKeySpec pbks=new PBEKeySpec(passwd);

           SecretKeyFactory kf=SecretKeyFactory.getInstance("PBEWithMD5AndDES");

           SecretKey k=kf.generateSecret(pbks);

          //获取随机数 (盐 )

           byte[] salt=new byte[8];

           FileInputStream f=new FileInputStream("PBEEnc.dat");

           f.read(salt);

           //获取加密结果

           int num=f.available();

           byte[ ] ctext=new byte[num];         

           f.read(ctext);

           //创建密码器,执行解密

           Cipher cp=Cipher.getInstance("PBEWithMD5AndDES");

           PBEParameterSpec ps=new PBEParameterSpec(salt,1000);

           cp.init(Cipher.DECRYPT_MODE, k,ps);

           byte ptext[]=cp.doFinal(ctext);

           // 显示解密结果

           System.out.println("显示解密结果: ");

           for(int i=0;i<ptext.length;i++){

                 System.out.print(ptext[i] +",");

           }

            System.out.println("");

            System.out.println("以字符串格式显示解密结果: ");

           // 以字符串格式显示解密结果

           for(int i=0;i<ptext.length;i++){

                 System.out.print((char) ptext[i]);

           }

       }

    }

    程序最后将解密后的得到的字节数组 ptext 中的内容打印出来,为了使显示出的结果更

    加直观,最后将字节数组 ptext 中的内容转换字符进行显示。

    运行程序

    输入 java PBEDec s7es1.886 来运行程序,其中命令行参数 s7es1.886 是解密所使用的口令,必须和加密时使用的口令一样。程序将输出:

    72,101,108,108,111,32,87,111,114,108,100,33,Hello World!

    如果使用的口令不对,将无法解密。如输入 java PBEDec s7es1.888 运行程序,将显示如下异常信息:

    Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded

    at com.sun.crypto.provider.DESCipher.engineDoFinal(DashoA6275)

    at com.sun.crypto.provider.DESCipher.engineDoFinal(DashoA6275)

    at com.sun.crypto.provider.PBEWithMD5AndDESCipher.engineDoFinal(DashoA6275)

    at javax.crypto.Cipher.doFinal(DashoA6275)

    at PBEDec.main(PBEDec.java:27)

     

    <!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:幼圆; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-alt:宋体; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:楷体_GB2312; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-alt:宋体; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@幼圆"; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@楷体_GB2312"; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->

    2.5 针对流的加密和解密

    2.2 和 2.3 节的加密和解密都是针对字节数组进行的,但实际编程中更常针对流进行加密,如对整个文件进行加密 /解密或对网络通信进行加密 /解密等。尽管我们可以先从流中读出字节然后进行加密 /解密,但使用 Java 中针对流提供的专门的类更加方便。本节介绍其基本编程方法。

    2.5.1 针对输入流的解密和解密

    实例说明

    本实例以最简单的程序演示了针对输入流的加密和解密,将指定文件中的内容进行加密和解密。

    编程思路:

    Java 中 CipherInputStream 提供了针对输入流的加密和解密,执行加密和解密的算法仍旧由以前使用的 Cipher 类担当, CipherInputStream 类的构造器中可以指定标准的输入流(如文件输入流)和密码器( Cipher 对象),当使用 CipherInputStream 类的 read()方法从流中读取数据时,会自动将标准输入流中的内容使用密码器进行加密或解密再读出。其基本步骤如下:

    1 ) 生成密钥

    FileInputStream f=new FileInputStream("key1.dat");

    ObjectInputStream ob=new ObjectInputStream(f);

    Key k=(Key)ob.readObject( );

    分析:这里和 2.3.1 小节一样从文件中读取以前保存的密钥,这样保证了本实例所

    用的密钥和以前相同,以便于对比加密结果。如果不需要作对比,也可以使用 2.2.1小节的步骤生成新的密钥。

    2 ) 创建并初始化密码器

    Cipher cp=Cipher.getInstance("DESede");

    cp.init(Cipher.ENCRYPT_MODE, k);

    分析:该步骤和以前相同,如果准备进行解密,则应将 Cipher.ENCRYPT_MODE 改为

    Cipher.DECRYPT_MODE。

    3 ) 创建要加密或解密的输入流

    FileInputStream in=new FileInputStream(args[0]);

    分析:这里以加密文件为例,因此创建文件输入流,文件名由命令行参数传入。

    4 ) 创建 CipherInputStream 对象

    CipherInputStream cin=new CipherInputStream(in, cp);

    分析: 将第 2 步创建的密码器和第 3 步创建的需要加密 /解密的流作为参数传递给

    CipherInputStream 对象。

    5 ) 读取输入流

    while( (b=cin.read()) !=-1 ){

            System.out.print((byte)b+",");

    }

    分析:像使用基本的输入流一样使用 read( )方法从 CipherInputStream 流中读取

    数据,则在读取过程中会自动根据第 2 步密码器中的设置进行加密或解密。

    //文件 :StreamIn.java

    import java.io.*;

    import java.security.*;

    import javax.crypto.*;

    public class StreamIn{

       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);

    //        cp.init(Cipher.ENCRYPT_MODE, k);

                FileInputStream in=new FileInputStream(args[0]);

            CipherInputStream cin=new CipherInputStream(in, cp);

            int b=0;

            while( (b=cin.read()) !=-1 ){

                  System.out.print((byte)b+",");

    //              System.out.print((char)b);

            }

       }

    }

    运行程序

    在当前目录下使用 Windows 中的记事本创建一个文本文件: StreamIn1.txt,在其中输入需要加密的字符串,可以输入多行。为了和以前的加密结果进行对比,不妨先只输入一行“ Hello World!”。

    输入 java StreamIn StreamIn1.txt 来运行程序,程序将输出加密以后的内容:

    -57,119,0,-45,-9,23,37,-56,-60,-34,-99,105,99,113,-17,76,该结果和 2.3.1 小节的运行结果相同。注意,本实例和 2.3.1 小节的运行结果相同的前提是使用的密钥相同(都从 key1.dat 文件中读取),算法相同(都是 DESede 算法及默认的填充和模式)以及相同的加密内容(都是“ Hello World!”)。如果在编辑 StreamIn1.txt 文件时在“ Hello World!”后面加了回车或使用其他的文本编辑器(如使用 DOS下的 edit 工具可能会在文件末尾自动加上一些隐藏字符),则结果可能会不同。

    本实例将加密的结果打印了出来,也可以再创建一个文件输出流,将加密结果保存起来,其内容将和 2.3.1 小节的 SEnc.dat 相同。将该实例稍作修改就可以对以文件形式保存的密文进行解密。如果将程序中的

    cp.init(Cipher.ENCRYPT_MODE, k);

    改为

    cp.init(Cipher.DECRYPT_MODE, k);

    则可进行解密操作。此时可将 2.3.1 小节输出的加密文件 SEnc.dat 拷贝到当前目录,运行java

    StreamIn SEnc.dat , 程序将输出解密结果:

    72,101,108,108,111,32,87,111,114,108,100,33,

    此即“ Hello World!”字节数组编码方式。若进一步将该实例中的

    System.out.print((byte)b+",");

    改为

    System.out.print((char)b);

    则进行解密时将直接输出“ Hello World!”。

    2.5.2 针对输出流的解密和解密

    实例说明

    本实例演示了针对输出流的加密和解密,将指定文件中的内容进行加密和解密,并把

    加密和解密的结果输入指定的另外一个文件。

    编程思路:

    和输入流类似, Java 中 CipherOutputStream 提供了针对输出流的加密和解密。

    CipherOutputStream 类的构造器中可以指定标准的输出流(如文件输出流)和密码器( Cipher对象),当使用 CipherOutputStream 类的 write()方法进行输出时,会自动将 write()方法参数中的内容使用密码器进行加密或解密后再写入标准输出流。其基本步骤如下:

    1 ) 生成密钥

    FileInputStream f=new FileInputStream("key1.dat");

    ObjectInputStream ob=new ObjectInputStream(f);

    Key k=(Key)ob.readObject( );

    分析: 该步骤和 2.5.1 小节的第 1 步一样。

    2 ) 创建并初始化密码器

    Cipher cp=Cipher.getInstance("DESede");

    if(args[0].equals("dec"))

    cp.init(Cipher.DECRYPT_MODE, k);

    else cp.init(Cipher.ENCRYPT_MODE, k);

    分析:该步骤和 2.5.1 小节的第 2 步一样,但为了使程序更具有通用性,这里不妨

    通过命令行参数确定密码器是加密模式还是解密模式。当第一个命令行参数为 enc 时,使用加密模式,否则为解密模式。

    3 ) 获取要加密或解密的内容

    FileInputStream in=new FileInputStream(args[1]);

    分析:要加密或解密的内容可以是各种形式,只要可以转换为整型或字节数组形式

    即可。如可以是一个字符串。本实例以加密文件为例,因此创建文件输入流,文件名由命令行的第 2 个参数传入。

    4 ) 获取加密或解密的输出以及 CipherOutputStream 对象

    FileOutputStream out=new FileOutputStream(args[2]);

    CipherOutputStream cout=new CipherOutputStream(out, cp);

    分析:加密和解密的结果可以输出到各种输出流中,本实例将加密结果保存为文件,因此创建文件输出流。将其和第 3 步创建的密码器一起作为参数传递给

    CipherOutputStream 对象。

    5 ) 写输出流

    while( (b=in.read())!=-1){

               cout.write(b);

    }

    分析:像使用基本的输出流一样使用 write( )方法向 CipherOutputStream 流中写

    数据(数据为需要加密的明文,本实例从文件中使用 read( )方法从文件中读取明文),则在写之前 CipherOutputStream 流会自动按照其参数中的密码器设置先进行加密或解密操作,然后再写入其参数中的输出流中。本实例

    //文件: StreamOut.java

    import java.io.*;

    import java.security.*;

    import javax.crypto.*;

    public class StreamOut{

       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");

            if(args[0].equals("dec"))

                cp.init(Cipher.DECRYPT_MODE, k);

            else         cp.init(Cipher.ENCRYPT_MODE, k);

            FileInputStream in=new FileInputStream(args[1]);

                FileOutputStream out=new FileOutputStream(args[2]);

            CipherOutputStream cout=new CipherOutputStream(out, cp);

            int b=0;

            while( (b=in.read())!=-1){

                cout.write(b);

            }

            cout.close();

            out.close();

            in.close();

       }

    }

    运行程序

    仍旧使用 2.5.1 小节的文本文件: StreamIn1.txt 进行试验,输入:

    java StreamOut encStreamIn1.txt mytest.txt

    来运行程序,则将把 StreamIn1.txt 中的内容加密成为文件 mytest.txt。

    若进一步运行 :

    java StreamOut decmytest.txt mytest2.txt

    则将文件 mytest.txt 中的密文解密为文件 mytest2.txt。打开 mytest2.txt,可以看到解密后的明文“ Hello World!”。解密时必须有加密时所用的完全相同的密钥才能正常运行。和 2.5.1 小节一样,被加密的文件可以不止一行。 2.5.1 和 2.5.2 小节都使用了文件输入 /输出流,也可针对其他的流进行加密和解密。此外,密码器也使用基于口令的加密和解密。

    2.6 加密方式的设定

    2.3.1 小节的程序加密的字符串如果是“ Hello123Hello123Hello123Hello123” (每 8 个字符相同),则加密后的结果如下:

    -46,-71,65,-43,48,105,-52,-13,

    -46,-71,65,-43,48,105,-52,-13,

    -46,-71,65,-43,48,105,-52,-13,

    -46,-71,65,-43,48,105,-52,-13,

    51,82,-102,-119,76,5,60,-114,

    可以看出加密结果每 8 个字节出现相同,这是因为数据在进行加密时其实不是一个一个字节进行加密,也不是一次处理加密字节,而是每 8 个字节( 64 位)作为一组进行加密,有些算法一次处理 16 个字节或更多。默认情况下,每组之间独立进行加密,因此相同的明文分组得到的加密结果也相同。 2.5.1 和 2.5.2 的例子使用密钥进行加密时,当文件 StreamIn1.txt 的内容为“ Hello123Hello123Hello123Hello123”(每 8 个字符相同),也同样具有规律性。使用其他加密方式可以解决这一问题,本节将介绍 CBC 加密方式。

    2.6.1 使用CBC 方式的加密

    实例说明

    本实例演示了使用 CBC 加密方式以及初始向量进行加密和解密编程步骤。

    编程思路:

    对明文分组的不同处理方式形成了不同的加密方式,本章前面各节的程序中没有指定加密方式,默认的加密方式是 ECB( Electronic Code Book),它对每个明文分组独立进行处理。所以明文若 8 个字节一组相同的话(如本节开头的“ Hello123Hello123Hello123Hello123”),加密出的结果也是 8 个字节一组相同的。另一种加密方式称为 CBC( Cipher Block Chaining),它先加密第一个分组,然后使用得到的密文加密第二个分组,加密第二个分组得到的密文再加密第三个分组,……。这样,即使两个分组相同,得到的密文也不同。剩下的问题是如果两个密文的开头 8 个字节相同,按照这种加密方式,只要使用的密钥相同,则每条密文的开头 8 个字节也将相同。为此, CBC 使用一个 8 个字节的随机数(称为初始向量, IV)来加密第一个分组,其作用类似于基于口令加密中的盐。

    因此,使用 CBC 方式首先要生成初始向量,然后在获取密码器对象时通过 getInstance( )方法的参数设定加密方式,在密码器初始化时传入初始向量。具体步骤如下:

    1 ) 生成密钥

    FileInputStream f=new FileInputStream("key1.dat");

    ObjectInputStream ob=new ObjectInputStream(f);

    Key k=(Key)ob.readObject( );

    分析: 该步骤和以前一样。

    2 ) 生成初始向量

    byte[] rand=new byte[8];

    Random r=new Random( );

    r.nextBytes(rand);

    IvParameterSpec iv=new IvParameterSpec(rand);

    分析:该步骤前三条语句和 2.4.1 小节的第 3 步一样,生成随机数,第 4 条语句则

    使用该随机数得到代表初始向量的 IvParameterSpec 对象。

    3 ) 获取密码器

    Cipher cp=Cipher.getInstance("DESede/CBC/PKCS5Padding");

    分析:在获取密码器时,通过 getInstance( )方法的参数指定加密方式,该参数

    “ DESede/CBC/PKCS5Padding”由三部分组成。第一部分“ DESede”代表所用的加密算法。由于本实例仍旧使用了 2.2.1 小节生成

    的密钥,因此这里必须仍旧使用 DEDede 算法。若 2.2.1 小节改为其他的算法,如“ DES”、“ Blowfish”等,则这里也必须相应改变。第二部分“ CBC”即加密模式,除了 CBC 外,还有 NONE、 ECB、 CFB、 OFB 和 PCBC等可以用。第三部分为填充模式,明文在被 64 位一组分成明文分组时,最后一个分组可能不足 64 位,因此加密算法一般使用一定规则对最后一个分组进行填充。对称加密常用的填充方式称为“ PKCS#5 padding”,其中的 PKCS 是 Public Key Cryptography Standard

    的缩写。如果加密算法不进行填充(填充方式为 No padding),则要求明文长度必须是 64 的整数倍。在本章前面各节的程序中没有指定填充方式,默认的填充方式就是“ PKCS#5 padding ”, 因此以前的语句 Cipher.getInstance("DESede") 和

    Cipher.getInstance("DESede/ECB/PKCS5Padding")是等价的。在本节的开头介绍加密字符串“ Hello123Hello123Hello123Hello123 ” 时, 输出结果最后多出的

    “ 51,82,-102,-119,76,5,60,-114,”就是由于填充的结果(使用 PKCS#5 padding 时,即使明文长度是 8 字节的整数倍,也会再数据最后加上一个完整的填充块。

    4 ) 初始化密码器,并执行加密

    cp.init(Cipher.ENCRYPT_MODE, k, iv);

    byte ptext[]=s.getBytes("UTF8");

    byte ctext[]=cp.doFinal(ptext);

    分析:和前面的程序相比,在其参数中增加了一项初始化向量,即第 2 步得到的

    iv。执行加密时同样使用 doFinal( )方法对字节数组进行加密。

    //文件: SEncCBC.java

    import java.io.*;

    import java.util.*;

    import java.security.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    public class SEncCBC{

       public static void main(String args[]) throws Exception{

            String s="Hello123Hello123Hello123Hello123";

     

          FileInputStream f1=new FileInputStream("key1.dat");

          ObjectInputStream b=new ObjectInputStream(f1);

          Key k=(Key)b.readObject( );

     

            byte[] rand=new byte[8]; 

            Random r=new Random( );

            r.nextBytes(rand);

            IvParameterSpec iv=new IvParameterSpec(rand);

     

          Cipher cp=Cipher.getInstance("DESede/CBC/PKCS5Padding");

            cp.init(Cipher.ENCRYPT_MODE, k, iv);

            byte ptext[]=s.getBytes("UTF8");

     

            byte ctext[]=cp.doFinal(ptext);

            for(int i=0;i<ctext.length;i++){

                 System.out.print(ctext[i] +",");

            }

          FileOutputStream f2=new FileOutputStream("SEncCBC.dat");

            f2.write(rand);

            f2.write(ctext);

       }

    }

    为了方便看到加密结果,程序中通过循环打印出字节数组的内容。为了以后进行解密,程序中通过文件将初始化向量和加密结果保存在一起。

    运行程序

    输入 java SEncCBC 运行程序,得到如下结果:

    47,-79,65,-41,25,-70,-62,-55,3,10,-3,118,-12,100,-113,2,124,-66,-84,93,-74,8,17,64,-80,-82,29,126,-23,-102,6,-98,-85,-110,-64,10,-23,-82,-30,-80,

    再运行一次,得到如下结果:

    118,-63,110,81,21,-99,44,-17,29,59,-121,-27,80,40,-89,-37,74,-117,-110,52,33,54,85,85,94,1

    21,-122,125,29,-39,11,-71,-80,-99,-50,0,22,-50,-72,-12,

    可见明文有规律性时,密文并无规律性,而且相同的明文加密后的结果不同。

    密文保存在文件“ SEncCBC.dat”中,其中前 8 个字节为该密文对应的初始化向量。

    2.6.2 使用CBC 方式的解密

    实例说明

    本实例演示了如何对 2.6.1 小节的密文进行解密。

    编程思路:

    同样加密一样,先要获取加密时所用的初始向量。由于 2.6.1 小节将初始化向量保存在文件 SEncCBC.dat 的开头 8 个子节中,因此可直接使用文件输入流读取。进而读取密文和密钥,最后在获取密码器对象时通过 getInstance( )方法的参数设定加密方式,在密码器初始化时传入初始向量。具体步骤如下:

    1 ) 获取初始向量

    FileInputStream f=new FileInputStream("SEncCBC.dat");

    byte[] rand=new byte[8];

    f.read(rand);

    IvParameterSpec iv=new IvParameterSpec(rand);

    分析:使用文件输入流的 read( )方法从文件 SEncCBC.dat 中读取 8 个字节的对应

    初始向量的随机数,并用其创建 IvParameterSpec 对象 。

    2 ) 获取密文和密钥

    int num=f.available();

    byte[ ] ctext=new byte[num];

    f.read(ctext);

    FileInputStream f2=new FileInputStream("key1.dat");

    ObjectInputStream b=new ObjectInputStream(f2);

    Key k=(Key)b.readObject( );

    分析:由于 SEncCBC.dat 中剩余部分为加密结果,因此使用文件输入流的

    available( )方法判断剩余字节的数量,并创建相应大小的字节数组,读入数据。密钥必须和 2.6.1 小节所用的密钥相同。

    3 ) 获取并初始化密码器

    Cipher cp=Cipher.getInstance("DESede/CBC/PKCS5Padding");

    cp.init(Cipher.DECRYPT_MODE, k, iv);

    byte []ptext=cp.doFinal(ctext);

    分析: 该步骤和 2.6.1 小节相同, 只是在初始化密码器时使用

    Cipher.DECRYPT_MODE,表明进行节密。

    //文件: SDecCBC.java

    import java.io.*;

    import java.security.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    public class SDecCBC{

       public static void main(String args[]) throws Exception{

             FileInputStream f=new FileInputStream("SEncCBC.dat");

             byte[] rand=new byte[8];

             f.read(rand);

             IvParameterSpec iv=new IvParameterSpec(rand);

     

            int num=f.available();

            byte[ ] ctext=new byte[num];         

            f.read(ctext);

     

          FileInputStream f2=new FileInputStream("key1.dat");

          ObjectInputStream b=new ObjectInputStream(f2);

          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);

       }

    }

    程序中最后将明文生成字符串加以显示。

    运行程序

    输入 java SDecCBC 运行程序,得到如下结果:

    Hello123Hello123Hello123Hello123

    解密成功。同样,对 2.5 节中的例子也可以类似地使用 CBC 加密方式。

    <!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:幼圆; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-alt:宋体; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:楷体_GB2312; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-alt:宋体; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@幼圆"; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@楷体_GB2312"; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->

    2.7 生成非对称加密的公钥和私钥

    前面几节的程序中,加密和解密使用的是同一个密钥,这种方式称为对称加密。使用对称密钥时,若 A 想让 B 向其秘密传送信息, A 必须先将密钥提供给 B ,或者由 B 将密钥提供给 A 。如果在传递密钥过程中密钥被窃取,则 A 和 B 之间的通信就不再安全了。非对称加密解决了这一问题。它将加密的密钥和解密的密钥分开。 A 事先生成一对密钥,一个用于加密,称为公钥(公钥),一个用于解密,称为私钥。由于产生这一对密钥的一些数学特性,公钥加密的信息只能用私钥解密。这样, A 只要将公钥对外公开,不论谁就可以使用这个公钥给 A 发送秘密信息了。 A 接收到加密信息后可以用私钥打开。由于只需要传递公钥,而公钥只能加密不能解密,因此即使攻击者知道了公钥也无济于事。本节以 RSA 算法为例介绍 Java 中如何生成公钥和私钥。

    实例说明

    本实例演示了如何使用 Java 中定义好的类创建 RSA 公钥和私钥。

    编程思路:

    Java 的 KeyPairGenerator 类提供了一些方法来创建密钥对以便用于非对称加密,密钥对创建好后封装在 KeyPair 类型的对象中,在 KeyPair 类中提供了获取公钥和私钥的方法。具体步骤如下:

    1 ) 创建密钥对生成器

    KeyPairGenerator kpg=KeyPairGenerator.getInstance("RSA");

    分析:密钥对生成器即 KeyPairGenerator 类型的对象,和 2.2.1 小节的第 1 步中介绍的 KeyGenerator 类一样, KeyPairGenerator 类是一个工厂类,它通过其中预定义

    的一个静态方法 getInstance( )获取 KeyPairGenerator 类型的对象。 getInstance()方法的参数是一个字符串,指定非对称加密所使用的算法,常用的有 RSA, DSA等。

    2 ) 初始化密钥生成器

    kpg.initialize(1024);

    分析:对于密钥长度。对于 RSA 算法,这里指定的其实是 RSA 算法中所用的模的位数。可以在 512 到 2048 之间。

    3 ) 生成密钥对

    KeyPair kp=kpg.genKeyPair( );

    分析:使用 KeyPairGenerator 类的 genKeyPair( )方法生成密钥对,其中包含了一对公钥和私钥的信息。

    4 ) 获取公钥和私钥

    PublicKey pbkey=kp.getPublic( );

    PrivateKey prkey=kp.getPrivate( );

    分析:使用 KeyPair 类的 getPublic( )和 getPrivate( )方法获得公钥和私钥对象。

    //文件: Skey_RSA.java

    import java.io.*;

    import java.security.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    public class Skey_RSA{

       public static void main(String args[]) throws Exception{

            KeyPairGenerator kpg=KeyPairGenerator.getInstance("RSA");

            kpg.initialize(1024);

            KeyPair kp=kpg.genKeyPair();

            PublicKey pbkey=kp.getPublic();

            PrivateKey prkey=kp.getPrivate();

            FileOutputStream  f1=new FileOutputStream("Skey_RSA_pub.dat");

            ObjectOutputStream b1=new  ObjectOutputStream(f1);

           b1.writeObject(pbkey);

            FileOutputStream  f2=new FileOutputStream("Skey_RSA_priv.dat");

            ObjectOutputStream b2=new  ObjectOutputStream(f2);

           b2.writeObject(prkey);

       }

    }

    分析:本实例和 2.2.1 小节一样使用对象流将密钥保存在文件中,所不同的是加密所用

    的公钥和解密所用的私钥分开保存。将公钥对外公布,供其他人加密使用,而把私钥秘密保

    存,在需要解密时使用。

    运行程序

    输入 java Skey_RSA 运行程序,当前目录下将生成两个文件: Skey_RSA_pub.dat 和

    Skey_RSA_priv.dat ,前者保存着公钥,后者保存着私钥。将文件 Skey_RSA_pub.dat 对外公

    布(如放在 Web 服务器上给大家下载,或者直接拷贝给所有需要的人),而 Skey_RSA_priv.dat

    秘密保存。

    2.8 使用 RSA 算法进行加密和解密

    2.7 节的程序创建了 RSA 的公钥和密钥,本节使用公钥进行加密,然后使用私钥对加密的信息进行解密。

    2.8.1 使用RSA 公钥进行加密

    实例说明

    本实例以加密一串最简单的字符串“ Hello World! ”为例,演示了如何使用 2.7 节生成的 RSA 公钥文件 Skey_RSA_pub.dat 进行加密。

    编程思路:

    使用 RSA 公钥进行加密的代码和 2.3.1 小节使用 DESede 进行加密其实没什么大的区别,只是 Cipher 类的 getInstance( ) 方法的参数中应该指定使用 RSA 。但由于 J2SDK1.4 中只实现了 RSA 密钥的创建,没有实现 RSA 算法,因此需要安装其他加密提供者软件才能直接使用 Cipher 类执行加密解密。其实有了 RSA 公钥和私钥后,自己编写程序从底层实现 RSA 算法也并不复杂。本实例给出简单的例子实现了 RSA 加密,使读者只使用 J2SDK1.4 便能直观地了解非对称加密算法。 RSA 算法是使用整数进行加密运算的,在 RSA 公钥中包含了两个信息:公钥对应的整数 e 和用于取模的整数 n 。对于明文数字 m ,计算密文的公式是: me mod n 。因此,编程步骤如下:

    1 ) 获取公钥

    FileInputStream f=new FileInputStream("Skey_RSA_pub.dat");

    ObjectInputStream b=new ObjectInputStream(f);

    RSAPublicKey pbk=(RSAPublicKey)b.readObject( );

    分析: 从 2.7 节生成的公钥文件 Skey_RSA_pub.dat 中读取公钥,由于 2.7 节使用的是 RSA 算法,因此从文件读取公钥对象后强制转换为 RSAPublicKey 类型,以便后面读取 RSA 算法所需要的参数 .

    2 ) 获取公钥的参数 (e, n)

    BigInteger e=pbk.getPublicExponent();

    BigInteger n=pbk.getModulus();

    分析:使用 RSAPublicKey 类的 getPublicExponent( )和 getModulus( )方法可以分别获得公始中 e 和 n 的值。由于密钥很长,因此对应的整数值非常大,无法使用一般的整型来存储, Java 中定义了 BigInteger 类来存储这类很大的整数并可进行各种运算。

    3 ) 获取明文整数 (m)

    String s="Hello World!";

    byte ptext[]=s.getBytes("UTF8");

    BigInteger m=new BigInteger(ptext);

    分析:明文是一个字符串,为了用整数表达这个字符串,先使用字符串的 getBytes( ) 方法 将其转换为 byte 类型数组,它其实是字符串中各个字符的二进制表达方式,这一串二进制数转换为一个整数将非常大,因此仍旧使用 BigInteger 类将这个二进制串转换为整型。本实例中出于简化,将整个字符串转换为一个整数。实际使用中,应该对明文进行分组,因为 RSA 算法要求整型数 m 的值必须小于 n。

    4 ) 执行计算

    BigInteger c=m.modPow(e,n);

    分析:计算前面的公式: me mod n。 BigInteger 类中已经提供了方法 modPow( )来执行这个计算。底数 m 执行这个方法,方法 modPow( )的第一个参数即指数 e,第二个参数即模 n。方法返回的结果即公式 me mod n 的计算结果,即密文。

    //文件: Enc_RSA.java

    import java.security.*;

    import java.security.spec.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    import javax.crypto.interfaces.*;

    import java.security.interfaces.*;

    import java.math.*;

    import java.io.*;

    public class Enc_RSA{

       public static void main(String args[]) throws Exception{

            String s="Hello World!";

            FileInputStream f=new FileInputStream("Skey_RSA_pub.dat");

            ObjectInputStream b=new ObjectInputStream(f);

            RSAPublicKey  pbk=(RSAPublicKey)b.readObject( );

            BigInteger e=pbk.getPublicExponent();

            BigInteger n=pbk.getModulus();

            System.out.println("e= "+e);

            System.out.println("n= "+n);

            byte ptext[]=s.getBytes("UTF8");

            BigInteger m=new BigInteger(ptext);

            BigInteger c=m.modPow(e,n);

            System.out.println("c= "+c);

            String cs=c.toString( );

            BufferedWriter out=

              new BufferedWriter(new OutputStreamWriter(

                new FileOutputStream("Enc_RSA.dat")));

            out.write(cs,0,cs.length( ));

            out.close( );

       }

    }程序最后将密文 c 打印出来,并以字符串形式保存在文件中。

    运行程序

    输入 java Enc_RSA 运行程序

    2.8.2 使用RSA 私钥进行解密

    实例说明

    本实例使用 2.7 节生成的私钥文件 Skey_RSA_priv.dat ,对 2.8.1 小节生成的密文文件 Enc_RSA.dat 进行解密。

    编程思路:

    和 2.8.1 小节类似,使用 RSA 私钥进行解密的代码也可以在 Cipher 类的 getInstance( ) 方法的参数中指定使用 RSA ,使用解密模式进行解密。但需要安装其他加密提供者软件才能直接使用 Cipher 类执行加密解密。本实例给出简单的例子从底层实现 RSA 解密,以便只使用 J2SDK1.4 便能直观地了解非对称加密算法。 RSA 算法的解密和加密类似,在 RSA 私钥中包含了两个信息:私钥对应的整数 d 和用于取模的整数 n 。其中的 n 和加密时的 n 完全相同。对于密文数字 c ,计算明文的公式是: cd mod

    n ,之所以加密时由公式 me mod n 得到的密文 c 通过这个公式计算一下就可以反过来得到原来的明文 m ,有其本身的数学规律决定。从编程角度只需要知道这个结果就行了。编程步骤如下:

    1 ) 读取密文

    BufferedReader in=

    new BufferedReader(new InputStreamReader(

    new FileInputStream("Enc_RSA.dat")));

    String ctext=in.readLine();

    BigInteger c=new BigInteger(ctext);

    分析: 从 2.8.1 小节生成的密文文件 Enc_RSA.dat 中读取密文,由于 2.8.1 小节保存的只是一行字符串,因此只要一条 readLine( )语句即可。由于这一行字符串表示的是一个很大的整型数,因此使用 BigInteger 类来表示这个整型数。

    2 ) 获取私钥

    FileInputStream f=new FileInputStream("Skey_RSA_priv.dat");

    ObjectInputStream b=new ObjectInputStream(f);

    RSAPrivateKey prk=(RSAPrivateKey)b.readObject( );

    分析: 从 2.7 节生成的私钥文件 Skey_RSA_priv.dat 中读取公钥,由于 2.7 节使用的是 RSA 算法,因此从文件读取公钥对象后强制转换为 RSAPrivateKey 类型,以便后面读取 RSA 算法所需要的参数。

    3 ) 获取私钥的参数 (d, n)

    BigInteger d=prk.getPrivateExponent( );

    BigInteger n=prk.getModulus( );

    分析:使用 RSAPrivateKey 类的 getPrivateExponent( ) 和 getModulus( )方法可以分别获得公始中 d 和 n 的值。

    4 ) 执行计算

    BigInteger m=c.modPow(d,n);

    分析:使用 BigInteger 的 modPow( )方法计算前面的公式: cd mod n。方法返回的结果即公式 cd mod n 的计算结果,即明文对应的整型数 m。

    5 ) 计算明文整型数对应的字符串

    byte[] mt=m.toByteArray();

    for(int i=0;i<mt.length;i++){

          System.out.print((char) mt[i]);

    }

    分析: RSA 算法解密的结果 m 是一个很大的整数,为了计算出其对应的字符串的值,先使用 BigInteger 类的 toByteArray( )方法得到代表该整型数的字节数组,然后将数组中每个元素转换为字符,组成字符串。

    //文件: Dec_RSA.java

    import java.security.*;

    import java.security.spec.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    import javax.crypto.interfaces.*;

    import java.security.interfaces.*;

    import java.math.*;

    import java.io.*;

    public class Dec_RSA{

       public static void main(String args[]) throws Exception{

            BufferedReader in=

              new BufferedReader(new InputStreamReader(new FileInputStream("Enc_RSA.dat")));

            String ctext=in.readLine();

            BigInteger c=new BigInteger(ctext);

            FileInputStream f=new FileInputStream("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 m=c.modPow(d,n);

            System.out.println("m= "+m);

            byte[] mt=m.toByteArray();

            System.out.println("PlainText is ");

            for(int i=0;i<mt.length;i++){

                 System.out.print((char) mt[i]);

           }

       }

    }

    运行程序

    输入 java Dec_RSA 运行程序

    2.9 使用密钥协定创建共享密钥

    非对称加密解决了密钥分发的难题,但其计算量比对称密钥大,因此一般并不使用非对称加密加密大量数据。常见的做法是:主要数据通过对称密钥加密,而使用非对称加密来分发对称密钥。将两者的优势结合了起来。例如若 A 和 B 之间想秘密传送大量数据,一方(如 A )先创建公钥和私钥对,公钥对外公布,另一方(如 B )创建对称密钥,然后使用公钥加密对称密钥,传递给 A , A 收到后用私钥解密,得到对称密钥,以后 A 和 B 之间就可以使用对称密钥加密通信了。除了这种方式以外,还可以使用密钥协定来交换对称密钥。执行密钥协定的标准算法是 DH 算法( Diffie-Hellman 算法),本节介绍在 Java 中如何使用 DH 算法来交换共享密钥。

    2.9.1 创建DH 公钥和私钥

    实例说明

    DH 算法是建立在 DH 公钥和私钥的基础上的, A 需要和 B 共享密钥时, A 和 B 各自生成 DH 公钥和私钥,公钥对外公布而私钥各自秘密保存。本实例将介绍 Java 中如何创建并部署 DH 公钥和私钥,以便后面一小节利用它创建共享密钥。

    编程思路:

    在 2.7 节中使用了 KeyPairGenerator 类创建 RSA 公钥和私钥,本节也一样,只是其参数中指定“ DH ”,此外在初始化时需要为 DH 指定特定的参数。具体步骤如下:

    1 ) 生成 DH 参数

    DHParameterSpec DHP=

    new DHParameterSpec(skip1024Modulus,skip1024Base);

    分析:和 RSA 算法类似, DH 算法涉及到一些指数和取模运算, DH 参数指定 A、 B双方在创建 DH 密钥时所公用的基数和模, Java 中 DHParameterSpec 类可以定义 DH 参数,其构造器的第一个参数指定模,第二个参数指定基数。模和基数的取值在 Internet协议简单密钥管理( SKIP)标准中已经标准化,在安装 J2SDK1.4 后,计算机 C 盘中 C:/j2sdk-1_4_0-doc/docs/guide/security/jce/JCERefGuide.html 文件也包含了密钥长度为 1024 的 DH 密钥中模和基数的定义, 可以直接拷贝下来使用, 在 JCERefGuide.html 文件中查找“ 1024 bit Diffie-Hellman modulus”注释语句,将其下的 skip1024ModulusBytes[ ]数组以及 BigInteger 类型的 skip1024Modulus 和 skip1024Base 变量拷贝下来即可。在本小节的“代码与分析”中也给出了完整的代码。此外 DH 密钥长度也可以是 512 或 2048 位。

    2 ) 创建密钥对生成器

    KeyPairGenerator kpg= KeyPairGenerator.getInstance("DH");

    分析:密钥对生成器即 KeyPairGenerator 类型的对象,和 2.7 节一样,通过其中预定义的一个静态方法 getInstance( )获取 KeyPairGenerator 类型的对象。 getInstance( )方法的参数指定为“ DH”。

    3 ) 初始化密钥生成器

    kpg.initialize(DHP);

    分析:初始化时使用的参数即第 1 步中生成的参数。

    4 ) 生成密钥对,获取公钥和私钥

    KeyPair kp=kpg.genKeyPair();

    PublicKey pbk=kp.getPublic( );

    PrivateKey prk=kp.getPrivate( );

    分析:和 2.7 节一样,使用使用 KeyPairGenerator 类的 genKeyPair( )方法生成密钥对,进而使用密钥对的 getPublic( ) 和 getPrivate( ) 获取公钥和私钥。

    //文件: Key_DH.java

    import java.io.*;

    import java.math.*;

    import java.security.*;

    import java.security.spec.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    import javax.crypto.interfaces.*;

    public class Key_DH{

    // The 1024 bit Diffie-Hellman modulus values used by SKIP

        private static final byte skip1024ModulusBytes[] = {

            (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

        };

        // The SKIP 1024 bit modulus

        private static final BigInteger skip1024Modulus

                  = new BigInteger(1, skip1024ModulusBytes);

        // The base used with the SKIP 1024 bit modulus

        private static final BigInteger skip1024Base = BigInteger.valueOf(2);

    public static void main(String args[ ]) throws Exception{

        DHParameterSpec DHP=new DHParameterSpec(skip1024Modulus,skip1024Base);

         KeyPairGenerator kpg= KeyPairGenerator.getInstance("DH");

         kpg.initialize(DHP);

         KeyPair kp=kpg.genKeyPair();

         PublicKey pbk=kp.getPublic();

         PrivateKey prk=kp.getPrivate();

         FileOutputStream  f1=new FileOutputStream(args[0]);

         ObjectOutputStream b1=new  ObjectOutputStream(f1);

         b1.writeObject(pbk);

         FileOutputStream  f2=new FileOutputStream(args[1]);

         ObjectOutputStream b2=new  ObjectOutputStream(f2);

         b2.writeObject(prk);

       }     

    程序最后将公钥和私钥以对象流的形式保存在文件中,文件名通过命令行参数指定,第一个命令行参数对应的文件保存公钥,第二个命令行参数对应的文件保存私钥。

    运行程序

    建立两个目录 A 和 B ,模拟需要秘密通信的 A 、 B 双方,由于 DH 算法需要 A 和 B 各自生成 DH 公钥和私钥,因此在这两个目录下都拷贝编译后文件 Key_DH 。首先由 A 创建自己的公钥和私钥,即在 A 目录下输入“ java Key_DH Apub.dat Apri.dat ”运行程序,这时在目录 A 下将产生文件 Apub.dat 和 Apri.dat ,前者保存着 A 的公钥,后者保存着 A 的私钥。然后由 B 创建自己的公钥和私钥,即在 B 目录下输入“ java Key_DH Bpub.dat Bpri.dat ”运行程序,这时在目录 B 下将产生文件 Bpub.dat 和 Bpri.dat ,前者保存着 B 的公钥,后者保存着 B 的私钥。最后发布公钥, A 将 Apub.dat 拷贝到 B 目录, B 将 Bpub.dat 拷贝到 A 的目录。这样, A 、 B 双方的 DH 公钥和私钥已经创建并部署完毕。

    2.9.2 创建共享密钥

    实例说明

    DH 算法中, A 可以用自己的密钥和 B 的公钥按照一定方法生成一个密钥, B 也可以用自己的密钥和 A 的公钥按照一定方法生成一个密钥,由于一些数学规律,这两个密钥完全相同。这样, A 和 B 间就有了一个共同的密钥可以用于各种加密。本实例介绍 Java 中在上一小节的基础上如何利用 DH 公钥和私钥各自创建共享密钥。

    编程思路:

    Java 中 KeyAgreement 类实现了密钥协定,它使用 init( ) 方法传入自己的私钥,使用 doPhase

    ( )方法传入对方的公钥,进而可以使用 generateSecret( ) 方法生成共享的信息具体步骤如下:

    1 ) 读取自己的 DH 私钥和对方的DH 公钥

    FileInputStream f1=new FileInputStream(args[0]);

    ObjectInputStream b1=new ObjectInputStream(f1);

    PublicKey pbk=(PublicKey)b1.readObject( );

    FileInputStream f2=new FileInputStream(args[1]);

    ObjectInputStream b2=new ObjectInputStream(f2);

    PrivateKey prk=(PrivateKey)b2.readObject( );

    分析:和 2.3.1 小节类似,从文件中获取密钥。只是分为公钥和私钥两个文件,通过命令行参数传入公钥和私钥文件名,第一个命令行参数为对方的公钥文件名,第二个命令行参数为自己的私钥文件名。

    2 ) 创建密钥协定对象

    KeyAgreement ka=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双方得到的该数组的内容完全相同,用它创建密钥也各方完全相同。如可使用 SecretKeySpec k=new SecretKeySpec(sb,"DESede"); 创建密钥。

    //文件: KeyAgree.java

    import java.io.*;

    import java.math.*;

    import java.security.*;

    import java.security.spec.*;

    import javax.crypto.*;

    import javax.crypto.spec.*;

    import javax.crypto.interfaces.*;

    public class KeyAgree{

       public static void main(String args[ ]) throws Exception{

          FileInputStream f1=new FileInputStream(args[0]);

          ObjectInputStream b1=new ObjectInputStream(f1);

          PublicKey  pbk=(PublicKey)b1.readObject( );

          FileInputStream f2=new FileInputStream(args[1]);

          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();

         for(int i=0;i<sb.length;i++){

            System.out.print(sb[i]+",");

         }

        SecretKeySpec k=new  SecretKeySpec(sb,"DESede");

      }

    程序最后将共享信息打印了出来,以便直观地对比 A 和 B 得到的信息是否相同。

    运行程序

    将程序 KeyAgree 编译后分别拷贝在 A 和 B 两个目录,首先在 A 目录输入“ java KeyAgree

    Bpub.dat Apri.dat ”运行程序,它使用文件 Bpub.dat 中对方的公钥和文件 Apri.dat 中自己的

    私钥创建了一段共享的字节数组。然后在 B 目录输入“ java KeyAgree Apub.dat Bpri.dat ”运行程序,它使用文件 Apub.dat 中对方的公钥和文件 Bpri.dat 中自己的私钥创建了一段共享的字节数组。

    可以看到 A 和 B 运行后得到的字节数组内容完全相同,因此使用它创建的密钥也将相同。由于 DH 算法内在的数学规律, A 和 B 在运行时只使用了对方可以公开的信息(公钥),而各自独立地得到了相同的密钥,完成了密钥分发工作。该部分介绍了对称加密和非对称加密的基本用法,并演示了使用密钥协定进行密钥分发。读者在掌握了其原理后,可以举一反三,互相组合,满足不同应用。出于实例的简洁,该部分的例子都使用文件来交换信息,读者也可以使用其它方式,如在密钥协定中可以使用 Socket 等多种方式在程序间传递信息。

     

    BASE64、MD5、SHA、HMAC几种加密算法

    本篇内容简要介绍BASE64MD5SHAHMAC 几种加密算法。
        BASE64 编码算法不算是真正的加密算法。
        MD5SHAHMAC 这三种加密算法,可谓是非可逆加密,就是不可解密的加密方法,我们称之为单向加密算法。我们通常只把他们作为加密的基础。单纯的以上三种的加密并不可靠。

    BASE64
    按照RFC2045的定义,Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The Base64 Content-Transfer-Encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable.)
    常见于邮件、http加密,截取http信息,你就会发现登录操作的用户名、密码字段通过BASE64加密的。



    通过java代码实现如下:

    Java代码   收藏代码
    1. /**  
    2.  * BASE64解密  
    3.  *   
    4.  * @param key  
    5.  * @return  
    6.  * @throws Exception  
    7.  */   
    8. public  static  byte[] decryptBASE64(String key)  throws Exception {  
    9.     return ( new BASE64Decoder()).decodeBuffer(key);  
    10. }  
    11.   
    12. /**  
    13.  * BASE64加密  
    14.  *   
    15.  * @param key  
    16.  * @return  
    17.  * @throws Exception  
    18.  */   
    19. public  static String encryptBASE64( byte[] key)  throws Exception {  
    20.     return ( new BASE64Encoder()).encodeBuffer(key);  
    21. }  


    主要就是BASE64Encoder、BASE64Decoder两个类,我们只需要知道使用对应的方法即可。另,BASE加密后产生的字节位数是8的倍数,如果不够位数以= 符号填充。

    MD5
    MD5 -- message-digest algorithm 5 (信息-摘要算法)缩写,广泛用于加密和解密技术,常用于文件校验。校验?不管文件多大,经过MD5后都能生成唯一的MD5值。好比现在的ISO校验,都是MD5校验。怎么用?当然是把ISO经过MD5后产生MD5的值。一般下载linux-ISO的朋友都见过下载链接旁边放着MD5的串。就是用来验证文件是否一致的。



    通过java代码实现如下:

    Java代码   收藏代码
    1. /**  
    2.  * MD5加密  
    3.  *   
    4.  * @param data  
    5.  * @return  
    6.  * @throws Exception  
    7.  */   
    8. public  static  byte[] encryptMD5( byte[] data)  throws Exception {  
    9.   
    10.     MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);  
    11.     md5.update(data);  
    12.   
    13.     return md5.digest();  
    14.   
    15. }  



    通常我们不直接使用上述MD5加密。通常将MD5产生的字节数组交给BASE64再加密一把,得到相应的字符串。

    SHA
    SHA(Secure Hash Algorithm,安全散列算法),数字签名等密码学应用中重要的工具,被广泛地应用于电子商务等信息安全领域。虽然,SHA与MD5通过碰撞法都被破解了, 但是SHA仍然是公认的安全加密算法,较之MD5更为安全。



    通过java代码实现如下:

    Java代码   收藏代码
    1.      /**  
    2.      * SHA加密  
    3.      *   
    4.      * @param data  
    5.      * @return  
    6.      * @throws Exception  
    7.      */  
    8.     public  static  byte[] encryptSHA( byte[] data)  throws Exception {  
    9.   
    10.         MessageDigest sha = MessageDigest.getInstance(KEY_SHA);  
    11.         sha.update(data);  
    12.   
    13.         return sha.digest();  
    14.   
    15.     }  
    16. }  



    HMAC
    HMAC(Hash Message Authentication Code,散列消息鉴别码,基于密钥的Hash算法的认证协议。消息鉴别码实现鉴别的原理是,用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输。接收方利用与发送方共享的密钥进行鉴别认证等。



    通过java代码实现如下:

    Java代码   收藏代码
    1. /**  
    2.  * 初始化HMAC密钥  
    3.  *   
    4.  * @return  
    5.  * @throws Exception  
    6.  */   
    7. public  static String initMacKey()  throws Exception {  
    8.     KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);  
    9.   
    10.     SecretKey secretKey = keyGenerator.generateKey();  
    11.     return encryptBASE64(secretKey.getEncoded());  
    12. }  
    13.   
    14. /**  
    15.  * HMAC加密  
    16.  *   
    17.  * @param data  
    18.  * @param key  
    19.  * @return  
    20.  * @throws Exception  
    21.  */   
    22. public  static  byte[] encryptHMAC( byte[] data, String key)  throws Exception {  
    23.   
    24.     SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);  
    25.     Mac mac = Mac.getInstance(secretKey.getAlgorithm());  
    26.     mac.init(secretKey);  
    27.   
    28.     return mac.doFinal(data);  
    29.   
    30. }  



    给出一个完整类,如下:

    Java代码   收藏代码
    1. import java.security.MessageDigest;  
    2.   
    3. import javax.crypto.KeyGenerator;  
    4. import javax.crypto.Mac;  
    5. import javax.crypto.SecretKey;  
    6.   
    7. import sun.misc.BASE64Decoder;  
    8. import sun.misc.BASE64Encoder;  
    9.   
    10. /**  
    11.  * 基础加密组件  
    12.  *   
    13.  * @author 梁栋  
    14.  * @version 1.0  
    15.  * @since 1.0  
    16.  */   
    17. public  abstract  class Coder {  
    18.     public  static  final String KEY_SHA =  "SHA";  
    19.     public  static  final String KEY_MD5 =  "MD5";  
    20.   
    21.     /**  
    22.      * MAC算法可选以下多种算法  
    23.      *   
    24.      * <pre>  
    25.      * HmacMD5   
    26.      * HmacSHA1   
    27.      * HmacSHA256   
    28.      * HmacSHA384   
    29.      * HmacSHA512  
    30.      * </pre>  
    31.      */  
    32.     public  static  final String KEY_MAC =  "HmacMD5";  
    33.   
    34.     /**  
    35.      * BASE64解密  
    36.      *   
    37.      * @param key  
    38.      * @return  
    39.      * @throws Exception  
    40.      */  
    41.     public  static  byte[] decryptBASE64(String key)  throws Exception {  
    42.         return ( new BASE64Decoder()).decodeBuffer(key);  
    43.     }  
    44.   
    45.     /**  
    46.      * BASE64加密  
    47.      *   
    48.      * @param key  
    49.      * @return  
    50.      * @throws Exception  
    51.      */  
    52.     public  static String encryptBASE64( byte[] key)  throws Exception {  
    53.         return ( new BASE64Encoder()).encodeBuffer(key);  
    54.     }  
    55.   
    56.     /**  
    57.      * MD5加密  
    58.      *   
    59.      * @param data  
    60.      * @return  
    61.      * @throws Exception  
    62.      */  
    63.     public  static  byte[] encryptMD5( byte[] data)  throws Exception {  
    64.   
    65.         MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);  
    66.         md5.update(data);  
    67.   
    68.         return md5.digest();  
    69.   
    70.     }  
    71.   
    72.     /**  
    73.      * SHA加密  
    74.      *   
    75.      * @param data  
    76.      * @return  
    77.      * @throws Exception  
    78.      */  
    79.     public  static  byte[] encryptSHA( byte[] data)  throws Exception {  
    80.   
    81.         MessageDigest sha = MessageDigest.getInstance(KEY_SHA);  
    82.         sha.update(data);  
    83.   
    84.         return sha.digest();  
    85.   
    86.     }  
    87.   
    88.     /**  
    89.      * 初始化HMAC密钥  
    90.      *   
    91.      * @return  
    92.      * @throws Exception  
    93.      */  
    94.     public  static String initMacKey()  throws Exception {  
    95.         KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);  
    96.   
    97.         SecretKey secretKey = keyGenerator.generateKey();  
    98.         return encryptBASE64(secretKey.getEncoded());  
    99.     }  
    100.   
    101.     /**  
    102.      * HMAC加密  
    103.      *   
    104.      * @param data  
    105.      * @param key  
    106.      * @return  
    107.      * @throws Exception  
    108.      */  
    109.     public  static  byte[] encryptHMAC( byte[] data, String key)  throws Exception {  
    110.   
    111.         SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);  
    112.         Mac mac = Mac.getInstance(secretKey.getAlgorithm());  
    113.         mac.init(secretKey);  
    114.   
    115.         return mac.doFinal(data);  
    116.   
    117.     }  
    118. }  



    再给出一个测试类:

    Java代码   收藏代码
    1. import  static org.junit.Assert.*;  
    2.   
    3. import org.junit.Test;  
    4.   
    5. /**  
    6.  *   
    7.  * @author 梁栋  
    8.  * @version 1.0  
    9.  * @since 1.0  
    10.  */   
    11. public  class CoderTest {  
    12.   
    13.     @Test  
    14.     public  void test()  throws Exception {  
    15.         String inputStr = "简单加密";  
    16.         System.err.println("原文:/n" + inputStr);  
    17.   
    18.         byte[] inputData = inputStr.getBytes();  
    19.         String code = Coder.encryptBASE64(inputData);  
    20.   
    21.         System.err.println("BASE64加密后:/n" + code);  
    22.   
    23.         byte[] output = Coder.decryptBASE64(code);  
    24.   
    25.         String outputStr = new String(output);  
    26.   
    27.         System.err.println("BASE64解密后:/n" + outputStr);  
    28.   
    29.         // 验证BASE64加密解密一致性  
    30.         assertEquals(inputStr, outputStr);  
    31.   
    32.         // 验证MD5对于同一内容加密是否一致  
    33.         assertArrayEquals(Coder.encryptMD5(inputData), Coder  
    34.                 .encryptMD5(inputData));  
    35.   
    36.         // 验证SHA对于同一内容加密是否一致  
    37.         assertArrayEquals(Coder.encryptSHA(inputData), Coder  
    38.                 .encryptSHA(inputData));  
    39.   
    40.         String key = Coder.initMacKey();  
    41.         System.err.println("Mac密钥:/n" + key);  
    42.   
    43.         // 验证HMAC对于同一内容,同一密钥加密是否一致  
    44.         assertArrayEquals(Coder.encryptHMAC(inputData, key), Coder.encryptHMAC(  
    45.                 inputData, key));  
    46.   
    47.         BigInteger md5 = new BigInteger(Coder.encryptMD5(inputData));  
    48.         System.err.println("MD5:/n" + md5.toString( 16));  
    49.   
    50.         BigInteger sha = new BigInteger(Coder.encryptSHA(inputData));  
    51.         System.err.println("SHA:/n" + sha.toString( 32));  
    52.   
    53.         BigInteger mac = new BigInteger(Coder.encryptHMAC(inputData, inputStr));  
    54.         System.err.println("HMAC:/n" + mac.toString( 16));  
    55.     }  
    56. }  



    控制台输出:

    Console代码   收藏代码
    1. 原文:  
    2. 简单加密  
    3. BASE64加密后:  
    4. 566A5Y2V5Yqg5a+G  
    5.   
    6. BASE64解密后:  
    7. 简单加密  
    8. Mac密钥:  
    9. uGxdHC+6ylRDaik++leFtGwiMbuYUJ6mqHWyhSgF4trVkVBBSQvY/a22xU8XT1RUemdCWW155Bke  
    10. pBIpkd7QHg==  
    11.   
    12. MD5:  
    13. -550b4d90349ad4629462113e7934de56  
    14. SHA:  
    15. 91k9vo7p400cjkgfhjh0ia9qthsjagfn  
    16. HMAC:  
    17. 2287d192387e95694bdbba2fa941009a  



        BASE64的加密解密是双向的,可以求反解。
        MD5、SHA以及HMAC是单向加密,任何数据加密后只会产生唯一的一个加密串,通常用来校验数据在传输过程中是否被修改。其中HMAC算法有一个密钥,增强了数据传输过程中的安全性,强化了算法外的不可控因素。
        单向加密的用途主要是为了校验数据在传输过程中是否被修改。

    JAVA实现AES加密

    1. 因子
           上次介绍了《JAVA实现AES加密》,中间提到近些年DES使用越来越少,原因就在于其使用56位密钥,比较容易被破解,近些年来逐渐被AES替代,AES已经变成目前对称加密中最流行算法之一;AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据。本文就简单介绍如何通过JAVA实现AES加密。
    2. JAVA实现
    闲话少许,掠过AES加密原理及算法,关于这些直接搜索专业网站吧,我们直接看JAVA的具体实现。
    2.1 加密
    代码有详细解释,不多废话。
    view plaincopy to clipboardprint?
    /** 
     * 加密 
     *  
     * @param content 需要加密的内容 
     * @param password  加密密码 
     * @return 
     */ 
    public static byte[] encrypt(String content, String password) {  
            try {             
                    KeyGenerator kgen = KeyGenerator.getInstance("AES");  
                    kgen.init(128, new SecureRandom(password.getBytes()));  
                    SecretKey secretKey = kgen.generateKey();  
                    byte[] enCodeFormat = secretKey.getEncoded();  
                    SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");  
                    Cipher cipher = Cipher.getInstance("AES");// 创建密码器  
                    byte[] byteContent = content.getBytes("utf-8");  
                    cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化  
                    byte[] result = cipher.doFinal(byteContent);  
                    return result; // 加密  
            } catch (NoSuchAlgorithmException e) {  
                    e.printStackTrace();  
            } catch (NoSuchPaddingException e) {  
                    e.printStackTrace();  
            } catch (InvalidKeyException e) {  
                    e.printStackTrace();  
            } catch (UnsupportedEncodingException e) {  
                    e.printStackTrace();  
            } catch (IllegalBlockSizeException e) {  
                    e.printStackTrace();  
            } catch (BadPaddingException e) {  
                    e.printStackTrace();  
            }  
            return null;  

            /**
             * 加密
             *
             * @param content 需要加密的内容
             * @param password  加密密码
             * @return
             */
            public static byte[] encrypt(String content, String password) {
                    try {          
                            KeyGenerator kgen = KeyGenerator.getInstance("AES");
                            kgen.init(128, new SecureRandom(password.getBytes()));
                            SecretKey secretKey = kgen.generateKey();
                            byte[] enCodeFormat = secretKey.getEncoded();
                            SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
                            Cipher cipher = Cipher.getInstance("AES");// 创建密码器
                            byte[] byteContent = content.getBytes("utf-8");
                            cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
                            byte[] result = cipher.doFinal(byteContent);
                            return result; // 加密
                    } catch (NoSuchAlgorithmException e) {
                            e.printStackTrace();
                    } catch (NoSuchPaddingException e) {
                            e.printStackTrace();
                    } catch (InvalidKeyException e) {
                            e.printStackTrace();
                    } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                    } catch (IllegalBlockSizeException e) {
                            e.printStackTrace();
                    } catch (BadPaddingException e) {
                            e.printStackTrace();
                    }
                    return null;
            }
    2.2 解密
    代码有详细注释,不多废话
    注意:解密的时候要传入byte数组
    view plaincopy to clipboardprint?
    /**解密 
     * @param content  待解密内容 
     * @param password 解密密钥 
     * @return 
     */ 
    public static byte[] decrypt(byte[] content, String password) {  
            try {  
                     KeyGenerator kgen = KeyGenerator.getInstance("AES");  
                     kgen.init(128, new SecureRandom(password.getBytes()));  
                     SecretKey secretKey = kgen.generateKey();  
                     byte[] enCodeFormat = secretKey.getEncoded();  
                     SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");              
                     Cipher cipher = Cipher.getInstance("AES");// 创建密码器  
                    cipher.init(Cipher.DECRYPT_MODE, key);// 初始化  
                    byte[] result = cipher.doFinal(content);  
                    return result; // 加密  
            } catch (NoSuchAlgorithmException e) {  
                    e.printStackTrace();  
            } catch (NoSuchPaddingException e) {  
                    e.printStackTrace();  
            } catch (InvalidKeyException e) {  
                    e.printStackTrace();  
            } catch (IllegalBlockSizeException e) {  
                    e.printStackTrace();  
            } catch (BadPaddingException e) {  
                    e.printStackTrace();  
            }  
            return null;  

            /**解密
             * @param content  待解密内容
             * @param password 解密密钥
             * @return
             */
            public static byte[] decrypt(byte[] content, String password) {
                    try {
                             KeyGenerator kgen = KeyGenerator.getInstance("AES");
                             kgen.init(128, new SecureRandom(password.getBytes()));
                             SecretKey secretKey = kgen.generateKey();
                             byte[] enCodeFormat = secretKey.getEncoded();
                             SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");           
                             Cipher cipher = Cipher.getInstance("AES");// 创建密码器
                            cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
                            byte[] result = cipher.doFinal(content);
                            return result; // 加密
                    } catch (NoSuchAlgorithmException e) {
                            e.printStackTrace();
                    } catch (NoSuchPaddingException e) {
                            e.printStackTrace();
                    } catch (InvalidKeyException e) {
                            e.printStackTrace();
                    } catch (IllegalBlockSizeException e) {
                            e.printStackTrace();
                    } catch (BadPaddingException e) {
                            e.printStackTrace();
                    }
                    return null;
            }
    2.3 测试代码
    view plaincopy to clipboardprint?
    String content = "test";  
    String password = "12345678";  
    //加密  
    System.out.println("加密前:" + content);  
    byte[] encryptResult = encrypt(content, password);  
    //解密  
    byte[] decryptResult = decrypt(encryptResult,password);  
    System.out.println("解密后:" + new String(decryptResult)); 
                    String content = "test";
                    String password = "12345678";
                    //加密
                    System.out.println("加密前:" + content);
                    byte[] encryptResult = encrypt(content, password);
                    //解密
                    byte[] decryptResult = decrypt(encryptResult,password);
                    System.out.println("解密后:" + new String(decryptResult));
    输出结果如下:
    加密前:test
    解密后:test
    2.4 容易出错的地方
    但是如果我们将测试代码修改一下,如下:
    view plaincopy to clipboardprint?
    String content = "test";  
    String password = "12345678";  
    //加密  
    System.out.println("加密前:" + content);  
    byte[] encryptResult = encrypt(content, password);  
    try {  
            String encryptResultStr = new String(encryptResult,"utf-8");  
            //解密  
            byte[] decryptResult = decrypt(encryptResultStr.getBytes("utf-8"),password);  
            System.out.println("解密后:" + new String(decryptResult));  
    } catch (UnsupportedEncodingException e) {  
            e.printStackTrace();  

                    String content = "test";
                    String password = "12345678";
                    //加密
                    System.out.println("加密前:" + content);
                    byte[] encryptResult = encrypt(content, password);
                    try {
                            String encryptResultStr = new String(encryptResult,"utf-8");
                            //解密
                            byte[] decryptResult = decrypt(encryptResultStr.getBytes("utf-8"),password);
                            System.out.println("解密后:" + new String(decryptResult));
                    } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                    }
    则,系统会报出如下异常:
    javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
            at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
            at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
            at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
            at javax.crypto.Cipher.doFinal(DashoA13*..)
    这主要是因为加密后的byte数组是不能强制转换成字符串的,换言之:字符串和byte数组在这种情况下不是互逆的;要避免这种情况,我们需要做一些修订,可以考虑将二进制数据转换成十六进制表示,主要有如下两个方法:
    2.4.1将二进制转换成16进制
    view plaincopy to clipboardprint?
    /**将二进制转换成16进制 
     * @param buf 
     * @return 
     */ 
    public static String parseByte2HexStr(byte buf[]) {  
            StringBuffer sb = new StringBuffer();  
            for (int i = 0; i < buf.length; i++) {  
                    String hex = Integer.toHexString(buf[i] & 0xFF);  
                    if (hex.length() == 1) {  
                            hex = '0' + hex;  
                    }  
                    sb.append(hex.toUpperCase());  
            }  
            return sb.toString();  

            /**将二进制转换成16进制
             * @param buf
             * @return
             */
            public static String parseByte2HexStr(byte buf[]) {
                    StringBuffer sb = new StringBuffer();
                    for (int i = 0; i < buf.length; i++) {
                            String hex = Integer.toHexString(buf[i] & 0xFF);
                            if (hex.length() == 1) {
                                    hex = '0' + hex;
                            }
                            sb.append(hex.toUpperCase());
                    }
                    return sb.toString();
            }
    2.4.2 将16进制转换为二进制
    view plaincopy to clipboardprint?
    /**将16进制转换为二进制 
     * @param hexStr 
     * @return 
     */ 
    public static byte[] parseHexStr2Byte(String hexStr) {  
            if (hexStr.length() < 1)  
                    return null;  
            byte[] result = new byte[hexStr.length()/2];  
            for (int i = 0;i< hexStr.length()/2; i++) {  
                    int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);  
                    int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);  
                    result[i] = (byte) (high * 16 + low);  
            }  
            return result;  

            /**将16进制转换为二进制
             * @param hexStr
             * @return
             */
            public static byte[] parseHexStr2Byte(String hexStr) {
                    if (hexStr.length() < 1)
                            return null;
                    byte[] result = new byte[hexStr.length()/2];
                    for (int i = 0;i< hexStr.length()/2; i++) {
                            int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
                            int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
                            result[i] = (byte) (high * 16 + low);
                    }
                    return result;
            }
    然后,我们再修订以上测试代码,如下:
    view plaincopy to clipboardprint?
    String content = "test";  
    String password = "12345678";  
    //加密  
    System.out.println("加密前:" + content);  
    byte[] encryptResult = encrypt(content, password);  
    String encryptResultStr = parseByte2HexStr(encryptResult);  
    System.out.println("加密后:" + encryptResultStr);  
    //解密  
    byte[] decryptFrom = parseHexStr2Byte(encryptResultStr);  
    byte[] decryptResult = decrypt(decryptFrom,password);  
    System.out.println("解密后:" + new String(decryptResult)); 
                    String content = "test";
                    String password = "12345678";
                    //加密
                    System.out.println("加密前:" + content);
                    byte[] encryptResult = encrypt(content, password);
                    String encryptResultStr = parseByte2HexStr(encryptResult);
                    System.out.println("加密后:" + encryptResultStr);
                    //解密
                    byte[] decryptFrom = parseHexStr2Byte(encryptResultStr);
                    byte[] decryptResult = decrypt(decryptFrom,password);
                    System.out.println("解密后:" + new String(decryptResult));
    测试结果如下:
    加密前:test
    加密后:73C58BAFE578C59366D8C995CD0B9D6D
    解密后:test
     
    2.5 另外一种加密方式
    还有一种加密方式,大家可以参考如下:
    view plaincopy to clipboardprint?
    /** 
          * 加密 
          * 
          * @param content 需要加密的内容 
          * @param password  加密密码 
          * @return 
          */ 
         public static byte[] encrypt2(String content, String password) {  
                 try {  
                         SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");  
                         Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");  
                         byte[] byteContent = content.getBytes("utf-8");  
                         cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化  
                         byte[] result = cipher.doFinal(byteContent);  
                         return result; // 加密  
                 } catch (NoSuchAlgorithmException e) {  
                         e.printStackTrace();  
                 } catch (NoSuchPaddingException e) {  
                         e.printStackTrace();  
                 } catch (InvalidKeyException e) {  
                         e.printStackTrace();  
                 } catch (UnsupportedEncodingException e) {  
                         e.printStackTrace();  
                 } catch (IllegalBlockSizeException e) {  
                         e.printStackTrace();  
                 } catch (BadPaddingException e) {  
                         e.printStackTrace();  
                 }  
                 return null;  
         } 
       /**
             * 加密
             *
             * @param content 需要加密的内容
             * @param password  加密密码
             * @return
             */
            public static byte[] encrypt2(String content, String password) {
                    try {
                            SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");
                            Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
                            byte[] byteContent = content.getBytes("utf-8");
                            cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
                            byte[] result = cipher.doFinal(byteContent);
                            return result; // 加密
                    } catch (NoSuchAlgorithmException e) {
                            e.printStackTrace();
                    } catch (NoSuchPaddingException e) {
                            e.printStackTrace();
                    } catch (InvalidKeyException e) {
                            e.printStackTrace();
                    } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                    } catch (IllegalBlockSizeException e) {
                            e.printStackTrace();
                    } catch (BadPaddingException e) {
                            e.printStackTrace();
                    }
                    return null;
            }
    这种加密方式有两种限制
    密钥必须是16位的
    待加密内容的长度必须是16的倍数,如果不是16的倍数,就会出如下异常:
    javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
            at com.sun.crypto.provider.SunJCE_f.a(DashoA13*..)
            at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
            at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
            at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
            at javax.crypto.Cipher.doFinal(DashoA13*..)
    要解决如上异常,可以通过补全传入加密内容等方式进行避免。

    展开全文
  • 这是作者的网络安全自学教程系列,...这篇文章将讲解虚拟机基础知识,包括XP操作系统安装、文件共享设置、网络快照及网络设置等,最后分享虚拟机中安装安全软件进行BBS密码抓取的实验。基础性文章,希望对您有所帮助。

    这是作者的网络安全自学教程系列,主要是关于安全工具和实践操作的在线笔记,特分享出来与博友们学习,希望您们喜欢,一起进步。前文分享了Procmon软件基本用法及文件进程、注册表查看,这是一款微软推荐的系统监视工具,功能非常强大可用来检测恶意软件。这篇文章将讲解虚拟机基础知识,包括XP操作系统安装、文件共享设置、网络快照及网络设置等,最后分享虚拟机中安装安全软件进行BBS密码抓取的实验。基础性文章,希望对您有所帮助。

    写这篇文章您可能会疑问“为什么连虚拟机安装系统这么简单的内容都要分享呢?”,其实安全这块,作者真是小白。可能对于熟悉虚拟机或安全大佬来说,这些知识可以默认都会,但是之前自己对这块就模棱两可的,为了更好地、更系统的分享这系列文章,所以作者也会详细讲解每一个基础知识点,希望您喜欢或海涵~

    作者作为网络安全的小白,分享一些自学基础教程给大家,主要是关于安全工具和实践操作的在线笔记,希望您们喜欢。同时,更希望您能与我一起操作和进步,后续将深入学习网络安全和系统安全知识并分享相关实验。总之,希望该系列文章对博友有所帮助,写文不易,大神们不喜勿喷,谢谢!如果文章对您有帮助,将是我创作的最大动力,点赞、评论、私聊均可,一起加油喔~

    PS:本文参考了安全网站和参考文献中的文章(详见参考文献),并结合自己的经验和实践进行撰写,也推荐大家阅读参考文献。

    下载地址:https://github.com/eastmountyxz/NetworkSec

    展开全文
  • HMAC-based One-Time Password 简写,表示基于 HMAC 算法加密的一次性密码。是事件同步,通过某一特定的事件次序及相同的种子值作为输入,通过 HASH 算法运算出一致的密码。 1.3、TOTP Time-based One-Ti...
  • 基于新一代多媒体加密技术,更高安全性、支持WIN7;通过国内某安全组织三防一免认证: 防破解: 加密后的视频,用户无法绕过播放密码! 防提取: 加密后的视频用户无法提取到原始视频文件; 防劫持: 防止各种Dll...
  • 基于新一代多媒体加密技术,更高安全性、支持XP、Vista、Win7、WIN8、Win10;支持各种视频的高速编码加密与高速解码播放;可以加密各种视频音频格式文件(wmv, avi, asf, mpg, rm, rmvb, mp4, flv, mp3, vob, mov, ...
  • 金盾视频加密

    2014-01-07 22:52:05
     屏幕翻录一直是视频产品被盗版的最大威胁之一,China-DRM推出的智能防录功能可以有效避免视频被翻录盗版的危险,加密视频播放时会智能分析客户电脑内所有进程的动态行为和操作,在播放后的3-10分钟内即可做出...
  • 硬盘加密

    千次阅读 2018-05-04 16:32:18
    目录1 修改分区表▪ 启动口令▪ 用户加密▪ 写保护2 磁盘扇区数据加密加密方案▪ 软盘加密▪ 卡加密▪ 软件锁▪ 光盘加密▪ 移动硬盘加密4 其他方案▪ 密码表▪ 序列号▪ 许可证5 加密软件▪ ...
  • 飓风视频加密8.08版

    2014-07-19 10:11:41
    2、可以设置播放时断开网络,禁止用户通过远程共享或者远程翻录; 3、可以设置播放时禁止开启其他窗口,以便学员可以专心学习; 4、可以为视频部分增加水印; 5、可以指定是否可以提供免费试看试听 6、可以指定产品...
  • 1、金盾视频加密器 - 强调安全性,适合各种视频加密安全性高,经测试最高支持到1.5G视频文件加密安全性要求高的场合使用。 2、Video2exe V2010A型 - 强调安全性,适合各种视频加密安全性高,生成单一exe格式;...
  • 金盾视频加密器支持各种视频的高速编码加密与高速解码播放;可以加密各种视频音频格式文件(wmv、avi、asf、mpg、rm、rmvb、mp4、flv等),加密后的文件可以通过离线方式授权播放,也可以通过网络方式授权播放;只...
  • 可以加密各种视频格式文件(wmv,avi,asf,mpg,rm,rmvb,mp4,flv,vob等),加密后的文件可以通过离线方式授权播放,也可以通过网络方式授权播放;支持500M以上大型视频文件的高效加密与解码播放; 1、可以进行...
  • 第四章 WLAN 加密缺陷 作者:Vivek Ramachandran, Cameron Buchanan 译者:飞龙 协议:CC BY-NC-SA 4.0 简介 任何人对于内存的需求,都不会超过640K。 – 比尔·盖茨,微软创始人 即使做了最充分的预测,...
  • 第一节 常见保护技巧1、序列号方式(1)序列号保护机制数学算法一项都是密码加密的核心,但在一般的软件加密中,它似乎并不太为人们关心,因为大多数时候软件加密本身实现的都是一种编程的技巧。但近几年来随着...
  • 软件加密方式

    千次阅读 2007-07-16 10:28:00
    软件加密方式加密一词来源已久,自从人们希望对自己私人的信息得到保护开始,就有了加密这个概念。软件行业的加密是软件厂商为了保护软件开发的利润而采取的一种软件保护方式,加密 的好坏直接影响到软件的销售,从 ...
  • 文本文件专用加密器永久注册版 文本文件加密器: 1、可以控制是否允许用户打印文档 2、可以控制是否允许客户复制文字,并可以精确控制允许复制的字符数 3、可以指定产品编号,以便用户可以管理个...9、可以防止拷
  • 安全性的算法(RSA 2048位密钥加密) 兼容性和稳定性 VProtect注重强度的同时,也是以兼容性为首的。 只有在保证加密后程序在所有系统上都正常执行的功能才会添加到程序中。程序代码级保护使用了高效的反汇编引擎...
  • EFS加密 解密

    千次阅读 2007-11-24 14:50:00
    随着稳定性和可靠性的逐步提高,Windows XP已经被越来越的人使用,很多人还用Windows XP自带的EFS加密功能把自己的一些重要数据加密保存。虽然EFS易用性不错,不过发生问题后就难解决了,例如不做任何准备就重装了...
  • 文件加密软件

    千次阅读 2012-09-17 09:38:53
    大量数据信息的创建、存储、传输以及共享方式已经发生了革命性的变化,主要表现在以下三个方面: 1)以信息化手段与传统机械化生产方式的市场竞争和技术创新;  2)以信息化手段取代纸媒档案保存企业业务信息,...
  • 加密视频解密过程

    万次阅读 2018-12-25 09:33:43
    视频共享、交互是互联网时代的具体表现,在线学习视频是现在人的生活方式,我们需要下载很视频进行学习,但是很优质的视频资源都进行了加密保护,当你发现下载的视频提示你需要输入播放密码的时候,点击视频不能...
  • 2、文件编号可以显示加密后的文件中,方便商家区分不同文件类别; 3、修正了Win7下无法使用断网功能的Bug; 4、增加了试播文件制作功能,您可以为用户制作试播文件,并可以控制文件的播放次 数和有效期,无需播放...
  • 2、文件编号可以显示加密后的文件中,方便商家区分不同文件类别; 3、修正了Win7下无法使用断网功能的Bug; 4、增加了试播文件制作功能,您可以为用户制作试播文件,并可以控制文件的播放次 数和有效期,无需播放...
  • 公司终于配上了双主机双系统双屏幕,编码是爽了,但是桌上的键盘有了一套,有没有什么软件能够在不同的电脑之间共享键盘和鼠标呢?后来发下了Synergy这款软件.不仅免费而且开源(支持下). 让办公桌上的台电脑共享...
  • 代理重加密(Proxy Re-encryption)

    万次阅读 多人点赞 2018-11-02 16:33:53
    代理重加密就是委托可信第三方或是半诚实代理商将自己公钥加密的密文转化为可用另一方私钥解开的密文从而实现密码共享  现实世界中绝大多数提供云计算服务的公司没有什么诚信可言,你不能保证它会用你的数据做什么...
  • 各种加密方案分析

    千次阅读 2007-03-25 11:36:00
    各种加密方案分析 当前软件加密方法多种多样,已经不可能找出一种分类方法来把各种加密方案很好地区分开来。基本上来说可以分为依赖特定硬件的加密方案和不依赖硬件的加密方案。 一、依赖硬件的加密方案 1. 软盘加密...
  • 功能强大安全性最好的视频加密工具。 支持各种视频的高速编码加密与高速解码播放,加密后的文件自带解码器和播放器;可以加密各种视频音频格式文件(wmv,avi,mpg,rm,rmvb,mp4,flv,vob等),加密后的文件可以通过离线...
  • 软件加密方式大全

    千次阅读 2016-05-26 09:34:49
    加密一词来源已久,自从人们希望对自己私人的信息得到保护开始,就有了加密这个概念。软件行业的加密是软件厂商为了保护软件开发的利润 而采取的一种软件保护方式,加密 的好坏直接影响到软件的销售,从 Apple II...
  • WifiDisplay(Miracast)技术原理及实现 WifiDisplay(Miracast)技术...3. WifiDisplay显示框架实现 4. Android WifiDisplay实现 4.1 Source端实现 4.1.1 设备扫描及发现 4.2 Sink端的实现 4.2.1 设备如何被发...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 28,376
精华内容 11,350
关键字:

多屏共享显示安全加密