后端 · 2024年 12月 20日 0

秘钥如何安全交换

首先看看代码实现

package java_se.encryption_security_example;

import javax.crypto.KeyAgreement;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.HexFormat;

public class KeyExchangeAlgorithmExample {
    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");
            keyAgreement.init(this.privateKey); // 自己的PrivateKey
            keyAgreement.doPhase(receivedPublicKey, true); // 对方的PublicKey
            // 生成SecretKey密钥:
            this.secretKey = keyAgreement.generateSecret();
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    public void printKeys() {
        System.out.println("Name: " + this.name);
        System.out.println("Private key: " + HexFormat.of().formatHex(this.privateKey.getEncoded()));
        System.out.println("Public key: " + HexFormat.of().formatHex(this.publicKey.getEncoded()));
        System.out.println("Secret key: " + HexFormat.of().formatHex(this.secretKey));
    }
}

详解代码:

详细步骤

  1. 初始化Person对象
    • 创建两个Person对象,分别代表Bob和Alice。
  2. 生成密钥对
    • 每个Person对象调用generateKeyPair()方法,使用DH算法生成一个密钥对(包含一个公钥和一个私钥)。这里的密钥长度设置为512位,但在实际应用中通常建议使用更长的密钥长度以增强安全性。
  3. 交换公钥并生成秘密密钥
    • Bob和Alice交换各自的公钥。
    • Bob使用Alice的公钥和自己的私钥来计算共享的秘密密钥。
    • Alice同样使用Bob的公钥和自己的私钥来计算共享的秘密密钥。
  4. 验证秘密密钥一致性
    • 调用printKeys()方法打印出Bob和Alice的私钥、公钥以及生成的秘密密钥,验证两者是否一致。

关键技术点

  • Diffie-Hellman密钥交换 :这是一种非对称加密算法,允许两个不共享任何共同信息的用户安全地建立一个共享的秘密密钥。
  • KeyPairGenerator :用于生成公钥/私钥对。
  • X509EncodedKeySpec :用于将公钥从字节数组转换为PublicKey对象。
  • KeyFactory :用于生成公钥对象。
  • KeyAgreement :用于执行密钥协商协议,这里使用的是DH算法。

协商算法详解

Diffie-Hellman 密钥交换算法

Diffie-Hellman 密钥交换算法是一种非对称加密算法,它允许两个实体在没有预先共享任何秘密的情况下安全地生成一个共享的秘密密钥。以下是详细的步骤:

直观的数字重现算法

如果源根和大素数是公开的,为什么不通过公钥推算私钥?

程序中直接就是交换公钥,并没有协商素数和源根啊,如何实现的?

在实际的 Diffie-Hellman 密钥交换过程中,确实需要预先确定一组参数,包括大素数 p 和原根 g。这些参数通常是公开的,并且在不同的实体之间共享。在你的 Java 示例代码中,这些参数并没有显式地定义和传递,而是由 Java 的 KeyPairGenerator 和相关库自动处理的。下面我将详细解释这些参数是如何在代码中隐式处理的,并且为什么它们不需要显式地协商。

1. 默认参数和预定义参数集

在实际应用中,Diffie-Hellman 密钥交换通常使用预定义的参数集。这些参数集经过精心设计,确保了安全性和效率。Java 的 KeyPairGenerator 库支持多种预定义的参数集,这些参数集包含了大素数 p 和原根 g

预定义参数集示例

Java 提供了一些预定义的 Diffie-Hellman 参数集,例如:

  • dh/DH 1024 bit prime with 160 bit subprime
  • dh/DH 2048 bit prime with 224 bit subprime
  • dh/DH 2048 bit prime with 256 bit subprime

这些参数集确保了大素数 p 和原根 g 的安全性。

2. Java 中的自动处理

在你的 Java 示例代码中,KeyPairGenerator 会自动选择合适的默认参数集来生成密钥对。具体来说,当你调用 KeyPairGenerator.getInstance("DH") 并初始化时,KeyPairGenerator 会选择一个合适的参数集。

KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
kpGen.initialize(512); // 这里的 512 表示密钥长度
KeyPair kp = kpGen.generateKeyPair();

详细步骤

  1. 选择密钥长度
    • 通过 initialize(512) 方法指定密钥长度。Java 会根据这个长度选择合适的参数集。
  2. 生成密钥对
    • KeyPairGenerator 使用选定的参数集生成公钥和私钥。
  3. 参数集的选择
    • Java 内部维护了一组预定义的参数集,根据指定的密钥长度选择合适的参数集。

3. 隐式参数集

在你的代码中,参数集的选择是隐式的,具体来说:

  • 密钥长度 initialize(512) 指定了密钥长度为 512 位。
  • 参数集 :Java 根据密钥长度选择合适的参数集,这些参数集包含了大素数 p 和原根 g

示例分析

假设你调用了 initialize(512),Java 会自动选择一个适合的 512 位参数集。这个参数集可能类似于以下内容:

  • 大素数 ( p ):一个 512 位的大素数。
  • 原根 g \):一个与 \( p 相关的原根。

这些参数集通常是经过严格测试和验证的,确保了安全性和性能。

使用自定义参数集实例

import javax.crypto.KeyAgreement;
import java.security.*;
import java.security.spec.DHParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HexFormat;

public class KeyExchangeAlgorithmExample {
    public static void main(String[] args) {
        // Bob和Alice:
        Person bob = new Person("Bob");
        Person alice = new Person("Alice");

        // 定义自定义参数集
        int primeSize = 512;
        int exponentSize = 160;
        try {
            AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DH");
            paramGen.init(primeSize, exponentSize, new SecureRandom());
            AlgorithmParameters params = paramGen.generateParameters();
            DHParameterSpec dhParams = params.getParameterSpec(DHParameterSpec.class);

            // 设置自定义参数集
            bob.setDHParameterSpec(dhParams);
            alice.setDHParameterSpec(dhParams);
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }

        // 各自生成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();
    }
}

class Person {
    public final String name;

    public PublicKey publicKey;
    private PrivateKey privateKey;
    private byte[] secretKey;
    private DHParameterSpec dhParamSpec;

    public Person(String name) {
        this.name = name;
    }

    public void setDHParameterSpec(DHParameterSpec dhParamSpec) {
        this.dhParamSpec = dhParamSpec;
    }

    // 生成本地KeyPair:
    public void generateKeyPair() {
        try {
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
            kpGen.initialize(dhParamSpec);
            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");
            keyAgreement.init(this.privateKey); // 自己的PrivateKey
            keyAgreement.doPhase(receivedPublicKey, true); // 对方的PublicKey
            // 生成SecretKey密钥:
            this.secretKey = keyAgreement.generateSecret();
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    public void printKeys() {
        System.out.println("Name: " + this.name);
        System.out.println("Private key: " + HexFormat.of().formatHex(this.privateKey.getEncoded()));
        System.out.println("Public key: " + HexFormat.of().formatHex(this.publicKey.getEncoded()));
        System.out.println("Secret key: " + HexFormat.of().formatHex(this.secretKey));
    }
}