做网站需要什么功能,商务网站开发实训任务书,网站首页适配规则,建设专业网站公司本文是从英文的官网摘了翻译的#xff0c;用作自己的整理和记录。水平有限#xff0c;欢迎指正。版本是#xff1a;22 原文地址#xff1a;https://docs.oracle.com/en/java/javase/22/security/java-cryptography-architecture-jca-reference-guide.html#GUID-815542FE-CF…本文是从英文的官网摘了翻译的用作自己的整理和记录。水平有限欢迎指正。版本是22 原文地址https://docs.oracle.com/en/java/javase/22/security/java-cryptography-architecture-jca-reference-guide.html#GUID-815542FE-CF3D-407A-9673-CAE9840F6231 Java Cryptography ArchitectureJCA是平台的重要组成部分它包含了一个“provider”架构和一系列API用于数字签名digital signatures、消息摘要hashes、证书certificates 及其验证 certificate validation、加密对称/非对称块/流密码encryption (symmetric/asymmetric block/stream ciphers)、密钥生成与管理key generation and management以及安全随机数生成secure random number generation等。
一、Java密码体系结构简介 Java平台非常强调安全性包括语言安全、密码学、公钥基础设施、身份验证、安全通信和访问控制。 JCA是该平台的一个主要部分包含一个“provider”架构和一组API用于数字签名、消息摘要哈希、证书和证书验证、加密对称/非对称块/流密码、密钥生成和管理以及安全随机数生成。这些API允许开发人员轻松地将安全性集成到应用程序代码中。该体系结构的设计遵循以下原则
实现独立性应用程序不需要实现安全算法。相反他们可以从Java平台请求安全服务。安全服务在提供程序中实现请参阅加密服务提供程序这些提供程序通过标准接口插入Java平台。一个应用程序可能依赖于多个独立的安全功能提供程序。实现互操作性提供者可以跨应用程序进行互操作。具体而言应用程序不绑定到特定的提供程序提供程序也不绑定到具体的应用程序。算法可扩展性Java平台包括许多内置的提供程序这些提供程序实现了当今广泛使用的一组基本安全服务。然而一些应用程序可能依赖于尚未实现的新兴标准或者依赖于专有服务。Java平台支持安装实现此类服务的自定义提供程序。 JDK中提供的其他加密通信库使用JCA提供程序体系结构但在其他地方进行了描述。JSSE组件提供对安全套接字层SSL、传输层安全性TLS和数据报传输层安全DTLS实现的访问请参阅《Java安全套接字扩展JSSE参考指南》。您可以使用Java通用安全服务JGSS通过KerberosAPI和简单身份验证和安全层SASL在通信应用程序之间安全地交换消息请参阅JAAS和Java GSS-API教程简介以及Java SASL API编程和部署指南。
1.1 JCA设计原则 CA Design Principles JCA是围绕以下原则设计的
实施独立性和互操作性算法独立性和可扩展性 实现独立性和算法独立性是相辅相成的您可以使用加密服务如数字签名和消息摘要而不必担心实现细节甚至不必担心构成这些概念基础的算法。虽然完全的算法独立性是不可能的但JCA提供了标准化的、特定于算法的API。当不希望实现独立性时JCA允许开发人员指示特定的实现。 算法独立性是通过定义加密“引擎engines”服务的类型并定义提供这些加密引擎功能的类来实现的。这些类称为引擎类例如MessageDigest、Signature、KeyFactory、KeyPairGenerator和Cipher类。 实现独立性是使用基于“provider提供者”的体系结构实现的。术语Cryptographic Service Provider加密服务提供商CSP与术语“provider提供商”可互换使用请参阅加密服务提供商是指实现一个或多个加密服务如数字签名算法、消息摘要算法和密钥转换服务的包或包集。程序可以简单地请求实现特定服务例如DSA签名算法的特定类型的对象例如Signature对象并从安装的提供者之一获得实现。如果需要程序可以改为向特定提供者请求实现。提供者可以对应用程序透明地进行更新例如当有更快或更安全的版本可用时。 实现互操作性意味着各种实现可以相互工作、使用彼此的密钥或验证彼此的签名。例如这意味着对于相同的算法一个提供者生成的密钥将被另一个提供者使用并且一个提供者所生成的签名将可被另一提供者验证。 实现独立性是通过采用基于“provider服务提供者”的架构来实现的。密码服务提供者Cryptographic Service ProviderCSP这个术语与“provider服务提供者”一词可以互换使用见密码服务提供者它指的是一个或一组包这些包实现了一个或多个加密服务如数字签名算法、消息摘要算法和密钥转换服务。程序可以简单地请求一种特定类型的对象例如Signature对象实现特定服务例如DSA签名算法并从已安装的服务提供者中获得实现。如果需要程序也可以请求来自特定服务提供者的实现。服务提供者可以在应用程序不知情的情况下进行透明更新例如当有更快或更安全的版本可用时。 实现互操作性意味着不同的实现可以相互协作使用对方的密钥或验证对方的签名。这意味着例如对于相同的算法一个服务提供者生成的密钥可以被另一个服务提供者使用一个服务提供者生成的签名也可以被另一个服务提供者验证。 算法可扩展性意味着可以很容易地添加适合支持的引擎类之一的新算法。
1.2 加密服务提供者架构 Provider Architecture 服务提供者包含一个包或一组包这些包为所advertised 宣传的加密算法提供具体的实现。
1.2.1 加密服务提供商 Cryptographic Service Providers java.security.Provider 是所有安全服务提供者的基础类。每个密码服务提供者CSP都包含这个类的实例该实例包含提供者的名称并列出它实现的所有安全服务/算法。当需要特定算法的实例时JCA框架会查询提供者的数据库如果找到了合适的匹配项就会创建该实例。
msp; 服务提供者包含一个包或一组包这些包为所宣传的加密算法提供具体的实现。每个JDK安装都有一个或多个默认安装和配置的服务提供者。可以静态或动态地添加额外的服务提供者。客户端可以配置它们的运行时环境以指定服务提供者的偏好顺序。偏好顺序是在没有请求特定服务提供者时搜索请求服务的服务提供者顺序。 要使用JCA应用程序只需请求一种特定类型的对象如MessageDigest以及特定算法或服务如SHA-256算法并从已安装的服务提供者中获得实现。例如以下语句从已安装的服务提供者请求一个SHA-256消息摘要 md MessageDigest.getInstance(SHA-256);msp; 或者程序可以从特定服务提供者请求对象。每个服务提供者都有一个用于引用它的名称。例如以下语句从名为ProviderC的服务提供者请求一个SHA-256消息摘要 md MessageDigest.getInstance(SHA-256, ProviderC);以下图表说明了请求SHA-256消息摘要实现的过程。它们展示了三个不同的服务提供者这些服务提供者实现了各种消息摘要算法SHA-256、SHA-384和SHA-512。服务提供者按照从左到右的偏好顺序排列1-3。在图2-1中一个应用程序请求SHA-256算法的实现而没有指定服务提供者的名称。按照偏好顺序搜索服务提供者由第一个提供特定算法的服务提供者ProviderB返回实现。在图2-2中应用程序从特定服务提供者ProviderC请求SHA-256算法的实现。这一次即使偏好顺序更高的服务提供者ProviderB也提供了MD5算法的实现但还是从ProviderC返回了实现。 Figure 2-1 获取消息摘要算法实现没有指定服务提供者 Figure 2-2 获取消息摘要算法指定服务提供者 ProvideB JDK中的加密实现通过几个不同的服务提供者Sun、SunJSSE、SunJCE、SunRsaSign进行分发这主要是出于历史原因但在较小程度上也是根据它们提供的功能性和算法类型。其他Java运行时环境不一定包含这些服务提供者因此应用程序不应请求特定于服务提供者的实现除非已知特定服务提供者将可用。 JCA提供了一组API允许用户查询已安装的服务提供者以及它们支持的服务。 这种架构还方便了最终用户添加额外的服务提供者。已经有许多第三方服务提供者实现可用。有关如何编写、安装和注册服务提供者的更多信息请参见“Provider Class 服务提供者类”。
1.2.2 加密服务提供者的实现 How Providers Are Actually Implemented 算法独立性是通过定义一个通用的高层应用程序接口API来实现的所有应用程序都使用这个API来访问服务类型。实现独立性是通过让所有服务提供者的实现都符合定义良好的接口来实现的。引擎类的实例因此由具有相同方法签名的实现类“支持”。应用程序调用通过引擎类路由并传递到底层的支持实现。实现处理请求并返回正确的结果。 每个引擎类中的应用程序API方法通过实现相应服务提供者接口SPI的类路由到服务提供者的实现。也就是说对于每个引擎类都有一个对应的抽象SPI类定义了每个加密服务提供者的算法必须实现的方法。每个SPI类的名称与其对应的引擎类名称相同后面加上Spi。例如Signature引擎类提供了访问数字签名算法的功能。实际的服务提供者实现是在SignatureSpi的子类中提供的。应用程序调用引擎类的API方法这些方法反过来调用实际实现中的SPI方法。 每个SPI类都是抽象的。为了提供特定算法的特定类型服务的实现服务提供者必须子类化相应的SPI类并为所有抽象方法提供实现。 对于API中的每个引擎类通过调用引擎类的getInstance()工厂方法来请求和实例化实现实例。工厂方法是一种静态方法返回一个类的实例。引擎类使用前面描述的框架服务提供者选择机制来获取实际的支持实现SPI然后创建实际的引擎对象。每个引擎类的实例都封装了作为一个私有字段相应SPI类的实例称为SPI对象。API对象的所有API方法都声明为final它们的实现调用封装的SPI对象的相应SPI方法。 Example 2-1 样例代码获取引擎类的实例 import javax.crypto.*;Cipher c Cipher.getInstance(AES);c.init(ENCRYPT_MODE, key);Figure 2-3 应用程序检索“AES”密码实例 在这里一个应用程序需要一个AES javax.crypto.Cipher 实例并不在意使用哪个服务提供者。应用程序调用 Cipher 引擎类的 getInstance() 工厂方法该方法反过来请求JCA框架找到第一个支持AES的服务提供者实例。框架查询每个已安装的服务提供者并获取服务提供者的 Provider 类实例。回想一下Provider 类是一个可用算法的数据库。框架搜索每个服务提供者最终在CSP3中找到一个合适的条目。这个数据库条目指向实现类 com.foo.AESCipher它扩展了 CipherSpi因此适合由 Cipher 引擎类使用。创建了 com.foo.AESCipher 的一个实例并封装在新创建的 javax.crypto.Cipher 实例中然后返回给应用程序。当应用程序现在对 Cipher 实例执行 init() 操作时Cipher 引擎类将请求路由到 com.foo.AESCipher 类中相应的 engineInit() 支持方法。 Java安全标准算法名称列出了为Java环境定义的标准名称。其他第三方服务提供者可能会定义这些服务的自己的实现甚至额外的服务。
1.2.3 密钥库 Keystores 一个名为“密钥库”的数据库可以用来管理密钥和证书的存储库。密钥库对需要数据身份认证、加密或签名的应用程序是可用的。 应用程序可以通过实现java.security包中的KeyStore类来访问密钥库。截至JDK 9默认且推荐的密钥库类型格式是pkcs12它基于RSA PKCS12个人信息交换语法标准。之前默认的密钥库类型是jks这是一种专有格式。还有其他可用的密钥库格式例如jceks这是另一种专有的密钥库格式以及基于RSA PKCS11标准的pkcs11支持访问加密令牌如硬件安全模块和智能卡。 应用程序可以通过之前描述的服务提供者机制从不同的服务提供者中选择不同的密钥库实现。参见密钥管理 Key Management.
1.3 引擎类和算法 Engine Classes and Algorithms 引擎类为特定类型的加密服务提供接口与特定的加密算法或提供程序无关。 引擎engines provides提供以下功能之一
密码操作加密、数字签名、消息摘要等encryption, digital signatures, message digests密码材料密钥和算法参数keys and algorithm parameters的生成器或转换器 generators or converters of cryptographic material对象密钥库或证书keystores or certificates这些对象封装了加密数据并且可以在更高的抽象层中使用。 有以下引擎类可以使用
SecureRandom用于生成随机数或伪随机数。MessageDigest用于计算指定数据的消息摘要hash。Signature签名 用密钥初始化用于对数据进行签名和验证数字签名。Cipher密码用密钥初始化这些密钥用于加密/解密数据。有各种类型的算法对称批量加密例如AES、非对称加密例如RSA和基于密码的加密例如PBE。Mac与MessageDigest一样消息验证码Mac也会生成哈希值但首先使用密钥进行初始化以保护消息的完整性。KEM双方用于从私钥/公钥对中派生共享密钥。KeyFactory用于将Key类型的现有不透明加密密钥转换为密钥规范底层密钥材料的透明表示反之亦然。SecretKeyFactory用于将SecretKey类型的现有不透明加密密钥转换为密钥规范底层密钥材料的透明表示反之亦然。SecretKeyFactorys是专门的KeyFactorys只创建秘密对称密钥KeyPairGenerator用于生成一对适合与指定算法一起使用的新公钥和私钥。KeyGenerator用于生成与指定算法一起使用的新密钥。KeyAgreement 密钥协议由两方或多方使用以商定并建立用于特定加密操作的特定密钥。AlgorithmParameters用于存储特定算法的参数包括参数编码和解码。AlgorithmParameterGenerator用于生成一组适合指定算法的AlgorithmParameters。KeyStore用于创建和管理密钥库。密钥库是一个密钥数据库。密钥库中的私钥有一个与之相关联的证书链用于验证相应的公钥。密钥库还包含来自受信任实体的证书。CertificateFactory用于创建公钥证书和证书吊销列表CRL。CertPathBuilder用于构建证书链也称为证书路径。CertPathValidator用于验证证书链。CertStore用于从存储库中检索证书和CRL。
二、核心类和接口 Core Classes and Interfaces 以下是JCA提供的核心类和接口。
Provider 和 SecuritySecureRandom, MessageDigest, Signature, Cipher, Mac, KEM, KeyFactory, SecretKeyFactory, KeyPairGenerator, KeyGenerator, KeyAgreement, AlgorithmParameter, AlgorithmParameterGenerator, KeyStore, 和 CertificateFactory 引擎类密钥接口和类KeyPairAlgorithmParameterSpec 接口AlgorithmParametersAlgorithmParameterGenerator以及在 java.security.spec 和 javax.crypto.spec 包中的算法参数规范接口和类。KeySpec 接口EncodedKeySpecPKCS8EncodedKeySpec和 X509EncodedKeySpec。SecretKeyFactoryKeyFactoryKeyPairGeneratorKeyGeneratorKeyAgreement和 KeyStore。
注意请参阅《Java PKI程序员指南》中的 CertPathBuilder、CertPathValidator 和 CertStore 引擎类。 本指南将首先介绍最有用的高级类Provider、Security、SecureRandom、MessageDigest、Signature、Cipher、Mac 和 KEM然后深入探讨各种支持类。目前只需简单地说密钥公钥、私钥和秘密密钥由各种JCA类生成和表示并作为它们操作的一部分被高级类使用。 本节展示了每个类和接口的主要方法签名。一些这些类MessageDigest、Signature、KeyPairGenerator、SecureRandom、KeyFactory 和密钥规范类的示例将提供在相应的代码示例部分。 相关的安全API包的完整参考文档可以在包摘要中找到
java.securityjavax.cryptojava.security.certjava.security.specjavax.crypto.specjava.security.interfacesjavax.crypto.interfaces
2.1 服务提供者 The Provider “密码服务提供者Cryptographic Service Provider”在本文档中与“服务提供者provider”一词交替使用指的是一个包或一组包这些包提供了JDK安全API加密特性的一个子集的具体实现。Provider类是这类包或包集的接口。它具有访问提供者名称、版本号和其他信息的方法。请注意除了注册加密服务的实现之外Provider类还可以用来注册可能作为JDK安全API或其扩展之一而定义的其他安全服务的实现。 为了提供加密服务的实现一个实体例如一个开发小组编写实现代码并创建Provider类的子类。Provider子类的构造函数设置了各种属性的值JDK安全API使用这些值来查找提供者实现的服务。换句话说子类指定了实现服务的类的名称。 Figure 2-4 Provider Class 提供程序包可以实现几种类型的服务请参阅引擎类和算法。 不同的实现可能有不同的特性。有些可能是基于软件的而其他一些可能是基于硬件的。有些可能是平台独立的而其他一些可能是特定平台的。有些服务提供者的源代码可能可供审查和评估而有些则可能不公开。JCA允许最终用户和开发者决定他们的需求是什么。 你可以找到有关最终用户如何安装符合他们需求的加密实现的信息以及开发者如何请求符合他们需求的实现。
2.1.1 服务提供者如何获取 How Provider Implementations Are Requested and Supplied 对于API中的每个引擎类参见引擎类和算法通过调用引擎类上的一个getInstance方法来请求和实例化一个实现实例指定所需算法的名称以及可选地指定所希望的服务提供者的名称或Provider类。
static EngineClassName getInstance(String algorithm)throws NoSuchAlgorithmExceptionstatic EngineClassName getInstance(String algorithm, String provider)throws NoSuchAlgorithmException, NoSuchProviderExceptionstatic EngineClassName getInstance(String algorithm, Provider provider)throws NoSuchAlgorithmException这里EngineClassName是所需的引擎类型(for example, Signature, MessageDigest, or Cipher). 例如: Signature sig Signature.getInstance(SHA256withRSA);KeyAgreement ka KeyAgreement.getInstance(DH, SunJCE);分别返回“SHA256withRSA”签名和“DH”密钥协商对象的实例。 Java安全标准算法名称包含了已标准化用于Java环境的名称列表。一些服务提供者可能选择也包括指向同一算法的别名。例如“SHA256”算法可能被称为“SHA-256”。应用程序应该使用标准名称而不是别名因为并非所有服务提供者都以相同的方式为算法名称设置别名。 如果未指定服务提供者getInstance 会在已注册的服务提供者中搜索与指定算法相关的请求的加密服务实现。在任何给定的Java虚拟机(JVM)中服务提供者按照特定的偏好顺序安装如果在调用时没有请求特定服务提供者则按照这个顺序搜索服务提供者列表。见“安装服务提供者”。例如假设在JVM中安装了两个服务提供者PROVIDER_1 和 PROVIDER_2。假设
PROVIDER_1 实现了 SHA256withRSA 和 AES。PROVIDER_1 有最高的引用顺序1。PROVIDER_2 实现了 SHA256withRSA、SHA256withDSA 和 RC5。PROVIDER_2 有引用顺序2。 现在让我们看三个场景
我们正在寻找 SHA256withRSA 实现两个服务提供者都提供了这样的实现。由于 PROVIDER_1 有最高的优先级并且首先被搜索所以返回 PROVIDER_1 的实现。我们正在寻找 SHA256withDSA 实现首先搜索 PROVIDER_1。没有找到实现所以接着搜索 PROVIDER_2。由于找到了实现所以返回它。我们正在寻找 SHA256withECDSA 实现因为没有已安装的服务提供者实现了它所以抛出 NoSuchAlgorithmException。 包含服务提供者参数的 getInstance 方法是为那些想要指定他们希望从哪个服务提供者获取算法的开发者准备的。例如一个联邦机构将希望使用一个已经获得联邦认证的服务提供者实现。假设 PROVIDER_1 没有获得这样的认证而 PROVIDER_2 获得了。 那么一个联邦机构的程序会有如下的调用指定 PROVIDER_2因为它有经过认证的实现
Signature s Signature.getInstance(SHA256withRSA, PROVIDER_2);在这种情况下如果没有安装PROVIDER_2则会引发NoSuchProviderException即使另一个已安装的提供程序实现了所请求的算法。 程序还可以选择获取所有已安装提供程序的列表使用安全类中的getProviders方法然后从列表中选择一个。
2.1.2 安装服务提供者 Installing Providers 为了使用加密服务提供者首先必须被安装然后无论是静态还是动态地进行注册。随此版本发布的Sun服务提供者有多种SUN、SunJCE、SunJSSE、SunRsaSign等它们已经安装并注册好了。以下部分描述了如何安装和注册额外的服务提供者。 所有JDK服务提供者已经安装并注册。然而如果你需要任何第三方服务提供者请参见“实施和整合服务提供者步骤”中的第8步“准备测试”以获取有关如何将服务提供者添加到类或模块路径、注册服务提供者静态或动态以及添加任何所需权限的信息。
2.1.3 服务提供者的方法 Provider Class Methods 每个Provider类实例都有一个当前区分大小写名称、一个版本号以及提供程序及其服务的字符串描述。 您可以通过调用以下方法查询提供程序实例以获取此信息 public String getName()
public double getVersion()
public String getInfo()
2.2 安全类 The Security Class Security 类管理已安装的服务提供者和全局安全属性。它只包含静态方法并且永远不会被实例化。添加或移除服务提供者的方法以及设置安全属性的方法只能由一个可信程序执行。目前“可信程序” 是以下之一
一个没有在安全管理器下运行的本地应用程序一个有权限执行指定方法的小应用程序或应用程序 要使代码被认为可信任以执行尝试的操作例如添加服务提供者需要为该特定操作授予小程序适当的权限。JDK安装的策略配置文件指定了来自指定代码源的代码允许的权限即可访问的系统资源类型。请参阅默认策略实现、策略文件语法和Java SE平台安全架构。 被执行的代码始终被认为是来自特定的“代码源”。代码源不仅包括代码原始位置URL还包括对可能用于签名代码的私钥对应的任何公钥的引用。代码源中的公钥通过用户别名symbolic alias names引用。 在策略配置文件中代码源由两个组成部分表示代码基URL和别名名称前面加上signedBy其中别名名称标识包含用于验证代码签名的公钥的密钥库条目。 此类文件中的每个“grant”语句为指定的代码源授予一组权限指定允许哪些操作。 以下是一个策略配置文件的示例
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文件的签名。。 可能缺少代码源的任何一个组件或两者都缺少。以下是一个配置文件的示例其中省略了codeBase
grant signedBy sysadmin {permission java.security.SecurityPermission insertProvider.*;permission java.security.SecurityPermission removeProvider.*;
};如果此策略有效则本地文件系统上由/home/sysadmin/目录签名的JAR文件中的代码可以添加或删除提供程序。代码不需要签名。 一个既不包括codeBase也不包括signedBy的示例是
grant {permission java.security.SecurityPermission insertProvider.*;permission java.security.SecurityPermission removeProvider.*;
};在这里由于缺少代码源的两个组件任何代码无论其来源、是否签名或由谁签名都可以添加或移除服务提供者。显然这绝对不是推荐的因为这种授权可能会打开一个安全漏洞。不可信的代码可能会安装一个服务提供者从而影响依赖于正常功能实现的后续代码。例如一个恶意的Cipher对象可能会捕获并存储它接收到的敏感信息。
2.2.1 管理提供商 Managing Providers
2.2.1.1 查询服务提供者 Querying Providers
MethodDescriptionstatic Provider[] getProviders()返回一个数组包含所有已安装的服务提供者技术上是每个包服务提供者的子类。数组中服务提供者的顺序代表了它们的优先级顺序。.static Provider getProvider (String providerName)返回名为providerName的提供程序。如果找不到提供程序则返回null。.
2.2.1.2 增加服务提供者Adding Providers
MethodDescriptionstatic int addProvider(Provider provider)将一个服务提供者添加到已安装服务提供者列表的末尾。如果成功添加它将返回服务提供者被添加时的优先级位置如果服务提供者因为已经安装而没有被添加则返回-1。.static int insertProviderAt (Provider provider, int position)在指定位置添加一个新的服务提供者。如果指定位置已经安装了服务提供者那么原先在该位置的服务提供者以及所有位置大于该位置的服务提供者都会向上移动一个位置即向列表末尾移动。这个方法返回服务提供者被添加时的优先级位置或者如果因为服务提供者已经安装而没有添加则返回-1。.
2.2.1.3 移除服务提供者 Removing Providers
MethodDescriptionstatic void removeProvider(String name)移除指定名称的服务提供者。如果服务提供者未安装则默默返回。当指定的服务提供者被移除时所有位于指定服务提供者之后位置的服务提供者都会向下移动一个位置朝已安装服务提供者列表的头部方向。.
2.2.2 安全属性 Security Properties Security 类维护着一个系统范围的安全属性列表。这些属性与系统属性类似但是与安全相关。这些属性可以静态设置通过 java-home/conf/security/java.security 文件也可以动态设置使用 API。请参阅“实现和整合服务提供者步骤”中的第 8.1 步使用 security.provider.n 安全属性静态配置服务提供者。如果你想动态设置属性可信程序可以使用以下方法
static String getProperty(String key)
static void setProperty(String key, String datum)2.3 安全随机数 The SecureRandom Class SecureRandom 类是一个引擎类参见引擎类和算法它提供密码学上强随机数可以通过访问伪随机数生成器PRNG一个从初始种子值产生伪随机序列的确定性算法或者通过读取本地随机源例如 /dev/random 或真正的随机数生成器。一个 PRNG 的例子是 NIST SP 800-90Ar1 指定的确定性随机位生成器DRBG。其他实现可能产生真正的随机数还有一些可能同时使用这两种技术。密码学上的强随机数至少符合 FIPS 140-2密码模块的安全要求第 4.9.1 节中指定的统计随机数生成器测试。 所有 Java SE 实现都必须在 java.security.Security 类的 securerandom.strongAlgorithms 属性中指明它们提供的最强最随机的 SecureRandom 实现。当需要特别强的随机值时可以使用这个实现。 securerandom.drbg.config 属性用于指定 SUN 服务提供者的 DRBG SecureRandom 配置和实现。securerandom.drbg.config 是 java.security.Security 类的一个属性。其他 DRBG 实现也可以使用 securerandom.drbg.config 属性。
2.3.1 创建 SecureRandom对象 Creating a SecureRandom Object 获取 SecureRandom 实例的几种方法 默认的 SecureRandom 实例: 所有 Java SE 实现都提供了一个默认的 SecureRandom使用无参数构造函数new SecureRandom()。这个构造函数遍历注册的安全服务提供者列表从最优先的服务提供者开始然后返回第一个支持 SecureRandom 随机数生成器RNG算法的服务提供者的新 SecureRandom 对象。如果没有服务提供者支持 RNG 算法则返回一个使用 SUN 服务提供者的 SHA1PRNG 的 SecureRandom 对象。 要获取 SecureRandom 的特定实现请使用 “服务提供者实现的请求和供应方式 How Provider Implementations Are Requested and Supplied” 中的一种。 使用 getInstanceStrong() 方法来获取由 java.security.Security 类的 securerandom.strongAlgorithms 属性定义的强 SecureRandom 实现。这个属性列出了适合生成重要值的平台实现。
2.3.2 设定种子或重新设定种子 Seeding or Re-Seeding the SecureRandom Object 除非对getInstance的调用后面跟着对以下setSeed方法之一的调用否则SecureRandom对象将使用随机种子进行初始化。 void setSeed(byte[] seed)void setSeed(long seed) 在首次调用 nextBytes 方法之前必须调用 setSeed 以防止任何环境随机性。 由 SecureRandom 对象产生的位的随机性取决于种子位的随机性。 SecureRandom 对象可以随时使用 setSeed 或 reseed 方法之一重新设置种子。setSeed 中给定的种子是对现有种子的补充而不是替代因此重复调用保证不会减少随机性。
2.3.3 使用SecureRandom对象 Using a SecureRandom Object 要获得随机字节调用者只需传递一个任意长度的数组然后用随机字节填充
void nextBytes(byte[] bytes)
2.3.4 正在生成种子字节 Generating Seed Bytes 如果需要可以调用generateSeed方法来生成给定数量的种子字节例如为其他随机数生成器种子 byte[] generateSeed(int numBytes)
2.4 消息摘要 The MessageDigest Class MessageDigest 类是一个引擎类参见引擎类和算法旨在提供密码学上安全的散列消息摘要的功能例如 SHA-256 或 SHA-512。密码学上安全的散列消息摘要接受任意大小的输入一个字节数组并生成一个固定大小的输出称为摘要或哈希值。 例如SHA-256 算法生成一个 32 字节的摘要而 SHA-512 的摘要是 64 字节。 摘要具有两个属性
计算上几乎不可能找到两个消息它们散列到同一个值。摘要不应当透露任何用于生成它的输入信息。 消息摘要用于生成数据的唯一且可靠的标识符。它们有时被称为“校验和”或数据的“数字指纹”。即使是消息中的一个比特位的更改也应该产生不同的摘要值。 消息摘要有多种用途可以确定数据是否被修改无论是有意还是无意。最近人们已经付出相当的努力去确定流行的算法是否存在任何弱点结果各异。在选择摘要算法时人们应该总是咨询最新的参考资料以确定其状态和适用性是否适合手头的任务。
2.4.1 创建摘要t对象 Creating a MessageDigest Object 创建 MessageDigest 对象的步骤。
要计算摘要需要创建一个消息摘要实例。MessageDigest 对象是通过使用 MessageDigest 类中的一个 getInstance() 方法来获取的。请参阅“服务提供者实现的请求和供应方式”。工厂方法返回一个已初始化的消息摘要对象。因此它不需要进一步的初始化。
2.4.2 更新摘要Updating a Message Digest Object 更新消息摘要对象的过程。 要计算某些数据的摘要必须将数据提供给初始化的消息摘要对象。它可以同时提供也可以分块提供。可以通过调用以下更新方法之一将片段馈送到消息摘要 void update(byte input)
void update(byte[] input)
void update(byte[] input, int offset, int len)
2.4.3 计算摘要 Computing the Digest 使用不同类型的digest方法计算摘要的过程。 数据块必须由对update方法的调用提供。请参阅更新邮件摘要对象。 摘要是使用对其中一个摘要方法的调用来计算的 byte[] digest()
byte[] digest(byte[] input)
int digest(byte[] buf, int offset, int len)
byte[] digest() 方法返回计算出的摘要。byte[] digest(byte[] input) 方法在调用 digest() 返回摘要字节数组之前使用输入的字节数组进行最终的 update(input) 更新。intdigest(byte[] buf, int offset, int len) 方法将计算出的摘要存储在提供的缓冲区 buf 中从 offset 开始。len 是在 buf 中为摘要分配的字节数该方法返回实际存储在 buf 中的字节数。如果缓冲区中没有足够的空间方法将抛出一个异常。
2.5 签名类 The Signature Class Signature 类是一个引擎类参见引擎类和算法旨在提供密码学数字签名算法的功能例如 SHA256withDSA 或 SHA512withRSA。密码学安全的签名算法接受任意大小的输入和私钥并生成一个相对较短通常是固定大小的字节串称为签名具有以下属性
只有私钥/公钥对的所有者才能创建签名。对于仅拥有公钥和一定数量签名的任何人来说计算上几乎不可能恢复私钥。给定用于生成签名的私钥对应的公钥应该能够验证输入的真实性和完整性。
Figure 2-7 Signature Class 签名对象使用私钥进行初始化用于签名并提供待签名的数据。生成的签名字节通常与签名数据一起保存。当需要验证时会创建并初始化另一个用于验证的签名对象并提供相应的公钥。将数据和签名字节提供给签名对象如果数据和签名匹配签名对象会报告成功。 尽管签名看起来与消息摘要相似但它们在提供的保护类型上有着非常不同的目的。实际上像 “SHA256WithRSA” 这样的算法使用消息摘要 “SHA256” 最初将大型数据集 “压缩” 成更易管理的形式然后使用 “RSA” 算法对生成的 32 字节消息摘要进行签名。 有关签名和验证数据的示例请参见使用生成的密钥生成和验证签名。
2.5.1 签名状态 Signature Object States 这意味着签名对象始终处于给定状态在该状态下它只能执行一种类型的操作。 状态由其各自类中定义的最终整型常量表示。 签名对象可能有的三种状态是
UNINITIALIZED未初始化SIGN签名VERIFY验证 当它首次创建时签名对象处于 UNINITIALIZED 状态。Signature 类定义了两个初始化方法initSign 和 initVerify分别将状态更改为 SIGN 和 VERIFY。
2.5.2 创建签名对象 Creating a Signature Object 签名或验证签名的第一步是创建签名实例。 签名对象是通过使用Signature getInstance静态工厂方法之一获得的。请参阅如何请求和提供提供程序实现。
2.5.3 初始化签名对象 Initializing a Signature Object 在使用签名对象之前必须对其进行初始化。初始化方法取决于该对象是用于签名还是用于验证。 如果用于签名对象首先必须使用将要生成签名的实体的私钥进行初始化。这个初始化通过调用以下方法完成
final void initSign(PrivateKey privateKey)这个方法将签名对象设置为 SIGN 状态。如果签名对象将用于验证它首先必须使用将要验证签名的实体的公钥进行初始化。这个初始化可以通过调用以下任一方法完成
final void initVerify(PublicKey publicKey)final void initVerify(Certificate certificate)这个方法将签名对象设置为 VERIFY 状态。
2.5.4 使用签名对象签名 Signing with a Signature Object 如果签名对象已经初始化用于签名即处于 SIGN 状态则可以将待签名的数据提供给该对象。这可以通过调用 update 方法之一或多次来完成
final void update(byte b)
final void update(byte[] data)
final void update(byte[] data, int off, int len)应持续调用 update 方法直到所有待签名的数据都已提供给签名对象。 要生成签名只需调用 sign 方法之一
final byte[] sign()
final int sign(byte[] outbuf, int offset, int len)第一个方法以字节数组的形式返回签名结果。第二个方法将签名结果存储在提供的缓冲区 outbuf 中从 offset 开始。len 是 outbuf 中为签名分配的字节数。该方法返回实际存储的字节数。 签名编码是特定于算法的。请参阅 Java 安全标准算法名称了解更多关于 Java 加密体系结构中 ASN.1 编码的使用。 调用 sign 方法会将签名对象重置为通过调用 initSign 进行初始化时的状态。也就是说如果需要对象将被重置并可用于使用相同的私钥通过新的 update 和 sign 调用生成另一个签名。 或者可以再次调用 initSign 指定不同的私钥或者调用 initVerify以初始化签名对象以验证签名。
2.5.1 使用签名对象验证签名 Verifying with a Signature Object 如果签名对象已经初始化用于验证即处于 VERIFY 状态则可以验证所谓的签名是否确实是与之相关联的数据的真实签名。开始此过程时将待验证的数据与签名本身相对提供给对象。通过调用 update 方法之一将数据传递给对象
final void update(byte b)
final void update(byte[] data)
final void update(byte[] data, int off, int len)应持续调用 update 方法直到所有待验证的数据都已提供给签名对象。现在可以通过调用 verify 方法之一来验证签名
final boolean verify(byte[] signature) final boolean verify(byte[] signature, int offset, int length)参数必须是一个包含签名的字节数组。这个字节数组将保存由先前调用 sign 方法之一返回的签名字节。 verify 方法返回一个布尔值指示编码的签名是否是提供给 update 方法的数据的真实签名。 调用 verify 方法会将签名对象重置为其通过调用 initVerify 进行初始化时的状态。也就是说对象将被重置并可用于验证由指定公钥的身份的另一个签名。 或者可以再次调用 initVerify 指定不同的公钥以初始化签名对象以验证来自不同实体的签名或者调用 initSign以初始化签名对象以生成签名。
2.6 密码 The Cipher Class Cipher 类提供了密码学密码的功能用于加密和解密。加密是将数据称为明文和密钥一起处理生成对不知道密钥的第三方来说无意义的数据密文。解密是相反的过程将密文和密钥一起处理生成明文。
Figure 2-8 The Cipher Class
2.6.1 对称加密和非对称加密 Symmetric vs. Asymmetric Cryptography 有两种主要的加密类型对称加密也称为密钥加密和非对称加密或公钥密码学。在对称加密中相同的密钥用于加密和解密数据。保持密钥的私密性对于保持数据的机密性至关重要。另一方面**非对称加密使用一对公钥/私钥来加密数据。用一个密钥加密的数据必须用另一个密钥来解密**。用户首先生成一对公钥/私钥然后将公钥发布在一个可信赖的数据库中任何人都可以访问。想要与该用户安全通信的用户使用检索到的公钥来加密数据。只有持有私钥的人才能解密。保持私钥的机密性对于这种方案至关重要。 非对称算法如 RSA通常比对称算法慢得多。这些算法并不设计用来高效地保护大量数据。在实践中非对称算法用于交换较小的密钥这些密钥用于初始化对称算法。
2.6.2 流密码与分组密码 Stream versus Block Ciphers 有两种主要类型的密码机块密码和流密码。块密码机一次处理整个数据块通常是许多字节的长度。如果数据不足以构成一个完整的输入块就必须进行填充也就是说在加密之前必须添加虚拟字节以构成密码块大小的倍数。然后在解密阶段去除这些字节。填充可以由应用程序完成或者通过初始化密码机使用某种填充类型如 “PKCS5PADDING” 来实现。 相比之下流密码机一次处理一个很小的数据单元通常是一个字节甚至一个比特。这允许密码机在不需要填充的情况下处理任意数量的数据。
2.6.3 工作模式 Modes Of Operation 当使用简单的块密码进行加密时两个相同的明文块将始终产生相同的密文块。密码分析者如果注意到重复的文本块将更容易破解密文。密码的工作模式通过基于块位置或其他密文块的值来改变输出块使密文不那么可预测。第一个块将需要一个初始值这个值称为初始化向量IV。由于IV只是在加密之前改变数据IV应该是随机的但不一定需要保密。有多种模式例如CBC密码块链接、CFB密码反馈模式和OFB输出反馈模式。ECB电子密码本模式是一种工作模式其中没有块位置或其他密文块的影响。因为如果使用相同的明文/密钥ECB密文是相同的这种模式通常不适合密码学应用因此不应使用。 一些算法如AES和RSA允许不同长度的密钥但其他算法的密钥长度是固定的如3DES。使用更长的密钥通常意味着更强的抵抗消息恢复的能力。像往常一样在安全性和时间之间存在权衡因此适当选择密钥长度。 大多数算法使用二进制密钥。大多数人类没有能力记住长序列的二进制数字即使以十六进制表示。字符密码更容易回忆。因为字符密码通常从少数字符中选择例如[a-zA-Z0-9]所以已经定义了像“基于密码的加密”PBE这样的协议它们采用字符密码并生成强大的二进制密钥。为了使攻击者从密码到密钥的任务变得非常耗时通过所谓的“彩虹表攻击”或“预计算字典攻击”其中预计算了常见的字典单词-值映射大多数PBE实现会混合一个随机数称为盐以减少预计算表的有用性。 一些更新的密码模式如带有关联数据的认证加密AEAD例如伽罗瓦/计数器模式GCM可以同时加密数据并认证生成的消息。可以在计算结果AEAD标签MAC期间使用额外的关联数据AAD但这个AAD数据不会作为密文输出。例如一些数据可能不需要保密但应该计算标签以检测修改。Cipher.updateAAD() 方法可以用来在标签计算中包含AAD。
2.6.4 Using an AES Cipher with GCM Mode AES Cipher 使用 GCMGalois/Counter Mode是一种 AEADAuthenticated Encryption with Associated Data认证加密与关联数据密码机它的使用模式与非 AEAD 密码机不同。除了常规数据外它还接受可选的 AADAdditional Authenticated Data附加认证数据这些数据对于加密/解密是可选的但是在加密/解密的数据之前必须提供 AAD。此外为了安全地使用 GCM调用者不应重复使用密钥和 IVInitialization Vector初始化向量组合。这意味着每次加密操作时都应该使用不同的参数集显式重新初始化密码对象。 以下是使用 AES Cipher 与 GCM 模式的示例代码
SecretKey myKey ...; // 密钥
byte[] myAAD ...; // 附加认证数据可选
byte[] plainText ...; // 明文数据
int myTLen ...; // 标签长度
byte[] myIv ...; // 初始化向量GCMParameterSpec myParams new GCMParameterSpec(myTLen, myIv); // GCM参数
Cipher c Cipher.getInstance(AES/GCM/NoPadding); // 获取密码机实例
c.init(Cipher.ENCRYPT_MODE, myKey, myParams); // 初始化为加密模式// AAD 是可选的如果存在必须在任何 update/doFinal 调用之前提供。
c.updateAAD(myAAD); // 如果 AAD 非空
byte[] cipherText new byte[c.getOutputSize(plainText.length)]; // 密文数组
// 加密操作的结束
int actualOutputLen c.doFinal(plainText, 0, plainText.length, cipherText);// 要解密必须提供相同的 AAD 和 GCM 参数
c.init(Cipher.DECRYPT_MODE, myKey, myParams); // 初始化为解密模式
c.updateAAD(myAAD); // 再次提供 AAD
byte[] recoveredText c.doFinal(cipherText, 0, actualOutputLen); // 密文解密// 如果要再次使用相同的密钥进行加密必须更改 IV 值
byte[] newIv ...;
myParams new GCMParameterSpec(myTLen, newIv); // 更新 GCM 参数在这段代码中我们首先设置了密钥、附加认证数据、明文、标签长度和初始化向量。然后我们创建了一个 GCM 参数规范并使用它来初始化密码机实例。在加密之前如果存在附加认证数据则使用 updateAAD 方法提供。接着使用 doFinal 方法完成加密操作。解密时使用相同的附加认证数据和 GCM 参数重新初始化密码机并再次使用 doFinal 方法来解密密文。如果再次使用相同的密钥进行加密必须更改初始化向量的值。
2.6.5 创建Cipher 对象 Creating a Cipher Object 密码对象是通过使用 Cipher 类的静态工厂方法 getInstance() 来获取的。请参阅“如何请求和供应服务提供者实现How Provider Implementations Are Requested and Supplied”。在这里算法名称与其它引擎类略有不同因为它指定的不仅仅是一个算法名称而是一个“转换”。转换是一个字符串描述了要在给定输入上执行的操作或操作集以产生某些输出。转换始终包括一个密码算法的名称例如AES并且可以后跟模式和填充方案。 转换的形式为
“algorithm/mode/padding”“algorithm” 例如以下是有效的转换
AES/CBC/PKCS5Padding
AES如果只指定了转换名称系统将确定环境中是否有请求转换的实现如果有多个则返回首选的实现。 如果同时指定了转换名称和包服务提供者则系统将确定请求的包中是否有请求转换的实现并在没有时抛出异常。 建议使用完整指定算法、模式和填充的转换。如果不这样做服务提供者将使用默认值。例如SunJCE 和 SunPKCS11 服务提供者将 ECB 作为默认模式将 PKCS5Padding 作为许多对称密码的默认填充。 这意味着在 SunJCE 服务提供者的情况下
Cipher c1 Cipher.getInstance(AES/ECB/PKCS5Padding);和
Cipher c1 Cipher.getInstance(AES);这两种方式是等效的因为后者将使用默认的模式和填充方案根据 SunJCE 服务提供者的默认设置这将是 ECB 模式和 PKCS5Padding 填充。 使用诸如 CFBCipher Feedback Mode密码反馈模式和 OFBOutput Feedback Mode输出反馈模式这样的模式块密码机可以以小于密码机实际块大小的单元加密数据。在请求这种模式时您可以通过将此数字附加到模式名称后面来选择每次处理的位数如 “AES/CFB8/NoPadding” 和 “AES/OFB32/PKCS5Padding” 转换中所示。如果没有指定这样的数字将使用服务提供者特定的默认值。例如SunJCE 服务提供者对 AES 使用 128 位的默认值。因此通过使用 8 位模式如 CFB8 或 OFB8可以将块密码机转换为面向字节的流密码机。 Java 安全标准算法名称包含了一个标准名称列表这些名称可以用来指定转换的算法名称、模式和填充方案组件。 工厂方法返回的对象是未初始化的在使用之前必须进行初始化。
2.6.6 初始化Cipher 对象 Initializing a Cipher Object 通过 getInstance 获取的 Cipher 对象必须为以下四种模式之一进行初始化这些模式在 Cipher 类中被定义为最终整型常量。这 些模式可以通过它们的象征性名称引用
ENCRYPT_MODE数据的加密。DECRYPT_MODE数据的解密。WRAP_MODE将 java.security.Key 包装成字节以便安全传输密钥。UNWRAP_MODE将之前包装的密钥解开恢复为 java.security.Key 对象。 每个 Cipher 初始化方法都需要一个操作模式参数opmode并根据该模式初始化 Cipher 对象。其他参数包括密钥key或包含密钥的证书certificate、算法参数params以及随机源random。 要初始化 Cipher 对象请调用以下 init 方法之一
public void init(int opmode, Key key);public void init(int opmode, Certificate certificate);public void init(int opmode, Key key, SecureRandom random);public void init(int opmode, Certificate certificate, SecureRandom random);public void init(int opmode, Key key, AlgorithmParameterSpec params);public void init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random);public void init(int opmode, Key key, AlgorithmParameters params);public void init(int opmode, Key key, AlgorithmParameters params, SecureRandom random);如果需要参数的 Cipher 对象例如初始化向量在初始化为加密模式时并且没有向 init 方法提供参数那么底层的密码实现应该自行提供所需的参数要么是通过生成随机参数要么是通过使用默认的、服务提供者特定的参数集。 然而如果需要参数的 Cipher 对象在初始化为解密模式时并且没有向 init 方法提供参数将根据使用的 init 方法抛出 InvalidKeyException 或 InvalidAlgorithmParameterException 异常。 请参阅“管理算法参数Managing Algorithm Parameters.”。 加密时使用的相同参数必须用于解密。 请注意当 Cipher 对象被初始化时它会丢失所有之前获得的状态。换句话说初始化 Cipher 等同于创建该 Cipher 的一个新实例并对其进行初始化。例如如果一个 Cipher 首先使用给定的密钥初始化为解密模式然后被重新初始化为加密模式它将丢失在解密模式下获得的任何状态。
2.6.7 加密和解密数据 Encrypting and Decrypting Data 数据可以一步加密或解密单部分操作也可以通过多个步骤进行多部分操作。如果您事先不知道数据的长度或者数据太长而无法一次性全部存储在内存中多部分操作就非常有用。 要进行单步加密或解密调用 doFinal 方法之一
public byte[] doFinal(byte[] input);public byte[] doFinal(byte[] input, int inputOffset, int inputLen);public int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output);public int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)要进行多步加密或解密调用 update 方法之一
public byte[] update(byte[] input);public byte[] update(byte[] input, int inputOffset, int inputLen);public int update(byte[] input, int inputOffset, int inputLen, byte[] output);public int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)多部分操作必须通过这些 doFinal 方法之一来终止如果最后一步仍有一些输入数据剩余或者通过以下 doFinal 方法之一来终止如果最后一步没有输入数据剩余
public byte[] doFinal();public int doFinal(byte[] output, int outputOffset);所有的 doFinal 方法都会处理任何必要的填充或去填充如果作为指定转换的一部分请求了填充或去填充。 调用 doFinal 会将 Cipher 对象重置为其通过调用 init 方法初始化时的状态。也就是说Cipher 对象被重置并且可用于加密或解密取决于在调用 init 时指定的操作模式更多的数据。
2.6.8 包装和解包秘钥 Wrapping and Unwrapping Keys 包装密钥可以安全地将密钥从一个位置传输到另一个位置。 使用包装/解包 API 使得代码编写更加方便因为它可以直接使用密钥对象。这些方法还支持硬件型密钥的安全传输。 要包装一个密钥首先为 Cipher 对象初始化为 WRAP_MODE然后调用以下方法
public final byte[] wrap(Key key);如果您要将包装后的密钥字节调用 wrap 的结果提供给其他人以进行解包请确保同时发送接收者解包所需的额外信息
密钥算法的名称。包装密钥的类型Cipher.SECRET_KEY、Cipher.PRIVATE_KEY 或 Cipher.PUBLIC_KEY 中的一个。 可以通过调用 Key 接口的 getAlgorithm 方法确定密钥算法名称
public String getAlgorithm();要解包之前调用 wrap 返回的字节首先为 Cipher 对象初始化为 UNWRAP_MODE然后调用以下方法
public final Key unwrap(byte[] wrappedKey,String wrappedKeyAlgorithm,int wrappedKeyType));这里wrappedKey 是之前调用 wrap 返回的字节wrappedKeyAlgorithm 是与包装密钥关联的算法wrappedKeyType 是包装密钥的类型。这必须是 Cipher.SECRET_KEY、Cipher.PRIVATE_KEY 或 Cipher.PUBLIC_KEY 中的一个。
2.6.9 管理算法参数 Managing Algorithm Parameters 底层 Cipher 实现使用的参数无论是应用程序明确传递给 init 方法的还是底层实现自身生成的都可以通过调用 Cipher 对象的 getParameters 方法来检索该方法返回一个 java.security.AlgorithmParameters 对象如果没有使用参数则返回 null。如果参数是初始化向量IV也可以通过调用 getIV 方法来检索。 在以下示例中实现了基于密码的加密PBE的 Cipher 对象仅使用密钥而没有参数进行初始化。然而选定的基于密码的加密算法需要两个参数 - 一个盐值和一个迭代计数。这些将由底层算法实现自身生成。应用程序可以从 Cipher 对象中检索生成的参数见示例 2-3。 用于加密的相同参数必须用于解密。它们可以从它们的编码实例化并用于初始化对应的 Cipher 对象进行解密见示例 2-4。 如果在初始化 Cipher 对象时没有指定任何参数并且您不确定底层实现是否使用任何参数您可以通过简单地调用 Cipher 对象的 getParameters 方法并检查返回值来找出。返回值为 null 表示没有使用参数。 SunJCE 提供者实现的以下密码算法使用参数
当在反馈模式下使用 AES、DES-EDE 和 Blowfish 时即 CBC、CFB、OFB 或 PCBC使用初始化向量 (IV)。javax.crypto.spec.IvParameterSpec 类可以用来初始化 Cipher 对象的给定 IV。此外CTR 和 GCM 模式需要 IV。PBE Cipher 算法使用一组参数包括盐值和迭代计数。javax.crypto.spec.PBEParameterSpec 类可以用来初始化实现 PBE 算法的 Cipher 对象例如PBEWithHmacSHA256AndAES_256的给定盐值和迭代计数。
请注意如果您使用 The SealedObject Class 类您不必担心存储或传输任何算法参数以供解密操作使用。这个类将密封加密时使用的参数附加到加密对象的内容中并使用相同的参数进行解封解密。
示例 2-3 从 Cipher 对象检索参数的示例代码 应用程序可以按照以下方式从 Cipher 对象检索加密生成的参数
import javax.crypto.*;
import java.security.AlgorithmParameters;// 获取基于密码加密的 Cipher 对象
Cipher c Cipher.getInstance(PBEWithHmacSHA256AndAES_256);// 初始化 Cipher 进行加密不提供任何参数。
// 这里假设 myKey 是一个已经生成的密钥。
c.init(Cipher.ENCRYPT_MODE, myKey);// 加密一些数据并存储起来以便稍后解密
byte[] cipherText c.doFinal(This is just an example.getBytes());// 检索底层 Cipher 实现生成的参数
AlgorithmParameters algParams c.getParameters();// 获取参数编码并存储起来
byte[] encodedAlgParams algParams.getEncoded();示例 2-4 用于解密的 Cipher 对象初始化的示例代码 用于加密的相同参数必须用于解密。它们可以从它们的编码实例化并按照以下方式用于初始化对应的 Cipher 对象进行解密
import javax.crypto.*;
import java.security.AlgorithmParameters;// 获取基于密码加密的参数对象
AlgorithmParameters algParams;
algParams AlgorithmParameters.getInstance(PBEWithHmacSHA256AndAES_256);// 使用前一个示例中的参数编码进行初始化
algParams.init(encodedAlgParams);// 获取基于密码加密的 Cipher 对象
Cipher c Cipher.getInstance(PBEWithHmacSHA256AndAES_256);// 使用接受 AlgorithmParameters 对象的 init() 方法之一进行解密初始化
// 并传递前一个示例中的 algParams 对象
c.init(Cipher.DECRYPT_MODE, myKey, algParams);2.6.10 注意事项 Cipher Output Considerations 在 Cipher 的一些 update 和 doFinal 方法中允许调用者指定一个输出缓冲区用于存放加密或解密后的数据。在这些情况下传递一个足够大的缓冲区以容纳加密或解密操作的结果非常重要。 以下 Cipher 中的方法可以用来确定输出缓冲区应该有多大
public int getOutputSize(int inputLen)2.7 其他基于Cipher的类 Other Cipher-based Classes 有一些助手类在内部使用密码以方便访问常见的密码用法。
2.7.1 Cipher字节输入流类 The CipherInputStream Class 这个类是一个 FilterInputStream它加密或解密通过它的数据。它由一个 InputStream 组成。CipherInputStream 表示一个安全的输入流其中嵌入了一个 Cipher 对象。CipherInputStream 的读取方法返回的数据是从底层 InputStream 读取的但还被嵌入的 Cipher 对象进一步处理过。在使用 CipherInputStream 之前Cipher 对象必须完全初始化。 例如如果嵌入的 Cipher 已初始化用于解密CipherInputStream 将在将数据返回给应用程序之前尝试解密从底层 InputStream 读取的数据。 这个类严格遵守其祖先类 java.io.FilterInputStream 和 java.io.InputStream 的语义特别是失败的语义。这个类具有其祖先类中指定的所有方法并覆盖了它们以便数据还被嵌入的密码进行额外的处理。此外这个类捕获了其祖先类没有抛出的所有异常。特别是skip(long) 方法只跳过已被 Cipher 处理过的数据。 对于使用这个类的程序员来说关键不要使用在这类中没有定义或覆盖的方法例如后来添加到超类之一中的新方法或构造函数因为这些方法的设计和实现不太可能考虑到了 CipherInputStream 在安全方面的影响。见示例 2-5 以了解其用法假设 cipher1 已初始化用于加密。程序读取并加密来自文件 /tmp/a.txt 的内容然后将结果加密的字节存储在 /tmp/b.txt 中。 示例 2-6 演示了如何轻松连接 CipherInputStream 和 FileInputStream 的几个实例。在这个示例中假设 cipher1 和 cipher2 分别已使用相应的密钥初始化用于加密和解密。程序将文件 /tmp/a.txt 的内容复制到 /tmp/b.txt只是内容首先被加密然后在从 /tmp/a.txt 读取时被解密回原样。当然由于这个程序只是简单地加密文本然后立即解密回来除了作为演示 CipherInputStreams 串联的简单方法外实际上并没有太大用处。 请注意CipherInputStream 的读取方法将阻塞直到从底层密码返回数据。如果使用块密码将需要从底层 InputStream 获得一个完整的密码文本块。
示例 2-5 使用 CipherInputStream 和 FileInputStream 的示例代码 以下代码展示了如何使用包含 Cipher 的 CipherInputStream 和 FileInputStream 对输入流数据进行加密
try (FileInputStream fis new FileInputStream(/tmp/a.txt);CipherInputStream cis new CipherInputStream(fis, cipher1);FileOutputStream fos new FileOutputStream(/tmp/b.txt)) {byte[] b new byte[8];int i cis.read(b);while (i ! -1) {fos.write(b, 0, i);i cis.read(b);}
}示例 2-6 连接 CipherInputStream 和 FileInputStream 的示例代码 以下示例演示了如何轻松连接 CipherInputStream 和 FileInputStream 的几个实例。在这个示例中假设 cipher1 和 cipher2 分别已使用相应的密钥初始化用于加密和解密
try (FileInputStream fis new FileInputStream(/tmp/a.txt);CipherInputStream cis1 new CipherInputStream(fis, cipher1);CipherInputStream cis2 new CipherInputStream(cis1, cipher2);FileOutputStream fos new FileOutputStream(/tmp/b.txt)) {byte[] b new byte[8];int i cis2.read(b);while (i ! -1) {fos.write(b, 0, i);i cis2.read(b);}
}2.7.2 Cipher字节输出流类 The CipherOutputStream Class 这个类是一个 FilterOutputStream它加密或解密通过它的数据。它由一个 OutputStream 或其子类以及一个 Cipher 组成。CipherOutputStream 表示一个安全的输出流其中嵌入了一个 Cipher 对象。CipherOutputStream 的写入方法首先使用嵌入的 Cipher 对象处理数据然后才将它们写入底层的 OutputStream。在使用 CipherOutputStream 之前Cipher 对象必须完全初始化。 例如如果嵌入的 Cipher 已初始化用于加密CipherOutputStream 将在将数据写入底层输出流之前对其进行加密。 这个类严格遵守其祖先类 java.io.OutputStream 和 java.io.FilterOutputStream 的语义特别是失败的语义。这个类具有其祖先类中指定的所有方法并覆盖了它们全部以便所有数据都被嵌入的密码进行额外的处理。此外这个类捕获了其祖先类没有抛出的所有异常。 对于使用这个类的程序员来说关键不要使用在这类中没有定义或覆盖的方法例如后来添加到超类之一中的新方法或构造函数因为这些方法的设计和实现不太可能考虑到了 CipherOutputStream 在安全方面的影响。 见示例 2-7说明其用法假设 cipher1 已初始化用于加密。程序读取文件 /tmp/a.txt 的内容然后加密并存储结果加密的字节在 /tmp/b.txt。 示例 2-7 演示了如何轻松连接 CipherOutputStream 和 FileOutputStream 的几个实例。在这个示例中假设 cipher1 和 cipher2 分别已使用相应的密钥初始化用于解密和加密。程序将文件 /tmp/a.txt 的内容复制到 /tmp/b.txt只是内容首先被加密然后在写入 /tmp/b.txt 之前被解密回原样。 使用块密码算法时需要记住的一点是在数据将被加密并发送到底层输出流之前必须给 CipherOutputStream 提供一个完整的明文数据块。 这个类的 flush 和 close 方法还有一个重要区别如果封装的 Cipher 对象实现了带有填充的块密码算法这一点就更加重要了
flush 通过强制写入已被封装的 Cipher 对象处理的所有缓冲输出字节来刷新底层 OutputStream。由封装的 Cipher 对象缓冲并等待其处理的任何字节将不会被写入。close 关闭底层 OutputStream 并释放与其关联的任何系统资源。它调用封装的 Cipher 对象的 doFinal 方法导致任何由其缓冲的字节被处理并写入底层流通过调用其 flush 方法。
示例 2-7 使用 CipherOutputStream 和 FileOutputStream 的示例代码 以下代码展示了如何使用包含 Cipher 的 CipherOutputStream 和 FileOutputStream 以便将数据加密写入输出流
try (FileInputStream fis new FileInputStream(/tmp/a.txt);FileOutputStream fos new FileOutputStream(/tmp/b.txt);CipherOutputStream cos new CipherOutputStream(fos, cipher1)) {byte[] b new byte[8];int i fis.read(b);while (i ! -1) {cos.write(b, 0, i);i fis.read(b);}cos.flush();
}示例 2-8 连接 CipherOutputStream 和 FileOutputStream 的示例代码 以下代码展示了如何轻松连接 CipherOutputStream 和 FileOutputStream 的几个实例。在这个示例中假设 cipher1 和 cipher2 分别已使用相应的密钥初始化用于解密和加密
try (FileInputStream fis new FileInputStream(/tmp/a.txt);FileOutputStream fos new FileOutputStream(/tmp/b.txt);CipherOutputStream cos1 new CipherOutputStream(fos, cipher1);CipherOutputStream cos2 new CipherOutputStream(cos1, cipher2)) {byte[] b new byte[8];int i fis.read(b);while (i ! -1) {cos2.write(b, 0, i);i fis.read(b);}cos2.flush();
}2.7.3 密封对象类 The SealedObject Class 这个类允许程序员创建一个对象并使用密码算法保护其机密性。 任何实现了 java.io.Serializable 接口的对象都可以通过创建一个 SealedObject 来封装原始对象以序列化格式即“深拷贝”并使用密码算法如 AES对其进行加密封印以保护其机密性。加密的内容稍后可以被解密使用正确的解密密钥和相应的算法并反序列化从而得到原始对象。 以下代码片段展示了典型用法为了封印一个对象你从一个要封印的对象和一个完全初始化的 Cipher 对象创建一个 SealedObject该 Cipher 对象将加密序列化的对象内容。在这个例子中字符串 “This is a secret” 使用 AES 算法进行封印。注意封印操作中可能使用的任何算法参数都存储在 SealedObject 中
// 创建 Cipher 对象
// 注意sKey 假定是指已经生成的 AES 密钥。
Cipher c Cipher.getInstance(AES);
c.init(Cipher.ENCRYPT_MODE, sKey);// 执行封印
SealedObject so new SealedObject(This is a secret, c);原始被封印的对象可以通过以下两种不同方式恢复
使用一个已经使用与封印对象时完全相同的算法、密钥、填充方案等初始化的 Cipher 对象
c.init(Cipher.DECRYPT_MODE, sKey);
try {String s (String)so.getObject(c);
} catch (Exception e) {// 采取一些措施
};这种方法的优点是解开封印的一方不需要知道解密密钥。例如一方可以使用所需的解密密钥初始化 Cipher 对象然后将其交给另一方后者随后解开封印的对象。
使用适当的解密密钥由于 AES 是对称加密算法我们使用相同的密钥进行封印和解开封印
try {String s (String)so.getObject(sKey);
} catch (Exception e) {// 采取一些措施
};这种方法中getObject 方法为适当的解密算法创建了一个 Cipher 对象并使用给定的解密密钥和存储在封印对象中的算法参数如果有的话进行初始化。这种方法的优点是解开封印的一方不需要记住封印对象时使用的参数例如IV。
2.8 消息认证码类 The Mac Class 类似于消息摘要消息认证码MAC提供了一种检查在不可靠介质上传输或存储的信息完整性的方法但在计算中包括了一个密钥。 只有拥有正确密钥的人才能够验证接收到的消息。通常消息认证码用于两个共享密钥的方之间以验证这些方之间传输的信息。 基于密码散列函数的MAC机制称为HMAC。HMAC可以与任何密码散列函数结合使用例如SHA-256以及一个秘密共享密钥。 Mac 类提供了消息认证码MAC的功能。请参阅 HMAC-SHA256 示例。
2.8.1 创建 Mac 对象 Creating a Mac Object 通过使用 Mac 类的静态工厂方法 getInstance() 来获取 Mac 对象。请参阅“如何请求和供应服务提供者实现”。
2.8.2 初始化 Mac 对象 Initializing a Mac Object Mac 对象始终使用秘密密钥进行初始化并且可以根据底层 MAC 算法的需要选择性地使用一组参数进行初始化。 要初始化 Mac 对象请调用其 init 方法之一
public void init(Key key);public void init(Key key, AlgorithmParameterSpec params);您可以使用实现 javax.crypto.SecretKey 接口的任何秘密密钥对象来初始化您的 Mac 对象。这可以是由 javax.crypto.KeyGenerator.generateKey() 返回的对象或者是密钥协商协议的结果如 javax.crypto.KeyAgreement.generateSecret() 返回的或者是 javax.crypto.spec.SecretKeySpec 的一个实例。 对于一些 MAC 算法用于初始化 Mac 对象的秘密密钥对象关联的秘密密钥算法并不重要这是 SunJCE 提供者 HMAC-MD5 和 HMAC-SHA1 实现的情况。然而对于其他算法秘密密钥算法确实很重要如果使用具有不适当秘密密钥算法的秘密密钥对象则会抛出 InvalidKeyException。
2.8.3 计算 MAC Computing a MAC 可以通过一步单部分操作或多步多部分操作计算 MAC。如果您事先不知道数据的长度或者数据太长而无法一次性全部存储在内存中则多部分操作非常有用。 要一步计算某些数据的 MAC请调用以下 doFinal 方法
public byte[] doFinal(byte[] input);要分多步计算某些数据的 MAC请调用 update 方法之一
public void update(byte input);public void update(byte[] input);public void update(byte[] input, int inputOffset, int inputLen);多部分操作必须通过 doFinal 方法终止如果最后一步仍有一些输入数据剩余或者通过以下 doFinal 方法之一终止如果没有输入数据剩余
public byte[] doFinal();public void doFinal(byte[] output, int outOffset);2.9 密钥封装机制类 The KEM Class KEM 类是一个引擎类参见引擎类和算法它提供了密钥封装机制Key Encapsulation Mechanism, KEM的功能。 您可以使用 KEM 通过两个方之间的非对称或公钥密码学来保护对称密钥。发送方调用 encapsulate 方法生成一个秘密密钥和密钥封装消息接收方调用 decapsulate 方法从密钥封装消息中恢复相同的密钥。
2.9 .1 准备 Preparation 接收方需要使用 KeyPairGenerator 创建一个密钥对。公钥被公布并提供给发送方私钥则保密。
2.9 .2 创建 KEM 对象 Creating KEM Objects 每个方都需要创建一个 KEM 对象。KEM 对象是通过使用 KEM 类的静态工厂方法之一 getInstance() 来创建的。请参阅“如何请求和供应服务提供者实现”。
2.9 .3 创建封装器和解封装器 Creating an Encapsulator and a Decapsulator 在发送方调用 KEM 对象的 newEncapsulator 方法之一来创建一个封装器。在此过程中使用接收方的公钥。在接收方调用 KEM 对象的 newDecapsulator 方法之一来创建一个解封装器。在此过程中使用接收方的私钥。
2.9 .4 封装和解封装 Encapsulation and Decapsulation 发送方在其新创建的 KEM.Encapsulator 对象中调用 encapsulate 方法之一该方法返回一个 KEM.Encapsulated 对象。KEM.Encapsulated 对象中的秘密密钥保密其中的密钥封装消息被发送给接收方。 接收方将发送方的密钥封装消息传递给新创建的 KEM.Decapsulator 对象的 decapsulate 方法之一该方法返回一个 SecretKey 对象。这个秘密密钥与发送方的秘密密钥相同。 发送方可以使用该密钥与接收方进行未来安全通信。 请参阅“封装和解封装密钥”的代码示例。
2.10 秘钥接口 Key Interfaces java.security.Key 接口是所有不透明密钥的最高级别接口。它定义了所有不透明密钥对象共享的功能。 到目前为止我们一直关注 JCAJava 加密体系结构的高级用途而没有深入探讨密钥是什么以及它们是如何生成/表示的。现在是我们关注密钥的时候了。 不透明密钥表示是指您无法直接访问构成密钥的密钥材料。换句话说“不透明”的只给您有限的密钥访问权限——只有 Key 接口定义的三个方法getAlgorithm、getFormat 和 getEncoded。 这与透明表示形成对比在透明表示中您可以通过相应的 KeySpec 接口中定义的 get 方法逐个访问每个密钥材料值见 “KeySpec 接口”。
所有不透明密钥都有三个特征 算法Algorithm该密钥的密钥算法。密钥算法通常是一个加密或非对称操作算法如 AES、DSA 或 RSA它将与这些算法以及相关算法如 SHA256withRSA一起工作。密钥的算法名称是使用以下方法获得的 String getAlgorithm()编码形式Encoded Form当需要在 Java 虚拟机外部的标准表示时使用的密钥的外部编码形式例如在将密钥传输给其他方时。密钥根据标准格式如 X.509 或 PKCS8进行编码并使用以下方法返回 byte[] getEncoded()格式Format编码密钥的格式名称。它由以下方法返回 String getFormat()通常密钥是通过密钥生成器如 KeyGenerator 类和 KeyPairGenerator 类、证书、密钥规范见 “KeySpec 接口”使用 KeyFactory或者通过访问用于管理密钥的密钥库数据库的密钥库实现来获得的。使用 KeyFactory还可以以算法依赖的方式解析编码的密钥。 还可以使用 CertificateFactory 解析证书。 以下是 java.security.interfaces 和 javax.crypto.interfaces 包中扩展 Key 接口的接口列表
SecretKeyPBEKeyPrivateKeyDHPrivateKeyDSAPrivateKeyECPrivateKeyRSAMultiPrimePrivateCrtKeyRSAPrivateCrtKeyRSAPrivateKeyPublicKeyDHPublicKeyDSAPublicKeyECPublicKeyRSAPublicKey PublicKey 和 PrivateKey 接口 PublicKey 和 PrivateKey 接口这两个接口都扩展了 Key 接口是无方法的接口用于类型安全性和类型识别。
2.11 密钥对类 The KeyPair Class KeyPair类是密钥对公钥和私钥的简单持有者。 它有两种公共方法一种用于返回私钥另一种用于归还公钥
PrivateKey getPrivate()
PublicKey getPublic()2.12 秘钥接口规范和类 Key Specification Interfaces and Classes 密钥对象和密钥规范KeySpecs是密钥数据的两种不同表示形式。密码机使用密钥对象来初始化它们的加密算法但密钥可能需要转换为更便携的格式以进行传输或存储。 透明表示的密钥意味着您可以通过相应的规范类中定义的某个 get 方法单独访问每个密钥材料值。例如DSAPrivateKeySpec 定义了 getX、getP、getQ 和 getG 方法用于访问私钥 x以及用于计算密钥的 DSA 算法参数素数 p、子素数 q 和基数 g。如果密钥存储在硬件设备上其规范可能包含有助于在设备上识别密钥的信息。 这种表示与 Key Interfaces 接口定义的不透明表示形成对比在不透明表示中您无法直接访问密钥材料字段。换句话说“不透明”表示只给您有限的密钥访问权限——只有 Key 接口定义的三个方法getAlgorithm、getFormat 和 getEncoded。 密钥可以以算法特定的方式指定或以算法独立的编码格式如 ASN.1指定。例如DSA 私钥可以通过其组成部分 x、p、q 和 g 来指定见 DSAPrivateKeySpec或者也可以使用其 DER 编码来指定见 PKCS8EncodedKeySpec。
emsp;emsp;KeyFactory 类和 SecretKeyFactory 类可以用来在不透明和透明密钥表示之间进行转换即密钥和密钥规范之间假设操作是可能的。例如智能卡上的私钥可能无法离开卡片。这样的密钥是不可转换的。 在以下部分中我们将讨论 java.security.spec 包中的密钥规范接口和类。
2.12.1 密钥规范接口KeySpec Interface 这个接口不包含任何方法或常量。它的唯一目的是对所有密钥规范进行分组并提供类型安全性。所有密钥规范都必须实现这个接口。
2.12.2 密钥规范子接口 The KeySpec Subinterfaces Like the Key interface, there are a similar set of KeySpec interfaces. SecretKeySpecEncodedKeySpec PKCS8EncodedKeySpecX509EncodedKeySpec DESKeySpecDESedeKeySpecPBEKeySpecDHPrivateKeySpecDSAPrivateKeySpecECPrivateKeySpecRSAPrivateKeySpec RSAMultiPrimePrivateCrtKeySpecRSAPrivateCrtKeySpec DHPublicKeySpecDSAPublicKeySpecECPublicKeySpecRSAPublicKeySpec
2.12.3 编码密钥规范类EncodedKeySpec Class 这个抽象类实现了 KeySpec 接口表示编码格式的公钥或私钥。它的 getEncoded 方法返回编码的密钥
abstract byte[] getEncoded();它的 getFormat 方法返回编码格式的名称
abstract String getFormat();请参阅下一部分关于具体实现 PKCS8EncodedKeySpec 和 X509EncodedKeySpec 的内容。
2.12.4 PKCS8EncodedKeySpec 类 这个类是 EncodedKeySpec 的子类表示根据 PKCS8 标准指定格式的私钥的 DER 编码。 它的 getEncoded 方法返回根据 PKCS8 标准编码的密钥字节。它的 getFormat 方法返回字符串 “PKCS#8”。
2.12.5 X509EncodedKeySpec 类 这个类是 EncodedKeySpec 的子类表示根据 X.509 标准指定格式的公钥的 DER 编码。 它的 getEncoded 方法返回根据 X.509 标准编码的密钥字节。它的 getFormat 方法返回字符串 “X.509”。
2.13 Generators and Factories Java和JCA API的新手有时可能不理解生成器generators和工厂factories之间的区别。
Figure 2-10 Generators and Factories 生成器用于生成全新的对象。生成器可以以算法依赖或算法独立的方式进行初始化。例如要创建一个Diffie-HellmanDH密钥对应用程序可以指定必要的P和G值或者生成器可以仅用适当的密钥长度进行初始化生成器将根据参数生成全新的密钥。 另一方面工厂用于将一种现有对象类型的数据转换为另一种。例如应用程序可能有DH私钥的组成部分并且可以将其包装为密钥规范接口The KeySpec Interface但需要将其转换为PrivateKey对象以便KeyAgreement对象使用反之亦然。或者他们可能有证书的字节数组但需要使用CertificateFactory将其转换为X509Certificate对象。应用程序使用工厂对象进行转换。
2.13.1 秘钥工厂 KeyFactory 类 The KeyFactory Class KeyFactory类是设计用于执行不透明密码学密钥接口和密钥规范接口与类密钥材料的透明表示之间转换的引擎类。 Figure 2-11 KeyFactory Class Key工厂是双向的。它们允许您根据给定的密钥规范密钥材料构建不透明的密钥对象或者以适当的格式检索密钥对象的底层密钥材料。 对于相同的密钥可以存在多个兼容的密钥规范。例如DSA公钥可以通过其组成部分y、p、q和g指定见java.security.spec.DSAPublicKeySpec或者也可以使用X.509标准中的DER编码指定见The X509EncodedKeySpec Class。 密钥工厂可用于在兼容的密钥规范之间进行转换。密钥解析可以通过在兼容的密钥规范之间进行转换来实现例如当您从X509EncodedKeySpec转换到DSAPublicKeySpec时您基本上是将编码的密钥解析为其组成部分。有关示例请参见“使用密钥规范和KeyFactory生成/验证签名”部分的末尾。
2.13.1.1 创建 KeyFactory 对象 Creating a KeyFactory Object KeyFactory对象是通过使用KeyFactorygetInstance()静态工厂方法之一获得的。见“如何请求和供应服务提供者实现”。
2.13.1.2 密钥规范与密钥对象之间的转换 Converting Between a Key Specification and a Key Object 如果您有公钥的密钥规范可以使用generatePublic方法从规范获取不透明的PublicKey对象
PublicKey generatePublic(KeySpec keySpec)类似地如果您有私钥的密钥规范可以使用generatePrivate方法从规范获取不透明的PrivateKey对象
PrivateKey generatePrivate(KeySpec keySpec)2.13.1.3 密钥对象与密钥规范之间的转换 Converting Between a Key Object and a Key Specification 如果您有一个Key对象可以通过调用getKeySpec方法获取相应的密钥规范对象
KeySpec getKeySpec(Key key, Class keySpec)keySpec标识应返回密钥材料的规范类。例如它可以是DSAPublicKeySpec.class表示密钥材料应以DSAPublicKeySpec类的实例返回。见“使用密钥规范和KeyFactory生成/验证签名”。
2.13.2 SecretKeyFactory 类 The SecretKeyFactory Class SecretKeyFactory类表示秘密密钥的工厂。与KeyFactory类不同见The KeyFactory Classjavax.crypto.SecretKeyFactory对象仅操作秘密对称密钥而java.security.KeyFactory对象处理密钥对的公钥和私钥组件。 Figure 2-12 SecretKeyFactory Class Key工厂用于将Key接口java.security.Key的不透明密码学密钥转换为Key规范接口和类以适当格式的底层密钥材料的透明表示反之亦然。 java.security.Key的类型的对象其中java.security.PublicKey、java.security.PrivateKey和javax.crypto.SecretKey是子类是不透明的密钥对象因为您无法了解它们的实现方式。底层实现是提供者依赖的可能是基于软件或硬件。密钥工厂允许提供者提供自己实现的密码学密钥。 例如如果您有一个由公共值y、素数模数p和基数g组成的Diffie-Hellman公钥规范并且您将相同的规范输入不同提供者的Diffie-Hellman密钥工厂生成的PublicKey对象很可能具有不同的底层实现。 提供者应记录其秘密密钥工厂支持的密钥规范。例如SunJCE提供者提供的DES密钥的SecretKeyFactory支持DESKeySpec作为DES密钥的透明表示DES-EDE密钥的SecretKeyFactory支持DESedeKeySpec作为DES-EDE密钥的透明表示PBE的SecretKeyFactory支持PBEKeySpec作为底层密码的透明表示。 以下是一个示例演示如何使用SecretKeyFactory将秘密密钥数据转换为SecretKey对象该对象可用作后续Cipher操作
// 注意以下字节不是现实的秘密密钥数据
// 字节仅作为使用数据的示例
// 字节密钥材料您已经拥有以构建DESedeKeySpec。byte[] desEdeKeyData getKeyData();
DESedeKeySpec desEdeKeySpec new DESedeKeySpec(desEdeKeyData);
SecretKeyFactory keyFactory SecretKeyFactory.getInstance(DESede);
SecretKey secretKey keyFactory.generateSecret(desEdeKeySpec);在这种情况下SecretKey的底层实现基于KeyFactory的提供者。 从相同的密钥材料创建功能等效的SecretKey对象的另一种提供者独立的方法是使用javax.crypto.spec.SecretKeySpec类它实现了javax.crypto.SecretKey接口
byte[] aesKeyData getKeyData();
SecretKeySpec secretKey new SecretKeySpec(aesKeyData, AES);2.13.2.1 创建 SecretKeyFactory 对象 Creating a SecretKeyFactory Object SecretKeyFactory对象是通过使用SecretKeyFactory getInstance()静态工厂方法之一获得的。见“如何请求和供应服务提供者实现”。
2.13.2.2 密钥规范与Secret Key对象之间的转换 Converting Between a Key Specification and a Secret Key Object 如果您有秘密密钥的密钥规范可以使用generateSecret方法从规范获取不透明SecretKey对象
SecretKey generateSecret(KeySpec keySpec)2.13.2.3 Secret Key对象与密钥规范之间的转换 Converting Between a Secret Key Object and a Key Specification 如果您有SecretKey对象可以通过调用getKeySpec方法获取相应的密钥规范对象
KeySpec getKeySpec(Key key, Class keySpec)keySpec标识应返回密钥材料的规范类。例如它可以是DESKeySpec.class表示密钥材料应以DESKeySpec类的实例返回。
2.13.3 KeyPairGenerator 类 The KeyPairGenerator Class KeyPairGenerator类是一个引擎类用于生成公钥和私钥对。 Figure 2-13 KeyPairGenerator Class 有两种生成密钥对的方式以算法独立的方式和以算法特定的方式。两者之间的唯一区别在于对象的初始化。 见“生成一对密钥”以获取调用KeyPairGenerator方法的示例。
2.13.3.1 创建 KeyPairGenerator Creating a KeyPairGenerator 所有密钥对生成都从KeyPairGenerator开始。KeyPairGenerator对象是通过使用KeyPairGenerator getInstance()静态工厂方法之一获得的。见“如何请求和供应服务提供者实现”。
2.13.3.2 初始化 KeyPairGenerator Initializing a KeyPairGenerator 特定算法的密钥对生成器创建可与该算法一起使用的公钥/私钥对。它还将算法特定的参数与生成的每个密钥关联。 在生成密钥之前密钥对生成器需要被初始化。在大多数情况下算法独立的初始化就足够了。但在其他情况下可以使用算法特定的初始化。
2.13.3.3 算法独立初始化 Algorithm-Independent Initialization 所有密钥对生成器共享密钥大小和随机源的概念。对于不同的算法密钥大小的解释不同。例如在DSA算法中密钥大小对应于模数的长度。 一个initialize方法接受两个普遍共享类型的参数
void initialize(int keysize, SecureRandom random)另一个initialize方法只接受密钥大小参数它使用系统提供的随机源
void initialize(int keysize)由于在调用这些算法独立的initialize方法时没有指定其他参数因此提供者将决定如何处理要与每个密钥关联的算法特定参数如果有。 如果算法是“DSA”算法且模数大小密钥大小是512、768、1024、2048或3072则SUN提供者使用一组预计算的p、q和g参数值。如果模数大小不是这些值之一则SUN提供者会创建一组新的参数。其他提供者可能对不仅仅是前述三种模数大小有预计算的参数集。还有一些可能根本没有预计算的参数列表而是总是创建新的参数集。
2.13.3.4 算法特定初始化 Algorithm-Specific Initialization 对于已经存在一组算法特定参数的情况例如DSA中的“社区参数”有两个initialize方法具有AlgorithmParameterSpec接口参数。一个还有SecureRandom参数而另一个的随机源由系统提供
void initialize(AlgorithmParameterSpec params, SecureRandom random)void initialize(AlgorithmParameterSpec params)2.13.3.5 生成密钥对 Generating a Key Pair 生成密钥对的过程始终相同无论初始化和算法如何。您总是从KeyPairGenerator调用以下方法
KeyPair generateKeyPair()对generateKeyPair的多次调用将产生不同的密钥对。
2.13.4 KeyGenerator 类 The KeyGenerator Class 密钥生成器用于为对称算法生成秘密密钥。 Figure 2-14 The KeyGenerator Class
2.13.4.1 创建KeyGenerator对象 Initializing a KeyGenerator Object KeyGenerator对象是通过使用KeyGenerator getInstance()静态工厂方法之一获得的。见“如何请求和供应服务提供者实现”。
2.13.4.2 初始化 KeyGenerator 对象 Algorithm-Independent Initialization 特定对称密钥算法的密钥生成器创建可与该算法一起使用的对称密钥。它还将算法特定的参数如果有与生成的密钥关联。 有两种生成密钥的方式以算法独立的方式和以算法特定的方式。两者之间的唯一区别在于对象的初始化
2.13.4.3 生成秘钥 Creating a Key 以下方法生成密钥
public SecretKey generateKey();2.14 密钥协商 The KeyAgreement Class 密钥协商是一种协议通过它两个或多个方可以在不交换任何秘密信息的情况下建立相同的加密密钥。 Figure 2-15 The KeyAgreement Class 每个参与方都用其私钥初始化他们的密钥协商对象然后输入将参与通信的每个方的公钥。在大多数情况下只有两方参与但像 Diffie-Hellman 这样的算法允许多个方3个或更多参与。当所有公钥都输入完毕后每个 KeyAgreement 对象将生成协商相同的密钥。 KeyAgreement 类提供了密钥协商协议的功能。用于建立共享秘密的密钥是由一个密钥生成器KeyPairGenerator 或 KeyGenerator、KeyFactory 或作为密钥协商协议中间阶段的结果创建的。
2.14.1 创建 KeyAgreement 对象 Creating a KeyAgreement Object 每个参与密钥协商的方都必须创建一个 KeyAgreement 对象。KeyAgreement 对象是通过使用 KeyAgreement 类的静态工厂方法之一 getInstance() 来获取的。见“如何请求和供应服务提供者实现”。
2.14.2 初始化 KeyAgreement 对象 Initializing a KeyAgreement Object 您用您的私有信息初始化一个 KeyAgreement 对象。在 Diffie-Hellman 的情况下您用您的 Diffie-Hellman 私钥来初始化它。其他初始化信息可能包含一个随机源和/或一组算法参数。请注意如果所请求的密钥协商算法需要指定算法参数并且只提供了密钥而没有提供参数来初始化 KeyAgreement 对象则密钥必须包含所需的算法参数。例如Diffie-Hellman 算法使用素数模 p 和基生成元 g 作为其参数。 要初始化 KeyAgreement 对象请调用它的一个 init 方法
public void init(Key key);
public void init(Key key, SecureRandom random);
public void init(Key key, AlgorithmParameterSpec params);
public void init(Key key, AlgorithmParameterSpec params, SecureRandom random);2.14.3 执行密钥协商阶段 Executing a KeyAgreement Phase 每个密钥协商协议都由一系列需要参与的每个方执行的阶段组成。 要执行密钥协商的下一个阶段请调用 doPhase 方法
public Key doPhase(Key key, boolean lastPhase);key 参数包含该阶段要处理的密钥。在大多数情况下这是密钥协商中其他方的公钥或是由前一阶段生成的中间密钥。doPhase 可能会返回一个中间密钥您可能需要将其发送给密钥协商的其他方以便他们在后续阶段进行处理。 lastPhase 参数指定要执行的阶段是否是密钥协商中的最后一个阶段值为 FALSE 表示这不是密钥协商的最后一个阶段还有更多阶段要跟随值为 TRUE 表示这是密钥协商的最后一个阶段并且密钥协商已完成即下一步可以调用 generateSecret。 在两方之间的 Diffie-Hellman 密钥交换示例中您调用一次 doPhase并将 lastPhase 设置为 TRUE。在三方之间的 Diffie-Hellman 示例中您调用 doPhase 两次第一次将 lastPhase 设置为 FALSE第二次将 lastPhase 设置为 TRUE。
2.14.4 生成共享密钥 Generating the Shared Secret 在每个参与方都执行了所有必需的密钥协商阶段之后它可以通过调用 generateSecret 方法之一来计算共享密钥
public byte[] generateSecret();
public int generateSecret(byte[] sharedSecret, int offset);
public SecretKey generateSecret(String algorithm);2.15 秘钥管理 Key Management 数据库称为“密钥库”keystore可用于管理密钥和证书的存储库。证书是一种由一个实体数字签名的声明表明另一个实体的公钥具有特定的值。
2.15.1 密钥库位置 Keystore Location 用户密钥库默认存储在用户主目录中的一个名为 .keystore 的文件中主目录由 user.home 系统属性确定其默认值取决于操作系统
Linux 和 macOS/home/username/WindowsC:\Users\username\ 当然密钥库文件可以按需定位。在某些环境中可能需要存在多个密钥库。例如一个密钥库可能保存用户的私钥另一个可能保存用于建立信任关系的证书。 除了用户的密钥库JDK 还维护一个系统范围的密钥库用于存储来自各种证书颁发机构CA的信任证书。这些 CA 证书可用于帮助做出信任决策。例如在 SSL/TLS/DTLS 中当 SunJSSE 提供者遇到来自远程对等方的证书时默认的 trustmanager 将查阅以下文件之一以确定连接是否可信
Linux 和 macOSjava-home/lib/security/cacertsWindowsjava-home\lib\security\cacerts 应用程序可以设置和使用自己的密钥库或者甚至使用前面描述的用户密钥库而不是使用系统范围的 cacerts 密钥库。
2.15.2 密钥库实现 Keystore Implementation
emsp;emsp; KeyStore 类提供了访问和修改密钥库信息的明确定义的接口。可能存在多个不同的具体实现其中每个实现是针对特定类型的密钥库。 目前有两个命令行工具使用 KeyStorekeytool 和 jarsigner。它还由策略引用实现使用当它处理指定来自各种来源的代码的权限允许访问系统资源的策略文件时。由于 KeyStore 是公开可用的JDK 用户可以编写使用它的其他安全应用程序。 应用程序可以从不同的提供者选择不同类型的密钥库实现使用 KeyStore 类中的 getInstance 工厂方法。密钥库类型定义了密钥库信息的存储和数据格式以及用于保护密钥库中的私钥和密钥库本身完整性的算法。不同类型的密钥库实现是不兼容的。 默认的密钥库实现是 “pkcs12”。这是一个基于 RSA PKCS12 个人信息交换语法标准的跨平台密钥库。此标准主要用于存储或传输用户的私钥、证书和各种秘密。PKCS12 密钥库中的个别条目可以关联任意属性。
keystore.typepkcs12要让工具和其他应用程序使用不同的默认密钥库实现您可以更改该行以指定不同的类型。 一些应用程序如 keytool还允许您覆盖默认的密钥库类型通过命令行参数 -storetype。 注意密钥库类型指定不区分大小写。例如“jks” 被视为与 “JKS” 相同。 PKCS12 是默认且推荐的密钥库类型。然而JDK 实现还包括其他三种类型的密钥库。
“jceks” 是 “jks” 的另一种专有密钥库格式使用带有三重 DES 的基于密码的加密。“jks” 实现将密钥库作为文件实现使用专有的密钥库类型格式。它使用自己的单独密码保护每个私钥并还使用一个可能不同的密码保护整个密钥库的完整性。“dks” 是一个域密钥库。它是一个密钥库的集合表现为单个逻辑密钥库。由给定域组成的密钥库由配置数据指定其语法在 DomainLoadStoreParameter 中描述。 密钥库实现是基于提供者的。如果你想编写自己的 KeyStore 实现请参见 Java 加密体系结构中的 “如何实现提供者”。
2.15.3 秘钥存储类 The KeyStore Class KeyStore 类提供了访问和修改密钥库信息的明确定义的接口。 KeyStore 类是一个引擎类和算法。 Figure 2-16 KeyStore Class 这个类代表内存中的密钥和证书集合。KeyStore 管理两种类型的条目 密钥条目这种类型的密钥库条目保存非常敏感的加密密钥信息必须防止未经授权的访问。通常存储在这种类型的条目中的密钥是秘密密钥或者是私钥以及认证相应公钥的证书链。 受信任的证书条目这种类型的条目包含属于另一方的单个公钥证书。之所以称为受信任的证书是因为密钥库所有者信任证书中的公钥确实属于证书的主题所有者所识别的身份。 每种类型的条目都可以通过 “别名” 字符串在密钥库中标识。在私钥及其关联的证书链的情况下这些字符串区分实体可能用于自我认证的不同方式。例如实体可能使用不同的证书颁发机构或使用不同的公钥算法进行自我认证。 密钥库是否持久化以及如果密钥库持久化密钥库使用的机制在这里没有指定。这种约定允许使用各种技术来保护敏感例如私钥或秘密密钥。智能卡或其他集成的加密引擎SafeKeyper是一个选项也可以使用更简单的机制如文件以各种格式。 以下是主要的 KeyStore 方法的描述。
2.15.3.1 创建 KeyStore 对象 Creating a KeyStore Object KeyStore 对象是通过使用 KeyStore 的 getInstance() 方法之一获得的。见“如何请求和供应服务提供者实现”。
2.15.3.2 加载特定的密钥库到内存 Loading a Particular Keystore into Memory 在 KeyStore 对象使用之前必须通过 load 方法将实际的密钥库数据加载到内存中
final void load(InputStream stream, char[] password)可选的密码用于检查密钥库数据的完整性。如果没有提供密码则不执行完整性检查。 要创建一个空的密钥库将 null作为InputStream参数传递给load 方法。 通过传递 DomainLoadStoreParameter到替代的load 方法来加载 DKS 密钥库
final void load(KeyStore.LoadStoreParameter param)2.15.3.3 获取密钥库别名列表 Getting a List of the Keystore Aliases 所有密钥库条目都通过唯一的别名访问。aliases 方法返回密钥库中别名名称的枚举
final EnumerationString aliases()2.15.3.4 确定密钥库条目类型 Determining Keystore Entry Types 如 KeyStore 类所述密钥库中有两种不同类型的条目。以下方法分别确定由给定别名指定的条目是密钥/证书还是受信任的证书条目
final boolean isKeyEntry(String alias)
final boolean isCertificateEntry(String alias)2.15.3.5 添加/设置/删除密钥库条目 Adding/Setting/Deleting Keystore Entries setCertificateEntry 方法将证书分配给指定的别名
final void setCertificateEntry(String alias, Certificate cert)如果别名不存在将创建一个带有该别名的受信任证书条目。如果别名存在并标识一个受信任的证书条目则与它关联的证书将被 cert 替换。
emsp;emsp;setKeyEntry 方法添加如果别名尚未存在或设置密钥条目
final void setKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
final void setKeyEntry(String alias, byte[] key, Certificate[] chain)在以字节数组形式的密钥方法中它是受保护格式的密钥字节。例如在 SUN 提供者提供的密钥库实现中预期密钥字节数组包含一个受保护的私钥按照 PKCS8 标准编码为 EncryptedPrivateKeyInfo。在另一种方法中密码是用来保护密钥的密码。 deleteEntry 方法用于删除条目
final void deleteEntry(String alias)PKCS #12 密钥库支持包含任意属性的条目。使用 PKCS12Attribute 类创建属性。创建新的密钥库条目时使用接受属性的构造方法。最后使用以下方法将条目添加到密钥库
final void setEntry(String alias, Entry entry, ProtectionParameter protParam)2.15.3.6 从密钥库获取信息 Getting Information from the Keystore
emsp;emsp;getKey 方法返回与给定别名关联的密钥。使用给定的密码恢复密钥
final Key getKey(String alias, char[] password)以下方法分别返回与给定别名关联的证书或证书链
final Certificate getCertificate(String alias)
final Certificate[] getCertificateChain(String alias)您可以通过以下方式确定证书与给定证书匹配的第一个条目的名称别名
final String getCertificateAlias(Certificate cert)PKCS #12 密钥库支持包含任意属性的条目。使用以下方法检索可能包含属性的条目
final Entry getEntry(String alias, ProtectionParameter protParam)然后使用 KeyStore.Entry.getAttributes方法提取这些属性并使用KeyStore.Entry.Attribute 接口的方法进行检查。
2.15.3.7 保存密钥库 Saving the KeyStore 可以通过 store 方法保存内存中的密钥库
final void store(OutputStream stream, char[] password)密码用于计算密钥库数据的
final void store(KeyStore.LoadStoreParameter param)2.16 算法参数类 Algorithm Parameters Classes 像密钥和密钥规范一样算法的初始化参数由 AlgorithmParameters 或 AlgorithmParameterSpecs 表示。 根据使用情况算法可以直接使用参数或者可能需要将参数转换为更便携的格式以进行传输或存储。 通过 AlgorithmParameterSpec 对参数集的透明表示意味着您可以单独访问集合中的每个参数值。您可以通过相应规范类中定义的 get 方法来访问这些值例如DSAParameterSpec 定义了 getP、getQ 和 getG 方法分别用于访问 p、q 和 g。 与此相反AlgorithmParameters 类类提供不透明的表示您无法直接访问参数字段。您只能获取与参数集相关联的算法名称通过 getAlgorithm和参数集的某种编码通过 getEncoded。
2.16.1 透明算法参数接口 The AlgorithmParameterSpec Interface AlgorithmParameterSpec 是一个用于透明指定密码学参数的接口。这个接口不包含任何方法或常量。它的唯一目的是对所有参数规范进行分组并提供类型安全性。所有参数规范都必须实现此接口。 以下是 java.security.spec 和 javax.crypto.spec 包中的算法参数规范接口和类
DHParameterSpecDHGenParameterSpecDSAParameterSpecECGenParameterSpecECParameterSpecGCMParameterSpecIvParameterSpecMGF1ParameterSpecOAEPParameterSpecPBEParameterSpecPSSParameterSpecRC2ParameterSpecRC5ParameterSpecRSAKeyGenParameterSpec 以下算法参数规范用于 XML 数字签名
Interface C14NMethodParameterSpecInterface DigestMethodParameterSpecInterface SignatureMethodParameterSpecInterface TransformParameterSpecInterface ExcC14NParameterSpecInterface HMACParameterSpecInterface XPathFilter2ParameterSpecInterface XPathFilterParameterSpecXSLTTransformParameterSpec
2.16.2 算法参数类 The AlgorithmParameters Class
emsp;emsp;AlgorithmParameters 类提供密码学参数的不透明表示。
2.16.2.1 创建 AlgorithmParameters 对象 Creating an AlgorithmParameters Object 通过使用 AlgorithmParameters 的静态工厂方法之一 getInstance() 来获取 AlgorithmParameters 对象。更多信息请参见“如何请求和供应服务提供者实现”。
2.16.2.2 初始化 AlgorithmParameters 对象 Initializing an AlgorithmParameters Object 实例化 AlgorithmParameters 对象后必须通过调用 init 方法使用适当的参数规范或参数编码来初始化
void init(AlgorithmParameterSpec paramSpec)
void init(byte[] params)
void init(byte[] params, String format)在这些 init 方法中params 是包含编码参数的数组format 是解码格式的名称。在只有 params 参数而没有 format 参数的 init 方法中使用参数的主要解码格式。如果参数存在 ASN.1 规范则主要解码格式为 ASN.1。
2.16.2.3 获取编码的参数 Obtaining the Encoded Parameters 可以通过调用 getEncoded 获取 AlgorithmParameters 对象中表示的参数的字节编码
byte[] getEncoded()此方法以主要编码格式返回参数。如果参数存在 ASN.1 规范则参数的主要编码格式为 ASN.1。 如果需要以指定的编码格式返回参数请使用
byte[] getEncoded(String format)如果 format 是 null则使用参数的主要编码格式如另一个 getEncoded 方法。
2.16.2.4 将 AlgorithmParameters 对象转换为透明规范 Converting an AlgorithmParameters Object to a Transparent Specification
可以通过调用 getParameterSpec 从 AlgorithmParameters 对象获取算法参数的透明参数规范
AlgorithmParameterSpec getParameterSpec(Class paramSpec)paramSpec 标识应返回参数的规范类。规范类可以是 DSAParameterSpec.class表示参数应以 DSAParameterSpec 类的实例返回。这个类在 java.security.spec 包中。
2.16.3 AlgorithmParameterGenerator 类 The AlgorithmParameterGenerator Class
AlgorithmParameterGenerator 类是一个引擎类和算法用于生成一组适用于特定算法的全新参数在创建 AlgorithmParameterGenerator 实例时指定算法。当您没有现有的算法参数集并且希望从头开始生成一个时使用此对象。
2.16.3.1 创建 AlgorithmParameterGenerator 对象 Creating an AlgorithmParameterGenerator Object
通过使用 AlgorithmParameterGenerator 的静态工厂方法之一 getInstance() 来获取 AlgorithmParameterGenerator 对象。见“如何请求和供应服务提供者实现”。
2.16.3.2 初始化 AlgorithmParameterGenerator 对象 Initializing an AlgorithmParameterGenerator Object
AlgorithmParameterGenerator 对象可以通过两种不同的方式进行初始化算法独立方式或算法特定方式。
算法独立方法使用所有参数生成器共享的概念“大小”和随机源。所有算法参数都普遍共享大小的概念尽管它对不同算法的解释不同。例如在 DSA 算法的参数中“大小”对应于素数模数的大小以位为单位。请参阅 Java 安全标准算法名称了解更多关于特定算法的大小。使用这种方法时如果有任何算法特定的参数生成值则默认为某些标准值。一个 init 方法接受这两种普遍共享类型的参数
void init(int size, SecureRandom random);另一个 init 方法只接受大小参数并使用系统提供的随机源
void init(int size)第三种方法使用算法特定的语义来初始化参数生成器对象这些语义由 AlgorithmParameterSpec 对象中提供的一组算法特定的参数生成值表示
void init(AlgorithmParameterSpec genParamSpec, SecureRandom random)void init(AlgorithmParameterSpec genParamSpec)例如生成 Diffie-Hellman 系统参数时参数生成值通常包括素数模数的大小和随机指数的大小两者都以位为单位指定。
2.16.3.3 生成算法参数 Generating Algorithm Parameters
创建并初始化 AlgorithmParameterGenerator 对象后您可以使用 generateParameters 方法生成算法参数
AlgorithmParameters generateParameters()2.17 认证工厂类 The CertificateFactory Class CertificateFactory 类定义了一个证书工厂的功能该工厂用于从它们的编码生成证书和证书撤销列表CRL对象。
emsp;emsp;CertificateFactory 类是一个引擎类和算法。 X.509 的证书工厂必须返回实例化为 java.security.cert.X509Certificate 的证书以及实例化为 java.security.cert.X509CRL 的 CRL。
2.17.1 创建 CertificateFactory 对象 Creating a CertificateFactory Object 通过使用 getInstance() 静态工厂方法之一来获取 CertificateFactory 对象。更多信息请参见“如何请求和供应服务提供者实现”。
2.17.2 生成证书对象 Generating Certificate Objects 要生成一个证书对象并用从输入流中读取的数据初始化它请使用 generateCertificate 方法
final Certificate generateCertificate(InputStream inStream)要返回从给定输入流中读取的证书的可能为空的集合视图请使用 generateCertificates 方法
final Collection generateCertificates(InputStream inStream)2.17.3 生成 CRL 对象 Generating CRL Objects 要生成一个证书撤销列表CRL对象并用从输入流中读取的数据初始化它请使用 generateCRL 方法
final CRL generateCRL(InputStream inStream)要返回从给定输入流中读取的 CRL 的可能为空的集合视图请使用 generateCRLs 方法
final Collection generateCRLs(InputStream inStream)2.17.4 生成 CertPath 对象 Generating CertPath Objects PKIX 证书路径构建器和验证器由 Internet X.509 公钥基础设施证书和 CRL 配置文件定义RFC 5280。 用于从集合和 LDAP 目录检索证书和 CRL 的证书存储实现使用 PKIX LDAP V2 Schema也可从 IETF 作为 RFC 2587 获取。 要生成一个 CertPath 对象并用从输入流中读取的数据初始化它请使用以下 generateCertPath 方法之一指定或不指定数据的编码
final CertPath generateCertPath(InputStream inStream)final CertPath generateCertPath(InputStream inStream, String encoding)要生成一个 CertPath 对象并用证书列表初始化它请使用以下方法
final CertPath generateCertPath(List? extends Certificate certificates)要检索此证书工厂支持的 CertPath 编码列表可以调用 getCertPathEncodings 方法
final IteratorString getCertPathEncodings()默认编码将首先列出。
三、如何在SSL/TLS实现中使用JCA How the JCA Might Be Used in a SSL/TLS Implementation 理解了 JCA 类之后我们可以考虑如何将这些类结合起来实现像 SSL/TLS 这样的高级网络协议。 在 TLS 和 DTLS 协议的 “SSL/TLS 概述” 部分从高层次描述了这些协议的工作原理。由于非对称公钥密码操作比对称操作密钥慢得多因此公钥密码学被用来建立密钥然后这些密钥被用来保护实际的应用程序数据。简化地讲SSL/TLS 握手涉及交换初始化数据执行一些公钥操作以得到一个密钥然后使用该密钥来加密进一步的通信。 注意这里呈现的细节仅展示了如何使用这些类。本节不会提供足够的信息来构建 SSL/TLS 实现。有关更多信息请参见 Java 安全套接字扩展JSSE参考指南和 RFC 5246传输层安全TLS协议版本 1.2。 假设这个 SSL/TLS 实现将作为 JSSE 提供者提供。首先编写一个具体的 Provider 类实现最终将在 Security 类的提供者列表中注册。此提供者主要提供从算法名称到实际实现类的映射。即“SSLContext.TLS”-“com.foo.TLSImpl”当应用程序请求一个 “TLS” 实例通过 SSLContext.getInstance(“TLS”)将查询提供者的列表以获取请求的算法并创建一个适当的实例。 在讨论实际握手的细节之前需要快速回顾一下 JSSE 的架构。JSSE 架构的核心是 SSLContext。上下文最终创建最终对象SSLSocket 和 SSLEngine这些对象实际实现了 SSL/TLS 协议。SSLContext 用两个回调类 KeyManager 和 TrustManager 初始化这些类允许应用程序首先选择要发送的认证材料其次验证对等方发送的凭据。 JSSE KeyManager 负责选择向对等方展示的凭据。有许多算法是可能的但常见的策略是维护一个 RSA 或 DSA 公钥/私钥对以及一个 X509Certificate它们由磁盘文件支持的 KeyStore。当 KeyStore 对象被初始化并从文件中加载时文件的原始字节被使用 KeyFactory 转换为 PublicKey 和 PrivateKey 对象证书链的字节使用 CertificateFactory 转换。当需要凭据时KeyManager 只需查询此 KeyStore 对象并确定要展示哪些凭据。 KeyStore 的内容可能最初是使用像 keytool 这样的实用程序创建的。keytool 创建一个 RSA 或 DSA KeyPairGenerator 并用适当的密钥大小初始化它。然后这个生成器被用来创建一个 KeyPairkeytool 将把其连同新创建的证书一起存储在 KeyStore 中最终写入磁盘。 JSSE TrustManager 负责验证从对等方接收到的凭据。验证凭据有许多方法其中之一是创建一个 CertPath 对象并让 JDK 内置的公钥基础设施PKI框架处理验证。在内部CertPath 实现可能会创建一个 Signature 对象并使用它来验证证书链中的每个签名。 有了对架构的基本了解我们可以看看 SSL/TLS 握手中的一些步骤。客户端首先向服务器发送一个 ClientHello 消息。服务器选择一个要使用的密码套件并在 ServerHello 消息中发送回来并开始基于套件选择创建 JCA 对象。我们将在以下示例中使用仅限服务器认证。
图 2-17 SSL/TLS 消息 图 2-17 的描述如下 “图 2-17 SSL/TLS 消息” 的描述 以下示例描述了仅限服务器认证。示例极大地简化了但给出了如何将 JSSE 类结合起来创建更高级别协议的概念 示例 2-9 SSL/TLS 服务器使用基于 RSA 的密码套件如 TLS_RSA_WITH_AES_128_CBC_SHA 查询服务器的 KeyManager并返回一个适当的 RSA 条目。服务器的凭据即证书/公钥在服务器的 Certificate 消息中发送。客户端的 TrustManager 验证服务器的证书如果接受客户端使用 SecureRandom 对象生成一些随机字节。然后使用已使用服务器证书中找到的 PublicKey 初始化的加密非对称 RSA Cipher 对象进行加密。这个加密数据在 Client Key Exchange 消息中发送。服务器将使用相应的 PrivateKey 通过类似的解密模式 Cipher 恢复字节。然后这些字节被用来建立实际的加密密钥。 示例 2-10 选择一个临时 Diffie-Hellman 密钥协商算法和 DSA 签名算法如 TLS_DHE_DSS_WITH_AES_128_CBC_SHA 双方必须各自使用 KeyPairGenerator 建立一个新的临时 DH 公钥/私钥对。每个生成器创建 DH 密钥然后可以使用 KeyFactory 和 DHPublicKeySpec 类进一步转换为片段。然后每一方创建一个 KeyAgreement 对象并用它们各自的 DH 私钥初始化它。服务器在 ServerKeyExchange 消息中发送其公钥片段由 DSA 签名算法保护客户端在 ClientKeyExchange 消息中发送其公钥。当公钥使用另一个 KeyFactory 重新组装时它们被送入协议对象。然后 KeyAgreement 对象生成一致的字节然后用来建立实际的加密密钥。 一旦确定了实际的加密密钥就使用密钥初始化对称 Cipher 对象该 Cipher 用来保护所有传输中的数据。为了帮助确定数据是否已被修改创建了一个 MessageDigest 并接收了一份要发送到网络的数据的副本。当数据包完成时将摘要哈希附加到数据上整个数据包由 Cipher 加密。如果使用像 AES 这样的块密码必须对数据进行填充以构成一个完整的块。在远程端步骤只是简单地反向执行。
四、加密强度配置 Cryptographic Strength Configuration 您可以使用司法管辖区策略文件见 Jurisdiction Policy File Format和安全属性文件配置 Java 加密扩展JCE架构的加密强度。 在 Oracle Java JDK 9 之前默认的加密强度由 Oracle 实现为“强但受限”例如AES 密钥限制为 128 位。为了移除这个限制管理员可以下载并安装一个单独的“无限强度”司法管辖区策略文件包。JDK 9 对司法管辖区策略文件机制进行了重新设计现在它允许更灵活的配置。Oracle JDK 现在默认值为“无限”而不是“有限”。像往常一样管理员和用户必须继续遵循他们地理位置的所有进出口指南。现在的激活加密强度是通过安全属性通常设置在 java.security 属性文件中与配置目录中找到的司法管辖区策略文件结合确定的。 提供无限加密强度或强但有限加密强度所需的所有 JCE 策略文件都随 JDK 捆绑提供。
4.1 加密强度配置 Cryptographic Strength Settings 每个 java_home/conf/security/policy 下的目录代表一组由它们包含的司法管辖区策略文件定义的策略配置。您可以通过设置 crypto.policy 安全属性在文件 java_home/conf/security/java.security 中配置指向该目录来激活由目录中的策略文件表示的特殊加密强度设置。 注意java.security 文件中的属性通常只解析一次。如果您已修改此文件中的任何属性请重新启动应用程序以确保更改得到正确反映。 JDK 附带了两个这样的目录limited 和 unlimited每个目录都包含多个策略文件。默认情况下crypto.policy 安全属性设置为
crypto.policy unlimited总体值是目录内文件的交集。这些策略文件设置是 VM 范围的影响在此 VM 上运行的所有应用程序。如果您想覆盖应用程序级别的加密强度请参见“如何使应用程序免受加密限制”。
4.2 无限目录内容 Unlimited Directory Contents 无限目录包含以下策略文件 java_home/conf/security/unlimited/default_US_export.policy // 默认美国出口政策文件。
grant { // 对任何算法没有限制。permission javax.crypto.CryptoAllPermission;
};注意由于目前对从美国出口的加密没有限制default_US_export.policy 文件没有设置限制。 java_home/conf/security/unlimited/default_local.policy // 没有对加密强度限制的国家的特定国家政策文件。
grant { // 对任何算法没有限制。permission javax.crypto.CryptoAllPermission;
};注意根据国家的不同可能会有本地限制但由于此策略文件位于无限目录中这里没有列出任何限制。 要将这些两个文件中定义的无限加密强度设置为 crypto.policy unlimited请在文件 java_home/conf/security/java.security 中设置。
4.3 有限目录内容 Limited Directory Contents 有限目录目前包含以下策略文件 java_home/conf/security/limited/default_US_export.policy // 默认美国出口政策文件。
grant { // 对任何算法没有限制。permission javax.crypto.CryptoAllPermission;
};注意即使这在有限目录中由于目前对从美国出口的加密没有限制默认的 default_US_export.policy 文件也没有设置限制。 java_home/conf/security/limited/default_local.policy // 一些国家对加密强度有进口限制。这个策略文件
// 是全球可进口的。
grant {permission javax.crypto.CryptoPermission DES, 64;permission javax.crypto.CryptoPermission DESede, *;permission javax.crypto.CryptoPermission RC2, 128, javax.crypto.spec.RC2ParameterSpec, 128;permission javax.crypto.CryptoPermission RC4, 128;permission javax.crypto.CryptoPermission RC5, 128, javax.crypto.spec.RC5ParameterSpec, *, 12, *;permission javax.crypto.CryptoPermission RSA, *;permission javax.crypto.CryptoPermission *, 128;
};注意此本地策略文件显示了默认限制。它应该被任何国家允许包括那些有进口限制的国家但请获得法律指导。 java_home/conf/security/limited/exempt_local.policy // 一些国家对加密强度有进口限制但如果使用豁免机制
// 可能会允许这些豁免。grant {// 如果执行密钥恢复则对任何算法没有限制。permission javax.crypto.CryptoPermission *, KeyRecovery; // 如果执行密钥托管则对任何算法没有限制。permission javax.crypto.CryptoPermission *, KeyEscrow; // 如果执行密钥削弱则对任何算法没有限制。permission javax.crypto.CryptoPermission *, KeyWeakening;
};注意有进口限制的国家应该使用“limited”但如果可以采用豁免机制则这些限制可能会放宽。请参阅“如何使应用程序免受加密限制”。请为您的情况获取法律指导。
4.4 自定义加密强度设置 Custom Cryptographic Strength Settings 要设置不同于有限或无限目录中策略文件的加密强度限制您可以创建一个新的目录并与有限和无限的目录平行将您的策略文件放在那里。例如您可以创建一个名为 custom 的目录。在这个 custom 目录中您包括文件 default_export.policy 和/或 exempt_local.policy。 要选定 custom 目录中文件定义的加密强度请在文件 java_home/conf/security/java.security 中设置 crypto.policy custom。
五、Jurisdiction策略文件格式Jurisdiction Policy File Format JCA 将其司法管辖区策略文件表示为具有相应权限声明的 Java 风格策略文件。如加密强度配置所述Java 策略文件指定了来自指定代码源的代码所允许的权限。权限表示对系统资源的访问。在 JCA 的情况下“资源”是加密算法并且不需要指定代码源因为加密限制适用于所有代码。 司法管辖区策略文件由一个非常基本的“授权条目”组成其中包含一个或多个“权限条目”。
grant {permission entries;
};权限条目在司法管辖区策略文件中的格式是
permission crypto permission class name[alg_name[[, exemption mechanism name][, maxKeySize[, AlgorithmParameterSpec class name,parameters for constructing an AlgorithmParameterSpec object]]]];一个包含将 AES 算法限制为最大密钥大小为 128 位的示例司法管辖区策略文件是
grant {permission javax.crypto.CryptoPermission AES, 128;// ...
};权限条目必须以“permission”一词开始。出现在权限条目中的项目必须按指定顺序出现。条目以分号结束。标识符的大小写grant, permission不重要但 或作为值传递的任何字符串的大小写很重要。星号*可以用作任何权限条目选项的通配符。例如alg_name 选项的星号意味着“所有算法”。 以下是权限条目选项的描述 Table 2-1 Permission Entry Options
OptionDescriptioncrypto permission class name特定的权限类名称如 javax.crypto.CryptoPermission。必选。加密权限类反映了应用程序在特定环境中使用具有特定密钥大小的特定算法的能力。有两个加密权限类CryptoPermission和CryptoAllPermission。特殊的CryptoAllPermission类意味着所有与加密相关的权限也就是说它指定不存在与加密有关的限制。alg_name指定加密算法的标准名称的引用字符串如 “AES” 或 “RSA”。可选。exemption mechanism name表示豁免机制的引用字符串如果执行可以减少加密限制。可选。豁免机制名称可以包括 “KeyRecovery”、“KeyEscrow” 和 “KeyWeakening”。maxKeySize指定允许使用的最大密钥大小以位为单位的整数。可选。AlgorithmParameterSpec class name指定算法强度的类名称。可选。对于一些算法仅指定密钥大小可能不足以表示算法强度。例如在 “RC5” 算法的情况下还必须考虑轮数。对于需要以多于密钥大小的方式表示强度的算法请使用此选项指定 AlgorithmParameterSpec 类名称例如 “RC5” 算法的 javax.crypto.spec.RC5ParameterSpec。parameters for constructing an AlgorithmParameterSpec object构造指定的 AlgorithmParameterSpec 对象的参数列表。如果已指定 并且需要参数则此项必选。
六、避免程序密码限制 How to Make Applications Exempt from Cryptographic Restrictions 注意大部分应用开发者应忽略本节。它仅适用于那些应用程序可能被出口到少数几个政府强制要求加密限制的国家并且如果希望这些应用程序拥有比强制要求的更少的加密限制。 默认情况下应用程序可以使用任何强度的加密算法。然而由于少数几个国家的政府进口控制限制您可能需要限制这些算法的强度。JCA 框架包括一种能力即在不同的司法管辖区地点对应用程序可用的加密算法的最大强度进行限制。您在司法管辖区策略文件中指定这些限制。有关司法管辖区策略文件以及如何创建和配置它们的更多信息请参见加密强度配置。 一些或所有这样的国家政府可能允许某些应用程序免受一些或全部加密限制。例如他们可能考虑某些类型的应用程序为“特殊”的因此免受限制。或者他们可能会豁免任何使用“豁免机制”的应用程序例如密钥恢复。被认为免受限制的应用程序可以获得比这些国家中非豁免应用程序允许的更强的加密。 为了使应用程序在运行时被识别为“免受限制”它必须满足以下条件
它必须有一个与它一起打包在 JAR 文件中的权限策略文件。权限策略文件指定了应用程序拥有的与加密相关的权限以及如果有的话在什么条件下。包含应用程序和权限策略文件的 JAR 文件必须使用在应用程序被接受为免受限制后颁发的代码签名证书进行签名。 以下是使应用程序免受某些加密限制所需的示例步骤。这是一个基本概述包括 JCA 识别和处理应用程序为免受限制所需的信息。您需要知道您希望应用程序能够运行的特定国家或国家的豁免要求这些国家的政府要求加密限制。您还需要知道有处理免受限制应用程序流程的 JCA 框架供应商的要求。请咨询此类供应商以获取更多信息。
Write and Compile Your Application CodeCreate a Permission Policy File Granting Appropriate Cryptographic PermissionsPrepare for Testing Apply for Government Approval From the Government Mandating Restrictions.Get a Code-Signing CertificateBundle the Application and Permission Policy File into a JAR fileStep 7.1: Get a Code-Signing CertificateSet Up Your Environment Like That of a User in a Restricted Country(only for applications using exemption mechanisms) Install a Provider Implementing the Exemption Mechanism Specified by the entry in the Permission Policy File Test Your ApplicationApply for U.S. Government Export Approval If RequiredDeploy Your Application
6.1 使用豁免机制的应用程序的特殊代码要求 Special Code Requirements for Applications that Use Exemption Mechanisms 当应用程序与它有关联的权限策略文件在同一 JAR 文件中且该权限策略文件指定了豁免机制时调用 Cipher 的 getInstance 方法实例化 Cipher 时JCA 代码会搜索已安装的供应商以找到一个实现指定豁免机制的供应商。如果找到这样的供应商JCA 会实例化一个与供应商实现相关的 ExemptionMechanism API 对象然后将 ExemptionMechanism 对象与 getInstance 返回的 Cipher 关联。 实例化 Cipher 后在通过调用 Cipher 的 init 方法初始化它之前您的代码必须调用以下 Cipher 方法
public ExemptionMechanism getExemptionMechanism()此调用返回与 Cipher 关联的 ExemptionMechanism 对象。然后您必须通过调用返回的 ExemptionMechanism 上的以下方法来初始化豁免机制实现
public final void init(Key key)您提供的参数应该是与您随后将提供给 Cipher init 方法的相同类型的参数。 一旦初始化了 ExemptionMechanism您可以像通常一样继续初始化和使用 Cipher。
6.2 权限策略文件 Permission Policy Files 为了使应用程序在运行时被识别为免受一些或全部加密限制它必须有一个与它一起打包在 JAR 文件中的权限策略文件。权限策略文件指定了应用程序拥有的与加密相关的权限以及如果有的话在什么条件下。 伴随免受限制应用程序的权限策略文件中的权限条目格式与 JDK 附带下载的司法管辖区策略文件的格式相同即
permission crypto permission class name[alg_name[[, exemption mechanism name][, maxKeySize[, AlgorithmParameterSpec class name,parameters for constructing an AlgorithmParameterSpec object]]]];见司法管辖区策略文件格式。
6.3 免受限制应用程序的权限策略文件 Permission Policy Files for Exempt Applications 一些应用程序可能被允许完全没有限制。因此伴随此类应用程序的权限策略文件通常只需要包含以下内容
grant {// 对任何算法没有限制。permission javax.crypto.CryptoAllPermission;
};如果应用程序只使用单一算法或几种特定算法那么权限策略文件可以只明确提及那个算法或算法而不是授予 CryptoAllPermission。 例如如果应用程序只使用 Blowfish 算法权限策略文件不必授予所有算法的 CryptoAllPermission。它可以直接指定如果使用 Blowfish 算法则没有加密限制。为了做到这一点权限策略文件将如下所示
grant {permission javax.crypto.CryptoPermission Blowfish;
};6.4 由于豁免机制而免受限制的应用程序的权限策略文件 Permission Policy Files for Applications Exempt Due to Exemption Mechanisms 如果应用程序被认为是“免受限制”的如果执行了豁免机制那么伴随应用程序的权限策略文件必须指定一个或多个豁免机制。在运行时如果执行了这些豁免机制中的任何一个应用程序将被认为是免受限制的。每个豁免机制必须在以下格式的权限条目中指定
// 如果执行了指定的豁免机制则没有算法限制。
permission javax.crypto.CryptoPermission *,ExemptionMechanismName;其中 ExemptionMechanismName 指定了豁免机制的名称。可能的豁免机制名称列表包括
KeyRecoveryKeyEscrowKeyWeakening 例如假设您的应用程序如果执行了密钥恢复或密钥托管则被认为是免受限制的。那么您的权限策略文件应该包含以下内容
grant {// 如果执行了密钥恢复则没有算法限制。permission javax.crypto.CryptoPermission *, KeyRecovery;// 如果执行了密钥托管则没有算法限制。permission javax.crypto.CryptoPermission *, KeyEscrow;
};注意指定豁免机制的权限条目不应同时指定最大密钥大小。实际允许的密钥大小是从安装的免受限制的司法管辖区策略文件中确定的如下一节所述。
6.5 打包的权限策略文件如何影响加密权限 How Bundled Permission Policy Files Affect Cryptographic Permissions 在运行时当应用程序实例化 Cipher通过调用其 getInstance 方法并且该应用程序有一个关联的权限策略文件时JCA 会检查权限策略文件是否有适用于 getInstance 调用中指定算法的条目。如果有并且该条目授予了 CryptoAllPermission 或没有指定必须执行豁免机制这意味着对这个特定算法没有加密限制。 如果权限策略文件有适用于 getInstance 调用中指定算法的条目并且该条目确实指定必须执行豁免机制那么将检查免受限制的司法管辖区策略文件。如果免受限制的权限包括相关算法和豁免机制的条目并且该条目由与应用程序捆绑的权限策略文件中的权限所暗示并且如果有注册供应商提供了指定豁免机制的实现那么 Cipher 的最大密钥大小和算法参数值就从免受限制的权限条目中确定。 如果与应用程序捆绑的权限策略文件中没有由相关条目暗示的免受限制的权限条目或者没有任何注册供应商提供了指定豁免机制的实现那么应用程序只允许使用标准默认的加密权限。
七、标准名称 Standard Names 标准名称文档包含了有关算法规范的信息。 Java 安全标准算法名称描述了 JDK 安全 API 所要求和使用的算法、证书和密钥库类型的标准名称。它还包含了更多关于算法规范的信息。特定服务提供者的信息可以在 JDK 服务提供者文档中找到。 JDK 中的密码学实现通过几个不同的服务提供者进行分发主要是出于历史原因Sun、SunJSSE、SunJCE、SunRsaSign。请注意这些服务提供者可能不是在所有 JDK 实现中都可用因此真正可移植的应用程序应该在不指定特定服务提供者的情况下调用 getInstance()。指定特定服务提供者的应用程序可能无法利用为底层操作系统环境如 PKCS 或 Microsoft 的 CAPI优化的本地服务提供者。 SunPKCS11 服务提供者本身不包含任何密码学算法而是将请求引导到底层的 PKCS11 实现。请查阅 PKCS#11 参考指南和底层的 PKCS11 实现以确定所需的算法是否可以通过 PKCS11 服务提供者获得。同样在 Windows 系统上SunMSCAPI 服务提供者不提供任何密码学功能而是将请求路由到底层操作系统进行处理。
八、打包你的应用 Packaging Your Application 模块化应用程序的三种类型
命名模块Named or explicit module出现在模块路径上的模块在module-info.class文件中包含模块配置信息。自动模块Automatic module出现在模块路径上的模块不包含module-info.class文件中的模块配置信息本质上是一个常规的JAR文件。未命名模块Unnamed module出现在类路径上的模块。 可能有或没有module-info.class文件如果有该文件将被忽略。 建议将应用程序打包为命名模块因为它们提供了更好的性能、更强的封装性和更简单的配置。它们还提供了更大的灵活性即使在非模块化的JDK中或者通过在模块化JDK的类路径中指定它们也可以作为未命名模块使用。 有关模块的更多信息参阅 The State of the Module System and JEP 261: Module System
九、Additional JCA Code Samples
9.1 计算摘要加密对象 Computing a MessageDigest Object 这些步骤描述了计算 MessageDigest 对象的过程。 1 创建 MessageDigest 对象如下例所示
MessageDigest sha MessageDigest.getInstance(SHA-256);这个调用为 sha 变量分配一个正确初始化的摘要对象。该实现实现了安全哈希算法SHA-256如国家标准与技术研究院NIST的 FIPS 180-4 文档中定义。 2 假设我们有三个字节数组 i1、i2 和 i3它们构成了我们想要计算其消息摘要的总输入。这个摘要或“哈希”可以通过以下调用计算
sha.update(i1);
sha.update(i2);
sha.update(i3);
byte[] hash sha.digest();3 可选一个等效的替代调用序列将是
sha.update(i1);
sha.update(i2);
byte[] hash sha.digest(i3);计算消息摘要后消息摘要对象会自动重置并准备好接收新数据并计算其摘要。所有之前的状态即提供给 update 调用的数据都会丢失。
9.1.1 示例 2-11 通过克隆实现哈希 Example 2-11 Hash Implementations Through Cloning 一些哈希实现可能支持通过克隆进行中间哈希。假设我们想要分别计算以下内容的哈希值
i1i1 和 i2i1、i2 和 i3 以下是计算这些哈希的一种方法但是这段代码只有在 SHA-256 实现是可克隆的情况下才有效
/* 计算 i1 的哈希 */
sha.update(i1);
byte[] i1Hash sha.clone().digest();/* 计算 i1 和 i2 的哈希 */
sha.update(i2);
byte[] i12Hash sha.clone().digest();/* 计算 i1、i2 和 i3 的哈希 */
sha.update(i3);
byte[] i123hash sha.digest();9.1.2 示例 2-12 确定哈希实现是否可克隆Example 2-12 Determine if the Hash Implementation is Cloneable or not Cloneable 一些消息摘要的实现是可克隆的其他的则不是。要确定是否可能克隆请尝试克隆 MessageDigest 对象并捕获潜在的异常如下所示
try {// 尝试克隆它/* 计算 i1 的哈希 */sha.update(i1);byte[] i1Hash sha.clone().digest();// ...byte[] i123hash sha.digest();
} catch (CloneNotSupportedException cnse) {// 做些其他事情例如在“如果哈希实现不可克隆则计算中间摘要”部分的代码
}9.1.3 示例 2-13 如果哈希实现不可克隆则计算中间摘要Example 2-13 Compute Intermediate Digests if the Hash Implementation is not Cloneable 如果消息摘要不可克隆计算中间摘要的另一种不太优雅的方式是创建多个摘要对象。在这种情况下必须提前知道要计算的中间摘要的数量
MessageDigest md1 MessageDigest.getInstance(SHA-256);
MessageDigest md2 MessageDigest.getInstance(SHA-256);
MessageDigest md3 MessageDigest.getInstance(SHA-256);byte[] i1Hash md1.digest(i1);md2.update(i1);
byte[] i12Hash md2.digest(i2);md3.update(i1);
md3.update(i2);
byte[] i123Hash md3.digest(i3);9.2 产生秘钥对 Generating a Pair of Keys 在这个例子中我们将为名为 “DSA”数字签名算法的算法生成一对公私钥并将这对密钥对用于后续的例子中。我们将生成一个 2048 位的模数的密钥。我们不关心哪个提供者提供了算法实现。
9.2.1 创建密钥对生成器 Creating the Key Pair Generator 第一步是获取一个用于生成 DSA 算法密钥的密钥对生成器对象
KeyPairGenerator keyGen KeyPairGenerator.getInstance(DSA);9.2.2 初始化密钥对生成器 Initializing the Key Pair Generator 下一步是初始化密钥对生成器。在大多数情况下算法独立的初始化就足够了但在某些情况下使用算法特定的初始化。
9.2.2.1 算法独立初始化 Algorithm-Independent Initialization 所有的密钥对生成器都共享密钥大小和随机源的概念。KeyPairGenerator 类的初始化方法至少需要一个密钥大小。如果未明确提供随机源将使用最高优先级已安装提供者的 SecureRandom 实现。因此要生成 2048 位密钥大小的密钥只需调用
keyGen.initialize(2048);以下代码演示了如何使用特定的、另外种子化的 SecureRandom 对象
SecureRandom random SecureRandom.getInstance(DRBG, SUN);
random.setSeed(userSeed);
keyGen.initialize(2048, random);由于在调用这些算法独立初始化方法时没有指定其他参数因此提供者将决定如何处理与每个密钥关联的算法特定参数如果有。提供者可能使用预计算的参数值或者可能生成新值。
9.2.2.2 算法特定初始化 Algorithm-Specific Initialization 对于已经存在一组算法特定参数的情况例如 DSA 中的“社区参数”有两个初始化方法具有 AlgorithmParameterSpec 参数。假设您的密钥对生成器是针对 “DSA” 算法的并且您有一组 DSA 特定参数 p、q 和 g您希望使用这些参数来生成密钥对。您可以执行以下代码来初始化您的密钥对生成器回想一下 DSAParameterSpec 是一个 AlgorithmParameterSpec
DSAParameterSpec dsaSpec new DSAParameterSpec(p, q, g);
keyGen.initialize(dsaSpec);9.2.3 生成密钥对 Generating the Pair of Keys 最后一步是实际生成密钥对。无论使用哪种类型的初始化算法独立或算法特定都使用相同的代码来生成 KeyPair
KeyPair pair keyGen.generateKeyPair();9.3 使用生成的密钥生成和验证签名 Generating and Verifying a Signature Using Generated Keys 使用生成的密钥生成和验证签名的例子。 以下签名生成和验证的例子使用了在生成密钥对中生成的 KeyPair。
9.3.1 生成签名 Generating a Signature 我们首先创建一个 Signature 类对象
Signature dsa Signature.getInstance(SHA256withDSA);接下来使用在密钥对示例中生成的密钥对我们使用私钥初始化该对象然后对一个名为 data 的字节数组进行签名。
/* 使用私钥初始化对象 */
PrivateKey priv pair.getPrivate();
dsa.initSign(priv);/* 更新并签名数据 */
dsa.update(data);
byte[] sig dsa.sign();9.3.2 验证签名 Verifying a Signature 验证签名很直接。注意在这里我们也使用在密钥对示例中生成的密钥对。
/* 使用公钥初始化对象 */
PublicKey pub pair.getPublic();
dsa.initVerify(pub);/* 更新并验证数据 */
dsa.update(data);
boolean verifies dsa.verify(sig);
System.out.println(signature verifies: verifies);9.4 使用密钥规范和KeyFactory生成/验证签名 Generating/Verifying Signatures Using Key Specifications and KeyFactory 假设您拥有 DSA 私钥的组成部分x私钥、p素数、q次素数和 g基而不是拥有一对公私钥如在生成密钥对部分生成的那样。 此外假设您想使用私钥对一些数据进行数字签名这些数据存储在名为 someData 的字节数组中。您将执行以下步骤这些步骤还说明了如何创建密钥规范并使用密钥工厂从密钥规范获取 PrivateKeyinitSign 需要一个 PrivateKey
DSAPrivateKeySpec dsaPrivKeySpec new DSAPrivateKeySpec(x, p, q, g);KeyFactory keyFactory KeyFactory.getInstance(DSA);
PrivateKey privKey keyFactory.generatePrivate(dsaPrivKeySpec);Signature sig Signature.getInstance(SHA256withDSA);
sig.initSign(privKey);
sig.update(someData);
byte[] signature sig.sign();假设 Alice 想使用您签名的数据。为了让她能够这样做并验证您的签名您需要给她发送三样东西
数据签名您用来签名数据的私钥对应的公钥 您可以将 someData 字节存储在一个文件中将签名字节存储在另一个文件中并将这些文件发送给 Alice。 对于公钥假设如前一个签名示例中一样您拥有与用来签名数据的 DSA 私钥对应的 DSA 公钥的组成部分。然后您可以从这些组成部分创建 DSAPublicKeySpec
DSAPublicKeySpec dsaPubKeySpec new DSAPublicKeySpec(y, p, q, g);您仍然需要提取密钥字节以便将它们放入文件中。为此您可以首先调用前面示例中已经创建的 DSA 密钥工厂的 generatePublic 方法
PublicKey pubKey keyFactory.generatePublic(dsaPubKeySpec);然后您可以通过以下方式提取编码的密钥字节
byte[] encKey pubKey.getEncoded();现在您可以将这些字节存储在文件中并与包含数据和签名的文件一起发送给 Alice。 现在假设 Alice 已经收到了这些文件并且她将数据文件中的数据字节复制到名为 data 的字节数组中将签名文件中的签名字节复制到名为 signature 的字节数组中将公钥文件中的编码公钥字节复制到名为 encodedPubKey 的字节数组中。 Alice 现在可以执行以下代码来验证签名。该代码还说明了如何使用密钥工厂从其编码实例化 DSA 公钥initVerify 需要一个 PublicKey。
X509EncodedKeySpec pubKeySpec new X509EncodedKeySpec(encodedPubKey);KeyFactory keyFactory KeyFactory.getInstance(DSA);
PublicKey pubKey keyFactory.generatePublic(pubKeySpec);Signature sig Signature.getInstance(SHA256withDSA);
sig.initVerify(pubKey);
sig.update(data);
sig.verify(signature);注意在前一个示例中Alice 需要从编码的密钥位生成 PublicKey因为 initVerify 需要一个 PublicKey。一旦她有了 PublicKey她还可以使用 KeyFactory 的 getKeySpec 方法将其转换为 DSAPublicKeySpec以便在需要时访问组成部分如下所示
DSAPublicKeySpec dsaPubKeySpec (DSAPublicKeySpec)keyFactory.getKeySpec(pubKey, DSAPublicKeySpec.class);现在她可以通过 DSAPublicKeySpec 类上的相应 “get” 方法getY、getP、getQ 和 getG访问 DSA 公钥组成部分 y、p、q 和 g。
9.5 生成随机数 Generating Random Numbers 以下是使用 SecureRandom 类的 DRBG 实现以不同安全强度配置生成随机数的示例代码
SecureRandom drbg;
byte[] buffer new byte[32];// 可以提供任何 DRBG
drbg SecureRandom.getInstance(DRBG);
drbg.nextBytes(buffer);SecureRandomParameters params drbg.getParameters();
if (params instanceof DrbgParameters.Instantiation) {DrbgParameters.Instantiation ins (DrbgParameters.Instantiation) params;if (ins.getCapability().supportsReseeding()) {drbg.reseed();}
}// 以下调用请求一个弱 DRBG 实例。它只保证支持 112 位的安全强度。
drbg SecureRandom.getInstance(DRBG,DrbgParameters.instantiation(112, NONE, null));// 接下来的两个调用很可能会失败因为 drbg 可能是用没有预测抵抗支持的较小强度实例化的。
drbg.nextBytes(buffer,DrbgParameters.nextBytes(256, false, more.getBytes()));
drbg.nextBytes(buffer,DrbgParameters.nextBytes(112, true, more.getBytes()));// 以下调用请求一个强 DRBG 实例带有一个个性化字符串。如果成功返回实例
// 那么该实例保证支持 256 位的安全强度并提供预测抵抗。
drbg SecureRandom.getInstance(DRBG, DrbgParameters.instantiation(256, PR_AND_RESEED, hello.getBytes()));// 这个单独的调用没有请求预测抵抗但是使用了额外的输入。
drbg.nextBytes(buffer,DrbgParameters.nextBytes(-1, false, more.getBytes()));// 这个调用也是如此。
drbg.reseed(DrbgParameters.reseed(false, extra.getBytes()));9.6 确定两个密码是否相等 Determining If Two Keys Are Equal 确定两个密钥是否相等的示例代码。 在许多情况下您可能想知道两个密钥是否相等然而默认的 java.lang.Object.equals 方法可能无法给出期望的结果。最不依赖于提供者的方法是比较编码后的密钥。如果这种比较不适当例如当比较 RSAPrivateKey 和 RSAPrivateCrtKey则应该比较每个组成部分。 以下代码演示了这个想法
static boolean keysEqual(Key key1, Key key2) {if (key1.equals(key2)) {return true;}if (Arrays.equals(key1.getEncoded(), key2.getEncoded())) {return true;}// 更多关于不同类型密钥的代码在这里。// 例如以下代码可以检查// RSAPrivateKey 和 RSAPrivateCrtKey 是否相等// if ((key1 instanceof RSAPrivateKey) // (key2 instanceof RSAPrivateKey)) {// if ((key1.getModulus().equals(key2.getModulus())) // (key1.getPrivateExponent().equals(// key2.getPrivateExponent()))) {// return true;// }// }return false;
}9.7 读取Base64编码的证书 Reading Base64-Encoded Certificates 以下示例读取包含 Base64 编码证书的文件每个证书开头都以
-----BEGIN CERTIFICATE-----结尾都以
-----END CERTIFICATE-----我们把 FileInputStream它不支持标记和重置转换为 ByteArrayInputStream它支持这些方法这样每次调用 generateCertificate 只消耗一个证书并且将输入流的读取位置定位到文件中的下一个证书
try (FileInputStream fis new FileInputStream(filename);BufferedInputStream bis new BufferedInputStream(fis)) {CertificateFactory cf CertificateFactory.getInstance(X.509);while (bis.available() 0) {Certificate cert cf.generateCertificate(bis); System.out.println(cert.toString());}
}9.8 分析证书回应 Parsing a Certificate Reply 解析证书回复的示例。 以下示例解析存储在文件中的 PKCS7 格式的证书回复并提取出所有的证书
try (FileInputStream fis new FileInputStream(filename)) {CertificateFactory cf CertificateFactory.getInstance(X.509);Collection? extends Certificate c cf.generateCertificates(fis);for (Certificate cert : c) {System.out.println(cert);}// 或者使用这个聚合操作代替 for 循环// c.stream().forEach(e - System.out.println(e));
}9.9 使用加密 Using Encryption 本节引导用户完成生成密钥、创建和初始化密码对象、加密文件然后解密的过程。在整个示例中我们使用高级加密标准AES。
9.9.1 生成密钥 Generating a Key 要创建 AES 密钥我们必须实例化 AES 的 KeyGenerator。我们没有指定提供者因为我们不关心特定的 AES 密钥生成实现。由于我们没有初始化 KeyGenerator系统将使用提供的随机源和默认密钥大小来创建 AES 密钥
KeyGenerator keygen KeyGenerator.getInstance(AES);
keygen.init(128);
SecretKey aesKey keygen.generateKey();生成密钥后可以重复使用相同的 KeyGenerator 对象来创建更多的密钥。
9.9.2生成密钥 创建密码对象 Creating a Cipher 下一步是创建 Cipher 实例。我们使用 Cipher 类的 getInstance 工厂方法之一来实现。我们必须指定请求转换的名称该名称包括以下组成部分用斜杠 (/) 分隔
算法名称模式可选填充方案可选 在这个示例中我们创建了一个使用密码块链接模式和 PKCS5 风格的填充的 AES 密码对象。我们没有指定提供者因为我们不关心请求转换的特定实现。 AES 的标准算法名称是 “AES”密码块链接模式的标准名称是 “CBC”PKCS5 风格填充的标准名称是 “PKCS5Padding”
Cipher aesCipher;// 创建密码对象
aesCipher Cipher.getInstance(AES/CBC/PKCS5Padding);我们使用之前生成的 aesKey 来初始化密码对象进行加密
// 初始化密码对象进行加密
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);// 我们的明文
byte[] cleartext This is just an example.getBytes();// 加密明文
byte[] ciphertext aesCipher.doFinal(cleartext);// 检索加密期间使用的参数以便正确地
// 初始化密码对象进行解密
AlgorithmParameters params aesCipher.getParameters();// 用相同的密码对象进行解密
aesCipher.init(Cipher.DECRYPT_MODE, aesKey, params);// 解密密文
byte[] cleartext1 aesCipher.doFinal(ciphertext);cleartext 和 cleartext1 是相同的。
9.10 使用基于密码的加密 Using Password-Based Encryption 在这个示例中我们提示用户输入一个密码从中我们派生出一个加密密钥。 看起来收集并存储密码在 java.lang.String 类型的对象中似乎是合乎逻辑的。然而这里有一个问题String 类型的对象是不可变的即没有定义的方法允许您在使用后更改覆盖或清零 String 的内容。这个特性使得 String 对象不适合存储诸如用户密码这样的安全敏感信息。您应该始终使用 char 数组来收集和存储安全敏感信息。出于这个原因javax.crypto.spec.PBEKeySpec 类以 char 数组的形式获取和返回密码。 为了使用 PKCS5 中定义的基于密码的加密PBE我们必须指定一个盐值和一个迭代计数。用于加密的相同的盐值和迭代计数必须用于解密。较新的 PBE 算法至少使用 1000 的迭代计数。
PBEKeySpec pbeKeySpec;
PBEParameterSpec pbeParamSpec;
SecretKeyFactory keyFac;// 盐
byte[] salt new SecureRandom().nextBytes(16); // 盐通常是16字节// 迭代计数
int count 1000;// 创建 PBE 参数集
pbeParamSpec new PBEParameterSpec(salt, count);// 提示用户输入加密密码。
// 将用户密码作为 char 数组收集并转换
// 成 SecretKey 对象使用 PBE 密钥
// 工厂。
char[] password System.console().readPassword(Enter encryption password: );
pbeKeySpec new PBEKeySpec(password);
keyFac SecretKeyFactory.getInstance(PBEWithHmacSHA256AndAES_256);
SecretKey pbeKey keyFac.generateSecret(pbeKeySpec);// 创建 PBE 密码对象
Cipher pbeCipher Cipher.getInstance(PBEWithHmacSHA256AndAES_256);// Initialize PBE Cipher with key and parameterspbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);// Our cleartextbyte[] cleartext This is another example.getBytes();// Encrypt the cleartextbyte[] ciphertext pbeCipher.doFinal(cleartext);9.11 封装和解密密钥 Encapsulating and Decapsulating Keys 有关密钥封装和解封装的更多信息请参阅KEM类。 // Receiver sidevar kpg KeyPairGenerator.getInstance(X25519);var kp kpg.generateKeyPair();// Sender sidevar kem1 KEM.getInstance(DHKEM);var sender kem1.newEncapsulator(kp.getPublic());var encapsulated sender.encapsulate();var k1 encapsulated.key();// Receiver sidevar kem2 KEM.getInstance(DHKEM);var receiver kem2.newDecapsulator(kp.getPrivate());var k2 receiver.decapsulate(encapsulated.encapsulation());assert Arrays.equals(k1.getEncoded(), k2.getEncoded());十、Sample Programs for Diffie-Hellman Key Exchange, AES/GCM, and HMAC-SHA256 10.1 Diffie-Hellman 两方密钥交换 Diffie-Hellman Key Exchange between Two Parties 该程序运行了 Diffie-Hellman 密钥协商协议由两个参与者完成。
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.*;
// ...public class DHKeyAgreement2 {// ...public static void main(String[] args) throws Exception {// 爱丽丝创建她自己的 DH 密钥对密钥长度为 2048 位System.out.println(ALICE: 生成 DH 密钥对 ...);KeyPairGenerator aliceKpairGen KeyPairGenerator.getInstance(DH);aliceKpairGen.initialize(2048);KeyPair aliceKpair aliceKpairGen.generateKeyPair();// 爱丽丝创建并初始化她的 DH 密钥协商对象System.out.println(ALICE: 初始化 ...);KeyAgreement aliceKeyAgree KeyAgreement.getInstance(DH);aliceKeyAgree.init(aliceKpair.getPrivate());// 爱丽丝对她的公钥进行编码并将其发送给鲍勃byte[] alicePubKeyEnc aliceKpair.getPublic().getEncoded();// 转向鲍勃。鲍勃已经接收到爱丽丝的公钥// 以编码格式。// 他从编码的密钥材料实例化一个 DH 公钥。// ...}// ...
}10.2 Diffie-Hellman 三方密钥交换 Diffie-Hellman Key Exchange between Three Parties 该程序运行了 Diffie-Hellman 密钥协商协议由三个参与者完成爱丽丝、鲍勃和卡罗尔。
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
// ...public class DHKeyAgreement3 {// ...public static void main(String[] args) throws Exception {// 爱丽丝创建她自己的 DH 密钥对密钥长度为 2048 位System.out.println(ALICE: 生成 DH 密钥对 ...);KeyPairGenerator aliceKpairGen KeyPairGenerator.getInstance(DH);aliceKpairGen.initialize(2048);KeyPair aliceKpair aliceKpairGen.generateKeyPair();// 这些 DH 参数也可以通过创建一个// DHParameterSpec 对象来使用约定的值DHParameterSpec dhParamShared ((DHPublicKey)aliceKpair.getPublic()).getParams();// 鲍勃使用相同的参数创建他自己的 DH 密钥对System.out.println(BOB: 生成 DH 密钥对 ...);KeyPairGenerator bobKpairGen KeyPairGenerator.getInstance(DH);bobKpairGen.initialize(dhParamShared);KeyPair bobKpair bobKpairGen.generateKeyPair();// 卡罗尔使用相同的参数创建她自己的 DH 密钥对System.out.println(CAROL: 生成 DH 密钥对 ...);KeyPairGenerator carolKpairGen KeyPairGenerator.getInstance(DH);carolKpairGen.initialize(dhParamShared);KeyPair carolKpair carolKpairGen.generateKeyPair();// 爱丽丝初始化System.out.println(ALICE: 初始化 ...);KeyAgreement aliceKeyAgree KeyAgreement.getInstance(DH);aliceKeyAgree.init(aliceKpair.getPrivate());// 鲍勃初始化System.out.println(BOB: 初始化 ...);KeyAgreement bobKeyAgree KeyAgreement.getInstance(DH);bobKeyAgree.init(bobKpair.getPrivate());// 卡罗尔初始化System.out.println(CAROL: 初始化 ...);KeyAgreement carolKeyAgree KeyAgreement.getInstance(DH);carolKeyAgree.init(carolKpair.getPrivate());// 爱丽丝使用卡罗尔的公钥Key ac aliceKeyAgree.doPhase(carolKpair.getPublic(), false);// 鲍勃使用爱丽丝的公钥Key ba bobKeyAgree.doPhase(aliceKpair.getPublic(), false);// 卡罗尔使用鲍勃的公钥Key cb carolKeyAgree.doPhase(bobKpair.getPublic(), false);// 爱丽丝使用卡罗尔的结果cbaliceKeyAgree.doPhase(cb, true);// 鲍勃使用爱丽丝的结果acbobKeyAgree.doPhase(ac, true);// 卡罗尔使用鲍勃的结果bacarolKeyAgree.doPhase(ba, true);// 爱丽丝、鲍勃和卡罗尔计算他们的秘密byte[] aliceSharedSecret aliceKeyAgree.generateSecret();System.out.println(爱丽丝的秘密: toHexString(aliceSharedSecret));byte[] bobSharedSecret bobKeyAgree.generateSecret();System.out.println(鲍勃的秘密: toHexString(bobSharedSecret));byte[] carolSharedSecret carolKeyAgree.generateSecret();System.out.println(卡罗尔的秘密: toHexString(carolSharedSecret));// 比较爱丽丝和鲍勃if (!java.util.Arrays.equals(aliceSharedSecret, bobSharedSecret))throw new Exception(爱丽丝和鲍勃不同);System.out.println(爱丽丝和鲍勃相同);// 比较鲍勃和卡罗尔if (!java.util.Arrays.equals(bobSharedSecret, carolSharedSecret))throw new Exception(鲍勃和卡罗尔不同);System.out.println(鲍勃和卡罗尔相同);}// ...
}10.3 AES/GCM Example 以下是一个示例程序演示了如何使用 AES/GCM 对数据进行加密/解密。
import java.security.AlgorithmParameters;
import java.util.Arrays;
import javax.crypto.*;public class AESGCMTest {public static void main(String[] args) throws Exception {// 略长于 1 个 AES 块128 位以展示 GCM 处理 PADDING。byte[] data {// 数据字节};// 创建一个 128 位的 AES 密钥。KeyGenerator kg KeyGenerator.getInstance(AES);kg.init(128);SecretKey key kg.generateKey();// 获取一个用于加密的 AES/GCM 密码对象。必须获取并使用参数以成功解密。Cipher encCipher Cipher.getInstance(AES/GCM/NOPADDING);encCipher.init(Cipher.ENCRYPT_MODE, key);byte[] enc encCipher.doFinal(data);AlgorithmParameters ap encCipher.getParameters();// 获取一个类似的密码对象并使用参数。Cipher decCipher Cipher.getInstance(AES/GCM/NOPADDING);decCipher.init(Cipher.DECRYPT_MODE, key, ap);byte[] dec decCipher.doFinal(enc);if (Arrays.compare(data, dec) ! 0) {throw new Exception(原始数据 ! 解密后的数据);}}
}10.4 HMAC-SHA256 Example 以下是一个示例程序演示了如何为 HMAC-SHA256 生成一个密钥对象并用它来初始化 HMAC-SHA256 对象。
import java.security.*;
import javax.crypto.*;public class initMac {public static void main(String[] args) throws Exception {// 为 HmacSHA256 生成密钥KeyGenerator kg KeyGenerator.getInstance(HmacSHA256);SecretKey sk kg.generateKey();// 获取实现 HmacSHA256 的 Mac 对象实例并用密钥 sk 初始化它Mac mac Mac.getInstance(HmacSHA256);mac.init(sk);byte[] result mac.doFinal(Hi There.getBytes());}
}