Showing posts with label Key. Show all posts
Showing posts with label Key. Show all posts

Friday, January 17, 2014

JCA - Signature 签名

我们可以通过验证消息摘要,来检测文件的完整性。但是消息摘要机制是否可以检测出有人恶意的对文件进行篡改呢?并不能完全检测出来。原因是这样的:首先消息摘要算法是公开的(因为需要用于验证消息的完整性,所以消息摘要算法必须要公开)。其次,尽管某些消息摘要算法提供了密钥机制——比如Hmac算法,但是,其密钥也是对文件接收方公开的。公开的消息摘要算法加上公开的密钥,就给了心存歹意的人修改文件,并且重新计算消息摘要的可能。举个例子:

  1. A给B发送了一个消息M1,并且附带了M1的消息摘要D1
  2. B收到消息,通过消息摘要D1验证了M1的完整性
  3. B把消息M1改成了M2,为了跟加具有欺骗性,B使用和A相同的消息摘要算法重新计算M2的消息摘要D2
  4. B把M2以及D2发送给了C,并且生成M2就是A发送给我的消息,如果不相信,你可以比对消息摘要
  5. C收到M2后,进行消息摘要,发现果然和D2是匹配的。但是实际上消息已经被B篡改了。

如何防止消息被人恶意篡改呢?其实,这里消息可以被篡改的主要原因就是算法和密钥是公开的。但是,如果算法和密钥不公开,那么收到消息的人如何对消息进行验证呢?问题似乎陷入了一个死循环中。终于,非对称加密算法帮助我们完美的解决了这一问题。

所谓非对称加密算法(Asymmetric Algorithm),实际上是指加密和解密所使用的密钥(Key)是不一样的。非对称加密算法的Key是成对出现的,分为公钥(Public Key)和私钥(Private Key)。如果使用公钥加密,则需要使用私钥解密;反之,如果使用私钥加密,则需要使用公钥解密。一般情况下私钥自己保存,公钥公开给对方。

这种利用非对称加密算法对消息进行摘要的方法叫做“签名(Signature)”

因为有了非对称加密算法,因此我们可以设计这样一个验证流程:

  1. 使用私钥对消息进行消息摘要(签名)
  2. 将消息/消息摘要(签名)/公钥,一起发送给接收方
  3. 接收方利用公钥验证消息摘要(签名)

这样一来,消息接收方可以利用公钥对消息摘要(签名)进行验证,但不可能在修改消息后重新计算签名,因为消息接收方是没有消息发送方的私钥的。

我们知道普通的消息摘要算法,无论数据有多长,它们的消息摘要都是固定长度的(MD5-128Bits, SHA1-160Bits)。但是,非对称加密算法主要是用于数据加密用的,因此它的计算结果的长度和原始数据的长度有关,也就是说原始数据越长,计算出的结果也就越长(一般会大于原始数据的长度,因为在加密之前需要进行Padding,稍后详细解释)。显然,这种加密结果并不适合用于消息摘要,因为这产生了很大的资源浪费,也降低了验证签名的效率。

为了解决这个问题,非对称加密算法通常和消息摘要算法一起使用,共同构成了签名算法。签名算法的流程基本上是这样的:

  1. 使用一种消息摘要算法(MD5/SHA...)对需要签名的消息M(不定长)进行消息摘要,得到消息摘要D(定长)。
  2. 使用非对称加密算法(DSA/RSA)对消息摘要D进行加密,并得到签名S。

也就是这样一个流程:M -> D -> S

通过将消息摘要算法和非对称加密算法进行组合使用,我们就完美的达到了计算消息签名的目的。

Java中的使用方法

在Java中需要借助Signature这个Engine类对消息进行签名和验证。

  1. 通过调用Signature.getInstance方法,向JCA请求一个Signature的实例
  2. 针对使用Signature的目的不同(签名或者验证),调用不同的初始化方法,并传入对应的Key(initSign/initVerify)。签名的时候需要传入Private Key,验证的时候需要传入Public Key
  3. 调用update方法,将待签名的数据传入Signature对象
  4. 调用sign方法对数据进行签名;或者,调用verify方法,对传入的签名进行验证。

