2024-03-21  阅读(10)
原文作者:小小工匠 原文地址: https://artisan.blog.csdn.net/article/details/136406607

202403212042170291.png


概述

对称加密算法解决了数据加密的问题,例如AES加密可以有效地保护文件的安全性。然而,一个关键的挑战是 如何在不安全的通信信道上安全地传输密钥

假设小明需要向路人甲发送一个加密文件,他可以先生成一个AES密钥,使用该密钥对文件进行加密,然后将加密后的文件发送给对方。但是,问题在于对方需要密钥才能解密文件,因此密钥的传输必须是安全的。

在传统的密钥传输方法中,密钥通常通过不同的方式发送,如口头告知、书面传递或者其他安全信道。 然而,在不安全的通信信道上,这些方法可能会暴露密钥,导致密钥被截获或篡改,从而威胁到加密数据的安全性

为了解决这个问题,出现了密钥交换算法,例如Diffie-Hellman算法。Diffie-Hellman算法允许通信双方在不安全的通信信道上协商一个共享密钥,而不需要事先共享任何秘密信息。通过该算法,通信双方可以在不直接传输密钥的情况下安全地协商出一个共享的密钥,从而实现安全的加密通信

DH算法是一种通过数学原理实现安全密钥交换的方法,它允许通信双方在不直接传输密钥的情况下协商出一个共享的密钥

综上所述,密钥交换算法的出现弥补了传统密钥传输方法的不足,在不安全的通信信道上安全地传输密钥,为加密通信提供了更加可靠的保障。


数学理论支持

Diffie-Hellman算法是一种用于安全地交换密钥的协议,通常用于在不安全的通信信道上建立共享密钥,以便进行加密通信。这个算法允许两个对等方在没有事先共享密钥的情况下,通过公开的交换来生成共享的密钥。Diffie-Hellman算法的核心思想是利用离散对数问题的困难性,使得即使在公开的通信信道上,攻击者也无法推导出共享密钥。

简单来说,Diffie-Hellman算法的步骤如下:

  1. 选取一个大素数p和一个原根g,并将它们公开。
  2. 每个对等方选择一个私密数(称为私钥),并将其保密。
  3. 每个对等方利用p、g和自己的私钥计算出一个公开的值(称为公钥)。
  4. 对等方交换公钥。
  5. 每个对等方使用自己的私钥和对方的公钥,计算出一个共享的密钥。

我们举个例子来看

  1. 甲方首先选择一个素数p和一个原根g,例如,p=97,g=5。这些参数是公开的,双方都知道。
  2. 甲方选择一个随机数a,例如,a=123,并计算A=g^a mod p,得到A=34。
  3. 甲方将p、g和A的值发送给乙方。
  4. 乙方收到甲方发送的参数后,选择一个随机数b,例如,b=456,并计算B=g^b mod p,得到B=75。同时,乙方计算出共享密钥s = A^b mod p,得到s=22。
  5. 乙方将B的值发送给甲方。
  6. 甲方收到乙方发送的B后,计算共享密钥s = B^a mod p,得到的结果与乙方计算的结果一样,都是22。

因此,最终双方都得到了相同的共享密钥s=22。需要注意的是,通过网络传输的参数p、g、A和B无法推算出密钥s,因为实际应用中选择的素数p非常大,计算s的复杂度很高,从而保证了密钥的安全性。

通过这种方式,甲乙双方在不直接传输密钥的情况下成功完成了密钥交换,从而实现了安全的通信。

由于Diffie-Hellman算法的数学基础比较复杂,它的安全性建立在一个数学难题上,即计算离散对数的困难性。攻击者需要解决这个数学难题才能推导出共享密钥,因此Diffie-Hellman算法被广泛应用于安全通信领域。

