Android-DH 秘钥交换

Android-RSA 分段加密解密
Android-Openssl创建RSA公钥和私钥
Android-AES加解密
Android-DH 秘钥交换

1. DH(Diffie-Hellman) 介绍

DH 是 Whitfield Diffie 和 Martin Hellman 在1976年共同发明的一种秘钥交换算法。主要用于在不安全的网络上客户端和服务端通过交换公钥,生成一个相同的秘钥,并将该秘钥作为对称加密算法的秘钥,达到使对称加密算法的秘钥可以动态修改的目的。这样便提高了数据在网络上传输的安全性。
DH 总共包含四个部分,分别是:质数原根对、公钥、私钥和秘钥。

2. DH 秘钥交换流程(交换公钥生成共同的秘钥)

1. 客户端和服务端使用相同的质数原根对:P=23 和 G=5,这是秘钥交换的必须条件。

2. 服务端生成随机整数 A = 6,并将 A 作为私钥,使用公钥计算公式:
公钥 = G 的 A 次方 取余 P,等于 Math.pow(5,6) % 23,服务端的公钥为: 8。

服务端计算公钥.png

3. 客户端生成随机整数 B = 7,并将 B 作为私钥,使用公钥计算公式:
公钥 = G 的 B 次方 取余 P,等于 Math.pow(5,7) % 23,客户端的公钥为: 17。

客户端计算公钥.png

4. 服务端用客户端的公钥生成秘钥,使用秘钥计算公式:
秘钥 = 17 的 A 次方 取余 P,等于 Math.pow(17,6) % 23,服务端的秘钥为: 12。

服务端计算秘钥.png

5. 客户端用服务端的公钥生成秘钥,使用秘钥计算公式:
秘钥 = 8 的 B 次方 取余 P,等于 Math.pow(8,7) % 23,客户端的秘钥为: 12。

客户端计算秘钥.png

客户端和服务端通过交换公钥,生成了相同的秘钥。

3. DH 代码实现

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.KeyAgreement;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;

/**
 * DH 秘钥交换
 */
public class DH {
   /**
     * DH 秘钥长度
     */
    private static final int KEY_LENGTH = 1024;

    /**
     * 秘钥交换算法
     */
    private static final String KEY_ALGORITHM = "DH";
    /**
     * 获取 DH 公钥
     */
    public static final String DH_PUBLIC_KEY = "DHPublicKey";
    /**
     * 获取 DH 私钥
     */
    public static final String DH_PRIVATE_KEY = "DHPrivateKey";
    
