在本节中,我们讨论Java安全包中处理密钥和证书的类。 密钥是许多加密算法的必要组成部分 ,密钥是创建和验证数字签名或执行加密所必需的。 不同的算法需要不同的密钥。 密钥通常有两种常见类型:非对称和对称。 非对称密钥包含公有密钥和私有密钥。 公钥和私钥是相关的,并且被称为密钥对。 对称密钥也称为密钥。

证书用于验证公钥; 当公钥以电子方式传输时,它们通常被嵌入证书中。 核心Java API附带了处理公钥和私钥及其证书所必需的类。 处理密钥所需的类来自于JCE。
密钥和证书通常与某个人或组织相关联,密钥的存储,传输和共享方式是需要着重考虑的。在本节中,我们将展示主要JAVA API如何与密钥和证书进行交互,如下图所示,引擎Generator和Key Factory 主要用于操作各种密钥。

密钥生成器类从头开始创建密钥。 在没有输入的情况下,生成器可以生成一个或多个密钥。 对称密钥由KeyGenerator类生成,而非对称密钥对由KeyPairGenerator类生成。

KeyFactory类在密钥对象及其外部表示之间进行转换,这些表示可以是字节数组或密钥规范。

  • Key接口

java.security.Key接口是所有不透明密钥的接口。 它定义了所有不透明密钥对象共享的功能。
一个不透明的密钥表示就是不能直接访问构成密钥的密钥材料。 换句话说:“不透明”能够有限地访问密钥 – 只有Key接口定义的三个方法:getAlgorithm,getFormat和getEncoded。

这与透明表示形式相反,在透明表示形式中,可以通过相应规范类中定义的某个get方法单独访问每个密钥中的材料值。

所有不透明的密钥有三个特征:

算法
该密钥的关键算法。 关键算法通常是加密或非对称操作算法(如AES,DSA或RSA),它们将与这些算法和相关算法(如SHA256withRSA)一起工作。使用此方法可获得密钥算法的名称:
String getAlgorithm()

编码
在Java虚拟机之外需要密钥的标准表示形式时使用的密钥的外部编码形式,就像将密钥传输给其他方式一样。 密钥按照标准格式(如X.509或PKCS8)进行编码,并使用以下方法返回:
byte[] getEncoded()

格式
编码密钥格式的名称。 它是由该方法返回的:
String getFormat()

密钥一般通过密钥生成器(如KeyGenerator和KeyPairGenerator),证书,密钥规范(使用KeyFactory)或访问用于管理密钥的密钥库数据库的KeyStore实现获得。 可以使用KeyFactory以算法相关的方式解析编码密钥。

也可以使用CertificateFactory解析证书。

以下是在java.security.interfaces和javax.crypto.interfaces包中扩展Key接口的接口列表:

SecretKey
PBEKey
PrivateKey
DHPrivateKey
DSAPrivateKey
ECPrivateKey
RSAMultiPrimePrivateCrtKey
RSAPrivateCrtKey
RSAPrivateKey
PublicKey
DHPublicKey
DSAPublicKey
ECPublicKey
RSAPublicKey

  • PublicKey和PrivateKey接口

PublicKey和PrivateKey接口(它们都扩展了Key接口)是无方法的接口,用于类型安全和类型标识。

  • KeyPair类

KeyPair类是密钥对(公钥和私钥)的简单持有者。 有两个公共方法,一个用于返回私钥,另一个用于返回公钥:

PrivateKey getPrivate()
PublicKey getPublic()

  • 密钥规范接口和类

密钥对象和密钥规范(KeySpecs)是密钥数据的两种不同表示。 密码使用密钥对象来初始化他们的加密算法,但密钥可能需要转换成更便携的格式进行传输或存储。

密钥的透明表示意味着可以通过相应规范类中定义的某个get方法单独访问每个关键字值。 例如,DSAPrivateKeySpec定义了getX,getP,getQ和getG方法来访问私钥x以及用于计算密钥的DSA算法参数:素数p,次数q和基数g。 如果密钥存储在硬件设备上,其规格可能包含有助于识别设备密钥的信息。