另外,Signature这个类的对象是有状态的,状态信息保存在内部的state属性中。它有三种可能的状态:

  • UNINITIALIZED - Signature类被实例化之后,没有调用init方法之前,signature就是这个状态
  • SIGN - 调用initSign方法之后,signature的状态变成SIGN
  • VERIFY - 调用initVerify方法之后,signature的状态变成VERIFY

根据Signature状态的不同,可以进行的操作也不同:

  • UNINITIALIZED - 只能进行initSign或者initVerify
  • SIGN - 只能进行update以及sign
  • VERIFY - 只能进行update以及verify
  • 但是允许在任何时候调用init方法,这样将会重新初始化signature,并且清空之前的数据,改变Signature的状态。

签名

public static byte[] sign(byte[] data, PrivateKey key, String algorithm) {
    try {
        Signature signature = Signature.getInstance(algorithm);
        signature.initSign(key);
        signature.update(data);
        byte[] sign = signature.sign();
        return sign;
    } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ex) {
        throw new RuntimeException(ex);
    }
}

public static String sign(String text, PrivateKey key, String algorithm) {
    try {
        byte[] data = text.getBytes("UTF-8");
        byte[] sign = sign(data, key, algorithm);
        String signHex = DatatypeConverter.printHexBinary(sign);
        return signHex;
    } catch (UnsupportedEncodingException ex) {
        throw new RuntimeException(ex);
    }
}

public static void main(String[] args) {
    String message = "Hello this is a sensitive message";
    KeyPair keyPair = generateKeyPair("RSA", 512);
    String sign = sign(message, keyPair.getPrivate(), "SHA256WithRSA");
    System.out.println("Signature: " + sign);

}
 

执行上面的程序,我的环境输出如下:

Signature: 61447BDAD564E65015CFEDE904AD2BEE7CA0B65CB83FCC09FCE6F18F16A4B54920DBDBBA5F416567F82D57D53360E142DC25DB5185C5E35BB37191DCF6168AD2
 

因为KeyPair每次都是随机生成的,所以每次运行输出都不一样。

下面我们来分析一下这个输出。目前使用的签名算法是SHA256WithRSA——先使用SHA256计算消息摘要,然后对消息摘要的结果进行RSA加密。SHA256的结果是256 Bits,也就是32 Bytes。32 Bytes如果用16进制表示法输出,应该是一个长度为64的字符串。可是为什么这个字符串的长度是128呢?原因就在RSA加密之前,其实还有一个补全(padding)的过程,这一步会将消息摘要的结果补全到一个特定的长度,之后在补全的基础上再进行加密。在Java的默认实现中,这个补全长度和key的长度是一致的。上例中是512 Bits。因此最后加密出来的结果也是512 Bits也就是64 Bytes。使用16进制表示就是长度为128的字符串。

验证

public static boolean verify(byte[] data, byte[] sign, PublicKey key, String algorithm) {
    try {
        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(key);
        signature.update(data);
        boolean verified = signature.verify(sign);
        return verified;
    } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ex) {
        throw new RuntimeException(ex);
    }
}

public static boolean verify(String text, String signHex, PublicKey key, String algorithm) {
    try {
        byte[] data = text.getBytes("UTF-8");
        byte[] sign = DatatypeConverter.parseHexBinary(signHex);
        boolean verified = verify(data, sign, key, algorithm);
        return verified;
    } catch (UnsupportedEncodingException ex) {
        throw new RuntimeException(ex);
    }
}

public static void main(String[] args) {
    String message = "Hello this is a sensitive message";
    String sign = ""; //使用上例中计算出的签名
    PublicKey publicKey; //使用上例中对应的PublicKey
    boolean verified = verify(message, sign, publicKey, "SHA256WithRSA");
    System.out.println("Verified: " + verified);

}
 

如果一切正常,运行程序将会得到如下输出:

Verified: true
 

尝试改变message的值,将会得到:

Verified: false
 

这样我们就可以验证消息是否曾被被恶意修改过。

关于密钥

一般而言签名所需要的公钥和私钥都不是在程序中临时生成的,而是之前已经生成好,并且以特定形式保存在文件中的。想要做到这一点有很多种方法,可以通过Key Management API来实现,也可以通过获取key中的属性,自己操作文件进行保存/读取。但是,目前这里暂时不讨论具体的实现方法。

总结