简单的Java实现示例:

    import java.math.BigInteger;
    import java.security.SecureRandom;
    
    public class DiffieHellman {
        
        // 素数p
        private static final BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
                + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
                + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
                + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
                + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
                + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
                + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
                + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", 16);
        
        // 原根g
        private static final BigInteger g = BigInteger.valueOf(2);
        
        // 生成私钥
        public static BigInteger generatePrivateKey() {
            SecureRandom random = new SecureRandom();
            BigInteger privateKey = new BigInteger(p.bitLength(), random);
            // 私钥必须小于p
            return privateKey.mod(p);
        }
        
        // 计算公钥
        public static BigInteger calculatePublicKey(BigInteger privateKey) {
            return g.modPow(privateKey, p);
        }
        
        // 计算共享密钥
        public static BigInteger calculateSharedSecret(BigInteger privateKey, BigInteger otherPublicKey) {
            return otherPublicKey.modPow(privateKey, p);
        }
    
        public static void main(String[] args) {
            // Alice生成私钥和公钥
            BigInteger alicePrivateKey = generatePrivateKey();
            BigInteger alicePublicKey = calculatePublicKey(alicePrivateKey);
            System.out.println("Alice's Private Key: " + alicePrivateKey);
            System.out.println("Alice's Public Key: " + alicePublicKey);
            
            // Bob生成私钥和公钥
            BigInteger bobPrivateKey = generatePrivateKey();
            BigInteger bobPublicKey = calculatePublicKey(bobPrivateKey);
            System.out.println("Bob's Private Key: " + bobPrivateKey);
            System.out.println("Bob's Public Key: " + bobPublicKey);
            
            // Alice和Bob计算共享密钥
            BigInteger aliceSharedSecret = calculateSharedSecret(alicePrivateKey, bobPublicKey);
            BigInteger bobSharedSecret = calculateSharedSecret(bobPrivateKey, alicePublicKey);
            
            // 验证共享密钥是否相同
            System.out.println("Alice's Shared Secret: " + aliceSharedSecret);
            System.out.println("Bob's Shared Secret: " + bobSharedSecret);
            System.out.println("Shared Secrets Match: " + aliceSharedSecret.equals(bobSharedSecret));
        }
    }

这个示例演示了两个对等方(Alice和Bob)如何使用Diffie-Hellman算法协商共享密钥。 每个对等方都生成一个私钥,并计算出对应的公钥。然后,它们交换公钥,并使用自己的私钥和对方的公钥计算出共享的密钥。最后,它们验证计算得到的共享密钥是否相同


使用Java实现DH算法

    package com.artisan.securityalgjava.DiffieHellman;
    
    import java.math.BigInteger;
    import java.security.*;
    import java.security.spec.*;
    
    import javax.crypto.KeyAgreement;
    
    /**
     * @author 小工匠
     * @version 1.0
     * @mark: show me the code , change the world
     */
    public class DHExample {
        public static void main(String[] args) {
            // Bob和Alice:
            Person bob = new Person("Bob");
            Person alice = new Person("Alice");
    
            // 各自生成KeyPair:
            bob.generateKeyPair();
            alice.generateKeyPair();
    
            // 双方交换各自的PublicKey:
            // Bob根据Alice的PublicKey生成自己的本地密钥:
            bob.generateSecretKey(alice.publicKey.getEncoded());
            // Alice根据Bob的PublicKey生成自己的本地密钥:
            alice.generateSecretKey(bob.publicKey.getEncoded());
    
            // 检查双方的本地密钥是否相同:
            bob.printKeys();
            alice.printKeys();
            // 双方的SecretKey相同,后续通信将使用SecretKey作为密钥进行AES加解密...
        }
    }
    
    class Person {
        public final String name;
    
        public PublicKey publicKey;
        private PrivateKey privateKey;
        private byte[] secretKey;
    
        public Person(String name) {
            this.name = name;
        }
    
        /**
         * 生成本地KeyPair
         */
        public void generateKeyPair() {
            try {
                KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
                kpGen.initialize(512);
                KeyPair kp = kpGen.generateKeyPair();
                this.privateKey = kp.getPrivate();
                this.publicKey = kp.getPublic();
            } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
            }
        }
    
        public void generateSecretKey(byte[] receivedPubKeyBytes) {
            try {
                // 从byte[]恢复PublicKey:
                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(receivedPubKeyBytes);
                KeyFactory kf = KeyFactory.getInstance("DH");
                PublicKey receivedPublicKey = kf.generatePublic(keySpec);
                // 生成本地密钥:
                KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
                // 自己的PrivateKey
                keyAgreement.init(this.privateKey);
                // 对方的PublicKey
                keyAgreement.doPhase(receivedPublicKey, true);
                // 生成SecretKey密钥:
                this.secretKey = keyAgreement.generateSecret();
            } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
            }
        }
    
        public void printKeys() {
            System.out.printf("Name: %s\n", this.name);
            System.out.printf("Private key: %x\n", new BigInteger(1, this.privateKey.getEncoded()));
            System.out.printf("Public key: %x\n", new BigInteger(1, this.publicKey.getEncoded()));
            System.out.printf("Secret key: %x\n", new BigInteger(1, this.secretKey));
        }
    }