这种表示形式与密钥接口中定义的不透明表示形式相反,在这种表示形式中,无法直接访问密钥材料字段。 换句话说,“不透明”的表示形式使得您可以有限地访问键 – 只有Key接口定义的三种方法:getAlgorithm,getFormat和getEncoded。

密钥可以按照算法特定的方式或者以独立于算法的编码格式(例如ASN.1)来指定。 例如,DSA私钥可以由其组件x,p,q和g(请参阅DSAPrivateKeySpec)指定,也可以使用DER编码指定(请参阅PKCS8EncodedKeySpec)。

KeyFactory和SecretKeyFactory类可以用来在不透明和透明的密钥表示之间进行转换(也就是说,在Keys和KeySpecs之间,进行转换)

  • KeySpec接口

这个接口不包含方法或常量。 其唯一目的是为所有规范进行分组和提供类型安全。 所有密钥规范都必须实现这个接口。

  • KeySpec子类

就像Key接口一样,KeySpec接口也是类似的。

SecretKeySpec
EncodedKeySpec
PKCS8EncodedKeySpec
X509EncodedKeySpec
DESKeySpec
DESedeKeySpec
PBEKeySpec
DHPrivateKeySpec
DSAPrivateKeySpec
ECPrivateKeySpec
RSAPrivateKeySpec
RSAMultiPrimePrivateCrtKeySpec
RSAPrivateCrtKeySpec
DHPublicKeySpec
DSAPublicKeySpec
ECPublicKeySpec
RSAPublicKeySpec

EncodedKeySpec类

该抽象类(实现KeySpec接口)表示编码格式的公钥或私钥。 它的getEncoded方法返回编码密钥:

abstract byte[] getEncoded();

它的getFormat方法返回编码格式的名称:
abstract String getFormat();

PKCS8EncodedKeySpec类

该类是EncodedKeySpec的一个子类,它表示根据PKCS8标准中指定的格式对私钥进行DER编码。 它的getEncoded方法返回按照PKCS8标准编码的密钥字节。 它的getFormat方法返回字符串“PKCS#8”。

X509EncodedKeySpec类

该类是EncodedKeySpec的一个子类,它表示公钥的DER编码,根据X.509标准中规定的格式。 它的getEncoded方法返回按照X.509标准编码的密钥字节。 它的getFormat方法返回字符串“X.509”。

  • 密钥生成器和工厂类

密钥生成器用于生成全新的密钥对象。 密钥生成器可以以算法相关或算法独立的方式进行初始化。 例如,要创建Diffie-Hellman(DH)密钥对,应用程序可以指定必要的P和G值,或者可以简单地使用适当的密钥长度对生成器进行初始化,生成器将选择适当的P和G值。 在这两种情况下,生成器将根据参数生成全新的密钥。

工厂用于将数据从一个现有的对象类型转换为另一个。 例如,应用程序可能具有DH私钥的组件,可以将它们打包为KeySpec,需要将它们转换为KeyAgreement对象可以使用的PrivateKey对象和Factory 对象,反之亦然。 或者可能拥有证书的字节数组,但需要使用CertificateFactory将其转换为X509Certificate对象。 应用程序使用工厂对象来进行转换。

  • KeyFactory类

KeyFactory类是一个引擎类,设计用于在不透明的加密密钥和密钥规范(底层密钥材料的透明表示)之间执行转换。

密钥工厂是双向的。 它们允许从给定的密钥规范(密钥材料)构建不透明的密钥对象,或者相反的过程。

同一个密钥可以存在多个兼容的密钥规范。 例如,DSA公钥可以由其组件y,p,q和g(参见java.security.spec.DSAPublicKeySpec)指定,也可以根据X.509标准使用其DER编码来指定。

密钥工厂可以用来在兼容的密钥规范之间进行转换。 可以通过兼容密钥规范之间的转换来实现密钥解析。

  • 创建KeyFactory

KeyFactory对象是通过使用KeyFactory的getInstance()静态工厂方法之一获得的。

在密钥规范和密钥对象之间进行转换

