标签(空格分隔): Java 安全

概念

密钥是加密算法不可缺少的部分。密钥在安全体系中至关重要,正如其名,私密的钥匙,打开安全的大门。密钥分两种:对称密钥和非对称密钥。非对称密钥里又包含公开密钥和私有密钥。

与密钥相关的还有一个概念是证书。证书主要用于鉴别密钥,通常将公开密钥放到证书里传输。

Java的安全体系里,密钥是通过JCE算法包实现的。操作密钥的引擎包含两部分:密钥生成器和密钥工厂。密钥生成器可以创建密钥,而密钥工厂将其进行包装展示到外部。所以对于编写程序来说,创建密钥包括两个步骤:1,用密钥生成器产生密钥;2,用密钥工厂将其输出为一个密钥规范或者一组字节码。

Java实现

Java里将密钥封装了一个接口——Key。非对称密钥有PublicKey和PrivateKey,均实现了该接口。从之前的“安全提供者框架”中的输出结果可以看到,不同的安全提供者提供了很多密钥生成算法,比较典型的是Sun的DSA和RSA以及JCE的Diffie-Hellman算法。

生成和表示key

密钥的生成,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 {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{</br>
<span class="hljs-keyword">try</span> {</br>
generateKeyPair();</br>
generateKey();</br>
} <span class="hljs-keyword">catch</span> (InvalidKeySpecException e) {</br>
e.printStackTrace();</br>
} <span class="hljs-keyword">catch</span> (NoSuchAlgorithmException e) {</br>
e.printStackTrace();</br>
}</br>
}</br></br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">generateKeyPair</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException </span>{</br>
KeyPairGenerator kpg = KeyPairGenerator.getInstance(<span class="hljs-string">"DSA"</span>);</br>
kpg.initialize(<span class="hljs-number">512</span>);</br>
KeyPair kp = kpg.generateKeyPair();</br>
System.out.println(kpg.getProvider());</br>
System.out.println(kpg.getAlgorithm());</br>
KeyFactory kf = KeyFactory.getInstance(<span class="hljs-string">"DSA"</span>);</br>
DSAPrivateKeySpec dsaPKS = kf.getKeySpec(kp.getPrivate(), DSAPrivateKeySpec.class);</br>
System.out.println(<span class="hljs-string">"\tDSA param G:"</span> + dsaPKS.getG());</br>
System.out.println(<span class="hljs-string">"\tDSA param P:"</span> + dsaPKS.getP());</br>
System.out.println(<span class="hljs-string">"\tDSA param Q:"</span> + dsaPKS.getQ());</br>
System.out.println(<span class="hljs-string">"\tDSA param X:"</span> + dsaPKS.getX());</br>
}</br></br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">generateKey</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException </span>{</br>
KeyGenerator kg = KeyGenerator.getInstance(<span class="hljs-string">"DES"</span>);</br>
SecretKey key = kg.generateKey();</br>
System.out.println(kg.getProvider());</br>
System.out.println(kg.getAlgorithm());</br>
SecretKeyFactory skf = SecretKeyFactory.getInstance(<span class="hljs-string">"DES"</span>);</br>
DESKeySpec desKS = (DESKeySpec) skf.getKeySpec(key, DESKeySpec.class);</br>
System.out.println(<span class="hljs-string">"\tDES key bytes size:"</span> + desKS.getKey().length);</br>
}</br>

}

Key生成的代码架构设计类图如下:

KeyGenerator与KPG类似,只是KPG生成KeyPair,KG
生成SecretKey。

密钥管理

关于证书

证书这东西,真不知道放哪里合适,就不单独拿出来讲了。考虑到证书可以验证密钥的合法性,就放这里说一下吧。

因为非对称密钥的场景,需要将公钥传输给对应的需求者。那么如何传输能确保这个公钥是我给你的而不是别人替换的呢?那就需要对这次传输加密签名,于是又进入了这样的循环。于是就引出了证书——证书可以保证内容和发源地是一致的,也就是说证书可以保证发给需求者的内容确实是属于内容拥有者的。

证书不是谁都能来发的,证书是通过一个公正实体(CA,证书授权机构)来颁发并验证合法性。证书包含三方面内容:
1,实体名,即证书持有者。
2,与主体相关的公开密钥。
3,验证证书信息的数字签名。证书由证书发行人签名。
Java中有对应的Certificate类来做证书相关的事情。但是证书不是我们这里要讨论的重点,而且Java本身对证书的支持就不完备。因此证书的内容就在这里插播一下。我们还是回到密钥的传输问题上来。

KeyStore

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(<span class="hljs-string">"myKeyEntry"</span>, key, password, <span class="hljs-literal">null</span>);</br></br>

    FileOutputStream fos = <span class="hljs-keyword">new</span> FileOutputStream(fileName);</br>
ks.store(fos, password);</br>
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"store key in "</span> + fileName);</br>
}</code></pre>

