您的位置:首页 > 编程语言 > Java开发

Java 安全编程加密了解1

2012-09-04 20:21 399 查看

JavaTM安全体系结构(JDK1.2)

3. 许可和安全策

3.1 许可类
?

许可类表示了对系统资源的访问。java.security.Permission类是一个抽象的类,且在适当的时候可生成子类以表示特定的访问。

作为许可的一个例子,下列代码可被用来生成一个阅读在/tmp目录下名为“abc”的文件的许可?br>

perm = new java.io.FilePermission ("/tmp/abc", "read");

新许可类可通过继承Permission类或它的子类(如java.security.BasicPermission) 来生成的。已成为子类的许可(不是BasicPermission)通常都属于它们自己的包。因此,FilePermission可在java.io.package中找到。

一个重要的并需要被每个许可新类实现的抽象方法是implies方法。一般来说,“a implies b”意味着如果你被授予了许可"a",那么你也就自然地被授予了许可"b"。这在访问控制决策中是十分重要的。

与抽象类java.security.Permission一起的是被称为java.security.PermissionCollection的抽象类和叶子类java.security.Permissions。

java.security.PermissionCollection类表示了单一类别的(如文件许可)Permission对象的集合(例如,允许复制的一个集),以便于分组。在许可能够被以任何顺序添加到PermissionCollection对象的情况下(如为文件许可),当implies功能被调用时,PermissionCollection对象能够确保其后语义的正确性是至关重要的。

java.security.Permissions类表示了Permission对象的集合的集合,或换句话说,是异类许可的超级集合。

应用程序可添加系统支持的许可的新类型。添加此种特殊应用程序的许可的方法将在后面讨论。

现在,我们来说明所有内置许可的句法和语义。

3.1.1 java.security.Permission?

这个抽象类是所有许可的祖先,它为所有许可定义了所需的基本功能。

典型地,每个许可实例通过将一个或多个字符串参数传递给构造函数而被生成。在有两个参数的普通情况下,第一个参数通常是“目标名”(如作为许可目标的一个文件的名称),第二个参数是动作(如对一个文件的“阅读”动作)。一般的,一组动作可用逗号分隔的复合串来一起指定。

3.1.2 java.security.PermissionCollection

这个类掌握了许可的一个同类收集。换言之,类的每个实例仅掌握同类型的许可。

3.1.3 java.security.Permissions

设计这个类是为了掌握许可的异类收集。基本上,它是java.security.PermissionCollection对象的收集。

3.1.4 java.security.UnresolvedPermission

在正常情况下,一个安全策略的内部状态是由与每个代码源相关联的许可对象来表示的。然而,鉴于Java技术的动态性,当该策略被启动时,那些用来实现特定许可类的实际代码可能还没有在Java环境中被装载和定义。例如,一个基准许可类可能在JAR文件中,而该文件将稍后再装载。

UnresolvedPermission类被用来掌握这种“未解决的”的许可。类似的,java.security.UnresolvedPermissionCollection类储存UnresolvedPermission许可的收集。

在对一个以前未解决的类型(但它的类已经被装载)的许可的访问控制检查中,未解决的许可是“解决的”,并且作出了适当的访问控制决策。即:如果可能的话,基于UnresolvedPermission的信息,适当的类的新的对象被实例化,这个新的对象代替了UnresolvedPermission。

如果此时许可仍然是不可解决的,则该许可被认为是无效的,就象在一个安全策略中,它从未被授予过一样。

3.1.5 java.io.FilePermission

这个类的目标可用下列方法来说明,在这里,目录和文件名是不包括空格的字符串。

