微型机与应用
MICROCOMPUTER & ITS APPLICATIONS
2000　Vol.19　No.4　P.29-30



Ｊａｖａ２的安全体系及其应用
金胜昔　步俊杰　吉逸
摘 要： 分析了Ｊａｖａ安全体系的演化过程，介绍了Ｊａｖａ２的安全体系的框架及其新特性，指出了Ｊａｖａ２安全体系的概念及其运行机制，并详细地讨论了关于Ｊａｖａ２安全性的一些应用。
关键词： Ｊａｖａ２ 安全体系 保护域
　　自从Java技术开始应用以来，人们对Java平台的安全性以及由于部署Java技术所引发的安全问题给予了极大的关注。特别是在1998年11月Java2发布后，Java的安全体系结构发生了根本的改进，对于终端用户而言，它可以保护文件和私人数据不被恶意的程序或病毒感染和破坏，鉴别代码提供者的身份。对于开发者而言，通过使用API方法，能够将安全性功能集成到应用程序中，因为API的体系结构能够定义和集成对特定的资源的使用权限、加密、安全性管理、策略管理，并提供了一些类来管理公钥／密钥对及信任用户群的公钥证书。同时系统管理员、开发者和用户可以使用它提供的工具管理钥匙库，在JAR文件中生成数字签名、签名的完整性检测、创建和修改策略文件。按照Java设计者的观点，Java安全包括2个方面的内容，首先将Java作为一种安全的平台提供给用户，在此平台上，可安全地运行Java程序；其次提供用Java编程语言实现的安全工具和服务，它使得诸如企业界这样一些对安全非常敏感的领域也可应用Java技术。本文将就这二个方面介绍Java2的安全性新特性及其应用。
1　Java安全体系结构的演化
　　Java从发布以来就通过一系列机制来实现其安全性。首先，Java语言被设计为类型安全的且便于使用的，它通过自动内存管理、内存垃圾回收及字符串和数组的越界检查等语言特性来减轻程序员的编程负担和出现错误的可能性。其次通过编译器和字节码校验器保证Java字节码的合法性，以及类装载器保证命名空间的独立性。最后，对重要系统资源的访问通过Java虚拟机来传递，并由SecurityManager类进行预先检查，这种最初的安全模型称为沙箱模型。沙箱模式提供了受限的环境，运行从网络下载的不可信任的代码，本地代码是可以信任的，对系统资源有完全的访问权限，从网络下载的代码是不可信任的，只有访问沙箱中提供的有限资源。1．0的安全性过于限制，例如程序员希望在执行过程中能在客户机上存储一些持久的信息数据，那么1．0的安全体系会令其失望。
　　从JDK1．1开始提出了签名的Applet的概念。如图1所示，JDK1．1的安全性是基于可以信任的数字签名，一个数字签名的JAR文件中包括身份认证证书和确认，证书说明是谁对程序进行签名，确认说明在程序进行签名后没有被他人改动。用于检验签名的公钥是可信任的，一个有着正确数字签字的Applet，如果它的签字被接收该Applet的最终系统确认为是可信的，则这个Applet被认为是可信代码，具有本地代码完全访问的资源能力，未签名的Applet仍然运行在沙箱中。签名的Applet将分别和它们各自的签名一起存放在1个签名的JAR文件中，并在网络中传输。在JDK1．1中已经包含了数字签名算法，信息摘要算法、密钥生成算法的实现。

