Java加密体系结构(JCA)参考指南
Posted 阿C_C
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java加密体系结构(JCA)参考指南相关的知识,希望对你有一定的参考价值。
介绍
Java平台强调安全性,包括语言安全,密码学,公钥基础设施,认证,安全通信和访问控制。
JCA是平台的一个主要部分,包含一个“Provider”体系结构和一组用于数字签名,消息摘要(哈希),证书和证书验证,加密(对称/非对称块/流密码),密钥生成 管理和安全随机数生成等等。 这些API允许开发人员将安全性轻松集成到应用程序代码中。 这个架构是围绕以下原则设计的:
-
实现独立性
- 应用程序不需要自己实现安全性。 相反,他们可以从Java平台请求安全服务。安全服务在Provider(见下文)中实现,通过标准接口接入Java平台。 应用程序可能依靠多个独立的提供者来提供安全功能。 实现互操作性
- Provider可以跨应用程序进行互操作。 具体而言,应用程序不绑定到特定的提供者,而提供者也不绑定到特定的应用程序。 算法可扩展性
- Java平台包含许多内置的Provider,这些Provider实现了当今广泛使用的一组基本的安全服务。但是,一些应用程序可能依赖尚未实施的新兴标准或专有服务。 Java平台支持安装实现这些服务的定制提供程序。
JDK中提供的其他密码通信库使用JCA提供程序体系结构,但在别处进行了介绍。 Java安全套接字扩展(JSSE)提供对安全套接字层(SSL)和传输层安全性(TLS)实现的访问。 Java通用安全服务(JGSS)(通过Kerberos)API以及简单身份验证和安全层(SASL)也可用于在通信应用程序之间安全地交换消息。
术语注释
- 在JDK 1.4之前,JCE是一个非捆绑产品,因此,JCA和JCE被定期称为独立的,独特的组件。 但是JCE现在被捆绑在JDK中,所以区别变得不那么明显了。 由于JCE使用与JCA相同的体系结构,所以JCE应该更适合作为JCA的一部分。
- JDK中的JCA包含两个软件组件:
- 定义和支持Provider为其提供实现的加密服务的框架。 这个框架包含了诸如java.security,javax.crypto,javax.crypto.spec和javax.crypto.interfaces等软件包。
- Sun,SunRsaSign,SunJCE等实际包含了实际的加密实现的提供商。
无论何时提及特定的JCA提供者,都将由提供者的名称明确提及。
警告:JCA可以轻松地将安全功能集成到您的应用程序中。 然而,除了基本介绍讨论API所需的概念之外,本文档不包括安全/密码学理论。 本文档也没有涵盖特定算法的优缺点,也不涵盖协议设计。 密码学是一个高级课题,为了充分利用这些工具,应该参考一个可靠的,最近最新的参考文献。
你应该总是明白你在做什么,为什么:不要简单地复制随机代码,并期望它完全解决你的使用场景。 由于选择了错误的工具或算法,许多已经部署了的应用程序包含重大安全或性能问题。
设计原则
JCA是围绕这些原则设计的:
- 实现独立性和互操作性
- 算法独立性和可扩展性
实现独立性和算法独立性是互补的; 您可以使用加密服务(如数字签名和消息摘要),而无需担心实现细节,甚至是构成这些概念基础的算法。 尽管完全的算法独立性是不可能的,但JCA提供了标准化的,算法特定的API。 当实现独立性不可取时,JCA可以让开发人员指出具体的实现。
通过定义密码“engines”(服务)的类型,并定义提供这些密码引擎的功能的类来实现算法独立性。 这些类被称为引擎类,例如MessageDigest,Signature,KeyFactory,KeyPairGenerator和Cipher类。
实现独立性是使用基于“Provider”的体系结构实现的。 术语 Cryptographic Service Provider (CSP)(在本文档中与“Provider”可互换使用)是指实现一个或多个密码服务(如数字签名算法,消息摘要算法和密钥转换服务)的包或一组包。 程序可以简单地请求实现特定服务(例如DSA签名算法)的特定类型的对象(例如签名对象),并从一个安装的提供者获得实现。 如果需要的话,程序可以改为请求来自特定提供者的实现。 提供商可能会更新透明的应用程序,例如,当一个更快或更安全的版本可用的时候。
实现互操作性意味着各种实现可以相互协作,使用彼此的密钥,或者验证彼此的签名。 这就意味着,对于相同的算法,由一个提供者生成的密钥可以被另一个提供者使用,并且由一个提供者生成的签名可以被另一个提供者验证。
算法可扩展性意味着可以容易地添加适合于所支持的引擎类之一的新算法。
架构
加密服务提供者(CSP)
java.security.Provider是所有安全提供程序的基类。 每个CSP都包含这个类的一个实例,它包含了提供者的名字,并列出了它实现的所有安全服务/算法。 当需要特定算法的实例时,JCA框架会咨询提供者的数据库,如果找到合适的匹配项,则创建该实例。
Provider包含一个包(或一组包),为声明的加密算法提供具体的实现。 每个JDK安装都默认安装并配置了一个或多个提供程序。 其他提供者可以静态或动态添加(参见Provider和Security类)。 客户端可以配置其运行时环境来指定提供程序的首选顺序。 首选顺序是在没有请求特定提供者时提供者搜索请求的服务的顺序。
要使用JCA,应用程序只需要请求特定类型的对象(如MessageDigest)和特定的算法或服务(如“SHA-256”算法),并从一个已安装的提供者获取实现。 或者,程序可以请求来自特定提供者的对象。 每个提供者都有一个名字来引用它。
md = MessageDigest.getInstance("SHA-256");
md = MessageDigest.getInstance("SHA-256", "ProviderC");
下图说明了请求“SHA-256”消息摘要实现。 这些图显示了实现各种消息摘要算法(“SHA-256”,“SHA-384”和“SHA-512”)的三个不同的提供者。 供应商按照优先顺序从左至右排列(1-3)。 在第一个示例中,应用程序请求SHA-256算法实现而不指定提供者名称。 提供程序按优先顺序搜索,并返回提供该特定算法ProviderB的第一个提供程序的实现。 在第二个图中,应用程序请求来自特定提供者ProviderC的SHA-256算法实现。 这次ProviderC的实现被返回,即使具有更高优先级的提供者ProviderB也提供SHA-256实现。
图1 自动搜索Provider
图2 指定特定的Provider
JDK中的加密实现主要是出于历史原因通过几个不同的提供者(Sun,SunJSSE,SunJCE,SunRsaSign)分发的,但在较小的程度上由它们提供的功能和算法的类型来分发。 其他Java运行时环境可能不一定包含这些Sun提供程序,因此除非知道特定的提供程序可用,否则应用程序不应请求提供程序特定的实现。
JCA提供了一组API,允许用户查询安装哪些提供程序以及支持哪些服务。
此架构还使最终用户可以轻松添加其他提供商。 许多第三方提供者实现已经可用。 有关如何编写,安装和注册提供程序的更多信息,请参阅Provider类。
Provider如何实际实现
如前所述,通过定义所有应用程序用于访问服务类型的通用高级应用程序编程接口(API)来实现算法独立性。 实现独立性是通过让所有提供者实现符合定义良好的接口来实现的。 引擎类的实例因此被具有相同方法签名的实现类“支持”。 应用程序调用通过引擎类路由,并传递到底层的后台实现。 该实现处理请求并返回正确的结果。
每个引擎类中的应用程序API方法都通过实现相应服务提供程序接口(SPI)的类路由到提供程序的实现。 也就是说,对于每个引擎类,都有一个对应的抽象SPI类,它定义了每个加密服务提供者算法必须实现的方法。 每个SPI类的名称与相应的引擎类相同,接着是Spi。 例如,Signature类提供对数字签名算法的功能的访问。 实际的提供者实现是在SignatureSpi的子类中提供的。 应用程序调用引擎类的API方法,在实际的实现中又调用SPI方法。
每个SPI类都是抽象的。 为了为特定算法提供特定类型的服务,提供者必须继承相应的SPI类,并提供所有抽象方法的实现。
对于API中的每个引擎类,通过调用引擎类中的getInstance()工厂方法来请求和实例化实现实例。 工厂方法是一个返回一个类的实例的静态方法。 引擎类使用上述框架Provider选择机制来获取实际的后台实现(SPI),然后创建实际的引擎对象。 引擎类的每个实例都封装(作为专用字段)相应SPI类的实例,称为SPI对象。 API对象的所有API方法都声明为final,并且它们的实现调用封装SPI对象的相应SPI方法。
为了使这个更清楚,请查看下面的代码和插图:
import javax.crypto.*;
Cipher c = Cipher.getInstance("AES");
c.init(ENCRYPT_MODE, key);
这里的应用程序需要一个“AES”算法的javax.crypto.Cipher实例,并不关心使用哪个提供者。应用程序调用Cipher引擎类的getInstance()工厂方法,然后请求JCA框架查找支持“AES”的第一个提供程序实例。该框架会咨询每个已安装的提供者,并获取提供者类的提供者实例。 (回想一下,Provider类是可用算法的数据库。)框架搜索每个提供者,最终在CSP3中找到合适的条目。这个数据库入口指向扩展CipherSpi的实现类com.foo.AESCipher,因此适用于Cipher引擎类。创建一个com.foo.AESCipher的实例,并将其封装在一个新创建的javax.crypto.Cipher实例中,该实例返回给应用程序。当应用程序现在对Cipher实例执行init()操作时,Cipher引擎类将请求路由到com.foo.AESCipher类中相应的engineInit()支持方法中。
附录A列出了为Java环境定义的标准名称。 其他第三方提供商可以定义他们自己的这些服务的实现,甚至是额外的服务。
密钥管理
称为“KeyStore”的数据库可用于管理密钥和证书的存储库。 密钥库可用于需要用于身份验证,加密或签名的数据的应用程序。
应用程序可以通过java.security包中的KeyStore类的实现来访问密钥库。 建议的密钥库类型(格式)是“pkcs12”,它基于RSA PKCS12个人信息交换语法标准。 默认的密钥库类型是“jks”,这是一种专有格式。 其他密钥库格式也是可用的,例如作为替代专有密钥库格式的“jceks”,以及基于RSA PKCS11标准的“pkcs11”,并且支持对硬件安全模块和智能卡等加密令牌的访问。
应用程序可以使用上述相同的提供程序机制,从不同的提供程序中选择不同的密钥库实现。
请参阅密钥管理部分了解更多信息。
JCA概念
本节介绍主要的JCA API。
Engine类和算法
引擎类为特定类型的密码服务提供接口,而不依赖于特定的密码算法或提供者。 引擎需要提供:
- 密码操作(加密,数字签名,消息摘要等),
- 发生器或密码材料的转换器(密钥和算法参数),或
- 对象(密钥库或证书)封装了密码数据,可以在更高的抽象层使用。
以下引擎类是可用的:
- SecureRandom:用于生成随机或伪随机数字。
- MessageDigest:用于计算指定数据的消息摘要(散列)。
- Signature:使用密钥初始化,这些签名用于签署数据并验证数字签
- Cipher:用密钥初始化,用于加密/解密数据。存在各种类型的算法:对称批量加密(例如AES),非对称加密(例如RSA)和基于密码的加密(例如PBE)。
- Message Authentication Codes(MAC):与MessageDigests一样,它们也会生成散列值,但是首先使用密钥初始化以保护消息的完整性。
KeyFactory:用于将Key类型的现有不透明密钥转换为密钥规范(底层密钥材料的透明表示),反之亦然。
SecretKeyFactory:用于将SecretKey类型的现有不透明加密密钥转换为密钥规范(底层密钥材料的透明表示),反之亦然。 SecretKeyFactorys是专门的KeyFactorys,只能创建密钥(对称)。
KeyPairGenerator:用于生成一对适用于指定算法的公钥和私钥。
KeyGenerator:用于生成与指定算法一起使用的新密钥。
KeyAgreement:由两方或多方使用,商定和建立一个特定的密钥,用于特定的密码操作。
AlgorithmParameters:用于存储特定算法的参数,包括参数编码和解码。
AlgorithmParameterGenerator:用于生成适合于指定算法的一组AlgorithmParameters。
KeyStore:用于创建和管理密钥库。密钥库是密钥的数据库。密钥库中的私钥具有与其关联的证书链,用于验证相应的公钥。密钥库还包含来自可信实体的证书。
CertificateFactory:用于创建公钥证书和证书吊销列表(CRL)。
CertPathBuilder:用于构建证书链(也称为证书路径)。
CertPathValidator:用于验证证书链。
CertStore:用于从存储库中检索证书和CRL。
注意:生成器可以创建具有全新内容的对象,而工厂只能从现有材料(例如编码)中创建对象。
核心类和接口
本节讨论JCA中提供的核心类和接口:
- Provider和Security类,SecureRandom,MessageDigest,Signature,Cipher,Mac,KeyFactory,SecretKeyFactory,KeyPairGenerator,KeyGenerator,KeyAgreement,AlgorithmParameters,AlgorithmParameterGenerator,KeyStore和CertificateFactory,引擎类,
- https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#Key这里写链接内容接口和类,
- Algorithm Parameter Specification接口和类以及Key Specification接口和类,以及
- 其他的支持和工具类。
注:有关CertPathBuilder,CertPathValidator和CertStore引擎类的更多信息,请参阅“Java PKI编程指南”。
本指南将首先介绍最有用的高级类(Provider,Security,SecureRandom,MessageDigest,Signature,Cipher和Mac),然后研究各种支持类。 现在,简单地说,密钥(公钥,私钥和加密)由各种JCA类生成和表示,并被高级类用作其操作的一部分。
本部分显示每个类和接口中主要方法的签名。 其中一些类(MessageDigest,Signature,KeyPairGenerator,SecureRandom,KeyFactory和密钥规范类)的示例在相应的示例部分提供。
相关安全API软件包的完整参考文档可以在软件包摘要中找到:
- java.security
- javax.crypto
- java.security.cert
- java.security.spec
- javax.crypto.spec
- java.security.interfaces
- javax.crypto.interfaces
Provider类
术语“加密服务提供者”(在本文档中与“提供者”可互换使用)是指提供JDK安全API加密特征子集的具体实现的一个或一组包。 Provider类是这种包或一组包的接口。 它具有访问提供程序名称,版本号和其他信息的方法。 请注意,除了注册加密服务的实现外,Provider类还可以用于注册可能被定义为JDK安全API或其扩展之一的其他安全服务的实现。
为了提供密码服务的实现,一个实体(例如开发组)编写实现代码并创建Provider类的子类。 Provider子类的构造函数设置各种属性的值; JDK安全API使用这些值来查找提供者实现的服务。 换句话说,子类指定实现服务的类的名称。
这里有几种类型的服务可以通过提供者包实现; 欲了解更多信息,请参阅Engine Classes and Algorithms
不同的实现可能具有不同的特性。 有些可能是基于软件的,有些可能是基于硬件的。 有些可能是平台无关的,有些可能是平台特定的。 一些供应商的源代码可能可用于审查和评估,而有些则可能不可用。 JCA让最终用户和开发者决定他们的需求。
在本节中,我们解释最终用户如何安装符合他们需求的加密实现,以及开发人员如何请求适合他们的实现。
注意:有关实现Provider的更多信息,请参阅指南How To Implement a Provider for the Java Cryptography Architecture
Provider的请求和服务如何实现
对于API中的每个引擎类,通过调用引擎类的getInstance方法之一来请求和实例化实现实例,指定想要实现的所需算法的名称以及可选的提供者(或提供者类)的名称。
static EngineClassName getInstance(String algorithm) throws NoSuchAlgorithmException
static EngineClassName getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException
static EngineClassName getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException
其中EngineClassName是所需的引擎类型(MessageDigest / Cipher / etc)。 例如:
MessageDigest md = MessageDigest.getInstance("SHA-256");
KeyAgreement ka = KeyAgreement.getInstance("DH", "SunJCE");
分别返回“SHA-256”MessageDigest和“DH”KeyAgreement对象的实例。
附录A包含已经标准化的用于Java环境的名称列表。 有些提供商可能会选择也包含也指向相同算法的别名。 例如,“SHA256”算法可能被称为“SHA-256”。 应用程序应该使用标准名称而不是别名,因为不是所有的提供者都可以用同样的方法来命名算法名称。
注意:算法名称不区分大小写。 例如,以下所有调用都是等同的:
MessageDigest.getInstance("SHA256")
MessageDigest.getInstance("sha256")
MessageDigest.getInstance("sHa256")
如果没有指定提供程序,getInstance将搜索已注册的提供程序,以获得与指定算法关联的请求的加密服务的实现。 在任何给定的Java虚拟机(JVM)中,提供程序都是以给定的优先顺序安装的,如果没有请求特定的提供程序,搜索提供程序列表的顺序就会被搜索到。 例如,假设在JVM,PROVIDER_1和PROVIDER_2中安装了两个提供程序。 假设:
- PROVIDER_1实现SHA-256和DESede。
- PROVIDER_1拥有优先顺序1(最高优先级)。
- PROVIDER_2使用DSA,SHA-256,RC5和RSA实现SHA256。
- PROVIDER_2具有优先顺序2。
现在让我们看看三种情况:
- 如果我们正在寻找一个SHA-256实现,那么这两个提供者都提供这样的实现。 由于PROVIDER_1具有最高优先级并且首先被搜索,所以PROVIDER_1实现被返回。
- 如果我们正在寻找SHA256withDSA签名算法,则首先搜索PROVIDER_1。 没有找到实现,因此搜索了PROVIDER_2。 由于实现被发现,它被返回。
- 假设我们正在寻找一个SHA256withRSA签名算法。 由于没有安装的提供者实现它,抛出了NoSuchAlgorithmException。
包含提供者参数的getInstance方法适用于想要指定哪个提供者需要算法的开发人员。 例如,联邦机构将希望使用已获得联邦认证的提供商实现方案。 假设来自PROVIDER_1的SHA256withDSA实施尚未获得此认证,而PROVIDER_2的DSA实现已获得此认证。
然后联邦机构计划将会有以下调用,并指定PROVIDER_2,因为它具有经过认证的实施:
Signature dsa = Signature.getInstance("SHA256withDSA", "PROVIDER_2");
在这种情况下,如果PROVIDER_2未安装,即使另一个已安装的提供程序实现了所请求的算法,也会引发NoSuchProviderException。
程序还可以选择获取所有已安装提供程序的列表(使用Security类中的getProviders方法),并从列表中选择一个。
注:通用应用程序不应该请求来自特定提供商的加密服务。 否则,应用程序被绑定到在其他Java实现上可能不可用的特定提供程序。 他们也可能无法利用可用的优化提供程序(例如,通过PKCS11的硬件加速器或Microsoft的MSCAPI等本机操作系统实现),这些提供程序的优先顺序高于特定的请求提供程序。
安装Providers
为了使用Provider,必须首先安装加密提供程序,然后静态或动态注册。 Sun发行版提供了多种Sun提供程序(SUN,SunJCE,SunJSSE,SunRsaSign等),已经安装并注册。 以下各节介绍如何安装和注册其他提供程序。
安装Provider类
有两种可能的方法来安装Provider类:
- 在普通的Java classpath中
在classpath的任何位置放置一个包含Provider类的zip或JAR文件。 一些算法类型(Cipher)要求提供者是一个签名的Jar文件。 作为已安装/捆绑的扩展
如果将提供程序置于标准扩展目录中,则该提供程序将被视为已安装的扩展程序。 在JDK中,这将位于:1.Solaris, Linux, or Mac OS X: JAVA_HOME/lib/ext
2.Windows:JAVA_HOME\\lib\\ext
这里JAVA_HOME是指安装运行时软件的目录,它是Java运行时环境(JRE)的顶层目录或Java JDK软件中的jre目录。 例如,如果在Solaris上将JDK 6安装在名为/home/user1/JDK1.6.0的目录中或在Microsoft Windows上的名为C:\\ Java \\ JDK1.6.0的目录中,则需要将JAR文件安装到 以下目录:
- Solaris, Linux, or Mac OS X: /home/user1/JDK1.6.0/jre/lib/ext
- Windows: C:\\JDK1.6.0\\jre\\lib\\ext
同样,如果在Solaris上将JRE 6安装在名为/home/user1/jre1.6.0的目录中或在Microsoft Windows上的名为C:\\ jre1.6.0的目录中,则需要将JAR文件安装在以下目录中:
- Solaris, Linux, or Mac OS X: /home/user1/jre1.6.0/lib/ext
- Windows: C:\\jre1.6.0\\lib\\ext
有关如何部署扩展的更多信息,请参阅如何扩展部署
注册Provider
下一步是将Provider添加到注册提供者列表中。 通过在运行Java应用程序之前编辑安全属性配置文件,或通过在运行时调用方法来动态地注册提供程序,可以静态注册提供程序。 为了防止将恶意提供程序的安装添加到运行时环境中,试图动态注册提供程序的应用程序必须拥有适当的运行时权限。
静态注册
配置文件位于以下位置:
- Solaris, Linux, or Mac OS X: JAVA_HOME/lib/security/java.security
- Windows: JAVA_HOME\\lib\\security\\java.security
对于每个注册的提供者,这个文件应该有以下形式的表单:
security.provider.n=masterClassName
这声明了一个提供者,并且指定了它的首选顺序n。 首选顺序是提供者搜索请求的算法的顺序(当没有请求特定的提供者时)。 顺序以1为基础:1是最优先的,然后是2,依此类推。
masterClassName必须指定提供者的主类的完全限定名称。 提供者的文档将指定它的主类。 这个类总是Provider类的一个子类。 子类构造函数设置Java加密API所需的各种属性的值,以查找提供者实现的算法或其他工具。
JDK标配自动安装和配置的提供程序,如“SUN”和“SunJCE”。 “SUN”提供者的主类是sun.security.provider包中的SUN类,相应的java.security文件条目如下:
security.provider.5=sun.security.provider.Sun
要利用另一个JCA提供者,请添加一条引用备用提供者的行,指定首选顺序(如果需要,对其他提供者的订单进行相应的调整)。
假设CompanyX的提供者的主类是com.companyx.provider.ProviderX,并且你想把这个提供者配置成第八个最优先的。 为此,您可以将以下行添加到java.security文件中:
security.provider.8=com.companyx.provider.ProviderX
动态注册
要动态注册提供者,应用程序调用Security类中的addProvider或insertProviderAt方法。 这种类型的注册在VM实例中不是永久性的,只能通过具有适当权限的“可信任”程序完成。 参见安全性。
设置Provider权限
每当使用加密提供者(即提供Cipher,KeyAgreement,KeyGenerator,Mac或SecretKeyFactory的实现的提供者),并且提供者不是已安装的扩展时可能需要授予使用JCA的applet或应用程序时的权限 安装了一个安全管理器。 每当一个applet运行的时候,通常都会安装一个安全管理器,并且可以通过应用程序本身的代码或通过命令行参数为应用程序安装安全管理器。 由于默认系统策略配置文件授予安装的扩展的所有权限(即安装在扩展目录中),因此不需要授予安装的扩展的权限。
您将要使用的每个提供商的供应商的文档应该包括所需的权限以及如何授予这些权限的信息。 例如,如果提供程序不是已安装的扩展程序并安装了安全管理器,则可能需要以下权限:
- java.lang.RuntimePermission“getProtectionDomain”获取类保护域。 提供者可能需要在进行自我完整性检查的过程中获得自己的保护域
- java.security.SecurityPermission“putProviderProperty.name”设置提供程序属性,其中name由实际提供程序名称替换。
例如,下面给出了一个样例语句,该语句授予名称为“MyJCE”且代码位于myjce_provider.jar中的提供程序的权限。 这样的陈述可以出现在政策文件中。 在这个例子中,假设myjce_provider.jar文件位于/ localWork目录中。
grant codeBase "file:/localWork/myjce_provider.jar"
permission java.lang.RuntimePermission "getProtectionDomain";
permission java.security.SecurityPermission
"putProviderProperty.MyJCE";
;
Provider类方法
每个Provider类实例都有一个(当前区分大小写的)名称,一个版本号以及提供者及其服务的字符串描述。 您可以通过调用以下方法来查询提供程序实例以获取此信息:
public String getName()
public double getVersion()
public String getInfo()
Security类
Security类管理已安装的提供程序和安全性属性。 它只包含静态方法,永远不会实例化。 用于添加或删除提供者以及设置安全属性的方法只能由受信任的程序执行。 目前,“可信程序”也是
- 一个不在安全管理器下运行的本地应用程序,或者
- 一个小程序或具有执行指定方法权限的应用程序(见下文)。
如果确定代码被信任以执行尝试的操作(例如添加提供者),则要求该小应用程序被授予适当的特定操作权限。 JDK安装的策略配置文件指定来自指定代码源的代码允许哪些权限(哪些类型的系统资源访问)被允许。 (有关详细信息,请参阅下面的“默认策略实施和策略文件语法”和“https://docs.oracle.com/javase/8/docs/technotes/guides/security/spec/security-spec.doc.html”文件。)
正在执行的代码总是被认为来自特定的“代码源”。 代码源不仅包括源代码的位置(URL),还包括对可能用于签名代码的私钥对应的任何公钥的引用。 代码源中的公钥由用户密钥库中的(符号)别名引用。
在策略配置文件中,代码源由两个组件表示:代码库(URL)和别名(以signedBy开头),其中别名名称标识包含必须用于验证的公钥的密钥库条目 代码的签名。
以下是一个示例策略配置文件:
grant codeBase "file:/home/sysadmin/", signedBy "sysadmin"
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
permission java.security.SecurityPermission "putProviderProperty.*";
;
此配置文件指定从本地文件系统上/ home / sysadmin /目录下的已签名JAR文件加载的代码可以添加或删除提供程序或设置提供程序属性。 (请注意,可以使用用户密钥库中的别名sysadmin引用的公钥来验证JAR文件的签名。)
代码源(或两者)的组件可能会省略。 下面是一个代码库被省略的配置文件的例子:
grant signedBy "sysadmin"
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
;
如果此策略有效,那么由sysadmin签名的JAR文件中的代码可以添加/删除提供程序,而不管JAR文件的来源在哪里。
这是一个没有签名者的例子:
grant codeBase "file:/home/sysadmin/"
permission java.security.SecurityPermission "insertProvider.*";
permission java.security.SecurityPermission "removeProvider.*";
;
在这种情况下,来自本地文件系统/ home / sysadmin /目录中任何地方的代码都可以添加/删除提供程序。 代码不需要签名。
不包括codeBase和signedBy的例子是:
grant permission java.security.SecurityPermission "insertProvider.*"; permission java.security.SecurityPermission "removeProvider.*"; ;
在这里,在缺少两个代码源组件的情况下,任何代码(无论它来自何处,是否签名,谁签名)都可以添加/删除提供程序。 显然,这是绝对不推荐,因为这可能会打开一个安全漏洞。 不受信任的代码可能会安装提供程序,从而影响后来取决于正常运行的实现的代码。 (例如,流氓Cipher对象可能会捕获并存储它收到的敏感信息。)
管理Providers
下表总结了Security类中的方法,您可以使用它来查询安装了哪些提供程序,以及在运行时安装或删除提供程序。
查询Provider
方法 | 描述 |
---|---|
static Provider[] getProviders() | 返回一个包含所有已安装提供程序的数组(技术上,每个包提供程序的提供程序子类)。 数组中提供者的顺序是他们的偏好顺序。 |
static Provider getProvider(String providerName) | 返回提供程序名providerName。 如果找不到Provider,它将返回null。 |
增加Provider
方法 | 描述 |
---|---|
static int addProvider(Provider provider) | 将提供程序添加到已安装提供程序列表的末尾。 它将返回提供者添加的偏好位置,如果提供者因为已经安装而未被添加,则返回-1。 |
static int insertProviderAt (Provider provider, int position) | 在指定的位置添加一个新的提供程序。 如果给定的供应商安装在要求的位置,则以前在该位置的供应商和位置大于位置的所有供应商向上移动一个位置(靠近清单的末尾)。 此方法返回提供程序添加的优先级位置,如果提供程序因为已经安装而未添加,则返回-1。 |
删除Provider
方法 | 描述 |
---|---|
static void removeProvider(String name) | 删除具有指定名称的提供程序。 如果没有安装提供程序,它将静默地返回。 当指定的提供者被移除时,位于比指定提供者所在位置更大的位置的所有提供者被向下移动一个位置(朝向已安装提供者列表的头部)。 |
注意:如果要更改提供者的偏好位置,则必须先将其删除,然后将其重新插入到新的偏好位置。
安全属性
Security类维护一个系统范围的安全属性的列表。 这些属性与系统属性类似,但与安全性相关。 这些属性可以静态或动态设置。 我们已经看到了一个静态安全属性的例子(即,通过“security.provider.i”安全属性静态注册一个提供者)。 如果要动态设置属性,可信程序可以使用以下方法:
static String getProperty(String key) static void setProperty(String key, String datum)
注意:安全提供程序的列表是在VM启动过程中建立的,因此必须使用上述方法来更改提供程序列表。
提醒一下,配置文件位于以下位置:
- Solaris, Linux, or Mac OS X: JAVA_HOME/lib/security/java.security
- Windows: JAVA_HOME\\lib\\security\\java.security
SecureRandom类
SecureRandom类是提供随机数生成器(RNG)功能的引擎类。 它不同于java.lang.Random类,因为它产生密码强的随机数。 如果生成器中的随机性不足,则会使保护机制变得更加容易。 在密码学中使用随机数字,例如生成加密密钥,算法参数等等。
所有Java SE实现都必须指出它们在java.security.Security类的securerandom.strongAlgorithms属性中提供的最强(最随机)的SecureRandom实现。 当需要特别强的随机值时,可以使用此实现。
创建SecureRandom对象
有几种方法可以获得SecureRandom的实例:
- 所有Java SE实现都使用无参数构造函数提供默认的SecureRandom:new SecureRandom()。
- 要获得SecureRandom的特定实现,请使用getInstance()静态工厂方法之一。
设置或者重设种子
SecureRandom实现尝试完全随机化发生器本身的内部状态,除非调用者通过调用其中一个setSeed方法来调用getInstance方法:
synchronized public void setSeed(byte[] seed)
public void setSeed(long seed)
一旦SecureRandom对象被设置了种子,它将产生与原始种子一样随机的比特。
在任何时候,SecureRandom对象都可以使用setSeed方法之一重新设置种子。 此时会对原来的种子进行补充,而不是取代现有的种子; 因此,保证重复的调用永远不会降低随机性。
使用SecureRandom对象
要获得随机字节,调用者只需传递任意长度的数组,然后用随机字节填充:
synchronized public void nextBytes(byte[] bytes)
生成种子字节
如果需要,可以调用generateSeed方法来生成给定数量的种子字节(例如,为其他随机数生成器生成种子):
byte[] generateSeed(int numBytes)
MessageDigest类
MessageDigest类是一个引擎类,用于提供密码安全的消息摘要(如SHA-256或SHA-512)的功能。 加密安全的消息摘要采用任意大小的输入(一个字节数组),并生成一个固定大小的输出,称为摘要或散列。
例如,SHA-256算法产生一个32字节的摘要,而SHA-512的是64字节。
摘要有两个属性:
- 找到两个哈希值相同的消息在计算上应该是不可行的。
- 摘要不应该透露任何关于用于生成它的输入。
消息摘要用于生成唯一且可靠的数据标识符。 它们有时被称为“校验和”或数据的“数字指纹”。 只改变消息的一位应该产生不同的摘要值。
消息摘要有许多用途,可以确定数据何时被修改,有意或无意。 最近,已经有相当大的努力来确定流行算法是否存在任何缺陷,结果不尽相同。 在选择一个摘要算法时,应该总是查阅最近的参考文献,以确定其当前任务的状态和适当性。
创建MessageDigest对象
计算摘要的第一步是创建一个消息摘要实例。 MessageDigest对象是通过使用MessageDigest类中的getInstance()静态工厂方法之一获得的。 工厂方法返回一个初始化的消息摘要对象。 因此不需要进一步的初始化。
更新Message Digest对象
计算某些数据的摘要的下一步是将数据提供给初始化的消息摘要对象。 它可以一次或一块地提供。 可以通过调用其中一种更新方法将消息提供给消息摘要:
void update(byte input)
void update(byte[] input)
void update(byte[] input, int offset, int len)
计算摘要
在通过调用更新数据块之后,使用对其中一个摘要方法的调用来计算摘要:
byte[] digest()
byte[] digest(byte[] input)
int digest(byte[] buf, int offset, int len)
第一种方法返回计算的摘要。 第二种方法在调用digest()之前用输入字节数组进行最终更新(输入),digest()返回摘要字节数组。 最后一个方法将计算的摘要存储在提供的缓冲区buf中,从偏移量开始。 len是分配给摘要的buf中的字节数,该方法返回实际存储在buf中的字节数。 如果缓冲区中没有足够的空间,该方法将抛出异常。
请参阅代码示例部分中的计算MessageDigest示例以获取更多详细信息。
Signature类
Signature类是一个引擎类,旨在提供加密数字签名算法(如DSA或RSAwithMD5)的功能。 密码安全签名算法采用任意大小的输入和私钥,并生成一个相对较短(通常是固定大小)的字节串,称为签名,具有以下属性:
- 只有私钥/公钥对的所有者才能创建签名。 任何拥有公钥的人都可以在计算上不可能恢复私钥
- 鉴于与用于生成签名的私钥相对应的公钥,应该有可能验证输入的真实性和完整性。
- 签名和公钥没有透露有关私钥的任何信息。
它也可以用来验证所谓的签名是否实际上是与其相关的数据的真实签名。
一个签名对象被初始化为用一个私钥进行签名,并被赋予待签名的数据。 结果签名字节通常与签名数据保持一致。 当需要验证时,另一个签名对象被创建和初始化以进行验证并给出相应的公钥。 数据和签名字节被馈送到签名对象,并且如果数据和签名匹配,则签名对象报告成功。
尽管签名看起来与消息摘要相似,但它们在提供的保护类型上却有着非常不同的目的。 事实上,诸如“SHA256withRSA”的算法使用消息摘要“SHA256”将大数据集初始“压缩”为更易于管理的形式,然后用“RSA”算法对得到的32字节消息摘要进行签名。
请参阅示例部分以获取签名和验证数据的示例。
Signature对象状态
签名对象是模态对象。 这意味着一个Signature对象总是处于一个给定的状态,它只能执行一种操作。 状态被表示为在其各自的类中定义的最终整数常量。
签名对象可能具有的三种状态是:
- UNINITIALIZED
- SIGN
- VERIFY
第一次创建时,Signature对象处于UNINITIALIZED状态。 Signature类定义了两个初始化方法initSign和initVerify,它们分别将状态更改为SIGN和VERIFY。
创建Signature对象
签名或验证签名的第一步是创建一个签名实例。 签名对象是通过使用Signature getInstance()静态工厂方法之一获得的。
初始化Signature对象
Signature对象在使用之前必须被初始化。 初始化方法取决于对象是要用于签名还是验证。
如果要用于签名,则必须首先使用要生成签名的实体的私钥来初始化该对象。 这个初始化通过调用方法完成:
final void initSign(PrivateKey privateKey)
此方法将Signature对象置于SIGN状态。
如果相反,Signature对象将用于验证,则必须首先使用将要验证签名的实体的公钥来初始化。 这个初始化是通过调用以下任一方法来完成的:
final void initVerify(PublicKey publicKey)
final void initVerify(Certificate certificate)
此方法将Signature对象置于VERIFY状态。
签名
如果签名对象已经被初始化为签名(如果它处于SIGN状态),则待签名的数据可以被提供给该对象。 这是通过对其中一个更新方法进行一个或多个调用完成的:
final void update(byte b)
final void update(byte[] data)
final void update(byte[] data, int off, int len)
应该调用update方法,直到所有要签名的数据都被提供给签名对象。
要生成签名,只需调用其中一个签名方法:
final byte[] sign()
final int sign(byte[] outbuf, int offset, int len)
第一个方法以字节数组的形式返回签名结果。 第二个将签名结果存储在提供的缓冲区outbuf中,从offset开始。 len是分配给签名的outbuf中的字节数。 该方法返回实际存储的字节数。
签名编码是特定于算法的。 有关在Java加密体系结构中使用ASN.1编码的更多信息,请参阅标准名称文档。
对sign方法的调用会将签名对象重置为之前通过调用initSign进行初始化以进行签名的状态。 也就是说,如果需要,该对象将被重置并可用于生成具有相同私钥的另一个签名,通过新的调用来更新和签名。或者,可以对指定不同私钥的initSign或initVerify(初始化Signature对象以验证签名)进行新的调用。
验证
如果签名对象已被初始化以进行验证(如果它处于VERIFY状态),则它可以验证所谓的签名实际上是与其相关联的数据的真实签名。 为了开始这个过程,要被验证的数据(而不是签名本身)被提供给对象。 通过调用其中一种更新方法将数据传递给对象:
final void update(byte b)
final void update(byte[] data)
final void update(byte[] data, int off, int len)
应该调用更新方法,直到所有要验证的数据都被提供给Signature对象。 现在可以通过调用其中一种验证方法来验证签名
以上是关于Java加密体系结构(JCA)参考指南的主要内容,如果未能解决你的问题,请参考以下文章