Signature的使用比较简单,在了解了Key的生成之后,基本没有什么特殊的问题。以下内容比较重要:

  1. 为什么要使用Signature,它是为了解决什么问题的?
  2. 什么是非对称加密算法,它有什么特点?
  3. 对数据进行签名的过程是怎样的?

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

Wednesday, January 8, 2014

Java Cryptography - Key

无论是哪一种加密算法,Key(密钥)都是不可或缺的。根据加密算法的不同,相应的key也是不一样的。在进行加密/解密的过程中,我们可能会涉及到这几个问题:Key的生成;Key的保存;根据保存的数据重新构造Key。下面将会对这几个问题一一讲解。

Key的分类

首先根据加密算法种类的不同,key可以分成两大类:
  1. SecretKey-对称密钥:对应于对称加密算法(Symmetric algorithm),即加密密钥和解密密钥使用的是同一个密钥,代表算法有DES, AES 等。
  2. KeyPair-非对称密钥;对应于非对称加密算法(Asymmetric algorithm),即分为Public Key(公钥)和Private Key(私钥)。使用Public Key加密的信息,只有用Private Key才能够解密;反之,使用Private Key加密的信息,也只有用对应的Public Key才能解密。代表算法有DSA, RSA 等。

Key的生成

根据Key的种类不同,生成方式也不一样。我们主要使用两个类来进行Key的生成:
  • KeyGenerator类,用于生成SecretKey
  • KeyPairGenerator类,用于生成KeyPair

KeyGenerator

使用KeyGenerator来生成SecretKey非常简单。首先,这是一个Engine类。好了那么我们就知道了它大概的用法:
  1. 调用静态的getInstance方法,通过Algorithm和Provider获取一个KeyGenerator的实例
  2. 调用init方法对KeyGenerator的实例进行初始化:主要是指定一个Key Size和一个可选的随机数生成器(RNG),如果没有传入RNG,那么KeyGenerator将会使用默认的RNG——SecureRandom
  3. 调用generateKey方法来生成Secretkey
下面是生成DES Key的示例代码:
public static SecretKey generateDESKey() {
    try {
            KeyGenerator kg = KeyGenerator.getInstance("DES");
            /**
             * 注意在SunJCE的默认实现中,要求DES Key Size必须是56,
             * 此处如果传入其它数值,将会抛出InvalidParameterException("Wrong keysize: must be equal to 56")
             * 详情请参见com.sun.crypto.provider.DESKeyGenerator.engineInit() 方法
             */
            kg.init(56);
            SecretKey key = kg.generateKey();
            return key;
    } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
    }
}
    
每种加密算法对Key都有不同的要求,尤其是Key Size,根据算法的不同,需要不同的Key Size。下表来自于Java官方文档,里面列出了JDK默认实现的加密算法所需要的Key Size,以及默认值:
Algorithm Name Default Keysize Restrictions/Comments
AES 128 Keysize must be equal to 128, 192, or 256.
ARCFOUR (RC4) 128 Keysize must range between 40 and 1024 (inclusive).
Blowfish 128 Keysize must be a multiple of 8, ranging from 32 to 448 (inclusive).
DES 56 Keysize must be equal to 56.
DESede (Triple DES) 168 Keysize must be equal to 112 or 168.
A keysize of 112 will generate a Triple DES key with 2 intermediate keys, and a keysize of 168 will generate a Triple DES key with 3 intermediate keys.
Due to the "Meet-In-The-Middle" problem, even though 112 or 168 bits of key material are used, the effective keysize is 80 or 112 bits respectively.
HmacMD5 512 No keysize restriction.
HmacSHA1 512 No keysize restriction.
HmacSHA256 256 No keysize restriction.
HmacSHA384 384 No keysize restriction.
HmacSHA512 512 No keysize restriction.
RC2 128 Keysize must range between 40 and 1024 (inclusive).

KeyPairGenerator

在说KeyPairGenerator之前,有必要先说一下KeyPair类。
KeyPair类没有什么实际的功能,其实它的唯一作用就是创建了一个类,用于组织Public Key和Private Key。对应的key保存在这个类中对应的属性里面(publicKey & privateKey)。我们可以通过调用getPublic和getPrivate方法获取这相应的Key。
KeyPairGenerator的使用方法和KeyGenerator基本一样,因为它也是一个Engine类:
  1. 调用静态的getInstance方法,通过Algorithm和Provider获取一个KeyPairGenerator的实例
  2. 调用initialize方法对KeyPairGenerator的实例进行初始化:主要是指定一个Key Size和一个可选的随机数生成器(RNG),如果没有传入RNG,那么KeyPairGenerator将会使用默认的RNG——SecureRandom
  3. 调用genKeyPair方法或者generateKeyPair方法来生成KeyPair。(genKeyPair方法内部直接调用了generateKeyPair方法)