file
firectory (与directory/一样)
firectory/file
firectory/* (在目录directory下的所有文件)
? (在当前目录中的所有文件)
firectory/- (在目录directory 下的文件系统中的所有文件)
? (在当前目录下的文件系统中的所有文件)
《ALL FILES》" (在文件系统中的所有文件)

?

注意“”是一个用来表示系统中所有文件的特殊字符串。在Unix系统中,它包括了根目录下的所有文件;在MS-DOS系统中,它包括了在所有驱动器中的所有文件。

这个类的动作是:读、写、删除和执行(read, white, delete and execute)。以下是创建文件许可的有效代码样本:

 port java.io.FilePermission;
 FilePermission p = new FilePermission("myfile", "read,write");
FilePermission p = new FilePermission("/home/gong/", "read");
FilePermission p = new FilePermission("/tmp/mytmp", "read,delete");
 FilePermission p = new FilePermission("/bin/*", "execute");
 FilePermission p = new FilePermission("*", "read");
 FilePermission p = new FilePermission("/-", "read,execute");
 FilePermission p = new FilePermission("-", "read,execute");
 FilePermission p = new FilePermission("《ALL FILES》", "read");

 在这个类中的implies方法可正确地说明文件系统。例如,
 FilePermission("/-","read,execute")隐含
 FilePermission("/home/gong/public_html/index.html", "read");
 FilePermission("/bin/*", "execute")隐含
 FilePermission("bin/emacs19.13", "execute")。


注:这些字符串的大部分都是以依赖于平台的格式给出。例如,要表示对在Windows系统C驱动器中的 "temp" 目录下名为 "foo"的文件的阅读访问,你可以使用:

FilePermission p = new FilePermission ("c://temp//foo", "read");

这里使用双反斜线来代表单反斜线是必要的。因为字符串是由一个tokenizer来处理的(java.io.StreamTokenizer),它允许 "/" 当作换行字符串来使用(例如, "/n"表示一个新的行),因此,我们需要用双反斜线来替代单反斜线的功能。在tokenizer对上述FilePermission目标字符串做过处理之后,再将双反斜线转换为单反斜线,最后结果是实际路径:

"c:/temp/foo"

在全局文件描述语言出现之前,字符串应该以依赖于平台的格式给出,这一点是必要的。还要注意的是,通配符(如 "*"和 "-"等)的使用影响了特殊文件名的使用。我们认为这是一个可以容忍的小限制。最后,要注意 "/-" 和 "《ALL FILES》" 在Unix系统中是同一个目标,它们都表示整个文件系统(如果有的话,它们也可表示多个文件系统);在其它操作系统中(如MS Windows,MacOS),这两个目标可能是不同的。

还要注意的一点是,象下列代码中仅指出目录和 "read" 动作的目标名,表示你只被允许列出那个目录中的文件名,而不能读它们。

FilePermission p = new FilePermission ("/home/gong", "read");

要允许对文件的阅读访问,你必须指出明确的文件名,或 "*",或 "- ",如下所示:?br>

FilePermission p = new FilePermission ("/home/gong/myfile", "read");
FilePermission p = new FilePermission ("/home/gong/*", "read");
FilePermission p = new FilePermission ("/home/gong/-", "read");


最后注意的是,代码总是自动具有对阅读位于与其相同的URL位置上的文件的许可,包括那个位置上的子目录;这不需要明确的许可。

3.1.6 java.net.SocketPermission

这个类表示通过sockets对一个网络的访问。这个类的目标可给为 "hostname:port_range",这里的hostname可用下列方式给出:

 hostname (一个主机)  IP address (一个主机)  localhost (本地机)  "" (相同于localhost")  hostname.domain (在域domain中的一个主机)  hostname.subdomain.domain  *.domain (在域domain中的所有主机)  *.subdomain.domain  * (所有主机) ?/pre>?

也就是说,主机被表示为一个DNS名,或数字IP地址,或 "localhost"(为本地的机器),或 ""(它与指定的 "localhost" 等同)。

通配符 "*"可能会在DNS名称主机规范中使用一次,在这种情况下,它必须被放置在最左边,如: "*.sun.com"。 ?br>

port_range可按如下方式给出:

N (一个单一的端口)

N- (端口N及以上的所有端口)

-N (端口N及以上下的所有端口)

N1-N2 (N1和N2之间的所有端口, 包括N1和N2)

这里的N、N1和N2为非负整数,范围为0至65535(2^16-1)。 ?br>

在socket上的动作有accept(接受), connect(连接), listen(侦听)和resolve(分辨) (它们是基本的DNS查询)。请注意,动作 "resolve"是由 "accept"、 "connect"和 "listen"所暗指的,也就是说,那些可以侦听、接受来自主机的连接或启动一个指向另一主机的连接的动作的类,应该能够查询远程主机的名称。

以下是socket许可的一些实例:

import java.net.SocketPermission;

SocketPermission p = new SocketPermission ("java.sun.com", "accept");

p = new SocketPermission ( "204.160.241.99", "accept");

p = new SocketPermission ( "*.com", "connect");

p = new SocketPermission ( "*.sun.com:80", "accept");

p = new SocketPermission ( "*.sun.com:-1023", "accept");

p = new SocketPermission ( "*.sun.com:1024-", "connect");

p = new SocketPermission ( "java.sun.com:8000-9000", "connect,accept");

p = new SocketPermission ( "localhost:1024-", "accept,connect,listen");
?

请注意, SocketPermission ( "java.sun.com:80,8080", "accept") 和 SocketPermission ( "java.sun.com,javasun.sun.com", "accept")不是有效的socket许可。

另外,listen是仅用于本地主机端口的动作,而accept是不但可用于本地主机端口,也可应用于远程主机端口的动作。两个动作都是必需的。

3.1.7 java.security.BasicPermission?/b>

BasicPermission类继承了Permission类,它可被当作许可的基类,并按照相同的命名惯例称作BasicPermission。

一个BasicPermission的名称是特定许可的名称(例如,"exitVM", "setFctory", "queuePrintJob"等)。命名是按照所有等级命名惯例来进行。星号可能出现在名称的最后以表示一个通配符匹配,其前面可能有个 ".",也可能没有。例如: "java.*"或 "*"是有效的, "*java"或 "a*b"则是无效的。

不能使用动作串(从Permission继承)。因此,BasicPermission一般被用作“已命名的”许可的基类(这些许可包含名称但无动作列表;你可能有已命名的许可,也可能没有)。如果愿意,子类可能在BasicPermission的上层实现动作。

某些BasicPermission的子类有java.lang.RuntimePermission,

java.security.SecurityPermission,java.util.PropertyPermission, ?br>java.net.NetPermission ?/font>?

3.1.8 java.util.PropertyPermission

这个类的基本目标是设置在各种属性文件中的Java属性名称,例如 "java.home"和 "os.name" 属性。目标可被指定为 "*" (全部属性)、 "a.*"(其名称具有前缀a.的全部属性)以及 "a.b.*"等等。请注意,通配符只能出现一次,且必须位于最右端。

这个类是BasicPermission子类之一,它在BasicPermission上实现动作。该动作是读和写,其定义如下: "read" 许可允许在java.lang.System中的getProperty方法被调用以得到属性值, "white" 许可允许setProperty方法被调用以设置属性值。

3.1.9 java.lang.RuntimePermission

一个RuntimePermission的目标可用任何字符串来表示,且没有动作与该目标相关联。例如,RuntimePermission("exitVM")表示退出Java虚拟机的许可。

目标名为:

 createClassLoader
 getClassLoader
 setContextClassLoader
 setSecurityManager
 createSecurityManager
 exitVM
 setFactory
 setIO
 modifyThread
 stopThread
 modifyThreadGroup
 getProtectionDomain
 readFileDescriptor
 writeFileDescriptor
 loadLibrary.{library name}
 accessClassInPackage.{package name}
 defineClassInPackage.{package name}
 accessDeclaredMembers.{class name}
 queuePrintJob

?

3.1.10 java.awt.AWTPermission

它与RuntimePermission相同,也是一个无动作的许可。其目标名为:

accessClipboard

accessEventQueue

listenToAllAWTEvents

showWindowWithoutWarningBanner

3.1.11 java.net.NetPermission

这个类包含下列目标且无动作:

requestPasswordAuthentication

setDefaultAuthenticator

specifyStreamHandler

3.1.12 java.lang.reflect.ReflectPermission


这是一个为进行反射操作的Permission类。一个ReflectPermission是一个命名的许可(与RuntimePermission类似)且无动作。目前定义的唯一名称是:

suppressAccessChecks?br>

它允许超越反射对象实施的标准Java编程语言的存取检查,即对其类中的公有的、缺省的、受保护的和私有成员的访问检查等。换句话说,当一个实例拥有该许可后,它可访问反射对象的公有的、缺省的、受保护的和私有成员。

3.1.13 java.io.SerializablePermission?br>

这个类包含下列目标并且无动作: ?br>

enableSubclassImplementation

enableSubstitution ?br>

3.1.14 java.security.SecurityPermission?br>

SecurityPermission控制对与安全相关的对象的访问,如对象Security, Policy, Provider, Signer和Identity等。这个类包含下列目标并且无动作:

 getPolicy
 setPolicy
 getProperty.{key}
 setProperty.{key}
 insertProvider.{provider name}
 removeProvider.{provider name}
setSystemScope
 setIdentityPublicKey
 setIdentityInfo
 printIdentity
 addIdentityCertificate
 removeIdentityCertificate
 clearProviderProperties.{provider name}
 putProviderProperty.{provider name}
 removeProviderProperty.{provider name}
 getSignerPrivateKey
 setSignerKeyPair



3.1.15 java.security.AllPermission


这个许可意味着全部许可。它是为简化系统管理员的工作而设计的,因为系统管理员可能需要执行多种要求全部(或大量)许可的任务。反复定义许可显然是烦琐的。请注意,AllPermission也意味着在未来定义的新的许可。 ?br>

当使用这个许可时,应慎重考虑。

3.1.16许可含义讨论

回想一下我们发现,许可经常被互相比较,而且,为了便于这种比较,我们要求每个许可类定义一个implies方法,它表示特定的许可类是如何与其它许可类发生关系的。例如,java.io.FilePermission("/temp/*", "read")隐含java.io.FilePermission("/temp/a.txt", "read"),但不隐含任何java.net.NetPermission。

另外一层含义可能对某些读者来说不能立即明了。假设一个Applet被授予了编写全部文件系统的许可,它假设允许该Applet来替换执行系统,包括JVM运行环境。这就有效地意味着Applet已经被授予了全部许可。 ?br>

另一个例子是,如果一个Applet被授予运行时创建类装载器的许可,它就被有效地授予了更多的许可。因为类装载器可执行敏感操作。 ?br>

其它一些“危险的”许可包括允许设置系统属性的许可、运行时定义包和装载本地代码库的许可(因为Java安全结构不能防止本地代码的恶意行为),当然,还有AllPermission。 ?br>

有关许可的更多信息,包括列举分配特殊许可的风险的表格和所有需要许可的JDK内置方法表格等,请查看: ?br>

?a href="http://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.html " target="_new">http://java.sun.com/products/jdk/1.2/docs

/guide/security/permissions.html

3.1.17如何创建新型许可

除了SUN Microsystems应该负责扩展内置于JDK中的许可外,无论是通过增加功能性,还是通过将附加目标关键词引入到一个类中(如java.lang.RuntimePermission),无人能够担当此任。这一点是必需的,它保持了JDK的连续性。

要创建一个新的许可,建议采用下面例子所示的步骤。假设ABC公司的一个应用程序开发员要创建一个“看电视”的用户化许可。

首先,创建一个新的类com.abc.Permission,它继承抽象类java.security.Permission(或它的一个子类);再创建另一个新的类com.abc.TVPermission,它继承com.abc.Permission。确认其它类中的implies方法被正确地实现(当然,com.abc.TVPermission可直接继承java.security.Permission;不需要中间的com.abc.Permission)。

public class com.abc.Permission extends java.security.Permission

public class com.abc.TVPermission extends com.abc.Permission

下图显示了子类之间的关系:



第二, 将这些新类加在应用系统包中。

每个想要允许这个新型许可的用户都要在策略文件中增加一个入口(策略文件句法的细节见以下章节)。授予来自http://java.sun.com/的代码许可,使其可以观看5频道电视的策略文件入口授权码为:

grant codeBase "http://java.sun.com/" {
 permission com.abc.TVPermission "channel-5", "watch";
}


在应用程序的资源管理代码中,要查看一个许可是否应该被准予时,使用一个com.abc.TVPermission对象作为参数来调用AccessController的checkPermission方法。

com.abc.TVPermission tvperm = new
com.abc.TVPermission ("channel-5", "watch");
AccessController.checkPermission(tvperm);


请注意,当增加一个新的许可时,你应该创建一个新的(许可)类,而不在SecurityManager中增加新的方法(过去,为了检查访问的新类型,你必须在SecurityManager类中增加新的方法)。

如果更精细的TVPermission (例如, "channel-1:13"或"channel-*")被允许的话,则需实现一个TVPermissionCollection对象,这个对象知道如何处理这些伪名称。

为了执行内置访问控制算法,新的代码应该总是通过调用AccessController类的checkPermission方法来调用一个许可检查。没有必要检查是否具有ClassLoader或SecurityManager。另一方面,如果这个算法留给已安装的安全管理器类,则应该调用方法SecurityManager.checkPermission。

3.2 java.security.CodeSource 

这个类继承了HTML内代码基址(codebase)的概念,目的是不仅封装代码位置(URL),而且封装包含公共密钥(这个公共密钥可用来校验来自那个位置的数字签字)的数字证书。请注意,这不是在HTML文件中的CodeBase标记的等价物。每个证书表示为java.security.cert.Certificate,每个URL表示为java.net.URL。


3.3 java.security.Policy


Java应用环境的系统安全策略说明了来自不同地方的代码的可用许可,它用Policy对象来表示。 在特别的情况下,用Policy子类来表示,该子类实现了Policy类中的抽象方法。

为了使一个Applet(或一个在SecurityManager下运行的应用程序)能被允许执行安全的动作,如阅读或编写文件,该Applet(或应用程序)必须被授予那个特定动作的许可。唯一例外是,代码总是自动具有阅读来自相同的出处的文件以及这个出处的子目录的文件的许可,而不需要明确的许可。

可能有多个Policy对象的实例,尽管在任何时候都仅有一个是“有效的”。通过调用getPolicy方法,可获得当前安装的Policy对象,并且,可通过调用setPolicy方法(由具有重设Policy的许可的代码来执行)来改变它。

当保护域启动它的许可集时,当前策略被一个ProtectionDomain所检查。保护域在一个CodeSource对象中通过,这封装了它的代码基址(URL)和证书属性。Policy对象评估全局策略并返回一个适当的Permission对象,它说明了来自指定代码源的代码的许可。

对Policy对象所采用的策略信息的资源定位等于Policy实现。策略的配置可被存储, 例如当作一个平面ASCII文件来存储,或当作一个Policy类的串行二进制文件来存储,或当作一个数据库来存储。有一个缺省Policy实现,它可获得来自静态策略配置文件的信息。

在制定安全策略时并没有涉及Policy对象。它只是策略配置在Java运行时的体现。

3.3.1策略文件格式  

在缺省Policy实现中, 策略可在一个或多个策略配置文件中指定。配置文件指出, 对来自指定代码源的代码, 哪些许可是被允许的。

一个策略配置文件一般都包含一个入口列表。它可能包含一个密钥库(keystore)入口, 并包含零个或多个 "grant"入口。

密钥库是一个数据库, 它装有私有密钥和它们的相关数字证书, 如用来认证相应的公共密钥的X.509证书链。keytool实用程序用来创建并管理密钥库。在策略配置文件中指定的密钥库可被用来查询在文件准予入口中指定的签字者的公共密钥。 如果任何准予入口说明了签字者的别名, 则一个密钥库入口必须出现在一个策略配置文件中。

此时, 在策略文件中可能只有一个密钥库入口(第一个之后的被忽略), 并且, 它可出现在文件准予入口之外的任何地方。它具有如下句法:

keystore "some_keystore_url", "keystore_type";

这里, "some_keystore_url"指定keystore的URL位置, "keystore_type"指定密钥库类型。后者是可选项, 如果未指定, 则被假定为在安全属性文件中的 "keystore.type"属性所指定的类型。

URL与策略文件的位置相关。因此, 如果策略文件在安全属性文件中被指定为:

policy.url.l=http://foo.bar.com/blah/some.policy

并且该策略文件有一个入口:

keystore ".keystore";

则keystore将被从如下位置装载:

?http://foo.bar.com/blah/.keystore

URL也可以是绝对地址。

密钥库类型定义了密钥库信息的存储形式和数据格式, 并定义了算法以保护密钥库中的私有密钥以及密钥库本身的完整性。Sun Microsystems支持的缺省类型是被称为 "JKS"的专用密钥库类型。

在策略文件中的每个准予入口基本上是由一个CodeSource和它的许可所组成。实际上,CodeSource是由一个URL和一个证书集所组成,而一个策略文件入口包括一个URL和一个签名者列表。在查询了密钥库以确定指定签名者的证书之后,系统创建相应的CodeSource。

在策略文件中的每个准予入口采用以下格式。前面的 "grant"是一个保留字,它表示一个新的入口的开始,可选项出现在括号内。在每个入口内,第一个 "permission" 也是一个保留字,它标志着在该入口内的一个新的许可的开始。每个准予入口都将一个许可集授予一个指定代码源。

 
grant [SignedBy "signer_names"] [, CodeBase "URL"] {
permission permission_class_name [ "target_name" ]
[, "action"] [, SignedBy "signer_names"];
permission ...
};


在任何逗号之前或之后,允许空格。许可类的名称必须全部是合格的类名称,如java.io.FilePermission, 且不能简化(如简化为FilePermission)

动作字段是可选项,如果许可类不需要它,则可将其忽略。如果出现动作字段,则必须紧接在目标字段之后。

一个CodeBase URL值的确切含义取决于结尾处的字符。用 "/"

结尾的CodeBase可在指定目录中与所有类文件匹配(不包括JAR文件);用 "/*"

结尾的CodeBase可在指定目录中与所有文件匹配(包括类文件和JAR文件);用 "/-"

结尾的CodeBase可在指定目录中及那个目录下的子目录中与所有文件匹配(包括类文件和JAR文件)。

CodeBase字段(URL)是可选项,如果忽略,它表示“任意代码基址”。

第一个签字字段是通过单独机制映射的字符串别名,这个映射对应与签字者相关的公共密钥集(在密钥库中的证书内)的映射。这些密钥被用来校验某个已签字的类是否真的是由那些签字者所签的字。

包括多个签名的字段可以是用逗号分隔的字符串。例如, "Adam,Eve,Charles"表示这是一个由Adam和Eve和Charles签的字(也就是说,他们的关系是“和”,而不是“或”)。

这个字段是可选项,如果被忽略,则表示“任意签字者”;换句话说,“这个代码是否被签字都无所谓”。

第二个签字字段(在一个Permission入口内)代表了包括公共密钥的密钥库入口的别名,这个公共密钥对应于用来签写实现上述许可类的字节码的私有密钥。只有在该字节码的实现被校验为是上述别名的正确签字时,这个许可入口才是有效的(也就是说,访问控制许可是基于这个入口而发出的)。

CodeBase和SignedBy字段之间的顺序是无关紧要的。

以下是一个非正式的为Policy文件格式设计的BNF语法,这里的非大写项是终结符。

?
?
PolicyFile -> PolicyEntry | PolicyEntry; PolicyFile
PolicyEntry -> grant {PermissionEntry}; |
grant SignerEntry {PermissionEntry} |
grant CodebaseEntry {PermissionEntry} |
grant SignerEntry, CodebaseEntry {PermissionEntry} |
grant CodebaseEntry, SignerEntry {PermissionEntry} |
keystore "url"
SignerEntry -> signedby (a comma-separated list of strings)
CodebaseEntry -> codebase (a string representation of a URL)
PermissionEntry -> OnePermission | OnePermission PermissionEntry
OnePermission -> permission permission_class_name
[ "target_name" ] [, "action_list"]
[, SignerEntry];

现在我们给出一些实例。下面的策略将许可a.b.Foo授予由Roland签字的代码:

 
grant signedBy "Roland" {
permission a.b.Foo;
};


下面的例子将一个FilePermission授予所有代码(不管签字者和/或codeBase):

grant {
permission java.io.FilePermission ".tmp", "read";
};

下面的例子将两个许可授予由Li和Roland共同签字的代码:

grant signedBy "Roland,Li" {
permission java.io.FilePermission "/tmp/*", "read";
permission java.util.PropertyPermission "user.*";
};


? 下面的例子将两个许可授予由Li签字的代码且来自http://java.sun.com

 
grant codeBase "http://java.sun.com/*", signedBy "Li" {
permission java.io.FilePermission "/tmp/*", "read";
permission java.io.SocketPermission "*", "connect";
};

? 下面的例子将两个许可授予由Li和Roland共同签字的代码,而且只有在由实现com.abc.TVPermission的字节码是由Li签字时才能成功:

 
grant signedBy "Roland,Li" {
permission java.io.FilePermission "/tmp/*", "read";
permission com.abc.TVPermission "channel-5", "watch",
signedBy "Li";
};


包括第二个签字者字段的原因是为了在一个许可类没有与Java运行安装在一起时防止欺骗。例如,com.abc.TVPermission的一个拷贝可作为一个远程JAR被下载,并且用户策略可能包括一个引用它的入口。因为该文档不是长命的,当com.abc.TVPermission类被第二次下载时(可能从不同的web地址),第二个拷贝是否可信,这一点是至关重要的,因为在用户策略中的许可入口的出现可能反映了用户对这个类字节码的第一个拷贝的信心和信任。

我们选择使用数字签字(而不是选择存储字节码的第一个拷贝的哈希值,然后在将第二个拷贝与之进行比较)以保证真实性的原因是,因为许可类的作者能合法地更改类文件以反映一个新的设计或实现。

请注意:文件路径字符串必须采用依赖于平台的格式;在一个全局文件描述语言出现之前,这是必要的。上面的例子已经显示了适用于Solaris系统的字符串。在Windows系统中,当你要用字符串直接指定一个文件路径时,你需要使用双反斜线来代替表示路径的单反斜线。举例如下:

 
grant signedBy "Roland" {
permission java.io.FilePermission "C://users//Cathy//*", "read";
};?/pre>

这是因为该字符串是由一个tokenizer来处理的(java.io.StreamTokenizer),它允许 "/"被用来当作转义字符串(例如: "/n"表示新的一行),这就要求用双反斜线来代替单反斜线。tokenizer处理上述FilePermission目标字符串时,将双反斜线转换为单反斜线,最终结果是实际路径:

"C:/users/Cathy/*"

? 3.3.2策略文件的属性扩展

在策略文件和安全属性文件中的属性扩展是可能的。

属性扩展与扩展在一个外壳上的变量相似。即:当如下字符串

"${some.property}"

 

出现在一个策略文件或一个安全属性文件中时,它将被扩展为指定系统的属性的值。例如:

permission java.io.Permission "${user.home}", "read";

将扩展 "${user,home}"以使用 "user.home"系统属性的值;如果那个属性的值是 "/home/cathy",则上述代码与下面的代码等价:

permission java.io.FilePermission "/home/cathy", "read";

为加速独立于平台的策略文件,你也可以使用特殊符号 “${/}",它是 "${file.separator}"的快捷方式。它允许在许可定义中使用。例如:

permission java.io.FilePermission "${user.home}${/}*", "read";

如果user.home为/home/cathy,并且你是在Solaris系统,上式可转换为:

permission java.io.FilePermission "/home/cathy/*", "read";

另一方面,如果user.home为C:/users/cathy,并且你是在Windows系统,则可转换为:

permission java.io.FilePermission "C:/users/cathy/*", "read";

另外,作为特殊情况,如果你在一个代码基址中扩展一个属性,如:

grant codeBase "file:/${java.home}/lib/ext/"

则任何file.separator字符将被自动转换为 /'s,因为代码基数是URLs,所以上述转换是我们所期望的。这样,在一个Windows系统上,甚至如果java,home被设置为C:/jdk1.2,上式也可转换为:

grant codeBase "file:/C:/jdk1.2/lib/ext/"

于是你不需要在代码基址字符串中使用${/}(你不应该使用)。

属性扩展可发生在策略文件中任何有双引号字符串的地方,这包括:signedby、代码基址、目标名和动作字段。

是否允许属性扩展,取决于安全属性文件中的 "policy.expandProperties"属性的值。如果这个值是true(缺省值),则允许扩展。

请注意:你不能使用嵌套的属性。例如:

"${user.${foo}}"

  上式将不起作用,甚至 "foo"属性被设置为 "home",也不起作用。原因是属性语法分析程序不能识别嵌套属性。它只寻找第一个 "${",然后继续寻找,直到发现第一个 "}"为止,并且试图解释结果 "${user.$foo}"为一个属性。如果没有这样的属性,则失败。

  另外还要注意,如果一个属性在一个准予入口、许可入口或密钥库入口不能被扩展,则那个入口将被忽略。例如:如果系统属性 "foo" 未被定义并且你具有:

 
grant  codebase "$ {foo}" {
permission  ?
permission  ?
};


则所有在准予入口的许可将被忽略。如果你具有:
?
 grant {
permission  Foo  "$ {foo}"
permission Bar;
};


则仅有"permission Foo ?入口被忽略。最后,如果你具有:

keystore "${foo }";

则密钥库入口被忽略。

最后注意一点,在Windows系统中,当你直接用字符串指定一个文件路径时,你需要使用双反斜线来代替在路径中的单反斜线:

"C://users//cathy//foo.bat"

这是因为该字符串是由一个tokenizer来处理的(java.io.StreamTokenizer),它允许 "/"被用来当作转义字符串(例如: "/n"表示新的一行),这就要求用双反斜线来代替单反斜线。tokenizer处理上述字符串时,将双反斜线转换为单反斜线,最终结果是:

"C:/users/Cathy/foo.bat"

  在一个字符串中的属性扩展发生在tokenizer处理完该字符串之后。于是,如果你有字符串

  "${user.home}//foo.bat"

  则tokenizer将首先处理该字符串,转换双反斜线为单反斜线,结果是:

  "${user.home}/foo.bat"

  然后 ${user.home}属性被扩展,最后结果是:

"C:/users/cathy/foo.bat"

假设user.home值为 "C:/users/cathy" (当然是独立于平台的),最好在一开始字符串就被指定,且不用任何显式斜线。如下所示:

"${user.home}${/}foo.bat"

3.3.3分配许可

当装载一个起源于特定CodeSource的新的类时,安全性机制查询策略对象以决定应准予什么样的许可。这是通过调用安装在VM上的Policy对象上的getPermission方法来完成的。

显然,一个特定的代码源可与多个入口所给定的代码源在该策略中相匹配。例如,因为允许使用通配符 "*"。

下列算法被用来在策略中定位适当的许可集:

1. 如果代码被签字的话,匹配公共密钥。

2.如果在策略中没有找到密钥,则忽略该密钥;如果每一个密钥都被忽略,则视该代码为未签字代码。

3. 如果密钥匹配或没有说明签字者{ 在策略中尝试为该密钥匹配所有的URLs }

4. 如果密钥或URL之一未得到匹配,使用内置的缺省的许可,该许可就是最初的沙箱许可。

一个策略入口CodeBase URL值的确切含义取决于结尾处的字符。用 "/"

结尾的CodeBase在指定目录中与所有类文件匹配(不包括JAR文件);用 "/*"

结尾的CodeBase在指定目录中与所有文件匹配(包括类文件和JAR文件);用 "/-"

结尾的CodeBase在指定目录中及那个目录下的子目录中与所有文件匹配(包括类文件和JAR文件)。

举例来说,在策略中给定了 "http://java.sun.com/- ",则在该web地址上的任意代码基址都将与策略入口匹配。匹配的代码基址包括

"http://java.sun.com/jdk/"和

"http://java.sun.com/people/gong/appl.jar"

如果多个入口被匹配,则所有在那些入口中所给定的许可将被准予。换言之,许可的分配是附加的。例如,如果用密钥A签字的代码获得了许可X,用密钥B签字的代码获得了许可Y,并且未指定特定的代码基基址,则由A和B共同签字的代码将获得许可X和Y。类似的,如果带有代码基址"http://java.sun.com/- "的代码获得了许可X, 带有代码基址 "http://java.sun.com/people/* "的代码获得了许可Y, 并且未指定特定签字者,则来自 "http://java.sun.com/people/applet.jar"的一个Applet将同时获得X和Y。

请注意,这里的URL匹配纯粹是句法上的。例如,一个策略可给定一个入口,这个入口指定一个URL "http://ftp.sun.com",只有在能够直接从ftp获得Java代码来执行时,这样的入口才是有用的。

为了为本地文件系统指定URLs,可使用文件URL。例如,为指定在Solaris系统中的/home/cathy/temp目录下的文件,你可以使用:

"file:/home/cathy/temp/*"

要指定在Windows系统中C驱动器上temp目录下的文件,你可以使用:

"file:/c:/temp/*"

注意:无论在什么平台上,代码基址URL总是使用斜线(不是反斜线)。

你也可以使用一个绝对路径名,如:

"/home/gong/bin/MyWonderfulJava"

3.3.4缺省系统和用户策略文件

在缺省Policy实现中,策略可在一个或多个策略配置文件中说明。配置文件指定什么样的许可能够为来自指定代码源的代码所使用。

一个策略文件可通过一个简单的文本编辑器来制作,也可以使用我们稍后将要讨论的图形Policy Tool实用程序来制作。

缺省地,我们具有一个单独的系统整体策略文件和一个单独的用户策略文件。

系统整体策略文件缺省地位于:

{java.home}/lib/security/java.policy (Solaris)?br> {java.home}/lib/security/java.policy (Windows)

这里的java.home是一个系统属性,它指定JDK被安装的目录。

用户策略文件缺省地位于:

{user.home}/.java.policy (Solaris)

{user.home}/.java.policy (Windows)

这里的user.home是一个指定用户的起始目录的系统属性。

当Policy被初始化时,系统整体策略首先被装入,然后用户策略被添加上去。如果这两个策略都不出现,则一个内置策略被使用。这个内置策略与初始沙箱策略相同。

策略文件定位在安全属性文件中被指定,它位于:

{java.home}/lib/security/java.security (Solaris)

{java.home}/lib/security/java.security (Windows)

策略文件定位被指定为某个属性的值,它的名称为如下形式:

policy.url.n

这里的n是一个数。你可以用如下一行形式来指定每一个这样的属性值:

policy.url.n=URL

这里的URL是一个URL说明。例如,缺省系统和用户策略文件在安全属性文件中被定义为:

policy.url.1=file:${java.home}/lib/security/java.policy

policy.url.2=file:${java.home}/.java.policy

实际上,你可以指定许多URLs,包括 "http://"形式的。所有指定的策略文件将被装载。你也可以注释或改变第二个URL,使缺省用户策略文件不能被阅读。

算法从policy.url.1开始,并保持递增直到它未发现一个URL为止。于是如果你具有policy.url.1和policy.url.3,则policy.url.3将总是读不到。

当调用一个应用程序的执行时,可能指定一个附加的或不同的策略文件。这可以通过 "-Djava.security.policy"命令行自变量来实现,它设置java.security.policy属性的值。例如,如果你使用:

java -Djava.security.manager -Djava.security.policy=pURL SomeApp

这里的pURL是一个URL,它指定一个策略文件位置,然后除了安全属性文件中指定的所有策略文件外,这个指定的策略文件也将被装载。( "-Djava.security.manager"自变量将保证缺省安全管理器被安装,并且应用程序服从于策略检查,就象 "Security Management for Applets and Applications"中所描述的那样。如果应用程序SomeApp安装一个安全管理器,则无必要)。

如果如下程序一样使用双等号,那么仅有指定的策略文件将被使用,其它将被忽略。

java -Djava.security.manager -Djava.security.policy = = pURL SomeApp

如果你要传递一个策略文件至appletviewer,请再次使用 "-Djava.security.policy"自变量,如下所示:

appletviewer -J-Djava.security.policy=pURL myApplet

请注意: 如果在安全属性文件中的 "policy.allowSystemProperty"属性被设置为假,"-Djava.security.policy"策略文件值将被忽略(对java 和 appletviewer都一样)。其缺省值为真。

3.3.5用户化策略评估

Policy类的当前设计不象以前那样复杂。我们已经充分地考虑了这个问题并谨慎地向前发展;部分原因是为了我们所设计的方法调用能够适合大多数的一般情况。同时,给出可选择的策略类以替代缺省策略类,只要前者是一个抽象Policy类的子类并实现getPermissions方法(需要时实现其它方法)。

通过将 "policy.provider"安全属性(在安全属性文件中)的值重新设置到所期望的Policy实现类名中,则缺省Policy实现可被改变。安全属性文件名称如下:

{java.home}/lib/security/java.security (Solaris)

{java.home}/lib/security/java.security (Windows)

这里,{java.home}指安装有JDK的目录。

属性policy.provider指定策略类的名称,其缺省值如下:

policy.provider=sun.security.provider.PolicyFile

为了用户化,你可以改变该属性的值以指定另一个类,如下所示:

policy.provider=com.mycom.MyPolicy

注意:MyPolicy类必须是java.security.Policy的一个子类。或许值得强调这样的策略类覆盖是暂时的解决方案,而更广泛的策略API将会替代这种方法。

? 3.4 jjava.security.GeneralSecurityException

这是一个新的异常类,它是java.lang.Exception的一个子类。它试图表示,应该有两个与安全和安全包相关的异常类型。

java.lang.SecurityException和它的子类应该是运行时异常(未检查的,未声明的),它可能会引起一个程序的执行被中断。

只有在某种安全性违规被检测到时,这样的异常才被扔出。例如,当某些代码试图访问一个文件,但它没有访问许可,该异常就被扔出。应用程序开发人员可捕捉这些异常。

java.security.GeneralSecurityException是java.lang.Exception(必须被声明或捕捉)的一个子类,它在所有其它情况下被从安全包中扔出。

这样的异常与安全有关但不是致命的。例如,传输一个无效的密钥可能不是安全违规, 它应该被开发人员捕捉并处理。

在java.security包中还有两个异常,它们是RuntimeException的子类,由于向后兼容的要求,此时我们不能改变它。

Java身份验证和授权规范(JAAS)

Java平台是创建企业应用程序的普遍选择。它所以受欢迎,主要原因之一是在创建Java语言时充分考虑了安全性,而且市场上也普遍认为Java是一种"安全的"语言。Java平台在两个层次上提供安全性:语言层次安全性和企业层次安全性。

1.语言层次安全性

最初的Java(JDK1.2)平台采用沙箱安全模型,基本安全模型由三部分来承担,这三部分构成Java运行环境的三个安全组件,分别是:类加载器,文件校验器,安全管理器

1.1类加载器

类加载器负责从特定位置加载类,类加载器是JVM的看门人,控制着哪些代码被加载或被拒绝,类加载器首先进行安全性检查,它往往从以下几个方面去检查,装入的字节码是否生成指针,装入的字节码是否违反访问限制,装入的字节码是否把对象当作它们本身来访问,它在确保执行代码的安全性方面至关重要。

1.2类文件校验器的校验

类文件校验器负责检查那些无法执行的明显有破坏性的操作,类文件校验器执行的一些检查通常有:变量要在使用之前进行初始化;方法调用和对象引用类型之间要匹配;没有违反访问私有数据和方法的规则;对本地变量的访问都在运行时堆栈内;运行时堆栈是否溢出.如果以上检查中任何一条没有通过,就认为该类遭到了破坏,不被加载。

1.3安全管理器

一旦某个类被类加载器加载到虚拟机中,并由类文件校验器检查过之后,JAVA的第三种安全机制安全管理器就会启动,安全管理器是一个负责控制某个操作是否允许执行的类,安全管理器负责检查包括以下几个方面的操作:当前线程是否能创建一个新的类加载器;当前线程是否能终止JVM的运行;某个类是否能访问另一个类的成员;当前线程是否能访问本地文件;当前线程是否能打开到达外部记住的socket连接;某个类是否能启动打印作业;某个类是否能访问系统剪贴板;某个类是否能访问AWT事件队列;当前线程是否可被信任以打开一顶层窗口。

尽管Java安全的支柱类加载器、类文件校验器、安全管理器每一个都有独特的功能,但它们又相互依赖、相辅相承。共同保证了Java语言的安全性。

2.企业层次的安全特性

即是构建安全的J2EE应用。Java平台在提供语言安全性的同时还提供其他API功能,为企业应用程序提供一个总体的安全性解决方案。下面将介绍几种方案。

2.1 Java加密扩展(JCE)

JCE是一组包,为加密、密钥生成、密钥协商和消息身份验证代码(MAC)算法提供一种框架和实现。JCE支持多种类型的加密,包括对称的、非对称的、块和流密码。在JDK 1.4之前,JCE是一个可选的包,现在它已经成为Java平台的一个标准组成部分。

2.2 Java安全套接字扩展(JSSE)

JSSE是支持安全的Internet通信的一组包。它是实现了SSL和传输层安全(TLS)协议的JAVA技术。它包括用于数据加密、服务器身份验证、消息完整性和可选的客户端身份验证的诸多功能。JSSE已被集成到JDK 1.4以上版本的平台中。

2.3 Java身份验证和授权规范(JAAS)

JAAS通过对运行程序的用户的进行验证,从而达到保护系统的目的。JAAS主要由两个部件构成,认证和授权,JAAS通过一个配置文件来定义认证机制。认证模块是基于可插的认证模块而设计的,它可以运行在客户端和服务器端,授权模块的设计是一个变化的过程,为了对资源的访问请求进行授权,首先需要应用程序认证请求的资源,subject术语来表示请求的资源,用java.security.auth.Subject类来表示subject。subject一旦通过了认证,就会和身份和主体想关联。在JAAS中将主体表示为javax.security.Principal对象,一个subject可能包含多个主题,除了和主题相关联外,subject还可能拥有与安全相关的属性或证书,证书是用户的数据,它包含这样的认证信息即认证subject所拥有的其他服务的信息。基于J2EE的分布式应用程序使用

JAAS一般有两种情况:第一种情况,一个单独的应用系统与一个远程的EJB系统连接,用户必须向应用系统提供证明身份的信息或应用系统向文件和其它的系统来检索可证明身份的信息。这个单独的应用系统将在调用EJB组件之前使用JAAS来验证用户,由应用服务器完成验证的任务。只有当用户通过JAAS的验证之后,客户端程序才可被信任地调用EJB方法。第二种情况,基于Web浏览器的客户端程序连接到Servlet/JSP层,客户端用户将向Servlet/JSP层提供证明身份的信息,而Servlet/JSP层可以采用JAAS验证用户。Web客户端一般可以采

用基本验证、基于表格的验证、摘要验证、证书验证等方式来提供证明身份的信息。这种支持选择不同认证方法的灵活性有助于支持在管理员层实施更为复杂的安全策略,而不是在编程层上去实现。一旦客户端通过应用服务器认证,安全上下文环境能被传播到EJB层。在应用程序中使用JAAS验证通常会涉及到以下几个步骤:

1.创建一个LoginContext的实例。并传递LoginModule配置

文件程序段和CallbackHandler的名称。

2.为了能够获得和处理验证信息,将一个CallbackHandler

对象作为参数传送给LoginContext。

3.通过调用LoginContext的login()方法来进行验证。

4.通过使用login()方法返回的Subject对象实现一些特殊

的功能(假设登录成功)。

举个例子:

LoginModel是jaas的一个核心接口,她负责实施用户认证。同时暴漏了initialize(),login(),commit(),abort(),logout()方法。

package sample;

import java.util.Map;

import javax.security.auth.Subject;

import javax.security.auth.callback.Callback;

import javax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.NameCallback;

import javax.security.auth.callback.PasswordCallback;

import javax.security.auth.callback.UnsupportedCallbackException;

import javax.security.auth.login.LoginException;

import javax.security.auth.spi.LoginModule;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

/**

*

* @author worldheart

*

*/

