精华内容
下载资源
问答
  • 密钥协商
    2021-04-06 17:51:50

    2021年04月06日 周二 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】


    1. 密钥协商相关概念

    密钥协商(百度百科): 两个或多个实体协商,共同建立会话密钥,任何一个参与者均对结果产生影响,不需要任何可信的第三方(TTP)。

    密钥协商协议(百度百科): 会话密钥由每个协议参与者分别产生的参数通过一定的计算得出。常见的密钥协商协议,如IKE。

    2. 密钥协商算法分类

    2.1 百度百科概念

    密钥协商算法分类: 可分为证书型无证书型证书型是指在会话密钥的产生过程中,由一个可信的证书中心(CA)给参与密钥协商的各方各分发一个证书,此证书中含有此方的公钥,ID及其他信息。证书型密钥协商协议的优点是提供认证,PKI(公钥密码体制)广泛部署,比较成熟,应用面广,且由PKG管理公私钥对有利于统一管理,缺点是计算代价大,需要一个可信的CA,同时证书还需要维护。无证书型是指各方在进行会话密钥的协商过程中不需要证书的参与,这是密钥协商协议的主流种类,优点是不需要CA的参与,减少了计算量,尤其是在低耗环境下应用的更多,同时安全性也不比证书型弱。几乎没有明显的缺点,只是设计一个安全的更加低耗的无证书密钥协商方案不是很容易。

    2.2 进一步理解

    本质上,关于密钥协商算法有两种:一种是RSA算法,RSA协商无非就是服务器将自己证书提供者客户端,客户端通过验证证书,然后提取出来公钥,然后通过公钥加密一个客户端自己提出的私钥,传输给服务器后,服务器提取出来。另一种是DH算法,DH算法可以全程透露自己的信息,DH算法不支持身份认证,必须结合签名算法使用,比若说RSA,DSA签名算法,常见的就是DH-RSA,DH-DSA等等。

    • DH算法简单理解: 双方先协商好使用什么的算法,算法参数,双方各自选定一个秘密的自然数作为随机数,可以称这个自然数为私钥,然后计算生成另一个可以共享的参数,这个参数可以称之为公钥,双发相互交换自己的公钥,然后大家可以计算出共享的密钥。

    • 算法依赖: DH算法是依赖离散对数的难题,ECDH算法是依赖椭圆曲线离散对数的难题,RSA算法是依赖大数的因子分解这个难题,对于DH和ECDH的改进算法是DHE和ECDHE两种算法,这两者算法主要是每次会话都重新协商一种密钥,且密钥用完就丢弃。

    3. 更详细的解释

    密钥交换(密钥协商)算法及其原理

    参考文献

    https://blog.csdn.net/a11211058/article/details/53583172

    https://baike.baidu.com/item/%E5%AF%86%E9%92%A5%E5%8D%8F%E5%95%86/1432648?fr=aladdin

    更多相关内容
  • 现有的基于证书认证密钥协商方案大多都采用了昂贵的双线性配对, 不适合计算资源有限的移动设备. 本文设计了一种轻量级的基于证书的AKA协议, 该协议用假名技术实现用户身份匿名. 该协议提供了前向保密, 抵抗中间人...
  • 随着量子理论的快速发展,离散对数问题和大整数分解问题在量子计算下存在多项式求解算法,其安全性受到严重威胁,因此,提出2个基于环上带误差学习问题的用户匿名三方口令认证密钥协商方案,包括基于格的隐式认证...
  • 由于对运算的计算量较大,且现有无双线性对的密钥协商协议存在会话临时秘密值泄露安全缺陷,提出一种无需对运算的无证书隐式认证和密钥协商协议,在随机预言机模型下证明了新协议的安全性。新协议基于椭圆曲线上的...
  •    ...因此,为满足无托管的要求,提出一个改进的基于身份的认证密钥协商协议,并在标准模型下证明其为安全的认证密钥协商协议。结果表明,改进后协议满足完善前向安全性和PKG前向安全性。
  • 用户撤销是基于属性的认证密钥协商(ABAKA, attribute-based authenticated key agreement)协议在实际应用中所必需解决的问题。通过将Waters的基于属性的加密方案和Boneh-Gentry-Waters的广播加密方案相结合,提出了...
  • 针对常数轮基于身份(ID-based)的可验证密钥协商协议存在协商通讯效率不高,以及没有涉及到当组成员变动时如何重新协商的问题,采用分布式两轮组密钥协商协议中的环形组成员结构,利用单轮三方密钥协商协议技术,使得...
  • 业务流程执行系统为业务流程管理的核心组成。针对该系统的应用场景及其中的负载均衡问题,提出了一种基于负载变化和分发代价的负载均衡方法——LVDCB(Load Variety and Distribution Cost Based algorithm)。...
  • 为了对密钥协商协议实现匿名认证,进而有效保护通信方身份秘密,提出了一种基于可信平台模块(trusted platform module,TPM)的单向匿名认证密钥协商协议.该协议基于可信计算平台,引入TPM技术,不但实现了认证和...
  • 摘 要:密钥协商协议是在公开的信道上,2 个或者多个参与方之间进行的共享密钥机制。利用安全单向函数,设计了一种基于椭圆曲线的多方密钥协商协议,协议能够满足密钥交换协议的安全特性,并且具有较高的计算效率。
  • 对Tseng协议构造了一种有效的中间人伪造攻击,敌手可以成功获得群会话密钥,因此Tseng协议不满足密钥认证性。然后基于Tseng协议的安全缺陷,提出改进协议,并进行安全性分析和性能分析。改进协议实现了通信节点之间的...
  • 移动Ad hoc网络自身的特点决定了该网络中节点资源的有限性,所以在移动Ad hoc网络中构建组密钥协商协议时,应尽量减少节点的资源开销。为了解决这个问题,提出了一种基于分簇-K叉树组模型结构的组密钥协商协议——CKT...
  • 本文考虑了经过身份验证的双向方的问题不安全的公共网络上的密钥协议协议。... 本文将改变周的隐式认证密钥协商协议进入了通过引入身份验证者,并证明所提议的协议是可证明的在随机预言模型下是安全的。
  • 基于混沌地图的第三方认证密钥协商协议的安全性
  • 采用MTI协议族的思想,设计了一个新的标准模型下基于身份的两方认证密钥协商协议IBAKE,并形式化证明了该协议的安全性。与现有的标准模型下基于身份的密钥协商协议相比,IBAKE协议在计算效率、通信效率等方面性能...
  • 针对双方认证和密钥协商协议中会话双方属于不同密钥产生中心的情况,利用双线性对性质和BDH假设,基于可证安全的eCK模型提出一种基于身份可证安全的双方密钥协商协议。该协议从测试会话在随机谕示模型中是否存在相应...
  • 最近,Lai等人利用加强的Chebyshev混沌映射设计了一个三方密钥协商协议,声称该协议能够抵抗各类攻击。针对Lai协议中存在的匿名传输等方面的缺陷,通过拦截、修改用户与可信第三方之间的传输数据,成功进行了中间人...
  • 区块链系统下的多方密钥协商协议,唐春明,高隆,密钥协商协议是在公开的信道上,两个或者多个参与方之间进行的共享密钥机制。传统的密钥协商协议需要在可信中心的帮助下去进行身
  • 密钥协商算法Diffie-Hellman的Java实现

    千次阅读 2022-03-03 11:18:29
    DH是密钥协商(key agreement)算法,通信双方并没有真正的交换密钥,而是通过交换部分可以公开的信息来计算生成出相同的对称密钥,然后通过对称密钥对消息进行加密,从而保证消息的机密性。 DH的流程原理如下: 1....

    目录

    • Diffie-Hellman的流程原理
    • 流程原理在Java中的对应
    • Java应用代码
    • JDK源码中封装的原理细节
      • 生成密钥对
      • 通过对方公钥和自己的私钥生成相同的对称密钥

    本文的思路:

    • 先了解Diffie-Hellman的流程原理,然后将其流程和Java的实现对应起来;理解了原理和Java实现的流程,再写应用代码进一步辅助验证;最后走一走源码流程中的相关细节,作最终验证。
    • 文末了解一下性能更好、安全性更高的ECDH(基于椭圆曲线来实现的Diffie-Hellman)

    一、Diffie-Hellman的流程原理

    DH的流程中涉及三个角色,主要是通信双方Alice、Bobby,和可能存在窃听的中间人Eaves。

    场景:Alice要和Bobby通信,发送的信息可能会被中间人Eaves窃听到,Alice和Bobby如何通过DH算法来保证通信的机密性?

    DH是密钥协商(key agreement)算法,通信双方并没有真正的交换密钥,而是通过交换部分可以公开的信息来计算生成出相同的对称密钥,然后通过对称密钥对消息进行加密,从而保证消息的机密性。

    DH的流程原理如下:

    • 1.发送方Alice:
      • 选择两个质数P和G——大质数P和生成元G
      • 然后生成一个随机数A作为自己的私钥
      • 接着计算(G^A)modP作为自己的公钥
      • 最后把P、G和公钥(G^A)modP通过可能被监听的网络发送给接收方Bobby
      • 注意:私钥A是不公开、不发送的,Alice自己保留
    • 2.接收方Bobby:
      • 收到Alice的消息后,首先选择一个随机数B作为自己的私钥
      • 然后计算(G^B)modP作为自己的公钥
      • 最后Bobby将自己的公钥(G^B)modP发送给Alice
      • 注意:私钥B是不公开、不发送的,Bobby自己保留
    • 3.双方计算出相同的对称密钥
      • Alice通过自己的私钥A和对方的公钥(G^B)modP计算出相同的对称密钥:((G^BmodP)^A)modP,即(G^(B*A))modP
      • Bobby通过自己的私钥B和对方的公钥(G^A)modP计算出相同的对称密钥:((G^AmodP)^B)modP,即(G^(A*B))modP
    • 4.最后算双方通过计算出的、相同的对称密钥进行加密通信

    再做进一步的抽象,四小步骤实际上可以抽象成两个阶段:

    1. 第一阶段(第1~3步):通过DH算法进行密钥协商,协商出对称密钥
    2. 第二阶段(第4步):通过协商出的对称密钥进行加密通信

    我们分析一下在Alice、Bobby协商密钥的过程中,中间人Eaves通过窃听到的信息能否计算出密钥:

    • Alice拥有的信息:大质数P、生成元G、自己的公钥(G^A)modP和私钥A、对方Bobby的公钥(G^B)modP,然后通过自己的私钥和对方的公钥计算出对称密钥
    • Bobby拥有的信息:大质数P、生成元G、自己的公钥(G^B)modP和私钥B、对方Alice的公钥(G^A)modP,然后通过自己的私钥和对方的公钥计算出对称密钥
    • Eaves能窃听到的公开信息:大质数P、生成元G、Alice的公钥(G^A)modP、Bobby的公钥公钥(G^B)modP,因为Eaves没有私钥A或B,所以无法计算出密钥

    二、流程原理在Java中的对应

    JDK的API封装了很多细节(大质数P、生成元G、私钥A/B,公钥(G^A)modP/(G^B)modP都封装到了PublicKey或PrivateKey对象中),对外程序员能看到的类就是公钥、私钥,DH流程原理的细节对应放在第四部分再剖析,这一部分只做大概的、整体上的流程对应,目的是为了顺利写出第三部分的应用代码。

    1.密钥协商阶段,这一部分主要是DH算法:

    • Alice通过KeyPairGenerator生成自己的密钥对,保留私钥PrivateKey,然后将公钥PublicKey发送给Bobby,通过网络一般不直接发送Java对象,而是发送公钥的字节数组;
    • Bobby收到Alice发过来的公钥数组:
      • 先通过X509EncodedKeySpec解析公钥规范,然后通过KeyFactory还原出公钥对象PublicKey(实际上公钥对象包含了大质数P、生成元G和Alice的公钥Y)
      • 接着通过还原出的公钥对象生成自己的密钥对
      • 然后将自己的公钥数组回送给Alice
    • 最后,Alice和Bobby都通过自己的私钥和对方的公钥经由KeyAgreement类计算出相同的共享密钥SecretKey(这里要重点关注)

    2.加密通信阶段,这一部分主要是对称加密算法,本文选择现役、流行的AES算法为例:

    • 通过Cipher.getInstance工厂方法拿到加解密对象
    • 设置加密的参数:cipher_mode、密钥协商阶段得到的SecretKey、分组密码分组的模式所需的初始化向量IvParameterSpec
    • doFinal完成加解密

    三、应用代码

    我们按照第二部分中的流程来。

    1.Alice生成密钥对

    	// 密钥协商算法密钥对生成入参,查看KeyPairGenerator.getInstance文档
    	private static String DH_KEY = "DH";
    
    	/***
    	 * Alice生成密钥对
    	 * @return Alice的密钥对
    	 * @throws Exception
    	 */
    	public static KeyPair generateKeyPair() throws Exception {
    		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
    		return keyPairGenerator.generateKeyPair();
    	}

    代码很简洁,几乎都是固定格式,你需要关注的是KeyPairGenerator.getInstance方法的入参,这个字符串从哪里去找?

    JDK中都在一个页面找,后面的getInstance都在相同的页面,只是要在页面找不同的类的文档,页面地址:​​​​​​Java Security Standard Algorithm Names

    找到KeyPairGenerator类的文档,截图如下:

     2.Bobby根据Alice的公钥生成自己的密钥对

    	/**
    	 * Bobby收到Alice的公钥数组:
    	 * 1.先将公钥数组解析成公钥对象
    	 * 	KeyFactory.getInstance的入参查看KeyFactory的文档
    	 *  文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
    	 * 2.根据Alice的公钥对象生成自己的密钥对
    	 * @param publicKeyArray 收到的、对方的公钥数组
    	 * @return
    	 * @throws Exception
    	 */
    	public static KeyPair generateKeyPair(byte[] publicKeyArray) throws Exception {
    		// 将Alice发过来的公钥数组解析成公钥对象
    		X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyArray);
    		KeyFactory keyFactory = KeyFactory.getInstance(DH_KEY);
    		PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
    		// Bobby根据Alice的公钥生成自己的密钥对
    		DHParameterSpec dhParameterSpec = ((DHPublicKey)publicKey).getParams();
    		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
    		keyPairGenerator.initialize(dhParameterSpec);
    		return keyPairGenerator.generateKeyPair();
    	}

    Alice将自己的公钥通过网络传送给Bobby,一般传送的是公钥数组,所以当Bobby收到Alice的公钥数组以后,通过X509EncodedKeySpec封装公钥数组,再通过KeyFactory还原出公钥对象,这里要关注两个问题:

    • 公钥私钥规范;
    • KeyFactory.getInstance的入参

    a.公钥私钥规范

    Java中公钥数组通过X509EncodedKeySpec规范来解析,私钥数组通过PKCS8EncodedKeySpec规范来解析,代码都是一样的。

    X509EncodedKeySpec:This class represents the ASN.1 encoding of a public key, encoded according to the ASN.1 type SubjectPublicKeyInfo.

    PKCS8EncodedKeySpec:This class represents the ASN.1 encoding of a private key, encoded according to the ASN.1 type PrivateKeyInfo.

    ASN.1是一种数据格式标准;PKCS(The Public-Key Cryptography Standards)是公钥密码标准,目前有15个标准,其中PKCS8是私钥格式标准,这一部分你可以自行拓展。

    b.KeyFactory.getInstance的入参

    需要查询的JDK文档和KeyPairGenerator是一样的,只是要查页面KeyFactory部分:

    3.Alice和Bobby根据自己的私钥和对方的公钥生成相同的对称密钥

    这一部分代码不多,但是你需要重点关注,因为这是将密钥协商阶段和加密通信阶段连接起来的部分,两个阶段通过这一部分计算出来的对称密钥衔接在一起。

    该部分需要关注JDK的版本,在JDK 8u161前后的写法是不一样的。我们先看JDK 8u161之前的写法,然后过一下JDK 8u161中的变化,最后再看看现在的写法。

    在JDK 8u161之前:

    	/**
    	 * Alice和Bobby通过自己的私钥和对方的公钥计算生成相同的对称密钥
    	 *  KeyAgreement.getInstance入参查看相同的文档
    	 *  文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
    	 * @param publicKey 对方的公钥
    	 * @param privateKey 自己的私钥
    	 * @return 用于信息加密的对称密钥
    	 * @throws Exception
    	 */
    	@Deprecated
    	public static SecretKey generateSecretKey(PublicKey publicKey, PrivateKey privateKey) throws Exception {
    		KeyAgreement keyAgreement = KeyAgreement.getInstance(DH_KEY);
    		keyAgreement.init(privateKey);
    		keyAgreement.doPhase(publicKey, true);
    		return keyAgreement.generateSecret(AES_KEY);
    	}

    KeyAgreement.gtInstance的入参找法和上面都是一样的,关注点在KeyAgreement.generateSecret方法。

    keyAgreement.generateSecret(AES_KEY)在JDK 8u161前是可以正常实用,之后不再推荐使用了,直接报异常:

    这个异常信息很迷啊,实际上并不是不支持AES算法,根据异常链的信息,你直接点到DHKeyAgreement类中去看:

    这个AllowKDF.VALUE就是一个系统变量:

    这个系统变量默认是没有设置的,你通过System.out.println(System.getProperty("jdk.crypto.KeyAgreement.legacyKDF"))打印出来值是null,所以在JDK 8u161之后你想要用这个方法,你要先设置这个系统变量:

    System.setProperty("jdk.crypto.KeyAgreement.legacyKDF", "true");

    但是Oracle官方并不推荐这样做,我们转去看看JDK 8u161的更新文档。

    JDK 8u161 Update Release Notes:Java™ SE Development Kit 8, Update 161 (oracle.com)

    不推荐使用的原因截图如下:

    那现在应该怎么做呢,官方给了三种方式,截图如下:

    A是根据相关密钥规范实现密钥派生功能,这对密码学素养要求比较高,对绝大多数程序员来说不现实,直接pass;

    C就是设置System.getProperty("jdk.crypto.KeyAgreement.legacyKDF")系统变量,启用keyAgreement.generateSecret(AES_KEY)方法,但是不安全,直接pass;

    那就只有B了,实现简单,也足够安全,代码也几乎是固定格式:

    	/**
    	 * Alice和Bobby通过自己的私钥和对方的公钥计算生成相同的对称密钥
    	 * 注意:当前推荐的用法
    	 * @param publicKey 对方的公钥
    	 * @param privateKey 自己的私钥
    	 * @return 生成的对称密钥
    	 * @throws Exception
    	 */
    	public static SecretKey generateSecretKeyBySHA256(PublicKey publicKey, PrivateKey privateKey) throws Exception {
    		KeyAgreement keyAgreement = KeyAgreement.getInstance(DH_KEY);
    		keyAgreement.init(privateKey);
    		keyAgreement.doPhase(publicKey, true);
    		// 当前推荐的做法
    		byte[] keyArray = keyAgreement.generateSecret();
    		MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    		byte[] digest = messageDigest.digest(keyArray);
    		return new SecretKeySpec(digest, AES_KEY);
    	}

    同样,MessageDigest.getInstance入参查询的也是同样的页面。

    Java对单向散列函数的支持是最容易上手的了,就是一个类MessageDigest和一个方法digest,然后你只要知道不再使用退役算法,熟悉现役、常见的算法就行了。例如本例中SHA-256,现役、常用的单向散列函数,安全强度128bit,散列值长度256bit。

    单向散列函数的命名是有规律的,安全强度是其输出的散列值的一半,输出的散列值长度就在算法名中,例如SHA256,安全强度128bit、散列值256bit;SHA384,安全强度192bit、散列值384bit;SHA512,安全强度256bit、散列值512bit。

    其他还需要知道的:

    • MD系列已经退役了,MD5散列值128bit,安全强度才仅仅18bit,只需要26W次计算就能破解,按照当前的算力分分钟就无了;
    • SHA1系列已经退役了;
    • SHA2系列,SHA-256往上都是现役;
    • SHA3系列,SHA3-256以上都是现役;
    • 单向散列函数一定要注意避免长度延展攻击,SHA-512/224和SHA-512/256已经避免了该问题,如果选用其他现役算法,那就要注意公开消息和私密消息的顺序问题;

    4.Alice和Bobby通过对称密钥对消息进行加解密后通信

    	/**
    	 * 生成分组密码分组模式的初始化向量
    	 * 本例中,AES的分组128bit,16字节
    	 * @param length
    	 * @return
    	 */
    	public static byte[] initIV(int length) {
    		return random.generateSeed(length);
    	}
    	
    	/**
    	 * 加密
    	 * @param secretKey 密钥协商阶段协商的对称密钥
    	 * @param iv 分组密码分组模式的初始化向量,加密和解密需要使用相同的iv
    	 * @param data 需要加密的明文字节数组
    	 * @return 密文
    	 * @throws Exception
    	 */
    	public static byte[] encryption(SecretKey secretKey, byte[] iv, byte[] data) throws Exception {
    		// 密钥和算法有关系,和加解密模式没有关系,所以上面分成了AES_KEY和CIPHER_MODE
    		Cipher cipher = Cipher.getInstance(CIPHER_MODE);
    		// 分组密码分组模式的初始化向量参数,本例中是CBC模式
    		IvParameterSpec ivp = new IvParameterSpec(iv);
    		cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivp);
    		return cipher.doFinal(data);
    	}
    	
    	/**
    	 * 解密
    	 * @param secretKey 密钥协商阶段协商的对称密钥
    	 * @param iv 分组密码分组模式的初始化向量,加密和解密需要使用相同的iv
    	 * @param data 需要解密的密文的字节数组
    	 * @return 解密后的明文
    	 * @throws Exception
    	 */
    	public static byte[] decryption(SecretKey secretKey, byte[] iv, byte[] data) throws Exception {
    		Cipher cipher = Cipher.getInstance(CIPHER_MODE);
    		// 分组密码分组模式的初始化向量参数,本例中是CBC模式
    		IvParameterSpec ivp = new IvParameterSpec(iv);
    		cipher.init(Cipher.DECRYPT_MODE, secretKey, ivp);
    		return cipher.doFinal(data);
    	}

    Cipher.getInstance的入参查询方法和上面一样。

    分组密码的模式参看java DES_厚积薄发者,轻舟万重山-CSDN博客

    5.最后看一下完整的代码和简单的测试用例

    package wxy.secret;
    
    import java.security.KeyFactory;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.MessageDigest;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.security.SecureRandom;
    import java.security.spec.X509EncodedKeySpec;
    import javax.crypto.Cipher;
    import javax.crypto.KeyAgreement;
    import javax.crypto.SecretKey;
    import javax.crypto.interfaces.DHPublicKey;
    import javax.crypto.spec.DHParameterSpec;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    
    public class DiffieHellman {
    	// JDK文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
    	// 密钥协商算法密钥对生成入参,查看KeyPairGenerator.getInstance文档
    	private static String DH_KEY = "DH";
    	// 对称加密算法密钥生成入参,参看KeyGenerator.getInstance文档
    	private static String AES_KEY = "AES";
    	// 分组密码的模式入参,参看Cipher.getInstance文档 
    	private static String CIPHER_MODE = "AES/CBC/PKCS5Padding";
    	private static SecureRandom random = new SecureRandom();
    	
    	/***
    	 * Alice生成密钥对
    	 * KeyPairGenerator.getInstance的入参查看KeyPairGenerator的文档
    	 *  文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
    	 * @return Alice的密钥对
    	 * @throws Exception
    	 */
    	public static KeyPair generateKeyPair() throws Exception {
    		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
    		return keyPairGenerator.generateKeyPair();
    	}
    	
    	/**
    	 * Bobby收到Alice的公钥数组:
    	 * 1.先将公钥数组解析成公钥对象
    	 * 	KeyFactory.getInstance的入参查看KeyFactory的文档
    	 *  文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
    	 * 2.根据Alice的公钥对象生成自己的密钥对
    	 * @param publicKeyArray
    	 * @return
    	 * @throws Exception
    	 */
    	public static KeyPair generateKeyPair(byte[] publicKeyArray) throws Exception {
    		// 将Alice发过来的公钥数组解析成公钥对象
    		X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyArray);
    		KeyFactory keyFactory = KeyFactory.getInstance(DH_KEY);
    		PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
    		// Bobby根据Alice的公钥生成自己的密钥对
    		DHParameterSpec dhParameterSpec = ((DHPublicKey)publicKey).getParams();
    		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
    		keyPairGenerator.initialize(dhParameterSpec);
    		return keyPairGenerator.generateKeyPair();
    	}
    	
    	/**
    	 * Alice和Bobby通过自己的私钥和对方的公钥计算生成相同的对称密钥
    	 *  KeyAgreement.getInstance入参查看相同的文档
    	 *  文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
    	 * 注意:这种实现方式不安全,已不推荐使用,参看JDK 8u161的更新文档
    	 * 	文档地址:https://www.oracle.com/java/technologies/javase/8u161-relnotes.html
    	 * @param publicKey 对方的公钥
    	 * @param privateKey 自己的私钥
    	 * @return 用于信息加密的对称密钥
    	 * @throws Exception
    	 */
    	@Deprecated
    	public static SecretKey generateSecretKey(PublicKey publicKey, PrivateKey privateKey) throws Exception {
    		KeyAgreement keyAgreement = KeyAgreement.getInstance(DH_KEY);
    		keyAgreement.init(privateKey);
    		keyAgreement.doPhase(publicKey, true);
    		// 关注keyAgreement.generateSecret(AES_KEY),在JDK 8u161前后有很大差异
    		return keyAgreement.generateSecret(AES_KEY);
    	}
    	
    	/**
    	 * Alice和Bobby通过自己的私钥和对方的公钥计算生成相同的对称密钥
    	 * 注意:当前推荐的用法
    	 * @param publicKey 对方的公钥
    	 * @param privateKey 自己的私钥
    	 * @return 生成的对称密钥
    	 * @throws Exception
    	 */
    	public static SecretKey generateSecretKeyBySHA256(PublicKey publicKey, PrivateKey privateKey) throws Exception {
    		KeyAgreement keyAgreement = KeyAgreement.getInstance(DH_KEY);
    		keyAgreement.init(privateKey);
    		keyAgreement.doPhase(publicKey, true);
    		// 当前推荐的做法
    		byte[] keyArray = keyAgreement.generateSecret();
    		MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    		byte[] digest = messageDigest.digest(keyArray);
    		return new SecretKeySpec(digest, AES_KEY);
    	}
    	
    	/**
    	 * 生成分组密码分组模式的初始化向量
    	 * 本例中,AES的分组128bit,16字节
    	 * @param length
    	 * @return
    	 */
    	public static byte[] initIV(int length) {
    		return random.generateSeed(length);
    	}
    	
    	/**
    	 * 加密
    	 * @param secretKey 密钥协商阶段协商的对称密钥
    	 * @param iv 分组密码分组模式的初始化向量,加密和解密需要使用相同的iv
    	 * @param data 需要加密的明文字节数组
    	 * @return 密文
    	 * @throws Exception
    	 */
    	public static byte[] encryption(SecretKey secretKey, byte[] iv, byte[] data) throws Exception {
    		// 密钥和算法有关系,和加解密模式没有关系,所以上面分成了AES_KEY和CIPHER_MODE
    		Cipher cipher = Cipher.getInstance(CIPHER_MODE);
    		// 分组密码分组模式的初始化向量参数,本例中是CBC模式
    		IvParameterSpec ivp = new IvParameterSpec(iv);
    		cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivp);
    		return cipher.doFinal(data);
    	}
    	
    	/**
    	 * 解密
    	 * @param secretKey 密钥协商阶段协商的对称密钥
    	 * @param iv 分组密码分组模式的初始化向量,加密和解密需要使用相同的iv
    	 * @param data 需要解密的密文的字节数组
    	 * @return 解密后的明文
    	 * @throws Exception
    	 */
    	public static byte[] decryption(SecretKey secretKey, byte[] iv, byte[] data) throws Exception {
    		Cipher cipher = Cipher.getInstance(CIPHER_MODE);
    		// 分组密码分组模式的初始化向量参数,本例中是CBC模式
    		IvParameterSpec ivp = new IvParameterSpec(iv);
    		cipher.init(Cipher.DECRYPT_MODE, secretKey, ivp);
    		return cipher.doFinal(data);
    	}
    }
    
    		// 不推荐通过该方式启用keyAgreement.generateSecret(AES_KEY)方法
    		//System.setProperty("jdk.crypto.KeyAgreement.legacyKDF", "true");
    		
    		// 1.Alice生成密钥对
    		KeyPair keyPairA = DiffieHellman.generateKeyPair();
    		// 2.Alice将自己的公钥发送给Bobby,实际上公钥对象中封装了大质数P、生成元G和Alice的公钥Y
    		PublicKey publicKeyA = keyPairA.getPublic();
    		byte[] publicKeyArray = publicKeyA.getEncoded();
    		// 3.Bobby根据Alice的公钥对象生成自己的密钥对
    		KeyPair keyPairB = DiffieHellman.generateKeyPair(publicKeyArray);
    		// 4.Bobby将自己的公钥回送给Alice
    		PublicKey publicKeyB = keyPairB.getPublic();
    		// 5.Alice根据自己的私钥和Bobby的公钥生成对称密钥
    		SecretKey secretKeyA = DiffieHellman.generateSecretKeyBySHA256(publicKeyB, keyPairA.getPrivate());
    		// 6.Bobby根据自己的私钥和Alice的公钥生成对称密钥
    		SecretKey secretKeyB = DiffieHellman.generateSecretKeyBySHA256(publicKeyA, keyPairB.getPrivate());
    		
    		System.out.print("Alice生成的对称密钥:");
    		HexStringTool.print(secretKeyA.getEncoded());
    		System.out.print("Bobby生成的对称密钥:");
    		HexStringTool.print(secretKeyB.getEncoded());
    		
    		String message = "Diffie-Hellman密钥协商算法简介";
    		byte[] data = message.getBytes(Charset.forName("UTF-8"));
    		
    		// 初始化分组密码分组模式的初始化向量,本例AES CBC模式,分组长度128bit,所以IV也是128bit
    		byte[] iv = DiffieHellman.initIV(16);
    		// 加密
    		byte[] ciphertext = DiffieHellman.encryption(secretKeyA, iv, data);
    		System.out.print("加密后的密文:");
    		HexStringTool.print(ciphertext);
    		// 解密
    		byte[] plaintext = DiffieHellman.decryption(secretKeyB, iv, ciphertext);
    		System.out.print("解密后的明文:");
    		HexStringTool.print(plaintext);
    		// 将解密后的字节数组还原成UTF8编码的字符
    		System.out.println("解密得到的明文数组还原成UTF8编码:"+new String(plaintext, Charset.forName("UTF-8")));
    Alice生成的对称密钥:3735c170f563519c92f80f89548bac53f68607f5a89a2a6d348b0f77a9fb6228
    Bobby生成的对称密钥:3735c170f563519c92f80f89548bac53f68607f5a89a2a6d348b0f77a9fb6228
    加密后的密文:4270b6ed0b6cea65bb927a6738ff1db96cb40960ac326eb7a0ee2e32a71accacd3df2612e205450ead6ef222d7e302b2
    解密后的明文:4469666669652d48656c6c6d616ee5af86e992a5e58d8fe59586e7ae97e6b395e7ae80e4bb8b
    解密得到的明文数组还原成UTF8编码:Diffie-Hellman密钥协商算法简介

    四、源码分析简述

    这一部分是将JDK API中封装的原理细节剖析出来,Diffie-Hellman原理参看第一部分,对应的Java API流程参看第二部分。

    1.生成密钥对

    入口:keyPairGenerator.generateKeyPair()

    实现:com.sun.crypto.provider.DHKeyPairGenerator.generateKeyPair()

        /**
         * Generates a key pair.
         * @return the new key pair
         */
        public KeyPair generateKeyPair() {
            if (random == null) {
                random = SunJCE.getRandom();
            }
    
            if (params == null) {
                try {
                    params = ParameterCache.getDHParameterSpec(pSize, random);
                } catch (GeneralSecurityException e) {
                    // should never happen
                    throw new ProviderException(e);
                }
            }
    
            BigInteger p = params.getP();
            BigInteger g = params.getG();
    
            if (lSize <= 0) {
                lSize = pSize >> 1;
                // use an exponent size of (pSize / 2) but at least 384 bits
                if (lSize < 384) {
                    lSize = 384;
                }
            }
    
            BigInteger x;
            BigInteger pMinus2 = p.subtract(BigInteger.TWO);
    
            //
            // PKCS#3 section 7.1 "Private-value generation"
            // Repeat if either of the followings does not hold:
            //     0 < x < p-1
            //     2^(lSize-1) <= x < 2^(lSize)
            //
            do {
                // generate random x up to 2^lSize bits long
                x = new BigInteger(lSize, random);
            } while ((x.compareTo(BigInteger.ONE) < 0) ||
                ((x.compareTo(pMinus2) > 0)) || (x.bitLength() != lSize));
    
            // calculate public value y
            BigInteger y = g.modPow(x, p);
    
            DHPublicKey pubKey = new DHPublicKey(y, p, g, lSize);
            DHPrivateKey privKey = new DHPrivateKey(x, p, g, lSize);
            return new KeyPair(pubKey, privKey);
        }

    其中,大质数P、生成元G,x = new BigInteger(lSize, random)生成的随机数x作为私钥,BigInteger y = g.modPow(x, p)计算出的y作为公钥(g的x次方mod p)。

    然后私钥对象封装了大质数P、生成元G和私钥X;公钥对象封装了大质数P、生成元G和公钥Y。

    2.通过自己的私钥和对方的公钥生成相同的对称密钥

    入口:keyAgreement.generateSecret();

    实现:com.sun.crypto.provider.DHKeyAgreement.engineGenerateSecret(byte[], int)

        /**
         * Generates the shared secret, and places it into the buffer
         * <code>sharedSecret</code>, beginning at <code>offset</code>.
         *
         * <p>If the <code>sharedSecret</code> buffer is too small to hold the
         * result, a <code>ShortBufferException</code> is thrown.
         * In this case, this call should be repeated with a larger output buffer.
         *
         * <p>This method resets this <code>KeyAgreementSpi</code> object,
         * so that it
         * can be reused for further key agreements. Unless this key agreement is
         * reinitialized with one of the <code>engineInit</code> methods, the same
         * private information and algorithm parameters will be used for
         * subsequent key agreements.
         *
         * @param sharedSecret the buffer for the shared secret
         * @param offset the offset in <code>sharedSecret</code> where the
         * shared secret will be stored
         *
         * @return the number of bytes placed into <code>sharedSecret</code>
         *
         * @exception IllegalStateException if this key agreement has not been
         * completed yet
         * @exception ShortBufferException if the given output buffer is too small
         * to hold the secret
         */
        protected int engineGenerateSecret(byte[] sharedSecret, int offset)
            throws IllegalStateException, ShortBufferException
        {
            if (generateSecret == false) {
                throw new IllegalStateException
                    ("Key agreement has not been completed yet");
            }
    
            if (sharedSecret == null) {
                throw new ShortBufferException
                    ("No buffer provided for shared secret");
            }
    
            BigInteger modulus = init_p;
            int expectedLen = (modulus.bitLength() + 7) >>> 3;
            if ((sharedSecret.length - offset) < expectedLen) {
                throw new ShortBufferException
                        ("Buffer too short for shared secret");
            }
    
            // Reset the key agreement after checking for ShortBufferException
            // above, so user can recover w/o losing internal state
            generateSecret = false;
    
            /*
             * NOTE: BigInteger.toByteArray() returns a byte array containing
             * the two's-complement representation of this BigInteger with
             * the most significant byte is in the zeroth element. This
             * contains the minimum number of bytes required to represent
             * this BigInteger, including at least one sign bit whose value
             * is always 0.
             *
             * Keys are always positive, and the above sign bit isn't
             * actually used when representing keys.  (i.e. key = new
             * BigInteger(1, byteArray))  To obtain an array containing
             * exactly expectedLen bytes of magnitude, we strip any extra
             * leading 0's, or pad with 0's in case of a "short" secret.
             */
            byte[] secret = this.y.modPow(this.x, modulus).toByteArray();
            if (secret.length == expectedLen) {
                System.arraycopy(secret, 0, sharedSecret, offset,
                                 secret.length);
            } else {
                // Array too short, pad it w/ leading 0s
                if (secret.length < expectedLen) {
                    System.arraycopy(secret, 0, sharedSecret,
                        offset + (expectedLen - secret.length),
                        secret.length);
                } else {
                    // Array too long, check and trim off the excess
                    if ((secret.length == (expectedLen+1)) && secret[0] == 0) {
                        // ignore the leading sign byte
                        System.arraycopy(secret, 1, sharedSecret, offset, expectedLen);
                    } else {
                        throw new ProviderException("Generated secret is out-of-range");
                    }
                }
            }
            return expectedLen;
        }

    重点就关注两句代码:

    byte[] secret = this.y.modPow(this.x, modulus).toByteArray();

    其中modulus就是大质数P:

    BigInteger modulus = init_p;

    所以this.y.modPow(this.x, modulus).toByteArray()这句代码中,y是对方的公钥,x是自己的私钥,modulus是大质数P,即y的x次方mod p。

    这些源码封装了的实现细节和第一部分的原理一模一样。

    鉴于篇幅问题,ECDH就不再写出来了,你可以自己试一试,本质都是DH密钥协商算法,只是通过ECC椭圆曲线来实现。实现代码几乎都是一致的,只是由于历史原因,ECDH中KeyPairGenerator.getInstance和KeyAgreement.getInstance的入参不一样,注意这个细节就行了。

    写文章确实很费时间,就到这里吧,感谢阅读。

    展开全文
  • 基于[(t,n)]门限秘密共享的思想,提出一种组密钥协商方案,该方案应用对称多项式可以不需要提前保证组内用户间通信信道的安全。每个用户根据share计算自己的分量(Component),通过秘密恢复机制实现组密钥协商和...
  • 密钥协商是实现参与者在公平的开放环境下建立会话密钥的重要手段。最近,Alvarez等人提出了一种新的密钥协商协议,该协议的会话密钥是通过分块上三角矩阵的幂乘得到的。但研究发现,该协议并不安全,其安全参数可以...
  • 借鉴Zheng和张串绒将签密技术运用到密钥协商协议中的思想,利用基于身份的签密方案,提出一种具有前向安全性的密钥协商协议。该协议在具有基于身份的公钥密码体制特点的同时,又拥有签密技术的优点。与已有的方案...
  • 针对物联网在用户身份验证上存在的安全性问题,提出一种轻量级的动态化密钥协商的物联网身份认证协议(DLT)。该协议在用户进行登录验证上使用了时间戳值,这使得恶意攻击者不能使用早期的消息,可以防范重放攻击...
  • 一个具有多个注册中心的双向认证与密钥协商协议
  • 通过分析无线局域网的安全协议,发现密钥协商机制——四次握手协议在第一次握手时未对消息进行任何处理,导致该协议存在拒绝服务攻击的安全隐患,就此提出加密管理帧的方法来消除该安全隐患,并对该方法加以论证。
  • 对于目前提出的无证书三方密钥协商方案大都使用了耗费时间的对运算,具有较大的计算量,根据离散对数(DL)问题并以公钥密码学理论为基础,设计出一个不用到对运算的无证书三方密钥协商协议,该协议解决了基于身份的...
  • 现有的三方认证密钥协商协议安全性低且计算开销较大,提出一种基于身份的增强三方认证密钥协商协议。新协议在实现密钥协商基本安全属性的同时,利用短签名和时间戳技术进一步提高安全性。分析表明,增强协议能满足...
  • #资源达人分享计划#
  • 密钥协商(GKA)协议在构建安全多播信道中扮演着主要角色。由于公钥管理的简洁性和高效性,基于身份的认证群密钥协商协议密码系统近年来成为热门研究方向。提出了一个基于Weil对和完全三叉树结构的群密钥协商协议...
  • 基于混沌映射的三方认证密钥协商方案的安全性

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,802
精华内容 10,720
关键字:

密钥协商

友情链接: fingerprint.rar