上篇文章中我们说道:在进行加密/解密的过程中,我们需要使用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有什么区别呢?没错,你猜得对。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类的时候,我总是很高兴,因为我基本不需要看文档就知道大概的用法了。
- 调用getInstace方法,从JCA中请求KeyFactory实例
- 调用KeyFactory中提供的方法,从而实现Key和KeySpec之间的转换。
Key -> KeySpec
把Key转换为KeySpec需要解决两个问题,也就是说KeyFactory需要解决这两个问题:
-
每种算法的Key和KeySpec的构成都是有区别的,所以不同算法的Key需要使用不同的KeyFactorySpi实现;
可以通过在getInstace的时候,传入对应的算法名称解决
-
一个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,它们的转换方法如下:
- 如果要把SecretKey转换成SecretKeySpec,可以直接通过SecretKey.getEncoded方法获取encoded key,然后在构造SecretKeySpec的时候,直接通过构造方法传入encoded key即可;
- 如果要把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