如果有公钥的密钥规范,则可以使用generatePublic方法从规范中获取不透明的PublicKey对象:

PublicKey generatePublic(KeySpec keySpec)

同样,如果拥有私钥的密钥规范,则可以使用generatePrivate方法从规范中获取不透明的PrivateKey对象:

PrivateKey generatePrivate(KeySpec keySpec)

在密钥对象和密钥规范之间进行转换

如果有Key对象,则可以通过调用getKeySpec方法来获取相应的密钥规范对象:

KeySpec getKeySpec(Key key, Class keySpec)

keySpec标识应该返回密钥材料的规范类。 例如,它可以是DSAPublicKeySpec.class,用于指示应该在DSAPublicKeySpec类的实例中返回密钥材料。

  • SecretKeyFactory类

这个类代表一个密钥的工厂。 与KeyFactory不同的是,javax.crypto.SecretKeyFactory对象仅在对称密钥上运行,而java.security.KeyFactory对象则处理密钥对的公钥和私钥组件。

  • 密钥

密钥工厂用于将密钥(java.security.Key类型的不透明密钥)转换为密钥规范(以合适的格式对基础密钥材料进行透明表示),反之亦然。

java.security.Key类型的对象(其中java.security.PublicKey,java.security.PrivateKey和javax.crypto.SecretKey是子类)是不透明的密钥对象,因为无法知道它们是如何实现的。 底层的实现依赖于提供者,可能是基于软件或硬件的。 密钥工厂允许供应商提供他们自己的加密密钥的实现。

例如,如果具有Diffie Hellman公钥的密钥规范(由公有值y,主模数p和基数g组成),并将相同的规范从不同的提供程序提供给Diffie-Hellman密钥工厂,则 结果PublicKey对象将很可能有不同的底层实现。

提供者应记录其密钥工厂支持的密钥规范。 例如,SunJCE提供者提供的DES密钥的SecretKeyFactory支持DESKeySpec作为DES密钥的透明表示,DES-EDE密钥的SecretKeyFactory支持DESedeKeySpec作为DES-EDE密钥的透明表示,而PBE的SecretKeyFactory支持PBEKeySpec作为 底层密码的透明表示。

以下是如何使用SecretKeyFactory将密钥数据转换为可用于后续密码操作的SecretKey对象的示例:

// Note the following bytes are not realistic secret key data
// bytes but are simply supplied as an illustration of using data
// bytes (key material) you already have to build a DESedeKeySpec.

byte[] desEdeKeyData = getKeyData();
DESedeKeySpec desEdeKeySpec = new DESedeKeySpec(desEdeKeyData);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey secretKey = keyFactory.generateSecret(desEdeKeySpec);

在这种情况下,SecretKey的底层实现基于KeyFactory的提供者。

使用相同的密钥材料创建功能上等价的SecretKey对象的另一种独立于提供者的方法是使用实现javax.crypto.SecretKey接口的javax.crypto.spec.SecretKeySpec类:

byte[] aesKeyData = getKeyData();
SecretKeySpec secretKey = new SecretKeySpec(aesKeyData, "AES");
  • 创建SecretKeyFactory对象

SecretKeyFactory对象是通过使用SecretKeyFactory getInstance()静态工厂方法之一获得的。

在密钥对象和密钥规范之间转换

如果拥有密钥的密钥规范,则可以使用generateSecret方法从规范中获取不透明的SecretKey对象:

SecretKey generateSecret(KeySpec keySpec)

keySpec标识应该返回密钥材料的规范类。 例如,它可以是DESKeySpec.class,以指示密钥材料应该在DESKeySpec类的实例中返回。

在密钥对象和密钥规范之间转换

如果有密钥对象,则可以通过调用getKeySpec方法来获取相应的密钥规范对象:

KeySpec getKeySpec(Key key, Class keySpec)

keySpec标识应该返回密钥材料的规范类。 例如,它可以是DESKeySpec.class,以指示密钥材料应该在DESKeySpec类的实例中返回。

  • KeyPairGenerator类

KeyPairGenerator类是用于生成公钥和私钥对的引擎类。