public class ScreenContentLoginModule
implements LoginModule
{

protected static
final Log
log = LogFactory.getLog(ScreenContentLoginModule.class);

private Subject
subject;

private UsernamePasswordPrincipal
principal;

private CallbackHandler
callbackhandler;

public ScreenContentLoginModule()
{

log.info("ScreenContentLoginModule()................");

}

//在初始化LoginModel后,LoginContext会调用这一方法,从而完成当前LoginModel的初始化工作

public void
initialize(Subject
subject,
CallbackHandler

callbackhandler,
Map state,
Map options)
{

log.info("进入initialize()................");

this.principal
= null;

this.subject
= subject;

this.callbackhandler
= callbackhandler;

}

//用户阶段1,并认证subject的方法,它可能会收集用户的凭证,比如用户名,密码,并将认证结果存储到LoginModel的实例中

public boolean
login()
throws LoginException
{

log.info("进入login()................");

Callback callbacks[]
= new
Callback[2];

callbacks[0]
= new
NameCallback("您的登录名:");

callbacks[1]
= new
PasswordCallback("您的密码:", false);

String username
= null;

String password
= null;

try {

this.callbackhandler.handle(callbacks);

username =
((NameCallback) callbacks[0]).getName();

password =
new String(((PasswordCallback) callbacks[1]).getPassword());

} catch
(java.io.IOException ioe)
{

throw
new LoginException(ioe.toString());

} catch
(UnsupportedCallbackException ce)
{

throw
new LoginException(ce.getCallback().toString());

}

if (username.equals("marissa")
|| username.equals("scott"))
{

this.principal
= new UsernamePasswordPrincipal(username,password);

return true;

} else
{

return false;

}

}

//表明认证操作成功,会获得阶段1(login())的认证结果,并将这一结果填充到subject中

public boolean
commit()
throws LoginException
{

log.info("进入commit()................");

if(this.principal
==
null)

return false;

this.subject.getPrincipals().add(this.principal);

return true;

}

//认证操作失败

public boolean
abort()
throws LoginException
{

log.info("进入abort()................");

if(this.principal
==
null)

return false;

this.principal
= null;

return true;

}

//完成subject的销毁工作

public boolean
logout()
throws LoginException
{

log.info("进入logout()................");

if(this.principal
==
null)

return false;

this.subject.getPrincipals().remove(principal);

this.principal
= null;

return true;

}

}


