Thursday, January 16, 2014

Java Cryptography - KeySpec & KeyFactory

上篇文章中我们说道:在进行加密/解密的过程中,我们需要使用Key。但是Key接口没有提供用于获取Key中属性的方法。如果想要获取Key中的属性,需要通过KeySpec。

Key和KeySpec之间的转换需要借助于KeyFactory来完成

KeySpec

KeySpec是一个接口,同时也代表了一系列类——Key Specification。前面说过,Key接口是Key的不透明表现形式(Opaque Representation);与之相对,KeySpec是Key的透明表现形式(Transparent Representation),可以从Key Specification中得到一些Key内部的属性。

对于每种加密算法,有各自的Key Specification实现,里面包含了对应算法的Key所需要的信息。比如RSA Public Key的Key Specification(RSAPublicKeySpec),包含了modulus和publicExponent属性;而DSA Public Key的Key Specification(DSAPublicKeySpec),包含了DSA算法中需要的p, q, g, y属性。

同时我们也可以看到在KeySpec接口中,没有包含任何方法或者属性。这个接口仅仅用于标识对象的类型,并不提供实际的功能。

注意:一种加密算法的Key,可能会对应多种KeySpec。比如RSA Public Key就对应了两种KeySpec:RSAPublicKeySpec、X509EncodedKeySpec。那么,这两种KeySpec有什么不同呢?

KeySpec的分类

按照KeySpec对数据的组织形式不同,我们可以将KeySpec大致分为两类:

  • 参数形式:将key中的数据组织成不同的参数,保存在属性中。比如我们之前所说,我想要获取RSA Public Key的modulus信息,就可以使用RSAPublicKeySpec,通过它的getModulus方法来获得modulus信息。当然,也可以通过setModulus方法来通来设置KeySpec的modulus属性。
  • 编码形式:将key中的数据编码后(以下称之为Encoded Key)保存在属性中。有点类似于Key接口中的getEncoded方法。但是,还记得吗?通过Key接口,只能获取Encoded Key,但是,没有办法利用Encoded Key重新构造Key。利用编码形式的KeySpec就可以实现这一点。比如X509EncodedKeySpec,我们可以在构造方法中传入X509编码Encoded Key,由此来构造一个X509EncodedKeySpec。当然,也可以使用getEncoded方法,获取X509编码的Key。

SecretKeySepc

对称加密算法的Key相对比较简单,它们使用的都是SecretKey,其内部存储的都是一个原始的byte数组。因此,尽管算法不同,但是Key Specification中的数据的表现形式都是一样的。因此Java为对称加密算法提供了通用的Key Specification——SecretKeySpec。

我们可以通过传入原始的Encoded Key以及算法名称来构造一个SecretKeySpec。如果想要为其它的算法生成SecretKeySpec,只需要将调用构造方法时传入的算法名称改成需要的即可。但是这里有一点需要注意一下:输入的Encoded Key必须要符合特定算法要求的Key Size,否则当Key在使用的时候会抛出异常。

当然,针对某些对称加密算法,Java也提供了更加具有针对性的Key Specification。比如DES算法。我们可以使用通用的SecretKeySpec类来构建DES Key。同时,Java也提供了DESKeySpec类,来单独的完成DES Key Specification的相关操作。通过源码可以看到,DESKeySpec类中加入了一些对于Key的验证的逻辑。

KeyFactory & SecretKeyFactory

除了SecretKeySpec能够直接被当作Key使用以外,大多数的KeySpec都不能被当作Key直接被用于加密解密。为了在Key和KeySpec之间转换,我们需要利用KeyFactory和SecretKeyFactory.

KeyFactory & SecretKeyFactory的作用

  1. 利用Key构造对应的KeySpec
  2. 利用KeySpec构造对应的Key

KeyFactory和SecretKeyFactory有什么区别呢?没错,你猜得对。KeyFactory是作用于非对称密钥(Asymmetric Key)的,SecretKeyFactory是作用于对称密钥(Symmetric Key)的。我一直觉得在JCA中的Generator和Factory的命名比较奇怪:作用于对称密钥的是KeyGenerator & SecretKeyFactory,作用于非对称密钥的是KeyPairGenerator & KeyFactory。注意到了么?Generator和Factory的前缀正好是对调的。不知道Sun当时为什么要这样命名,是否是有什么历史原因,我没有深究。

因为KeyFactory和SecretKeyFactory在使用上基本是一样的,通过它们暴露的接口也可以发现,基本所有的方法签名都是一致的。因此,下面虽然用的是KeyFactory,但是对于SecretKeyFactory同样适用。只需要记住,在对称加密算法的时候使用SecretKeyFactory;在非对称加密算法中使用KeyFactory即可。

KeyFactory使用方法

KeyFactory是Engine类,每当我意识到某个类是Engine类的时候,我总是很高兴,因为我基本不需要看文档就知道大概的用法了。

  1. 调用getInstace方法,从JCA中请求KeyFactory实例
  2. 调用KeyFactory中提供的方法,从而实现Key和KeySpec之间的转换。
Key -> KeySpec