下面是使用KeyPairGenerator生成RSA KeyPair的示例代码:
public static KeyPair generateRSAKeyPair() {
    try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(2048);
            KeyPair keyPair = kpg.genKeyPair();
            return keyPair;
    } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
    }
}
        
对于KeyPairGenerator来说,每种算法对于Key Size也有不同的要求。而且,即使是相同的算法,在不同的Provider实现中要求的Key Size也是不一样的。具体信息可以查看Java的官方文档(http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html)。
比如上面所用的RSA算法,在Java的安装文件中有两个Provider都提供了RSA算法的实现:SunRSASign Provider和SunMSCAPI Provider。在SunRSASign中要求Key Size在[512 bits, 65536 bits]之间;而在SumMSCAPI中要求RSA的Key Size在 [512 bits, 16384 bits]之间。所以,如果不清楚算法实现对Key Size的要求,最好的方法就是查看上面提到的Java官方文档,或者通过查看源码,自己发现要求的KeySize。

Key接口

在Java中,Key接口是所有具体Key的接口。无论是Symmetric Key还是Asymmetric Key都需要实现Key接口。Key接口是Key的一种不透明的表现形式(Opaque Representation),与之相对的是透明的表现形式KeySpec(Transparent Representation)。关于KeySpec的相关信息这里暂时不说。Key接口中只提供了非常有限的三个方法访问Key中封装的数据:
/**
 * 返回能够使用这个Key的加密算法。
 * 比如生成的是AES Key,那么这个方法将返回"AES"。
 */
public String getAlgorithm();

/**
 * 返回Key的编码格式。
 * Key的种类不同,保存的信息也不一样。
 * 有的Key比较简单,只是保存了一些字节比如AES和DES,
 * 调用这些Key的getFormat方法将返回RAW,意思是原本的字节信息;
 * 另外有些key包含的信息就比较复杂了,
 * 比如RSA的Public Key,里面包含了modulus以及publicExponent,
 * 因此就需要把这些信息按照一定的编码方式来进行编码。
 * 通过调用getFormat这个方法,就会返回使用的编码方式。
 */
public String getFormat();

/**
 * 这个方法根据getFormat方法返回的编码方式,对Key进行编码,并返回编码后的结果。
 * 这个方法可以用于将程序中生成的Key导出为byte 数组的形式。
 * 利用导出的byte数组,我们可以完成许多功能。
 * 比如将byte数组保存在文件中,这样就将Key持久化到了介质上;
 * 或者将byte数组通过网络传输给另一个太机器,实现Key的共享等等。
 */
public byte[] getEncoded();
    
介绍了Key接口中的方法,我们就可以明白为什么说“Key接口是Key的不透明表现形式(Opaque Representation)”了。因为通过Key接口我们不能得到Key中的各项信息,能够得到的仅仅是经过编码后的Key。比如我无法从Key中得到RSA Public Key里面的modulus信息。
下面是将key导出到文件的一个简单例子:
public static void saveKey(Key key, Path path) {
    //获取编码后的key
    byte[] keyData = key.getEncoded();

    //将编码后的key转换成base64的字符串,方便在文件中查看
    String keyStr = DatatypeConverter.printBase64Binary(keyData);

    try {
            Files.write(path, keyStr.getBytes("UTF-8"), StandardOpenOption.WRITE);
    } catch (IOException ex) {
            throw new RuntimeException(ex);
    }
}
 

Key接口的局限

我们可以看到在Key接口中只提供了getEncoded方法,并没有对应的set方法。那么这样就有个问题了,我将Key导出成byte 数组之后,如何通过这个byte 数组还原Key呢?
另外,如果我就是想要获取Key中的信息呢?比如我想看看我的RSA Public Key中modulus到底是什么,应该怎么办呢?
想要回答以上两个问题,仅仅使用Key接口是做不到的。需要用到KeySpec和KeyFactory。