下面是UsernamePasswordCallbackhandler类:

package sample;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import javax.security.auth.callback.Callback;

import javax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.NameCallback;

import javax.security.auth.callback.PasswordCallback;

import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

/**

*

* @author worldheart

*

*/

public class UsernamePasswordCallbackHandler implements CallbackHandler {

protected static final Log log = LogFactory.getLog(UsernamePasswordCallbackHandler.class);

public void handle(Callback callbacks[]) throws IOException, UnsupportedCallbackException {

log.info("进入handle()........................");

for (Callback cb: callbacks) {

if (cb instanceof NameCallback) {

NameCallback nc = (NameCallback) cb;

log.info(nc.getPrompt());

//采集用户名

String username = (new BufferedReader(new InputStreamReader(

System.in))).readLine();

nc.setName(username);

} else if(cb instanceof PasswordCallback){

PasswordCallback pc = (PasswordCallback) cb;

log.info(pc.getPrompt());

//采集用户密码

String password = (new BufferedReader(new InputStreamReader(

System.in))).readLine();

pc.setPassword(password.toCharArray());

}

}

}

}

一旦用户收集到用户账号后NameCallback,PasswordCallback对象都会存储他们,与此同时,上述login()方法会基于账号信构建UsernamePasswordPrincipal对象,并保留在登录模块中,而且login()会返回true,当login方法顺利完成用户凭证信息的收集工作后,commit会被触发,她将UsernamePasswordPrincipal对象摆到Subject对象中。