这里带来一些概念:

  • 密钥库:也就是上面说的KeyStore,用来管理存放密钥和证书的地方。Java的密钥管理是基于密钥库来构建的。
  • 密钥项:密钥库里存放的是一条条的密钥项。密钥项要么保存一个非对称密钥对,要么保存一个秘密密钥。如果保存的是密钥对,那还可能保存一个证书链。证书链的第一个证书包含公钥。
  • 别名:每个密钥都会可以有个别名,可以理解为密钥项的名字。
  • 标识名:密钥库中的实体的标识名是其完整的X.500名的子集,比如一个DN是

    CN=Yu Jia, OU=ALI, O=ALIBABA, L=HZ, ST=ZJ, C=CN
  • 证书项:只包含一个公钥证书,保存的是证书而不是证书链。
  • JKS,JCEKS,PKCS12:密钥库算法,Java默认是JKS,只能保存私钥,要想保存对称密钥的秘密密钥,需要使用JCEKS,这也就是上面代码中提到的KeyStore ks = KeyStore.getInstance("jceks");。可以通过修改java.security文件中的keystore.type=JCEKS来更改默认算法。

Keytool

光是这样,还欠点什么,因为上面的代码放到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]: CN
CN=Yu Jia, OU=ALI, O=ALIBABA, L=HZ, ST=ZJ, C=CN是否正确?
[否]: Y 输入 <changedi> 的密钥口令

(如果和密钥库口令相同, 按回车):

再次输入新口令:

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
输入密钥库口令:
输入 <changedi> 的密钥口令
新<changedi> 的密钥口令:
重新输入新<changedi> 的密钥口令:

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:26

mykeyentry, 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搞定好了。

  </div>

Java安全——密钥那些事的更多相关文章

  1. Java日志性能那些事(转)

    在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索.绝大多数人都认可日志的重要性,但是又有多少人仔细想过该怎么打日志,日志对性能的影响究竟有多大呢?今天 ...

  2. JAVA获取密钥公钥的keytool的使用

    一.keytool的概念 keytool 是个密钥和证书管理工具.它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向别的用户/服务认证自己)或数据完整性以及认证服务.在 ...

  3. 关于JAVA多线程的那些事__初心者

    前言 其实事情的经过也许会复杂了点,这事还得从两个月前开始说.那天,我果断不干IT支援.那天,我立志要做一个真正的程序猿.那天,我26岁11个月.那天,我开始看Android.那天,我一边叨念着有朋自 ...

  4. JAVA容器的那些事—集合

    1.首先我们先讲下Collection接口 Collection接口:Collection是最基本的集合接口,它是由一个独立元素所组成的序列,这些元素服务一条或多条规则.一个Collection代表一 ...

  5. Java 枚举那点事

    目录 最近有需求,想存自定义的枚举值,比如 HOTLINE("Hotline") 我想存 Hotline 于是研究了一下Java的枚举问题 如下数据库的Entity (贫血模型哈) ...

  6. .NET与JAVA RSA密钥格式转换

    一.该篇内容用于记录.net和Java之间,RSA公密钥的转换 using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.X509; ...

  7. 安卓、IOS端AEC密钥加密 Java端密钥解密通用实现(16进制表现形式)

    由于业务需求,需要实现在客户端对重要信息进行加密,在服务端进行解密.客户端包括IOS和安卓的 服务端位Java. 注意密钥 需要保持一致,可以自己定义 . 安卓端加密代码: ============= ...

  8. Java: For循环那些事

    title: Java-for循环那些事 toc: true date: 2019-05-21 10:28:44 categories: Java tags: - Tips --- Java for循 ...

  9. 两万字长文总结,梳理 Java 入门进阶那些事

    大家好,我是程序员小跃,一名在职场已经写了6年程序的老程序员,从一开始的菊厂 Android 开发到现在某游戏公司的Java后端架构,对Java还是相对了解的挺多. 大概是半年前吧,在知乎上有个知友私 ...

随机推荐

  1. spring-cloud服务网关中的Timeout设置

    本文转载自:https://segmentfault.com/a/1190000014370360 大家在初次使用spring-cloud的gateway的时候,肯定会被里面各种的Timeout搞得晕 ...

  2. PLSQL直接通过客户端连接远程

  3. Maven实战04_使用Archetype生成项目骨架

    在上一章中的HelloWorld中,我们的项目遵循了一些Maven项目的约定 在项目的根目录中放置pom.xml 在src/main/java目录中放置项目的主代码 在src/test/java目录中 ...

  4. js前台中获取后台传的值

    后台controller String ifOffice = "yes";req.setAttribute("ifOffice", ifOffice); 前台j ...

  5. 免费提取百度文库 doc 文件

    首先说明,今天要推荐的这款软件,不能不能不能免费提取百度文库里 PDF 格式的文件. 对于其他的格式,无论收费与否都能免费提取. 只是口头说说免不了耍流氓的嫌疑,举栗如下: 百度文库里<喜迎党的 ...

  6. KOA 学习(六)superAgent

    原文地址 http://www.2cto.com/kf/201611/569080.html 基本请求 初始化一个请求可以通过调用request模块中适当的方法,然后使用.end()来发送请求,例如一 ...

  7. Laravel Carbon获取 某个时间后N个月的时间

    $time = "2020-11-20 00:00:00"; $res = (new Carbon)->setTimeFromTimeString($time)->ad ...

  8. 通过游戏学python 3.6 第一季 第九章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改--锁定账号--锁定次数--菜单功能'menufile

      通过游戏学python 3.6 第一季 第九章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改--锁 ...

  9. Mysql千万级访问量架构

    1.HTML 静态化 其实大家都知道,效率最高.消耗最小的就是纯静态化的html页面,所以我们尽可能是我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法.但是对于大量内容并且频 ...

  10. Linux下读写UART串口的代码

    Linux下读写UART串口的代码,从IBM Developer network上拿来的东西,操作比較的复杂,就直接跳过了,好在代码能用,记录一下- 两个实用的函数- //////////////// ...