把Key转换为KeySpec需要解决两个问题,也就是说KeyFactory需要解决这两个问题:

  1. 每种算法的Key和KeySpec的构成都是有区别的,所以不同算法的Key需要使用不同的KeyFactorySpi实现;

    可以通过在getInstace的时候,传入对应的算法名称解决

  2. 一个Key可能会对应多种KeySpec(还记得KeySpec的数据组织形式么?),因此我们在通过KeyFactory获取KeySpec的时候,需要让KeyFactory知道我们需要的是哪一种KeySpec。

    在获取KeySpec的时候,通过传入KeySpec的类型来告知KeyFactory我们需要哪种类型的KeySpec

下面是使用SecretKeyFactory将SecretKey转换成KeySpec的例子:

public static DESKeySpec getDESKeySpec(SecretKey key) {
    try {
        //获取实现了DES算法的SecretKeyFactory
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        //获取DESKeySpec
        DESKeySpec keySpec = (DESKeySpec)keyFactory.getKeySpec(key, DESKeySpec.class);
        return keySpec;
    } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
        throw new RuntimeException(ex);
    }
}
   

我们再来看一个KeyFacotry的例子:

public static KeySpec getRSAPrivateKeySpec(Key key) {
    try {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        //获取参数形式的KeySpec
        RSAPrivateKeySpec parameterKeySpec = keyFactory.getKeySpec(key, RSAPrivateKeySpec.class);

        //获取编码形式的KeySpec
        PKCS8EncodedKeySpec encodedKeySpec = keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class);

        //根据需要返回特定的KeySpec
        return encodedKeySpec;
        //return parameterKeySpec;
    } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
        throw new RuntimeException(ex);
    }
}
   
KeySpec -> Key

同样,这个转换也是需要由KeyFactory来完成。因为一个KeySpec只能对应于一种Key,所以将KeySpec转换成Key的时候,不再需要传入目标Key的类型了。只需要调用相应的方法,传入KeySpec,就可以得到对应的Key。

  • SecretKeyFactory中调用generateSecret方法。
  • KeyFactory中调用generatePublic(公钥)或者generatePrivate(私钥)。

下面的代码将DESKeySpec转换成SecretKey:

public static SecretKey generateDESSecretKey(DESKeySpec keySpec) {
    try {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey key = keyFactory.generateSecret(keySpec);
        return key;
    } catch (NoSuchAlgorithmException ex) {
        throw new RuntimeException(ex);
    } catch (InvalidKeySpecException ex) {
        throw new RuntimeException(ex);
    }
}
   

同样,在给出一个转换RSAKeyPrivateKeySpec到RSAPrivateKey的例子:

public static RSAPrivateKeySpec getRSAPrivateKeySpec(Key key) {
    try {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKeySpec keySpec = keyFactory.getKeySpec(key, RSAPrivateKeySpec.class);
        return keySpec;
    } catch (NoSuchAlgorithmException ex) {
        throw new RuntimeException(ex);
    } catch (InvalidKeySpecException ex) {
        throw new RuntimeException(ex);
    }
}
   

SecretKeySpec & SecretKey的特殊性

SecretKeySpec和SecretKey是Java默认实现的对称加密算法的通用Key。注意,他们之间不能通过SecretKeyFactory进行转换。其实想想,也无需使用SecretKeyFactory,它们的转换方法如下:

  1. 如果要把SecretKey转换成SecretKeySpec,可以直接通过SecretKey.getEncoded方法获取encoded key,然后在构造SecretKeySpec的时候,直接通过构造方法传入encoded key即可;
  2. 如果要把SecretKeySpec转换成SecretKey则更加简单,完全不需要额外的操作,因为SecretKeySpec已经继承了SecretKey,也就是说SecretKeySpec就是SecretKey,可以直接使用。

最后,给出一个Key与KeySpec的对应关系表

Symmetric Key

Algorithm Key KeySpce
DES DESKey DESKeySpec
Symmetric SecretKey SecretKeySpec

Asymmetric Key

Algorithm Key KeySpce
DiffieHellman DHPublicKey DHPublicKeySpec
X509EncodedKeySpec
DHPrivateKey DHPrivateKeySpec
PKCS8EncodedKeySpec
DSA DSAPublicKey DSAPublicKeySpec
X509EncodedKeySpec
DSAPrivateKey DSAPrivateKeySpec
PKCS8EncodedKeySpec
RSA RSAPublicKey RSAPublicKeySpec
X509EncodedKeySpec
RSAPrivateKey RSAPrivateKeySpec
RSAPrivateCrtKeySpec
PKCS8EncodedKeySpec

总结

Key在加密/解密过程中是不可或缺的,没有Key一切都无从谈起。

Key的生成需要使用Generator类:KeyGenerator 和 KeyPairGenerator

想要获取Key中的属性,需要将Key转换为KeySpec,然后,从KeySpec中获得需要的属性

想要利用已有的Key的属性重建Key,也需要先去构造KeySpec,并且设置好Key的属性,然后,才能将KeySpec转换为Key

SecretKey是所有对称加密算法通用的Key类型。同样,SecretKeySpec也是所有对称加密算法通用的KeySpec类型

Key和KeySpec之间的转换需要通过Factory来完成(SecretKeyFactory, KeyFactory)

SecretKey和SecretKeySpec之间的转换不能使用KeyFactory

No comments :

Post a Comment