当login方法未能顺利完成用户凭证信息的收集工作后,abort会被触发,将principal等信息破换掉。当登录用户完满的完成自身的业务操作后便可以考虑退出当前的应用,调用logout方法。下面是Principal对象:

package sample;

import java.security.Principal;

/**

*

* @author worldheart

*

*/

//Acegi中的Authentication接口继承了Principal接口

public class UsernamePasswordPrincipal implements Principal {

private String username;

private String password;

//存储用户名、密码,比如marissa/koala

public UsernamePasswordPrincipal(String username, String password) {

this.username = username;

this.password = password;

}

public String getName() {

return this.username;

}

public String toString() {

return this.username + "->" + this.password;

}

}

为了使用上述登录模块,需要准备一个jaas配置文件:

Loginmodel.conf放在src下面

ScreenContent {

sample.ScreenContentLoginModule required;

};

客户应用:

package sample;

import java.io.File;

import java.security.PrivilegedAction;

import javax.security.auth.Subject;

import javax.security.auth.login.LoginContext;

import javax.security.auth.login.LoginException;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

/**

*

* @author worldheart

*

*/

public class JaasSecurityClient {

protected static final Log log = LogFactory

.getLog(JaasSecurityClient.class);

public static void main(String argv[]) throws LoginException,

SecurityException {

LoginContext ctx = null;

ctx = new LoginContext("ScreenContent", new UsernamePasswordCallbackHandler());

//marissa用户登录到当前应用中

ctx.login();

log.info("当前用户已经通过用户认证");

Subject subject = ctx.getSubject();

log.info(subject);

// log.info("启用JAAS用户授权能力");

// log.info("临时目录为," + Subject.doAsPrivileged(subject, new PrivilegedAction() {

// public Object run() {

// log.info("当前用户正在经过JAAS授权操作的考验,并正调用目标业务操作");

// new File("D:/eclipse/workspace/contactsforchapter8/src/loginmodule.conf").exists();

// return System.getProperty("java.io.tmpdir");

// }

// }, null));

// 退出当前已登录marissa用户

ctx.logout();

}

}

在运行客户应用之前还需要提供JVM参数,即引用到loginmoudel.conf配置文件:

-Djava.security.auth.login.config=src/loginmoudel.conf

或者通过javahome/jre/lib/security目录中的java.security配置文件指定上述loginmoudel.conf配置文件:

#login.config.url.l=file:${user.home}/.java.login.config

login.config.url.l=file:d:/eclipse/src/loginmoudel.conf

SecurityContextLoginModule是Acegi内置的一个LoginModel实现,当开发Jaas应用时,用户凭证信息的获取可能来自Acegi,此时,我们便可以采用内置的SecurityContextLoginModel。要使用SecurityContextLoginModule,我们需要在Jaas配置文件中配置它:

ACEGI {

org.acegisecurity.providers.jaas.SecurityContextLoginModule

required ignoreMissingAuthentication=true;

};

客户端应用:

package sample;

import java.io.File;

import java.security.PrivilegedAction;

import javax.security.auth.Subject;

import javax.security.auth.login.LoginContext;

import javax.security.auth.login.LoginException;

import org.acegisecurity.context.SecurityContextHolder;

import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

/**

*

* @author worldheart

*

*/

public class AcegiSecurityClient {

protected static final Log log = LogFactory

.getLog(AcegiSecurityClient.class);

public static void main(String argv[]) throws LoginException, SecurityException {

LoginContext ctx = null;

//在实际企业应用中,Authentication对象的构建形式多种多样

SecurityContextHolder.getContext().setAuthentication(

new UsernamePasswordAuthenticationToken("marissa", "koala"));

ctx = new LoginContext("ACEGI");

// marissa用户登录到当前应用中

ctx.login();

log.info("当前用户已经通过用户认证");

Subject subject = ctx.getSubject();

log.info(subject);

// log.info("启用JAAS用户授权能力");

// log.info("临时目录为,"

// + Subject.doAsPrivileged(subject, new PrivilegedAction() {

// public Object run() {

// log.info("当前用户正在经过JAAS授权操作的考验,并正调用目标业务操作");

// new File(

// "D:/eclipse/workspace/contactsforchapter8/src/loginmodule.conf")

// .exists();

// return System.getProperty("java.io.tmpdir");

// }

// }, null));

// 退出当前已登录marissa用户

ctx.logout();

//清除已注册的SecurityContext

SecurityContextHolder.clearContext();

}

}

注意到我们并未为LoginContext提供CallbackHandler对象,由于Acegi负责提供兼容于Principal的Authentication对象,因此用户凭证的收集也不用CallbackHandler操心了。

在运行客户应用之前还需要提供JVM参数,即引用到loginmoudel.conf配置文件:

-Djava.security.auth.login.config=src/loginmoudel.conf

或者通过javahome/jre/lib/security目录中的java.security配置文件指定上述loginmoudel.conf配置文件:

#login.config.url.l=file:${user.home}/.java.login.config

login.config.url.l=file:d:/eclipse/src/loginmoudel.conf

启用Java安全管理器:大部分java开发者都知道,借助如下JVM参数能够启用java安全管理器,-Djava.security.manager。既然如此,我们通过如下JVM参数运行JaasSecurityClient客户端和AcegiSecurityClient客户端:

-Djava.security.manager -Djava.security.auth.login.config=src/loginmodule.conf

但是这样会出错:java.security.auth.login.config.AccessControlException:access denied

出错原因:默认时,直接借助“-Djava.security.manager”启动java安全管理器,JVM会采用javahome/jre/lib/security中的java.policy策略文件,而这一策略文件并未对上述涉及到的各种权限(比如:createLoginContext.ScreenContent,读取acegi.security.strategyJava属性)进行授权因此抛出了异常。

为此我们可以提供新的授权信息jaassecuritypolicy.txt策略文件。由于我们需要同LoginContext进行各类操作因此需要提供相关AuthPermission权限给Acegi

SecurityClient,同时我们使用了Commons-Logging,Log4j管理日志,因此还必须将相应的操作权限给这一客户,在操作日志的过程中,客户应用需要操控的:d:/ddlog.log日志文件因此需要将读写权限授给这一客户应用。

grant codebase "file:./-"{

permission java.io.FilePermission "D:/contactsforchapter8.log", "read, write";

permission javax.security.auth.AuthPermission "createLoginContext";

permission javax.security.auth.AuthPermission "modifyPrincipals";

};

grant codeBase

"file:D:/eclipse/workspace/contactsforchapter8/context/WEB-INF/lib/log4j-1.2.14.jar" {

permission java.security.AllPermission;

};

grant codeBase

"file:D:/eclipse/workspace/contactsforchapter8/context/WEB-INF/lib/commons-logging-1.0.4.jar" {

permission java.security.AllPermission;

};

实际上java的策略文件编写可以通过policytool工具。

运行JaasSecurityClient客户端应用:

-Djava.security.manager -Djava.security.policy=src/jaassecuriypolicy.txt

-Djava.security.auth.login.config=src/loginmodule.conf

类似的运行AcegiSecurityClient的策略文件:

grant codebase "file:./-"{

permission java.util.PropertyPermission "acegi.security.strategy", "read";

permission java.io.FilePermission "D:/contactsforchapter8.log", "read, write";

permission javax.security.auth.AuthPermission "createLoginContext";

permission javax.security.auth.AuthPermission "modifyPrincipals";

};

grant codeBase

"file:D:/eclipse/workspace/contactsforchapter8/context/WEB-INF/lib/log4j-1.2.14.jar" {

permission java.security.AllPermission;

};

grant codeBase

"file:D:/eclipse/workspace/contactsforchapter8/context/WEB-INF/lib/commons-logging-1.0.4.jar" {

permission java.security.AllPermission;

};

运行AcegiSecurityClient客户端应用:

