本文共 7358 字,大约阅读时间需要 24 分钟。
标签(空格分隔): Java 安全
密钥是加密算法不可缺少的部分。密钥在安全体系中至关重要,正如其名,私密的钥匙,打开安全的大门。密钥分两种:对称密钥和非对称密钥。非对称密钥里又包含公开密钥和私有密钥。
与密钥相关的还有一个概念是证书。证书主要用于鉴别密钥,通常将公开密钥放到证书里传输。
Java的安全体系里,密钥是通过JCE算法包实现的。操作密钥的引擎包含两部分:密钥生成器和密钥工厂。密钥生成器可以创建密钥,而密钥工厂将其进行包装展示到外部。所以对于编写程序来说,创建密钥包括两个步骤:1,用密钥生成器产生密钥;2,用密钥工厂将其输出为一个密钥规范或者一组字节码。
Java里将密钥封装了一个接口——Key。非对称密钥有PublicKey和PrivateKey,均实现了该接口。从之前的“安全提供者框架”中的输出结果可以看到,不同的安全提供者提供了很多密钥生成算法,比较典型的是Sun的DSA和RSA以及JCE的Diffie-Hellman算法。
密钥的生成,Java提供了两个生成器类——KeyPairGenerator和KeyGenerator,前者用于生成非对称密钥,后者用于生成对称密钥。对应密钥的表示,KeyFactory类表示非对称密钥,SecretKeyFactory表示对称密钥。
我们来看一个DSA的例子:
import java.security.KeyFactory;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.NoSuchAlgorithmException;import java.security.spec.DSAPrivateKeySpec;import java.security.spec.InvalidKeySpecException;import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;public class KeyTest { public static void main(String[] args) { try { generateKeyPair(); generateKey(); } catch (InvalidKeySpecException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } } public static void generateKeyPair() throws NoSuchAlgorithmException, InvalidKeySpecException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(512); KeyPair kp = kpg.generateKeyPair(); System.out.println(kpg.getProvider()); System.out.println(kpg.getAlgorithm()); KeyFactory kf = KeyFactory.getInstance("DSA"); DSAPrivateKeySpec dsaPKS = kf.getKeySpec(kp.getPrivate(), DSAPrivateKeySpec.class); System.out.println("\tDSA param G:" + dsaPKS.getG()); System.out.println("\tDSA param P:" + dsaPKS.getP()); System.out.println("\tDSA param Q:" + dsaPKS.getQ()); System.out.println("\tDSA param X:" + dsaPKS.getX()); } public static void generateKey() throws NoSuchAlgorithmException, InvalidKeySpecException { KeyGenerator kg = KeyGenerator.getInstance("DES"); SecretKey key = kg.generateKey(); System.out.println(kg.getProvider()); System.out.println(kg.getAlgorithm()); SecretKeyFactory skf = SecretKeyFactory.getInstance("DES"); DESKeySpec desKS = (DESKeySpec) skf.getKeySpec(key, DESKeySpec.class); System.out.println("\tDES key bytes size:" + desKS.getKey().length); }}
Key生成的代码架构设计类图如下:
KeyGenerator与KPG类似,只是KPG生成KeyPair,KG
生成SecretKey。证书这东西,真不知道放哪里合适,就不单独拿出来讲了。考虑到证书可以验证密钥的合法性,就放这里说一下吧。
因为非对称密钥的场景,需要将公钥传输给对应的需求者。那么如何传输能确保这个公钥是我给你的而不是别人替换的呢?那就需要对这次传输加密签名,于是又进入了这样的循环。于是就引出了证书——证书可以保证内容和发源地是一致的,也就是说证书可以保证发给需求者的内容确实是属于内容拥有者的。
证书不是谁都能来发的,证书是通过一个公正实体(CA,证书授权机构)来颁发并验证合法性。证书包含三方面内容:
1,实体名,即证书持有者。2,与主体相关的公开密钥。3,验证证书信息的数字签名。证书由证书发行人签名。Java中有对应的Certificate类来做证书相关的事情。但是证书不是我们这里要讨论的重点,而且Java本身对证书的支持就不完备。因此证书的内容就在这里插播一下。我们还是回到密钥的传输问题上来。Java中KeyStore类负责密钥的管理,KeyStore有个setKeyEntry()方法。通用的流程是KeyStore将key设置为一个key entry。然后通过store()方法保存为.keystore文件。使用方得到.keystore文件,利用load()方法读取Key entry,然后使用。
如果是非对称密钥的秘密密钥,写入密钥项的使用方法如下:
public static void secretKeyStore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { char[] password = "123456".toCharArray(); String fileName = System.getProperty("user.home") + File.separator + ".keystore"; FileInputStream fis = new FileInputStream(fileName); KeyStore ks = KeyStore.getInstance("jceks"); ks.load(fis, password); KeyGenerator kg = KeyGenerator.getInstance("DES"); SecretKey key = kg.generateKey(); ks.setKeyEntry("myKeyEntry", key, password, null); FileOutputStream fos = new FileOutputStream(fileName); ks.store(fos, password); System.out.println("store key in " + fileName); }
这里带来一些概念:
标识名:密钥库中的实体的标识名是其完整的X.500名的子集,比如一个DN是
CN=Yu Jia, OU=ALI, O=ALIBABA, L=HZ, ST=ZJ, C=CN
KeyStore ks = KeyStore.getInstance("jceks");
。可以通过修改java.security文件中的keystore.type=JCEKS来更改默认算法。光是这样,还欠点什么,因为上面的代码放到main函数里还是无法执行,而且也有个疑问,明明是要创建keystore,干嘛还要先load?
看看KeyStore中store()方法的源码:public final void store(OutputStream stream, char[] password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { if (!initialized) { throw new KeyStoreException("Uninitialized keystore"); } keyStoreSpi.engineStore(stream, password); }
未初始化的Keystore是要抛出KeyStoreException的。而初始化动作是在load()方法里做的。那这就奇怪了,第一个keystore难道是自己随便在系统目录里touch的?
这就引出了keytool工具,这是一个JRE提供的管理工具,方便管理密钥库的。keytool是命令行接口,使用keytool命令可以管理密钥库,具体命令各个参数可以man keytool或者keytool -help了解。
我这里列出我的程序是如何初始化一个keystore的:
1,我先生成了一个别名叫做changedi的密钥项,其算法是RSA非对称算法zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -genkey -alias changedi -keyalg RSA输入密钥库口令: 再次输入新口令: 您的名字与姓氏是什么? [Unknown]: Yu Jia您的组织单位名称是什么? [Unknown]: ALI您的组织名称是什么? [Unknown]: ALIBABA您所在的城市或区域名称是什么? [Unknown]: HZ您所在的省/市/自治区名称是什么? [Unknown]: ZJ该单位的双字母国家/地区代码是什么? [Unknown]: CNCN=Yu Jia, OU=ALI, O=ALIBABA, L=HZ, ST=ZJ, C=CN是否正确? [否]: Y输入的密钥口令 (如果和密钥库口令相同, 按回车): 再次输入新口令:
2,依照提示输入完成DN后,keystore就创建好了,可以查看一下
zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -list输入密钥库口令: 密钥库类型: JKS密钥库提供方: SUN您的密钥库包含 1 个条目changedi, 2016-7-7, PrivateKeyEntry, 证书指纹 (SHA1): 76:C8:CE:EA:4C:29:6D:0E:FF:8C:02:BE:F4:F4:55:97:63:1F:C8:26
3,可以看到,这个库还是JKS的,需要更改为JCEKS,于是做下面的事
zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -keypasswd -alias changedi -storetype jceks输入密钥库口令: 输入的密钥口令新 的密钥口令: 重新输入新 的密钥口令:
4,再list时,要选择storetype,因为刚才虽然是修改密码,但是其实核心目的是要更改密钥库类型
zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -list -storetype jceks输入密钥库口令: 密钥库类型: JCEKS密钥库提供方: SunJCE您的密钥库包含 1 个条目changedi, 2016-7-7, PrivateKeyEntry, 证书指纹 (SHA1): 76:C8:CE:EA:4C:29:6D:0E:FF:8C:02:BE:F4:F4:55:97:63:1F:C8:26
5,运行刚才的程序,写一个对称密钥的秘密密钥进去,作为这个keystore的一个密钥项,再list
zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -list -storetype jceks输入密钥库口令: 密钥库类型: JCEKS密钥库提供方: SunJCE您的密钥库包含 2 个条目changedi, 2016-7-7, PrivateKeyEntry, 证书指纹 (SHA1): 76:C8:CE:EA:4C:29:6D:0E:FF:8C:02:BE:F4:F4:55:97:63:1F:C8:26mykeyentry, 2016-7-7, SecretKeyEntry,
其实上面的例子,在创建第一个密钥项时就可以指定storetype是JCEKS,我这里只是展示一下如何切换密钥库类型。另外在RSA的私钥密钥项在未指定证书的情况下也会生成一个自签名证书。
回到刚才的代码里,我们看看setKeyEntry的细节:
public final void setKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException { if (!initialized) { throw new KeyStoreException("Uninitialized keystore"); } if ((key instanceof PrivateKey) && (chain == null || chain.length == 0)) { throw new IllegalArgumentException("Private key must be " + "accompanied by certificate " + "chain"); } keyStoreSpi.engineSetKeyEntry(alias, key, password, chain); }
可以看到,如果是非对称密钥的生产,需要提供一个证书链,否则就抛异常。考虑到这样的情况,我们一般不是做专业的企业级安全。还是keytool搞定好了。
转载地址:http://lryax.baihongyu.com/