输出

    Name: Bob
    Private key: 3081d202010030819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4020201800433023100d28250d0fe90e3b85afa61ada18204c97f3e4073618cfcdbeea3ca18336e42bda8d1143b976a34eac1954e94f1e26f76
    Public key: 3081df30819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4020201800343000240172702a9a278c0fb05770ae3904e2002dbc3cd5fa1bb98263053c3ca871228139481cb619f3d2178ce591c7c1caf7fccb2092c3bf1fb00d2dd00f4b308c283d4
    Secret key: f2b99ef3c77657dbe06e1c9b037c9e5ae0b44ee294239b2bee0166d4d6f9a05d0a9c00bf669e05562fe83d63049d23a7f6a4ac1ab3a0aae5e9169d0a4889141c
    Name: Alice
    Private key: 3081d202010030819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4020201800433023100a5e352efc6a8cbf155fd780fdfdac894327960cc025f71c3a17739d2fba898ab92fef6286656408265db80a4ab17d1cb
    Public key: 3081df30819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca40202018003430002400e8f0bee5867c4964a71ff3e1ca0b777f05a38fd7bae6255c42af346f4464465e77390254ed6dc474451b0e9a6b7e1d9c15fc2058adf4bbe64622a4e726c353b
    Secret key: f2b99ef3c77657dbe06e1c9b037c9e5ae0b44ee294239b2bee0166d4d6f9a05d0a9c00bf669e05562fe83d63049d23a7f6a4ac1ab3a0aae5e9169d0a4889141c

DH算法的缺点

Diffie-Hellman(DH)算法是一种强大的密钥协商协议,但它也存在一些缺点:

  1. 中间人攻击(Man-in-the-Middle Attack): DH算法本身并未提供对通信双方身份的认证,因此受到中间人攻击的威胁。在DH密钥交换过程中,中间人可以拦截并篡改通信双方的公钥,然后将自己的公钥发送给双方,从而获取他们之间的共享密钥并进行窃听或篡改通信内容。
  2. 密钥验证问题: DH算法生成的共享密钥虽然是安全的,但双方无法确保对方是否真的持有与其公钥相对应的私钥。如果通信双方无法进行身份验证,攻击者可能会冒充其中一方,从而利用共享密钥进行欺骗或篡改通信。
  3. 密钥协商效率: DH算法的密钥协商过程需要大量的计算和通信开销,尤其是在选择较大的素数p和底数g时,计算复杂度更高。这可能会影响通信的效率和性能。
  4. 依赖于安全的素数: DH算法的安全性取决于选择的素数p和底数g的安全性。如果选择的素数不够大或者底数不够随机,可能会导致算法受到攻击。

综上所述,虽然Diffie-Hellman算法在密钥交换方面具有重要的优势,但在实际应用中,必须结合其他安全机制来解决身份认证和中间人攻击等问题,以确保通信的安全性和可靠性。

202403212042177772.png


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文