-Djava.security.manager -Djava.security.policy=src/acegisecuriypolicy.txt

-Djava.security.auth.login.config=src/loginmodule.conf

启用Jaas的用户授权功能:jaas的授权能力依赖java策略文件,下面提供了另一个版本的jaasSecurityClient客户应用,新增了两行java代码:

LoginContext ctx = null;

ctx = new LoginContext("ScreenContent", new UsernamePasswordCallbackHandler());

//marissa用户登录到当前应用中

ctx.login();

log.info("当前用户已经通过用户认证");

Subject subject = ctx.getSubject();

log.info(subject);

new File("D:/eclipse/workspace/contactsforchapter8/src/loginmodule.conf").exists();

System.getProperty("java.io.tmpdir");

// 退出当前已登录marissa用户

ctx.logout();

此时开发者必须往jaassecuritypolicy.txt策略文件中添加如下权限到其中:

permission java.io.FilePermission "D:/eclipse/workspace/contactsforchapter8/src/loginmodule.conf", "read";

permission java.util.PropertyPermission "java.io.tmpdir", "READ";

如果客户要求只具有marissa用户才有权利运行上述两行代码,那么应该这样:

LoginContext ctx = null;

ctx = new LoginContext("ScreenContent", new UsernamePasswordCallbackHandler());

//marissa用户登录到当前应用中

ctx.login();

log.info("当前用户已经通过用户认证");

Subject subject = ctx.getSubject();

log.info(subject);

// log.info("启用JAAS用户授权能力");

// log.info("临时目录为," + Subject.doAsPrivileged(subject, new PrivilegedAction() {

// public Object run() {

// log.info("当前用户正在经过JAAS授权操作的考验,并正调用目标业务操作");

// new File("D:/eclipse/workspace/contactsforchapter8/src/loginmodule.conf").exists();

// return System.getProperty("java.io.tmpdir");

// }

// }, null));

// 退出当前已登录marissa用户

ctx.logout();

那么jaassecuritypolicy.txt策略文件应该添加如下内容:

grant codebase "file:./-",

Principal sample.UsernamePasswordPrincipal "marissa" {

permission java.io.FilePermission "D:/eclipse/workspace/contactsforchapter8/src/loginmodule.conf", "read";

permission java.util.PropertyPermission "java.io.tmpdir", "READ";

};

启动jaassecurityclient客户端:

-Djava.security.manager -Djava.security.policy=src/jaassecuriypolicy.txt

-Djava.security.auth.login.config=src/loginmodule.conf

那么对于acegisecurityclient客户应用,acegisecuritypolicy.txt应该增加:

grant codebase "file:./-",

Principal org.acegisecurity.providers.UsernamePasswordAuthenticationToken "marissa" {

permission java.io.FilePermission "D:/eclipse/workspace/contactsforchapter8/src/loginmodule.conf", "read";

permission java.util.PropertyPermission "java.io.tmpdir", "read";

};

启动:

-Djava.security.manager -Djava.security.policy=src/acegisecuriypolicy.txt

-Djava.security.auth.login.config=src/loginmodule.conf

直击JaasAuthenticationProvider

配置:

<bean id="jaasAuthenticationProvider" class="org.acegisecurity.providers.jaas.JaasAuthenticationProvider">

<property name="authorityGranters"

<bean class="sample.TestAuthorityGranter"/>

</property>

<property name="callbackHandlers"

<list>

<bean class="org.acegisecurity.providers.jaas.JaasNameCallbackHandler"/>

<bean class="org.acegisecurity.providers.jaas.JaasPasswordCallbackHandler"/>

</property>

<property name="loginConfig" value="classpath:acegi.conf"/>

<property name="liginContextName" value="ACEGI"/>

</bean>

另外需要将JaasAuthenticationProvider添加到认证管理器:

acegi.conf的内容:

ACEGI {

sample.TestLoginModule required;

};

注释:authorityGranters属性能够为已经认证用户提供角色映射信息,由于这里的Jaas仅负责用户认证,而授权仍然被acegi接管。TestAuthorityGranter实现类:

package sample;

import java.security.Principal;

import java.util.HashSet;

import java.util.Set;

import org.acegisecurity.providers.jaas.AuthorityGranter;

/**

*

* @author worldheart

*

*/

public class TestAuthorityGranter implements AuthorityGranter {

public Set grant(Principal principal) {

Set<String> rtnSet = new HashSet<String>();

if (principal.getName().equals("TEST_PRINCIPAL")) {

rtnSet.add("ROLE_USER");

rtnSet.add("ROLE_ADMIN");

}

return rtnSet;

}

}

下面是TestLoginModel类:

package sample;

import java.security.Principal;

import java.util.Map;

import javax.security.auth.Subject;

import javax.security.auth.callback.Callback;

import javax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.NameCallback;

import javax.security.auth.callback.PasswordCallback;

import javax.security.auth.login.LoginException;

import javax.security.auth.spi.LoginModule;

/**

*

* @author worldheart

*

*/

public class TestLoginModule implements LoginModule {

private String user;

private String password;

private Subject subject;

public boolean abort() throws LoginException {

return true;

}

public boolean commit() throws LoginException {

return true;

}

public void initialize(Subject subject, CallbackHandler callbackHandler,

Map sharedState, Map options) {

this.subject = subject;

try {

NameCallback nameCallback = new NameCallback("prompt");

PasswordCallback passwordCallback = new PasswordCallback("prompt",

false);

callbackHandler.handle(new Callback[] {nameCallback, passwordCallback });

user = nameCallback.getName();

password = new String(passwordCallback.getPassword());

} catch (Exception e) {

throw new RuntimeException(e);

}

}

public boolean login() throws LoginException {

if (!user.equals("marissa")) {

throw new LoginException("用户名不对");

}

if (!password.equals("koala")) {

throw new LoginException("密码不对");

}

subject.getPrincipals().add(new Principal() {

public String getName() {

return "TEST_PRINCIPAL";

}

});

subject.getPrincipals().add(new Principal() {

public String getName() {

return "NULL_PRINCIPAL";

}

});

return true;

}

public boolean logout() throws LoginException {

return true;

}

}

JAVA多种加密算法的实例(MD5/SHA-1,DSA,DES等)

第1章基础知识

1.1. 单钥密码体制

单钥密码体制是一种传统的加密算法,是指信息的发送方和接收方共同使用同一把密钥进行加解密。

通常,使用的加密算法比较简便高效,密钥简短,加解密速度快,破译极其困难。但是加密的安全性依靠密钥保管的安全性,在公开的计算机网络上安全地传送和保管密钥是一个严峻的问题,并且如果在多用户的情况下密钥的保管安全性也是一个问题。

单钥密码体制的代表是美国的DES

1.2. 消息摘要

一个消息摘要就是一个数据块的数字指纹。即对一个任意长度的一个数据块进行计算,产生一个唯一指印(对于SHA1是产生一个20字节的二进制数组)。

消息摘要有两个基本属性:

两个不同的报文难以生成相同的摘要

难以对指定的摘要生成一个报文,而由该报文反推算出该指定的摘要

代表:美国国家标准技术研究所的SHA1和麻省理工学院Ronald Rivest提出的MD5

1.3. Diffie-Hellman密钥一致协议

密钥一致协议是由公开密钥密码体制的奠基人Diffie和Hellman所提出的一种思想。

先决条件,允许两名用户在公开媒体上交换信息以生成"一致"的,可以共享的密钥

代表:指数密钥一致协议(Exponential Key Agreement Protocol)

1.4. 非对称算法与公钥体系

1976年,Dittie和Hellman为解决密钥管理问题,在他们的奠基性的工作"密码学的新方向"一文中,提出一种密钥交换协议,允许在不安全的媒体上通过通讯双方交换信息,安全地传送秘密密钥。在此新思想的基础上,很快出现了非对称密钥密码体制,即公钥密码体制。在公钥体制中,加密密钥不同于解密密钥,加密密钥公之于众,谁都可以使用;解密密钥只有解密人自己知道。它们分别称为公开密钥(Public key)和秘密密钥(Private key)。

迄今为止的所有公钥密码体系中,RSA系统是最著名、最多使用的一种。RSA公开密钥密码系统是由R.Rivest、A.Shamir和L.Adleman俊教授于1977年提出的。RSA的取名就是来自于这三位发明者的姓的第一个字母

1.5. 数字签名

所谓数字签名就是信息发送者用其私钥对从所传报文中提取出的特征数据(或称数字指纹)进行RSA算法操作,以保证发信人无法抵赖曾发过该信息(即不可抵赖性),同时也确保信息报文在经签名后末被篡改(即完整性)。当信息接收者收到报文后,就可以用发送者的公钥对数字签名进行验证。 

在数字签名中有重要作用的数字指纹是通过一类特殊的散列函数(HASH函数)生成的,对这些HASH函数的特殊要求是:

接受的输入报文数据没有长度限制;

对任何输入报文数据生成固定长度的摘要(数字指纹)输出

从报文能方便地算出摘要;

难以对指定的摘要生成一个报文,而由该报文反推算出该指定的摘要;

两个不同的报文难以生成相同的摘要

代表:DSA

第2章在JAVA中的实现

2.1. 相关

Diffie-Hellman密钥一致协议和DES程序需要JCE工具库的支持,可以到 http://java.sun.com/security/index.html 下载JCE,并进行安装。简易安装把 jce1.2.1lib 下的所有内容复制到 %java_home%libext下,如果没有ext目录自行建立,再把jce1_2_1.jar和sunjce_provider.jar添加到CLASSPATH内,更详细说明请看相应用户手册

2.2. 消息摘要MD5和SHA的使用

使用方法:

首先用生成一个MessageDigest类,确定计算方法

java.security.MessageDigest alga=java.security.MessageDigest.getInstance("SHA-1");

添加要进行计算摘要的信息

alga.update(myinfo.getBytes());

计算出摘要

byte[] digesta=alga.digest();

发送给其他人你的信息和摘要

其他人用相同的方法初始化,添加信息,最后进行比较摘要是否相同

algb.isEqual(digesta,algb.digest())

相关AIP

java.security.MessageDigest 类

static getInstance(String algorithm)

返回一个MessageDigest对象,它实现指定的算法

参数:算法名,如 SHA-1 或MD5

void update (byte input)

void update (byte[] input)

void update(byte[] input, int offset, int len)

添加要进行计算摘要的信息

byte[] digest()

完成计算,返回计算得到的摘要(对于MD5是16位,SHA是20位)

void reset()

复位

static boolean isEqual(byte[] digesta, byte[] digestb)

比效两个摘要是否相同

代码:

import java.security.*;