   /**
     * 甲方初始化 公钥 和 私钥
     *
     * @return 可以通过 {@link this#getPublicKey(Map)} 获取公钥,
     * 可以通过 {@link this#getPrivateKey(Map)} 获取私钥钥
     */
    public static Map<String, Object> initDHKey() {
        try {
            // 实例化密钥对生成器
            KeyPairGenerator keyPairGenerator = 
            KeyPairGenerator.getInstance(KEY_ALGORITHM);
            // 初始化密钥对生成器 默认是 1024 512-1024 & 64的倍数
            keyPairGenerator.initialize(KEY_LENGTH);
            // 生成密钥对
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            // 得到甲方公钥
            DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
            // 得到甲方私钥
            DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
            // 将公钥和私钥封装在Map中, 方便之后使用
            Map<String, Object> keyMap = new HashMap<>();
            keyMap.put(DH_PUBLIC_KEY, publicKey);
            keyMap.put(DH_PRIVATE_KEY, privateKey);
            return keyMap;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
  
   /**
     * 乙方根据甲方公钥初始化并返回密钥对
     *
     * @param hexStrPubKey 甲方的公钥 16 进制字符串
     * @return 可以通过 {@link this#getPublicKey(Map)} 获取公钥,
     * 可以通过 {@link this#getPrivateKey(Map)} 获取私钥钥
     */
    public static Map<String, Object> initDHKey(String hexStrPubKey) {
        return initDHKey(getPublicKey(hexStrPubKey));
    }

    /**
     * 乙方根据甲方公钥初始化并返回密钥对
     *
     * @param dhPublicKey 甲方的公钥 16 进制字符串
     * @return 可以通过 {@link this#getPublicKey(Map)} 获取公钥,
     * 可以通过 {@link this#getPrivateKey(Map)} 获取私钥钥
     */
    public static Map<String, Object> initDHKey(byte[] dhPublicKey) {
        return initDHKey(getPublicKey(dhPublicKey));
    }

        /**
     * 乙方根据甲方公钥初始化并返回密钥对
     *
     * @param dhPublicKey 甲方的公钥
     * @return 可以通过 {@link this#getPublicKey(Map)} 获取公钥,
     * 可以通过 {@link this#getPrivateKey(Map)} 获取私钥钥
     */
    public static Map<String, Object> initDHKey(DHPublicKey dhPublicKey) {
        try {
            // 实例化密钥对生成器
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            // 用甲方公钥初始化密钥对生成器
            keyPairGenerator.initialize(dhPublicKey.getParams());
            // 产生密钥对
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            // 得到乙方公钥
            DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
            // 得到乙方私钥
            DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
            // 将公钥和私钥封装在Map中, 方便之后使用
            Map<String, Object> keyMap = new HashMap<>();
            keyMap.put(DH_PUBLIC_KEY, publicKey);
            keyMap.put(DH_PRIVATE_KEY, privateKey);
            return keyMap;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

  /**
     * 根据对方的公钥和自己的私钥生成 本地密钥,返回SecretKey对象的16进制字符串
     *
     * @param publicKey  公钥
     * @param privateKey 私钥
     * @return 秘钥 16 进制字符串
     */
    public static String getSecretKeyHexString(DHPublicKey publicKey, DHPrivateKey privateKey) {
        byte[] secretKey = getSecretKeyBytes(publicKey, privateKey);
        return DataUtils.byte2HexString(secretKey);
    }

   /**
     * 根据对方的公钥和自己的私钥生成 本地密钥,返回的是SecretKey对象的字节数组
     *
     * @param publicKey  公钥
     * @param privateKey 私钥
     * @return 秘钥数组
     */
    public static byte[] getSecretKeyBytes(DHPublicKey publicKey, DHPrivateKey privateKey) {
        try {
            // 实例化 KeyAgreement
            KeyAgreement keyAgreement = KeyAgreement.getInstance(KEY_ALGORITHM);
            // 用自己的私钥初始化keyAgreement
            keyAgreement.init(privateKey);
            // 结合对方的公钥进行运算
            keyAgreement.doPhase(publicKey, true);
            // 开始生成本地密钥 SecretKey
            // https://blog.csdn.net/fengzun_yi/article/details/104497160
            return keyAgreement.generateSecret();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

   /**
     * 获取 DHPublicKey 对象
     *
     * @param hexStrPubKey DH 16 进制公钥字符串
     */
    public static DHPublicKey getPublicKey(String hexStrPubKey) {
        byte[] pubKey = DataUtils.hexString2Byte(hexStrPubKey);
        return getPublicKey(pubKey);
    }

    /**
     * 获取 DHPublicKey 对象
     *
     * @param publicKey DH 公钥数组
     */
    public static DHPublicKey getPublicKey(byte[] publicKey) {
        try {
            // 实例化密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            // 将公钥从字节数组转换为PublicKey
            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKey);
            return (DHPublicKey) keyFactory.generatePublic(pubKeySpec);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取 DHPrivateKey 对象
     *
     * @param hexStrPrvKey DH 16 进制私钥字符串
     */
    public static DHPrivateKey getPrivateKey(String hexStrPrvKey) {
        byte[] prvKey = DataUtils.hexString2Byte(hexStrPrvKey);
        return getPrivateKey(prvKey);
    }

    /**
     * 获取 DHPrivateKey 对象
     *
     * @param privateKey DH 私钥数组
     */
    public static DHPrivateKey getPrivateKey(byte[] privateKey) {
        try {
            // 实例化密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            // 将私钥从字节数组转换为 PrivateKey
            PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(privateKey);
            return (DHPrivateKey) keyFactory.generatePrivate(priKeySpec);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取 DHPublicKey 对象 16 进制字符串
     */
    public static String getPublicKeyHexString(DHPublicKey dhPublicKey) {
        return DataUtils.byte2HexString(dhPublicKey.getEncoded());
    }

    /**
     * 从 Map 中取得私钥
     */
    public static String getPrivateKeyHexString(DHPrivateKey dhPrivateKey) {
        return DataUtils.byte2HexString(dhPrivateKey.getEncoded());
    }

    /**
     * 从 Map 中取得公钥
     */
    public static DHPublicKey getPublicKey(Map<String, Object> keyMap) {
        return (DHPublicKey) keyMap.get(DH_PUBLIC_KEY);
    }

    /**
     * 从 Map 中取得私钥
     */
    public static DHPrivateKey getPrivateKey(Map<String, Object> keyMap) {
        return (DHPrivateKey) keyMap.get(DH_PRIVATE_KEY);
    }
}

4. 数据工具类

import android.util.Base64;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;

/**
 * 数据工具类
 */
public class DataUtils {
   /**
     * 将 Base64 字符串 解码成 字节数组
     */
    public static byte[] base64Decode(String data) {
        return Base64.decode(data, Base64.NO_WRAP);
    }
    /**
     * 将 字节数组 转换成 Base64 编码
     */
    public static String base64Encode(byte[] data) {
        return Base64.encodeToString(data, Base64.NO_WRAP);
    }
}

5. DH 使用测试

public class{
    public static void main(String[] args) {
            // =================== 甲方 ===================
            Map<String, Object> map1 = DH.initDHKey();
            DHPublicKey dhPublicKey1 = DH.getPublicKey(map1);
            DHPrivateKey dhPrivateKey1 = DH.getPrivateKey(map1);
            logDHKey("甲-公钥: " + DH.getPublicKeyHexString(dhPublicKey1));
            logDHKey("甲-私钥: " + DH.getPrivateKeyHexString(dhPrivateKey1));

            // =================== 乙方 ===================
            Map<String, Object> map2 = DH.initDHKey(dhPublicKey1);
            DHPublicKey dhPublicKey2 = DH.getPublicKey(map2);
            DHPrivateKey dhPrivateKey2 = DH.getPrivateKey(map2);
            logDHKey("乙-公钥: " + DH.getPublicKeyHexString(dhPublicKey2));
            logDHKey("乙-私钥: " + DH.getPrivateKeyHexString(dhPrivateKey2));

            // =================== 甲方-计算秘钥 ===================
            // 乙方的公钥 和 自己的私钥
            String secretKey1 = DH.getSecretKeyHexString(dhPublicKey2, dhPrivateKey1);
            logDHKey("甲-秘钥: " + secretKey1);

            // =================== 乙方-计算秘钥 ===================
            // 甲方的公钥 和 自己的私钥
            String secretKey2 = DH.getSecretKeyHexString(dhPublicKey1, dhPrivateKey2);
            logDHKey("乙-秘钥: " + secretKey2);
            if (secretKey1.equals(secretKey2)) {
                logDHKey("两个秘钥相等...");
            }
    }

    public static void logDHKey(String msg){
        System.out.println(msg);
    }
}



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342