图1 JDK1．1安全模式
　　JDK1．2对1．1作了一些改进，它的安全模式可以允许网络管理员对于签名的代码选择性地释放原始沙箱的某些限制，网络管理员可以提供创建自定义的沙箱来实现更加灵活的安全策略。被置于自定义沙箱中的类在装载时仍然基于数字签名。在新的安全模式中无论是本地的或远程的代码，都能够被赋予安全性策略，在安全性策略中定义了1组权限，用户和系统管理员能够配置和管理这组权限。对于签名的代码，每个权限定义了可以访问的资源，如：读写特定的文件或目录、连接访问器和端口等。运行时系统将代码按照域进行管理，每个域都包含有1组类，它们的实例被赋予了相同的1组权限。1个域可以像原始的沙箱模式一样进行管理，所以Applet仍然能够运行在用户和管理员希望的受限环境中，缺省情况下，Application仍然像原来一样可以不受限的运行，同时也可以通过对其赋予一定的安全性策略后进行优化。总之，安全性在一开始就被考虑到在Java虚拟机和核心库的体系结构中，最初的安全体系结构是不灵活的而且难以定义，Java2的安全性框架的目标是达到更强的灵活性和易于定制。
2　Java2的安全性新特性
　　Java2采用了如图2所示的新的安全体系结构，并基于这种安全体系结构提供了很多新特性。

图2 JDK1．2安全模式
2．1　密纹访问控制
　　这种能力从一开始就在JDK中存在。但要使用它，应用程序的编写者不得不做大量的编程工作例如，创建SecurityManager和Classloader类的子类并使其用户化。HotJava1．0就是一个这样的应用程序，它允许浏览器用户在几个不同的安全等级上进行选择。然而，这种编程涉及非常敏感的安全问题，它要求程序员对计算机安全有精深的理解和纯熟的技巧。新的安全体系结构将使这些变得简单而安全。
2．2　易于配置的安全策略
　　与上述情况相似，这种能力在原来的JDK中也是存在的，但是不便于使用，而且编写安全代码也不是简单明了的事情。于是，人们期望能够允许应用程序的编写者和用户能够不通过编程来设置安全策略。
2．3　便于扩展的访问控制结构
　　一直到JDK1．1为止，为了创建1个新的访问许可，你必须在SecurityManager类中增加1个新的check方法。新的安全体系结构则允许设置各类访问许可（每个都表示对1个系统资源的访问），并能对所有正确访问许可（包括未定义的许可）进行自动处理。
2．4　安全检查扩展至所有Java程序
　　那种所有本地代码是可信的内置概念将不复存在，取而代之的将是本地代码（例如非系统代码，安装在本地的应用程序包等）服从于与Applet相同的安全控制，但是可以声明对本地代码的政策是最宽容的，从而使这些代码可被认为是完全可信而有效地运行。上述原则也可应用于已签字的Applet和任何Java应用程序。
3　Java2安全体系的概念及运行机制
3．1　保护域
　　Java2安全体系结构中的一个基本的概念是保护域（Protected Domain）。1个域可通过对象集来划分范围，这些对象当前可由1个主体直接访问。而主体是在计算机系统中被授予许可的实体。JDK1．0所利用的沙箱就是一个有着固定边界的保护域实例。保护域的概念是一种在保护单元间起着分组和隔离作用的便利机制。例如，我们可以将保护域分开以避免它们之间的直接交互作用，于是，任何允许的交互作用必须通过可信系统代码或被有关的域所明确允许。
　　保护域通常分为明确的2个类别，系统域和应用程序域。所有被保护的外部资源如：文件系统、网络设施以及屏幕和键盘等仅能通过系统域来访问。图3中显示了1个Java应用环境的域的组成。从概念上讲，1个域包括1组类，这些类的实例被授予相同的一组许可。保护域是由现行策略所确定的。Java应用程序环境保持了来自代码（类和实例）到它们的保护域然后再到它们的许可的映射，如图4所示。1个线程的执行可能完全发生在1个单一的保护域中，也可能涉及1个应用程序域或是系统域。例如：1个打印消息的应用程序将不得不与系统域发生交互作用，因为系统域是唯一对输出流的访问点。在此种情况下的任何时候，应用程序域都不能通过调用系统域获得除打印消息外的任何额外许可，否则将是一个严重的安全性隐患。在相反的情形下，1个系统域从1个应用程序域中调用1个方法，如当1个AWT系统域调用1个Applet的绘画方法来显示这个Applet时，有效访问权限与应用程序域所允许的当前权限在任何时候都相同，这一点也是同样至关重要的。换句话说，一个具有较低权限的域不能通过调用一个更高权限的域，或被一个更高权限的域所调用来获得额外的许可。上述有关1个线程涉及2个保护域的讨论自然地归纳为1个遍历多重保护域的线程，计算许可的一个简单而谨慎的经验做法是：

图3 Java应用环境的域的组成

图4 类→保护域→权限的映射
　　（1）一个执行线程的许可集可被认为是由该线程所遍历的所有保护域的许可的交集。
　　（2）当1条代码调用doPrivileged方法时，执行线程的许可集被认为是包括所有代码的保护域以及由它直接或间接调用的保护域的权限。即通过doPrivileged方法可使1条可信代码能临时访问更多的资源，这在某些情况下是必要的。例如，1个应用程序可能不被允许直接访问包含字体的文件，但是，显示文本的系统实用程序必须代表用户获得那些字体。
　　在执行期间，当请求访问1个关键系统资源（如文件I／O和网络I／O）或者API方法需要执行1个敏感的操作时，例如读1个文件，资源处理代码直接或间接地调用1个特殊的称为访问控制（AccessController）类的方法，访问控制类通过检查调用栈来作出决定是否准予该请求或操作发生。在调用栈中是执行该操作需要调用的一些类的成员方法，因为每个类都属于一些保护域，每个保护域都建立了一些策略，因此在调用栈的每个方法都被分配了1组权限。访问控制类由栈顶开始，自顶向下检查每个方法，看是否方法被所在的保护域所允许，如果发现一个方法权限没有得到允许，访问控制类就抛出安全性异常；反之，如果到达栈底仍未抛出异常，即说明调用栈中的所有方法均满足保护域的权限要求，访问控制允许操作发生。其中有一种特殊的情况，即当访问控制遍历调用栈时，将查找是否存在优先域（Privileged Domain），如果存在优先域，即使没有到达栈底，访问控制也将停止遍历调用栈并允许操作发生。虽然新的安全机制初看上去增加了许多调用API方法的消耗，但是Java2确实使用了一些技术去加速检查权限的过程，例如访问控制将过滤掉重复的域并在遇到第一个优先域时停止检查，这说明额外的操作将是一个本地的方法调用，SUN的基准测试显示了新的安全检查是相当快的。
　　最后，每个域包括系统或应用程序域可以对其域边界内的内部资源进行附加保护。例如，一个银行系统的应用程序可能需要支持并保护其内部的一些概念，如查帐、存款和取款等。由于此种保护的语义不像那些可预测的语义可以被JDK预置，因而，在这个层次上的保护最好留给系统或应用程序开发员来做。
　　目前，1个域单独地由1个代码来源（CodeSource）鉴别，它封装了在该域中运行的代码的2个特性：代码基址和公共密钥证书集，公共密钥对应于在该域中为所有代码签字的私有密钥。因而，由相同的密钥签字和来自相同URL的类被放在同一个域中。1个域还包含在该域中授予代码的许可，它是由现行安全策略所决定的。
3．2　证书、钥匙库及其相关工具
　　在Java2的安全体系下，1个Applet开发和运行的过程如下：
　　在代码的分发端：
　　（1）开发Java源程序并对其进行编译。
　　（2）用JAR工具对类文件和资源文件进行封装。
　　（3）用keytool创建公钥和密钥，生成X。509V1签名证书，输出证书。
　　（4）通过jarsigner工具用生成的密钥对JAR文件进行数字签名。
　　在代码的接收端：
　　（1）用keytool输入证书视其为可信任。
　　（2）用policytool创建和修改安全性策略配置文件，授权请求的访问权限。
　　（3）从网络取得字节码，用公钥验证数字签名证书和文档代码的完整性。
　　（4）验证字节码的合法性，根据策略文件分配相应权限。
　　（5）执行代码，完成后被垃圾回收器回收内存。
　　在用公钥验证数字签名证书之前，接收方需要确认公钥自身的可靠性，因此通常情况是提供一个包含公钥的证书而不是公钥自身。1个证书包括：
　　（1）1个公钥。
　　（2）1个唯一的名字实体（个人或公司），它是证书的所有者，包含用户名字、公司、组织、城市、地址、国家代码、省份等信息。
　　（3）数字签名：1个证书被1个分发者的实体签名，保证证书确实包含另1个实体（所有者）的公钥。
　　（4）分发者的标识名信息。
　　对于接收者可以用分发者的公钥来验证他的数字签名，检查证书的合法性。然而公钥可能包含在另一个证书中，而数字签名需要用另一个证书的分发者的公钥来验证，这样嵌套下去，直到一个公钥被接收者确认是可信任的。如果接收者不能建立信任链，例如：1个分发者的证书不合法，那么可以用keytool－import命令来计算指纹，每个指纹是一个相关的短数字，它唯一可靠地标识证书（指纹是一个用信息摘要算法计算的证书信息的哈希值），接收者可以呼叫证书的所有者，并比较发出的证书和接收证书的指纹，如果指纹相同，则证书不同。因此能够保证证书在传递的过程中未被修改。另一个潜在的问题是发送者身份的标识，有时一个证书是自签名的，即使用证书中的公钥相对应的密钥进行签名，如果接收者已经知道或信任发送者，那么就没有任何问题。否则发送者需要从一个可信任的第3方得到证书，这个第3方通常是一个证书的授权机构CA，那么首先发送一个自签名的证书签名请求CSR给CA，由CA验证CSR的签名及发送CSR的身份、许可证以及其它信息。然后CA通过一个用CA的密钥进行签名的证书，授权CSR的发送者作为公钥的所有者，任何人只要信任CA的公钥，都可以用之来验证证书的签名，很多情况下CA自身有一个来自更高一级的CA的证书，从而构成证书链。所有信任的证书实体都可以作为信任证书被引入钥匙库，每个证书中的公钥都可以用来验证用相应的密钥生成的签名。
　　发送者在发送签名的代码和文档时还相应提供包含与签名的密钥相应的公钥证书。用keytool－export命令或API函数可以从钥匙库中输出证书到文件中，然后将这个文件发送给需要的接收者，由接收者用keytool－import命令或API函数将其引入钥匙库中。如果用jarsigner工具为JAR文件生成签名，他会从钥匙库中取出证书及证书链，并和签名一起放入JAR文件。
　　密钥和相应的公钥证书存放在一个由口令保护的数据库中，称为钥匙库（keystore）。1个钥匙库包含2种类型的条目，可信任的证书条目，钥匙和证书条目，每个都包含1个密钥和与密钥相应的公钥证书，在钥匙库中的每个条目都有1个别名进行标识。1个钥匙库的所有者在钥匙库中可以有多个钥匙，可以通过不同的别名进行访问，每个别名通常是用钥匙库的所有者使用的钥匙的特定角色来命名，别名也可以标识钥匙的目的。例如：SignPersonalEmail可以被用来标识1个钥匙库的条目，它的密钥用于签名个人邮件，SignJarFiles用于标识1个条目，它的密钥用于签名JAR文件。
4　Java2安全性的应用
　　Java2提供了开放的、无垄断的标准。对于高可靠性的IT行业如金融、政府和健康医疗、WEB服务、电子商务站点是非常重要的。有了Java2的企业安全特性，IT厂商可以从那些为安全框架所作的繁重的编码及维护工作中解放出来，将应用程序的体系架构交第3方厂商解决，或者手工的在多个计算网络中集成安全性连接。新的Java2的安全模式还承诺能够降低开发分布式系统的时间及费用，例如：Java加密体系API希望成为Java技术阵营中事实的标准。开发者只要学习加密1次即可在任何地方重用。JCA／JCE1．2将安全性连接视同原来的输入流技术，也即程序员可以将安全性连接看作原先的基于I／O的同步网络技术流。
　　增强的Java安全性加密体（JCA）允许开发人员增强数据服务去保护当今的分布式计算的安全性，例如传输数字证书；在SSL上的流式服务；公钥／密钥的生成、存储和返回；密钥在不同的密钥表示之间进行转换。所有这些都是Java平台的一次巨大的飞跃。直到现在，一些支持Java技术的IT仍然对原始的沙箱模式存在争论，沙箱模式将自动地将用户、Applet、应用程序和资源分成完全独立的二类，完全可信任和不可信任。Java2的企业安全模式不再将Applet和应用程序限制在沙箱中。沙箱的概念是具有创造性的，但存在局限性，SUN正在将无控制状态下的安全保护转移到建立在保护域概念上的体系完整的安全系统。在分布式应用中为保护域设置策略，这样开发人员可以容易地处理现代安全机制，如权限、加密、数字签名和证书。安全性已经在开发人员及管理员的控制之下。
　　加密API和I／O方法是一致的。但IT策划人员和独立的软件开发商仍然能够自由地增强基础的Java2安全模式，而无需破坏基于原先的安全模式的企业应用。使用平台的扩展对象体系，开发商可以使他们自己的基于Java API的加密实现更加灵活。
　　Java2新的安全模式最重要的是能够建立分布式系统的新形式。它允许应用体系安全扩展和开拓企业计算资源，从掌上机、桌面到服务，从内部到外部的后台办公资源的任何Java技术支撑的运行环境存在的地方。它是安全性策略的核心，因为将策略表达式从实现安全性策略的实际运行环境代码中分离出来，在其它的系统如UNIX或Windows中不能不修改核心安全性代码而增加资源并保证它们的安全性。使用Java2，能够从应用程序的可定义对象中取得权限的语义，所以开发人员能够很容易地增加资源和权限。如果希望构建一个从智能卡到主框架的可伸缩的平台，那么上述内容是非常重要的，因为我们并不知道它们将运行在何设备上。
　　Java2的安全模式能够快速简单地实现标准的商用组件。例如信用卡事务的SSL，提供更强壮的机制去为严格的商业—商业的事务增加安全性。
　　用Java语言编写的软件访问本地桌面资源，可以使用Java对象的持久化存储来提高应用程序的性能并同时保证对资源访问的安全性。只要把经常使用的组件存放在本地，减少下载类的时间，这样的持久化存储对象在原始的沙箱模式中很难实现，因为沙箱模式把本地存储的代码认为是完全可信任的，对本地保护的资源具有完全的访问权限。另外系统体系可以充分利用本地持久化存储的对象来保存客户的资料和产品的信息，并对他们进行离线处理，防止通过HTTP Cookies方式保存客户状态信息带来的不安全性。
5　结束语
　　Java2的安全模式向IT管理员和开发商提供了完整的机制。过去的沙箱不能使用户访问本地桌面资源，Java2的安全模式使用户能够为Java应用程序分配策略权限和访问控制，包括用户、组、程序、操作系统服务、客户机和服务器以及任何组件对象，精确定义与需要访问的本地桌面资源进行互操作的安全级别。这样能够安全、充分地利用本地资源，如DLL、文件、目录以及操作系统提供的服务。在这样的安全性保障下能够让Java语言编写的类和C＋＋编写的本地应用程序具有同样的功能。
金胜昔（南京东南大学计算机科学与工程系210096）
步俊杰（南京东南大学计算机科学与工程系210096）
吉逸（南京东南大学计算机科学与工程系210096）
收稿日期：１９９９－１２－２０