public class myDigest {

public static void main(String[] args) {

myDigest my=new myDigest();

my.testDigest();

}

public void testDigest()

{

try {

String myinfo="我的测试信息";

//java.security.MessageDigest alg=java.security.MessageDigest.getInstance("MD5");

java.security.MessageDigest alga=java.security.MessageDigest.getInstance("SHA-1");

alga.update(myinfo.getBytes());

byte[] digesta=alga.digest();

System.out.println("本信息摘要是:"+byte2hex(digesta));

//通过某中方式传给其他人你的信息(myinfo)和摘要(digesta) 对方可以判断是否更改或传输正常

java.security.MessageDigest algb=java.security.MessageDigest.getInstance("SHA-1");

algb.update(myinfo.getBytes());

if (algb.isEqual(digesta,algb.digest())) {

System.out.println("信息检查正常");

}

else

{

System.out.println("摘要不相同");

}

}

catch (java.security.NoSuchAlgorithmException ex) {

System.out.println("非法摘要算法");

}

}

public String byte2hex(byte[] b) //二行制转字符串

{

String hs="";

String stmp="";

for (int n=0;n<b.length;n++)

{

stmp=(java.lang.Integer.toHexString(b
& 0XFF));

if (stmp.length()==1) hs=hs+"0"+stmp;

else hs=hs+stmp;

if (n<b.length-1) hs=hs+":";

}

return hs.toUpperCase();

}

}

2.3. 数字签名DSA

对于一个用户来讲首先要生成他的密钥对,并且分别保存

生成一个KeyPairGenerator实例

java.security.KeyPairGenerator keygen=java.security.KeyPairGenerator.getInstance("DSA");

如果设定随机产生器就用如相代码初始化

SecureRandom secrand=new SecureRandom();

secrand.setSeed("tttt".getBytes()); //初始化随机产生器

keygen.initialize(512,secrand); //初始化密钥生成器

否则

keygen.initialize(512);

生成密钥公钥pubkey和私钥prikey

KeyPair keys=keygen.generateKeyPair(); //生成密钥组

PublicKey pubkey=keys.getPublic();

PrivateKey prikey=keys.getPrivate();

分别保存在myprikey.dat和mypubkey.dat中,以便下次不在生成

(生成密钥对的时间比较长

java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myprikey.dat"));

out.writeObject(prikey);

out.close();

out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("mypubkey.dat"));

out.writeObject(pubkey);

out.close();

用他私人密钥(prikey)对他所确认的信息(info)进行数字签名产生一个签名数组

从文件中读入私人密钥(prikey)

java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("myprikey.dat"));

PrivateKey myprikey=(PrivateKey)in.readObject();

in.close();

初始一个Signature对象,并用私钥对信息签名

java.security.Signature signet=java.security.Signature.getInstance("DSA");

signet.initSign(myprikey);

signet.update(myinfo.getBytes());

byte[] signed=signet.sign();

把信息和签名保存在一个文件中(myinfo.dat)

java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myinfo.dat"));

out.writeObject(myinfo);

out.writeObject(signed);

out.close();

把他的公钥的信息及签名发给其它用户

其他用户用他的公共密钥(pubkey)和签名(signed)和信息(info)进行验证是否由他签名的信息

读入公钥

java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("mypubkey.dat"));

PublicKey pubkey=(PublicKey)in.readObject();

in.close();

读入签名和信息

in=new java.io.ObjectInputStream(new java.io.FileInputStream("myinfo.dat"));

String info=(String)in.readObject();

byte[] signed=(byte[])in.readObject();

in.close();

初始一个Signature对象,并用公钥和签名进行验证

java.security.Signature signetcheck=java.security.Signature.getInstance("DSA");

signetcheck.initVerify(pubkey);

signetcheck.update(info.getBytes());

if (signetcheck.verify(signed)) { System.out.println("签名正常");}

对于密钥的保存本文是用对象流的方式保存和传送的,也可可以用编码的方式保存.注意要

import java.security.spec.*

import java.security.*

具休说明如下

public key是用X.509编码的,例码如下: byte[] bobEncodedPubKey=mypublic.getEncoded(); //生成编码

//传送二进制编码

//以下代码转换编码为相应key对象

X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);

KeyFactory keyFactory = KeyFactory.getInstance("DSA");

PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);

对于Private key是用PKCS#8编码,例码如下: byte[] bPKCS=myprikey.getEncoded();

//传送二进制编码

//以下代码转换编码为相应key对象

PKCS8EncodedKeySpec priPKCS8=new PKCS8EncodedKeySpec(bPKCS);

KeyFactory keyf=KeyFactory.getInstance("DSA");

PrivateKey otherprikey=keyf.generatePrivate(priPKCS8);

常用API

java.security.KeyPairGenerator 密钥生成器类

public static KeyPairGenerator getInstance(String algorithm) throws NoSuchAlgorithmException

以指定的算法返回一个KeyPairGenerator 对象

参数: algorithm 算法名.如:"DSA","RSA"

public void initialize(int keysize)

以指定的长度初始化KeyPairGenerator对象,如果没有初始化系统以1024长度默认设置

参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数

public void initialize(int keysize, SecureRandom random)

以指定的长度初始化和随机发生器初始化KeyPairGenerator对象

参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数

random 一个随机位的来源(对于initialize(int keysize)使用了默认随机器

public abstract KeyPair generateKeyPair()

产生新密钥对

java.security.KeyPair 密钥对类

public PrivateKey getPrivate()

返回私钥

public PublicKey getPublic()

返回公钥

java.security.Signature 签名类

public static Signature getInstance(String algorithm) throws NoSuchAlgorithmException

返回一个指定算法的Signature对象

参数 algorithm 如:"DSA"

public final void initSign(PrivateKey privateKey)

throws InvalidKeyException

用指定的私钥初始化

参数:privateKey 所进行签名时用的私钥

public final void update(byte data)

throws SignatureException

public final void update(byte[] data)

throws SignatureException

public final void update(byte[] data, int off, int len)

throws SignatureException

添加要签名的信息

public final byte[] sign()

throws SignatureException

返回签名的数组,前提是initSign和update

public final void initVerify(PublicKey publicKey)

throws InvalidKeyException

用指定的公钥初始化

参数:publicKey 验证时用的公钥

public final boolean verify(byte[] signature)

throws SignatureException

验证签名是否有效,前提是已经initVerify初始化

参数: signature 签名数组

*/

import java.security.*;

import java.security.spec.*;

public class testdsa {

public static void main(String[] args) throws java.security.NoSuchAlgorithmException,java.lang.Exception {

testdsa my=new testdsa();

my.run();

}

public void run()

{

//数字签名生成密钥

//第一步生成密钥对,如果已经生成过,本过程就可以跳过,对用户来讲myprikey.dat要保存在本地

//而mypubkey.dat给发布给其它用户

if ((new java.io.File("myprikey.dat")).exists()==false) {

if (generatekey()==false) {

System.out.println("生成密钥对败");

return;

};

}

//第二步,此用户

//从文件中读入私钥,对一个字符串进行签名后保存在一个文件(myinfo.dat)中

//并且再把myinfo.dat发送出去

//为了方便数字签名也放进了myifno.dat文件中,当然也可分别发送

try {

java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("myprikey.dat"));

PrivateKey myprikey=(PrivateKey)in.readObject();

in.close();

// java.security.spec.X509EncodedKeySpec pubX509=new java.security.spec.X509EncodedKeySpec(bX509);

//java.security.spec.X509EncodedKeySpec pubkeyEncode=java.security.spec.X509EncodedKeySpec

String myinfo="这是我的信息"; //要签名的信息

//用私钥对信息生成数字签名

java.security.Signature signet=java.security.Signature.getInstance("DSA");

signet.initSign(myprikey);

signet.update(myinfo.getBytes());

byte[] signed=signet.sign(); //对信息的数字签名

System.out.println("signed(签名内容)="+byte2hex(signed));

//把信息和数字签名保存在一个文件中

java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myinfo.dat"));

out.writeObject(myinfo);

out.writeObject(signed);

out.close();

System.out.println("签名并生成文件成功");

}

catch (java.lang.Exception e) {

e.printStackTrace();

System.out.println("签名并生成文件失败");

};

//第三步

//其他人通过公共方式得到此户的公钥和文件

//其他人用此户的公钥,对文件进行检查,如果成功说明是此用户发布的信息.

//

try {

java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("mypubkey.dat"));

PublicKey pubkey=(PublicKey)in.readObject();

in.close();

System.out.println(pubkey.getFormat());

in=new java.io.ObjectInputStream(new java.io.FileInputStream("myinfo.dat"));

String info=(String)in.readObject();

byte[] signed=(byte[])in.readObject();

in.close();

java.security.Signature signetcheck=java.security.Signature.getInstance("DSA");

signetcheck.initVerify(pubkey);

signetcheck.update(info.getBytes());

if (signetcheck.verify(signed)) {

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

System.out.println("签名正常");

}

else System.out.println("非签名正常");

}

catch (java.lang.Exception e) {e.printStackTrace();};

}

//生成一对文件myprikey.dat和mypubkey.dat---私钥和公钥,

//公钥要用户发送(文件,网络等方法)给其它用户,私钥保存在本地

public boolean generatekey()

{

try {

java.security.KeyPairGenerator keygen=java.security.KeyPairGenerator.getInstance("DSA");

// SecureRandom secrand=new SecureRandom();

// secrand.setSeed("tttt".getBytes()); //初始化随机产生器

// keygen.initialize(576,secrand); //初始化密钥生成器

keygen.initialize(512);

KeyPair keys=keygen.genKeyPair();

// KeyPair keys=keygen.generateKeyPair(); //生成密钥组

PublicKey pubkey=keys.getPublic();

PrivateKey prikey=keys.getPrivate();

java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myprikey.dat"));

out.writeObject(prikey);

out.close();

System.out.println("写入对象 prikeys ok");

out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("mypubkey.dat"));

out.writeObject(pubkey);

out.close();

System.out.println("写入对象 pubkeys ok");

System.out.println("生成密钥对成功");

return true;

}

catch (java.lang.Exception e) {

e.printStackTrace();

System.out.println("生成密钥对失败");

return false;

};

}

public String byte2hex(byte[] b)

{

String hs="";

String stmp="";

for (int n=0;n<b.length;n++)

{

stmp=(java.lang.Integer.toHexString(b
& 0XFF));

if (stmp.length()==1) hs=hs+"0"+stmp;

else hs=hs+stmp;

if (n<b.length-1) hs=hs+":";

}

return hs.toUpperCase();

}

}

2.4. DESede/DES对称算法

首先生成密钥,并保存(这里并没的保存的代码,可参考DSA中的方法)

KeyGenerator keygen = KeyGenerator.getInstance(Algorithm);

SecretKey deskey = keygen.generateKey();

用密钥加密明文(myinfo),生成密文(cipherByte)

Cipher c1 = Cipher.getInstance(Algorithm);

c1.init(Cipher.ENCRYPT_MODE,deskey);

byte[] cipherByte=c1.doFinal(myinfo.getBytes());

传送密文和密钥,本文没有相应代码可参考DSA

.............

用密钥解密密文

c1 = Cipher.getInstance(Algorithm);

c1.init(Cipher.DECRYPT_MODE,deskey);

byte[] clearByte=c1.doFinal(cipherByte);

相对来说对称密钥的使用是很简单的,对于JCE来讲支技DES,DESede,Blowfish三种加密术

对于密钥的保存各传送可使用对象流或者用二进制编码,相关参考代码如下

SecretKey deskey = keygen.generateKey();

byte[] desEncode=deskey.getEncoded();

javax.crypto.spec.SecretKeySpec destmp=new javax.crypto.spec.SecretKeySpec(desEncode,Algorithm);