有两种方法来生成密钥对:以独立于算法的方式,以特定于算法的方式。 两者之间的唯一区别是对象的初始化方法。

创建KeyPairGenerator

所有密钥对生成都以KeyPairGenerator开始。 KeyPairGenerator对象是通过使用KeyPairGenerator getInstance()静态工厂方法之一获得的。

初始化KeyPairGenerator

用于特定算法的密钥对生成器创建可用于该算法的公钥/私钥对。 它还将算法特定的参数与每个生成的密钥相关联。

密钥对生成器在生成密钥之前需要进行初始化。 在大多数情况下,独立于算法的初始化就足够了。 但在其他情况下,可以使用算法特定的初始化。

算法独立初始化

所有的密钥对生成器都共享一个密钥的概念和一个随机的来源。 对于不同的算法,密钥大小的解释是不同的。 例如,在DSA算法的情况下,密钥大小对应于模数的长度。

初始化方法需要两个参数类型:

void initialize(int keysize, SecureRandom random)

另一个初始化方法只需要一个keysize参数;

void initialize(int keysize)

特定于算法的初始化

对于已经存在一组算法特定参数的情况,有两个具有AlgorithmParameterSpec参数的初始化方法。 一个也有一个SecureRandom参数:

void initialize(AlgorithmParameterSpec params, SecureRandom random)

void initialize(AlgorithmParameterSpec params)

生成密钥对

无论初始化(和算法)如何,生成密钥对的过程总是相同的。 始终从KeyPairGenerator调用以下方法:

KeyPair generateKeyPair()

多次调用generateKeyPair将产生不同的密钥对。

  • KeyGenerator类

密钥生成器用于为对称算法生成密钥。

创建KeyGenerator

KeyGenerator对象是通过使用KeyGenerator getInstance()静态工厂方法之一获得的。

初始化KeyGenerator对象

用于特定对称密钥算法的密钥生成器创建可用于该算法的对称密钥。 它还将算法特定的参数与生成的密钥相关联。

有两种方法来生成密钥:以独立于算法的方式,以特定于算法的方式。 两者之间唯一的区别是对象的初始化:

算法独立初始化
所有的密钥生成器都共享一个密钥的概念和一个随机的来源。 有一个init方法使用这两个普遍共享类型的参数。 还有一个只需要一个密钥大小的参数,并使用系统提供的随机源;

public void init(SecureRandom random);

public void init(int keysize);

public void init(int keysize, SecureRandom random);

特定于算法的初始化
对于已经存在一组特定于算法的参数的情况,有两个具有AlgorithmParameterSpec参数的init方法。 一个也有一个SecureRandom参数;

public void init(AlgorithmParameterSpec params);

public void init(AlgorithmParameterSpec params, SecureRandom random);

如果客户端没有明确初始化KeyGenerator(通过调用init方法),则每个提供者都必须提供一个默认的初始化。

  • KeyAgreement类

密钥协议是两方或多方可以建立相同密码密钥而不必交换任何秘密信息的协议。

各方用自己的私钥初始化密钥协商对象,然后输入参与通信的各方的公钥。 在大多数情况下,只有两方,但Diffie-Hellman等算法允许多方(3个或更多)参与。 当所有的公钥都被输入时,每个KeyAgreement对象都会生成相同的密钥。

创建KeyAgreement对象

参与密钥协议的每一方都必须创建一个KeyAgreement对象。 KeyAgreement对象是通过使用KeyAgreement getInstance()静态工厂方法之一获得的。

初始化KeyAgreement

要初始化一个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);

执行密钥协商阶段

每个密钥协议都由许多阶段组成,这些阶段需要由密钥协议中涉及的每一方执行。

要执行密钥协议中的下一个阶段,请调用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。

生成共享密钥

在每一方执行了所有需要的密钥协商阶段之后,可以通过调用一个generateSecret方法来计算共享密钥:

public byte[] generateSecret();

public int generateSecret(byte[] sharedSecret, int offset);

public SecretKey generateSecret(String algorithm);


关注微信服务号,手机看文章
关注微信服务号,手机看文章