SecretKey mydeskey=destmp;

相关API

KeyGenerator 在DSA中已经说明,在添加JCE后在instance进可以如下参数

DES,DESede,Blowfish,HmacMD5,HmacSHA1

javax.crypto.Cipher 加/解密器

public static final Cipher getInstance(java.lang.String transformation)

throws java.security.NoSuchAlgorithmException,

NoSuchPaddingException

返回一个指定方法的Cipher对象

参数:transformation 方法名(可用 DES,DESede,Blowfish)

public final void init(int opmode, java.security.Key key)

throws java.security.InvalidKeyException

用指定的密钥和模式初始化Cipher对象

参数:opmode 方式(ENCRYPT_MODE, DECRYPT_MODE, WRAP_MODE,UNWRAP_MODE)

key 密钥

public final byte[] doFinal(byte[] input)

throws java.lang.IllegalStateException,

IllegalBlockSizeException,

BadPaddingException

对input内的串,进行编码处理,返回处理后二进制串,是返回解密文还是加解文由init时的opmode决定

注意:本方法的执行前如果有update,是对updat和本次input全部处理,否则是本inout的内容

/*

安全程序 DESede/DES测试

*/

import java.security.*;

import javax.crypto.*;

public class testdes {

public static void main(String[] args){

testdes my=new testdes();

my.run();

}

public void run() {

//添加新安全算法,如果用JCE就要把它添加进去

Security.addProvider(new com.sun.crypto.provider.SunJCE());

String Algorithm="DES"; //定义 加密算法,可用 DES,DESede,Blowfish

String myinfo="要加密的信息";

try {

//生成密钥

KeyGenerator keygen = KeyGenerator.getInstance(Algorithm);

SecretKey deskey = keygen.generateKey();

//加密

System.out.println("加密前的二进串:"+byte2hex(myinfo.getBytes()));

System.out.println("加密前的信息:"+myinfo);

Cipher c1 = Cipher.getInstance(Algorithm);

c1.init(Cipher.ENCRYPT_MODE,deskey);

byte[] cipherByte=c1.doFinal(myinfo.getBytes());

System.out.println("加密后的二进串:"+byte2hex(cipherByte));

//解密

c1 = Cipher.getInstance(Algorithm);

c1.init(Cipher.DECRYPT_MODE,deskey);

byte[] clearByte=c1.doFinal(cipherByte);

System.out.println("解密后的二进串:"+byte2hex(clearByte));

System.out.println("解密后的信息:"+(new String(clearByte)));

}

catch (java.security.NoSuchAlgorithmException e1) {e1.printStackTrace();}

catch (javax.crypto.NoSuchPaddingException e2) {e2.printStackTrace();}

catch (java.lang.Exception e3) {e3.printStackTrace();}

}

public String byte2hex(byte[] b) //二行制转字符串

{

String hs="";

String stmp="";

for (int n=0;n<b.length;n++)

{

stmp=(java.lang.Integer.toHexString(b
& 0XFF));

if (stmp.length()==1) hs=hs+"0"+stmp;

else hs=hs+stmp;

if (n<b.length-1) hs=hs+":";

}

return hs.toUpperCase();

}

}

2.5. Diffie-Hellman密钥一致协议

公开密钥密码体制的奠基人Diffie和Hellman所提出的 "指数密钥一致协议"(Exponential Key Agreement Protocol),该协议不要求别的安全性先决条件,允许两名用户在公开媒体上交换信息以生成"一致"的,可以共享的密钥。在JCE的中实现用户alice生成DH类型的密钥对,如果长度用1024生成的时间请,推荐第一次生成后保存DHParameterSpec,以便下次使用直接初始化.使其速度加快

System.out.println("ALICE: 产生 DH 对 ...");

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

aliceKpairGen.initialize(512);

KeyPair aliceKpair = aliceKpairGen.generateKeyPair();

alice生成公钥发送组bob

byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();

bob从alice发送来的公钥中读出DH密钥对的初始参数生成bob的DH密钥对

注意这一步一定要做,要保证每个用户用相同的初始参数生成的

DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();

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

bobKpairGen.initialize(dhParamSpec);

KeyPair bobKpair = bobKpairGen.generateKeyPair();

bob根据alice的公钥生成本地的DES密钥

KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");

bobKeyAgree.init(bobKpair.getPrivate());

bobKeyAgree.doPhase(alicePubKey, true);

SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");

bob已经生成了他的DES密钥,他现把他的公钥发给alice,

byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();

alice根据bob的公钥生成本地的DES密钥

,,,,,,解码

KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");

aliceKeyAgree.init(aliceKpair.getPrivate());

aliceKeyAgree.doPhase(bobPubKey, true);

SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");

bob和alice能过这个过程就生成了相同的DES密钥,在这种基础就可进行安全能信

常用API

java.security.KeyPairGenerator 密钥生成器类

public static KeyPairGenerator getInstance(String algorithm)

throws NoSuchAlgorithmException

以指定的算法返回一个KeyPairGenerator 对象

参数: algorithm 算法名.如:原来是DSA,现在添加了 DiffieHellman(DH)

public void initialize(int keysize)

以指定的长度初始化KeyPairGenerator对象,如果没有初始化系统以1024长度默认设置

参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数

注意:如果用1024生长的时间很长,最好生成一次后就保存,下次就不用生成了

public void initialize(AlgorithmParameterSpec params)

throws InvalidAlgorithmParameterException

以指定参数初始化

javax.crypto.interfaces.DHPublicKey

public DHParameterSpec getParams()

返回

java.security.KeyFactory

public static KeyFactory getInstance(String algorithm)

throws NoSuchAlgorithmException

以指定的算法返回一个KeyFactory

参数: algorithm 算法名:DSH,DH

public final PublicKey generatePublic(KeySpec keySpec)

throws InvalidKeySpecException

根据指定的key说明,返回一个PublicKey对象

java.security.spec.X509EncodedKeySpec

public X509EncodedKeySpec(byte[] encodedKey)

根据指定的二进制编码的字串生成一个key的说明

参数:encodedKey 二进制编码的字串(一般能过PublicKey.getEncoded()生成)

javax.crypto.KeyAgreement 密码一至类

public static final KeyAgreement getInstance(java.lang.String algorithm)

throws java.security.NoSuchAlgorithmException

返回一个指定算法的KeyAgreement对象

参数:algorithm 算法名,现在只能是DiffieHellman(DH)

public final void init(java.security.Key key)

throws java.security.InvalidKeyException

用指定的私钥初始化

参数:key 一个私钥

public final java.security.Key doPhase(java.security.Key key,

boolean lastPhase)

throws java.security.InvalidKeyException,

java.lang.IllegalStateException

用指定的公钥进行定位,lastPhase确定这是否是最后一个公钥,对于两个用户的

情况下就可以多次定次,最后确定

参数:key 公钥

lastPhase 是否最后公钥

public final SecretKey generateSecret(java.lang.String algorithm)

throws java.lang.IllegalStateException,

java.security.NoSuchAlgorithmException,

java.security.InvalidKeyException

根据指定的算法生成密钥

参数:algorithm 加密算法(可用 DES,DESede,Blowfish)

*/

import java.io.*;

import java.math.BigInteger;

import java.security.*;

import java.security.spec.*;

import java.security.interfaces.*;

import javax.crypto.*;

import javax.crypto.spec.*;

import javax.crypto.interfaces.*;

import com.sun.crypto.provider.SunJCE;

public class testDHKey {

public static void main(String argv[]) {

try {

testDHKey my= new testDHKey();

my.run();

} catch (Exception e) {

System.err.println(e);

}

}

private void run() throws Exception {

Security.addProvider(new com.sun.crypto.provider.SunJCE());

System.out.println("ALICE: 产生 DH 对 ...");

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

aliceKpairGen.initialize(512);

KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); //生成时间长

// 张三(Alice)生成公共密钥 alicePubKeyEnc 并发送给李四(Bob) ,

//比如用文件方式,socket.....

byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();

//bob接收到alice的编码后的公钥,将其解码

KeyFactory bobKeyFac = KeyFactory.getInstance("DH");

X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec (alicePubKeyEnc);

PublicKey alicePubKey = bobKeyFac.generatePublic(x509KeySpec);

System.out.println("alice公钥bob解码成功");

// bob必须用相同的参数初始化的他的DH KEY对,所以要从Alice发给他的公开密钥,

//中读出参数,再用这个参数初始化他的 DH key对

//从alicePubKye中取alice初始化时用的参数

DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();

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

bobKpairGen.initialize(dhParamSpec);

KeyPair bobKpair = bobKpairGen.generateKeyPair();

System.out.println("BOB: 生成 DH key 对成功");

KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");

bobKeyAgree.init(bobKpair.getPrivate());

System.out.println("BOB: 初始化本地key成功");

//李四(bob) 生成本地的密钥 bobDesKey

bobKeyAgree.doPhase(alicePubKey, true);

SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");

System.out.println("BOB: 用alice的公钥定位本地key,生成本地DES密钥成功");

// Bob生成公共密钥 bobPubKeyEnc 并发送给Alice,

//比如用文件方式,socket.....,使其生成本地密钥

byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();

System.out.println("BOB向ALICE发送公钥");

// alice接收到 bobPubKeyEnc后生成bobPubKey

// 再进行定位,使aliceKeyAgree定位在bobPubKey

KeyFactory aliceKeyFac = KeyFactory.getInstance("DH");

x509KeySpec = new X509EncodedKeySpec(bobPubKeyEnc);

PublicKey bobPubKey = aliceKeyFac.generatePublic(x509KeySpec);

System.out.println("ALICE接收BOB公钥并解码成功");

;

KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");

aliceKeyAgree.init(aliceKpair.getPrivate());

System.out.println("ALICE: 初始化本地key成功");

aliceKeyAgree.doPhase(bobPubKey, true);

// 张三(alice) 生成本地的密钥 aliceDesKey

SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");

System.out.println("ALICE: 用bob的公钥定位本地key,并生成本地DES密钥");

if (aliceDesKey.equals(bobDesKey)) System.out.println("张三和李四的密钥相同");

//现在张三和李四的本地的deskey是相同的所以,完全可以进行发送加密,接收后解密,达到

//安全通道的的目的

/*

* bob用bobDesKey密钥加密信息

*/

Cipher bobCipher = Cipher.getInstance("DES");

bobCipher.init(Cipher.ENCRYPT_MODE, bobDesKey);

String bobinfo= "这是李四的机密信息";

System.out.println("李四加密前原文:"+bobinfo);

byte[] cleartext =bobinfo.getBytes();

byte[] ciphertext = bobCipher.doFinal(cleartext);

/*

* alice用aliceDesKey密钥解密

*/

Cipher aliceCipher = Cipher.getInstance("DES");

aliceCipher.init(Cipher.DECRYPT_MODE, aliceDesKey);

byte[] recovered = aliceCipher.doFinal(ciphertext);

System.out.println("alice解密bob的信息:"+(new String(recovered)));

if (!java.util.Arrays.equals(cleartext, recovered))

throw new Exception("解密后与原文信息不同");

System.out.println("解密后相同");

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: