HTTPDNS 加解密实践

最近更新时间:2023-09-28 15:35:41

我的收藏

操作场景

使用 DES、AES 加密算法可以对请求参数进行加密,并对其响应数据解密,防止明文请求在传输过程中被恶意篡改。本文将指导您如何使用 DES、AES 加密算法。
说明
使用 HTTPS 请求方式查询,传输的数据会因为 TLS 通道而被加密保护,因此不需要主动对传入的数据额外加密。

前提条件

掌握 Java、JavaScript、Go 或者 C++ 语言。

操作步骤

Java 版本

AES 加密

首先,创建org/example目录,并在org/example目录下,创建文件HTTPDNSAESHelper.java,执行javac ./org/example/HTTPDNSAESHelper.java命令,得到HTTPDNSAESHelper$1.classHTTPDNSAESHelper.class文件。

img


执行java org.example.HTTPDNSAESHelper命令。
依次输入 HTTPDNS 的 id、查询的域名、16位的 AES 加解密的密钥。

img


HTTPDNSAESHelper.java代码
package org.example;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;

public class HTTPDNSAESHelper {

private static final int IVSize = 16;
private static final int KeySize = 16;
private static final String AlgorithmName = "AES";
// 下面代码中有对应的pkcs7算法实现
private static final String AlgorithmCombinationName = "AES/CBC/NoPadding";
private static final String Base16Table = "0123456789ABCDEF";
private static final Map<Character, Byte> ReversedBase16Table = new HashMap<Character, Byte>() {
{
put('0', (byte) 0);
put('1', (byte) 1);
put('2', (byte) 2);
put('3', (byte) 3);
put('4', (byte) 4);
put('5', (byte) 5);
put('6', (byte) 6);
put('7', (byte) 7);
put('8', (byte) 8);
put('9', (byte) 9);
put('A', (byte) 10);
put('B', (byte) 11);
put('C', (byte) 12);
put('D', (byte) 13);
put('E', (byte) 14);
put('F', (byte) 15);
put('a', (byte) 10);
put('b', (byte) 11);
put('c', (byte) 12);
put('d', (byte) 13);
put('e', (byte) 14);
put('f', (byte) 15);
}
};

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

System.out.println("请输入你的id:");
String id = scanner.nextLine();
System.out.println("请输入你要查询的域名:");
String data = scanner.nextLine();
System.out.println("请输入你的key:");
// 16个字节的key
String key = scanner.nextLine();

if (key.length() != 16) {
throw new IllegalArgumentException("key的长度必须为16字节");
}

HTTPDNSAESHelper aesHelper = new HTTPDNSAESHelper();

System.out.println("测试域名加密和解密是否正确: ");
String encrypt = aesHelper.encrypt(data, key);
System.out.println("加密后的域名: " + encrypt);

String decrypt = aesHelper.decrypt(encrypt, key);
System.out.println("解密后: " + decrypt);

try {
String line;
StringBuilder response = new StringBuilder();
URL url = new URL("http://119.29.29.98/d?dn=" + encrypt + "&id=" + id + "&alg=aes");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("GET");

int responseCode = conn.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new RuntimeException("请求HTTP DNS接口失败, 响应码: " + responseCode);
}

try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
while ((line = reader.readLine()) != null) {
response.append(line);
}

System.out.println("响应结果: " + response);
System.out.println("解密后: " + aesHelper.decrypt(response.toString(), key));
}
} catch (MalformedURLException e) {
throw new RuntimeException("请求HTTP DNS接口失败", e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* base16编码
*
* @param data 字节数组
* @return base16编码后的字符串
*/
public static String hexEncode(byte[] data) {
if (data == null || data.length == 0) {
return "";
}

StringBuilder result = new StringBuilder(data.length * 2);
for (byte datum : data) {
int high = (datum >> 4) & 0xf;
int low = datum & 0xf;
result.append(Base16Table.charAt(high));
result.append(Base16Table.charAt(low));
}

return result.toString();
}

/**
* base16解码
*
* @param data base16编码后的字符串
* @return 字节数组
*/
public static byte[] hexDecode(String data) {
if (data == null || data.length() == 0) return null;

if (data.length() % 2 != 0) {
throw new RuntimeException("invalid hex encoded string");
}

byte[] result = new byte[data.length() / 2];
for (int i = 0, j = 0; i < result.length; i++, j += 2) {
byte high = ReversedBase16Table.get(data.charAt(j));
byte low = ReversedBase16Table.get(data.charAt(j + 1));
result[i] = (byte) ((high << 4) | (low & 0xf));
}

return result;
}

public static byte[] getUTF8Bytes(String str) {
try {
return str.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("invalid data", e);
}
}

public static boolean isBlank(String str) {
return str == null || str.length() == 0;
}

/**
* 加密
*
* @param data 数据
* @param key 16个字节长度的key
* @return 加密后的数据
*/
public String encrypt(String data, String key) {
if (key == null || key.length() != KeySize) {
throw new IllegalArgumentException("key length must be 16");
} else if (isBlank(data)) {
throw new IllegalArgumentException("data is blank");
}

// 使用pkcs7Padding算法填充, 参考rfc5652
byte[] rawData = pkcs7Padding(getUTF8Bytes(data), IVSize);
byte[] ivBytes = generateRandomIVBytes();
byte[] keyBytes = getUTF8Bytes(key);

SecretKeySpec aesKey = new SecretKeySpec(keyBytes, AlgorithmName);

IvParameterSpec iv = new IvParameterSpec(ivBytes);

try {
// AES, CBC模式加密
Cipher cipher = Cipher.getInstance(AlgorithmCombinationName);
cipher.init(Cipher.ENCRYPT_MODE, aesKey, iv);
byte[] encryptedBytes = cipher.doFinal(rawData);
// hex编码
return hexEncode(concatBytes(ivBytes, encryptedBytes));
} catch (Exception e) {
throw new RuntimeException("failed to encrypt data", e);
}
}

/**
* 解密
*
* @param data 数据
* @param key 16个字节长度的key
* @return 解密后的数据
*/
public String decrypt(String data, String key) {
if (key == null || key.length() != KeySize) {
throw new IllegalArgumentException("key length must be 16");
} else if (isBlank(data)) {
throw new IllegalArgumentException("data is blank");
}

// hex解码
byte[] decodedBytes = hexDecode(data);

if (decodedBytes == null || decodedBytes.length < IVSize) {
throw new IllegalArgumentException("invalid encryptedData, missing iv");
}

byte[] keyBytes = getUTF8Bytes(key);
byte[] encryptedBytes = extractEncryptedData(decodedBytes);

SecretKeySpec aesKey = new SecretKeySpec(keyBytes, AlgorithmName);
IvParameterSpec iv = extractIV(decodedBytes);

try {
// AES, CBC模式解密
Cipher cipher = Cipher.getInstance(AlgorithmCombinationName);
cipher.init(Cipher.DECRYPT_MODE, aesKey, iv);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
// 去掉pkcs7Padding填充的字节
return new String(unPkcs7Padding(decryptedBytes, IVSize));
} catch (Exception e) {
throw new RuntimeException("failed to decrypt data", e);
}
}

private byte[] concatBytes(byte[] first, byte[] second) {
if (first == null || first.length == 0) {
return second;
}

if (second == null || second.length == 0) {
return first;
}

byte[] target = new byte[first.length + second.length];
System.arraycopy(first, 0, target, 0, first.length);
System.arraycopy(second, 0, target, first.length, second.length);
return target;
}

/**
* 从响应数据中提取iv参数
*
* @param data 响应数据
* @return iv参数
*/
private IvParameterSpec extractIV(byte[] data) {
if (data.length < IVSize) {
throw new IllegalArgumentException("missing iv part");
}

byte[] ivBytes = new byte[IVSize];
System.arraycopy(data, 0, ivBytes, 0, ivBytes.length);
return new IvParameterSpec(ivBytes);
}

/**
* 从响应数据中提取加密数据
*
* @param data 响应数据
* @return 加密数据
*/
private byte[] extractEncryptedData(byte[] data) {
if (data.length - IVSize <= 0) {
throw new IllegalArgumentException("missing encrypted data part");
}

byte[] encryptedBytes = new byte[data.length - IVSize];
System.arraycopy(data, IVSize, encryptedBytes, 0, encryptedBytes.length);
return encryptedBytes;
}

/**
* 生成随机的16字节的IV
*
* @return 随机的16字节的IV
*/
private byte[] generateRandomIVBytes() {
byte[] iv = new byte[16];
Random random = new Random();
random.nextBytes(iv);
return iv;
}

private byte[] pkcs7Padding(byte[] data, int blockSize) {
int paddingBytesAmount = blockSize - (data.length % blockSize);
byte[] paddingResult = new byte[data.length + paddingBytesAmount];
System.arraycopy(data, 0, paddingResult, 0, data.length);
for (int i = 0; i < paddingBytesAmount; i++) {
paddingResult[data.length + i] = (byte) paddingBytesAmount;
}
return paddingResult;
}

private byte[] unPkcs7Padding(byte[] data, int blockSize) {
if (data == null || data.length == 0 || data.length % blockSize != 0) {
throw new RuntimeException("data isn't aligned to blockSize");
}

int paddingBytesAmount = data[data.length - 1];
for (int i = 0; i < paddingBytesAmount; i++) {
if (data[data.length - i - 1] != paddingBytesAmount) {
throw new RuntimeException("padding had malformed entries");
}
}

byte[] result = new byte[data.length - paddingBytesAmount];
System.arraycopy(data, 0, result, 0, result.length);
return result;
}
}


DES 加密

首先,创建org/example目录,并在org/example目录下,创建文件HTTPDNSDESHelper.java,执行javac ./org/example/HTTPDNSDESHelper.java命令,得到HTTPDNSDESHelper$1.classHTTPDNSDESHelper.class文件。

img


执行java org.example.HTTPDNSDESHelper命令。
依次输入 HTTPDNS 的 id、查询的域名、8位的 DES 加解密的密钥。

img


HTTPDNSDESHelper.java代码
package org.example;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class HTTPDNSDESHelper {

private static final int KeySize = 8;
private static final String AlgorithmName = "DES";
private static final String AlgorithmCombinationName = "DES/ECB/PKCS5Padding";
private static final String Base16Table = "0123456789ABCDEF";
private static final Map<Character, Byte> ReversedBase16Table = new HashMap<Character, Byte>() {
{
put('0', (byte) 0);
put('1', (byte) 1);
put('2', (byte) 2);
put('3', (byte) 3);
put('4', (byte) 4);
put('5', (byte) 5);
put('6', (byte) 6);
put('7', (byte) 7);
put('8', (byte) 8);
put('9', (byte) 9);
put('A', (byte) 10);
put('B', (byte) 11);
put('C', (byte) 12);
put('D', (byte) 13);
put('E', (byte) 14);
put('F', (byte) 15);
put('a', (byte) 10);
put('b', (byte) 11);
put('c', (byte) 12);
put('d', (byte) 13);
put('e', (byte) 14);
put('f', (byte) 15);
}
};

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

System.out.println("请输入你的id:");
String id = scanner.nextLine();
System.out.println("请输入你要查询的域名:");
String data = scanner.nextLine();
System.out.println("请输入你的key:");
// 8个字节的key
String key = scanner.nextLine();

if (key.length() != 8) {
throw new IllegalArgumentException("key的长度必须为8字节");
}

HTTPDNSDESHelper desHelper = new HTTPDNSDESHelper();

System.out.println("测试域名加密和解密是否正确: ");
String encrypt = desHelper.encrypt(data, key);
System.out.println("加密后的域名: " + encrypt);

String decrypt = desHelper.decrypt(encrypt, key);
System.out.println("解密后: " + decrypt);

try {
String line;
StringBuilder response = new StringBuilder();
URL url = new URL("http://119.29.29.98/d?dn=" + encrypt + "&id=" + id + "&alg=des");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("GET");

int responseCode = conn.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new RuntimeException("请求HTTP DNS接口失败, 响应码: " + responseCode);
}

try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
while ((line = reader.readLine()) != null) {
response.append(line);
}

System.out.println("响应结果: " + response);
System.out.println("解密后: " + desHelper.decrypt(response.toString(), key));
}
} catch (MalformedURLException e) {
throw new RuntimeException("请求HTTP DNS接口失败", e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* base16编码
*
* @param data 字节数组
* @return base16编码后的字符串
*/
public static String hexEncode(byte[] data) {
if (data == null || data.length == 0) {
return "";
}

StringBuilder result = new StringBuilder(data.length * 2);
for (byte datum : data) {
int high = (datum >> 4) & 0xf;
int low = datum & 0xf;
result.append(Base16Table.charAt(high));
result.append(Base16Table.charAt(low));
}

return result.toString();
}

/**
* base16解码
*
* @param data base16编码后的字符串
* @return 字节数组
*/
public static byte[] hexDecode(String data) {
if (data == null || data.length() == 0) return null;

if (data.length() % 2 != 0) {
throw new RuntimeException("invalid hex encoded string");
}

byte[] result = new byte[data.length() / 2];
for (int i = 0, j = 0; i < result.length; i++, j += 2) {
byte high = ReversedBase16Table.get(data.charAt(j));
byte low = ReversedBase16Table.get(data.charAt(j + 1));
result[i] = (byte) ((high << 4) | (low & 0xf));
}

return result;
}

public static byte[] getUTF8Bytes(String str) {
return str.getBytes(StandardCharsets.UTF_8);
}

public static boolean isBlank(String str) {
return str == null || str.length() == 0;
}

public String encrypt(String data, String key) {
if (key == null || key.length() != KeySize) {
throw new IllegalArgumentException("key length must be 8");
} else if (isBlank(data)) {
throw new IllegalArgumentException("data is blank");
}

byte[] rawBytes = getUTF8Bytes(data);
byte[] keyBytes = getUTF8Bytes(key);

SecretKeySpec desKey = new SecretKeySpec(keyBytes, AlgorithmName);

try {
Cipher cipher = Cipher.getInstance(AlgorithmCombinationName);
cipher.init(Cipher.ENCRYPT_MODE, desKey);
byte[] encryptedData = cipher.doFinal(rawBytes);
return hexEncode(encryptedData);
} catch (Exception e) {
throw new RuntimeException("failed to encrypt", e);
}
}

public String decrypt(String data, String key) {
if (key == null || key.length() != KeySize) {
throw new IllegalArgumentException("key length must be 8");
} else if (isBlank(data)) {
throw new IllegalArgumentException("data is blank");
}

byte[] encryptedBytes = hexDecode(data);
byte[] keyBytes = getUTF8Bytes(key);

SecretKeySpec desKey = new SecretKeySpec(keyBytes, AlgorithmName);

try {
Cipher cipher = Cipher.getInstance(AlgorithmCombinationName);
cipher.init(Cipher.DECRYPT_MODE, desKey);
byte[] decryptedData = cipher.doFinal(encryptedBytes);
return new String(decryptedData);
} catch (Exception e) {
throw new RuntimeException("failed to decrypt", e);
}
}
}

Go 版本

创建一个目录test,进入test目录,创建main.go文件,把以下代码复制到main.go文件中,然后运行go run main.go命令。
分别会进行 AES 加解密和 DES 加解密,在此过程中需要分别输入 HTTPDNS 的 id、请求域名、16位的 AES 加解密密钥、HTTPDNS 的 id、请求域名、8位的 DES 加解密密钥。
package main

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/des"
"encoding/hex"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
)

var (
ErrInvalidBlockSize = errors.New("invalid block size")
ErrInvalidPKCSData = errors.New("invalid PKCS data")
ErrInvalidPKCSPadding = errors.New("invalid padding on input")
)

// AesDecrypt aes解密
// 步骤:
// 1. 先把加密文本base16(hex)解码
// 2. 从解码后的数据前16位提取出IV, 其他部分作为待解密数据
// 3. 根据IV和key解密数据
// 4. 把解密后的数据尝试去除填充数据(pkcs7)
func AesDecrypt(encryptedData, key string) (string, error) {
cipherText, err := hex.DecodeString(encryptedData)
if err != nil {
return "", err
}

if len(cipherText) < aes.BlockSize {
return "", errors.New("missing iv")
}

// 前16字节为IV
iv := cipherText[:aes.BlockSize]
cipherText = cipherText[aes.BlockSize:]

c, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}

mode := cipher.NewCBCDecrypter(c, iv)

mode.CryptBlocks(cipherText, cipherText)

unPaddingData, err := pkcs7UnPad(cipherText, aes.BlockSize)
if err != nil {
return "", err
}

return string(unPaddingData), nil
}

// AesEncrypt aes加密
// 步骤:
// 1. 生成16位的随机IV
// 2. 对加密数据尝试进行填充(pkcs7)
// 3. 使用key和IV加密填充后的数据
// 4. 拼接IV和加密后的数据
// 5. 把字节数组通过base16(hex)编码为字符串
func AesEncrypt(data, key string) (string, error) {
iv := randomIV(aes.BlockSize)
c, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}

mode := cipher.NewCBCEncrypter(c, iv)

cipherText, err := pkcs7([]byte(data), aes.BlockSize)
if err != nil {
return "", err
}

mode.CryptBlocks(cipherText, cipherText)

// 前16字节为IV
cipherText = append(iv, cipherText...)

return hex.EncodeToString(cipherText), nil
}

func randomIV(blockSize int) []byte {
iv := make([]byte, blockSize)
for i := 0; i < blockSize; i++ {
iv[i] = byte(rand.Int())
}
return iv
}

func DesEncrypt(data, key string) (string, error) {
c, err := des.NewCipher([]byte(key))
if err != nil {
return "", err
}

cipherText, err := pkcs5([]byte(data), des.BlockSize)
if err != nil {
return "", err
}

dst := make([]byte, len(cipherText))
for i := 0; i < len(cipherText); i += des.BlockSize {
c.Encrypt(dst[i:i+des.BlockSize], cipherText[i:i+des.BlockSize])
}

return hex.EncodeToString(dst), nil
}

func DesDecrypt(encryptedData, key string) (string, error) {
cipherText, err := hex.DecodeString(encryptedData)
if err != nil {
return "", err
}

if len(cipherText)%des.BlockSize != 0 {
return "", errors.New("invalid encryptedData")
}

c, err := des.NewCipher([]byte(key))
if err != nil {
return "", err
}

dst := make([]byte, len(cipherText))
for i := 0; i < len(cipherText); i += des.BlockSize {
c.Decrypt(dst[i:i+des.BlockSize], cipherText[i:i+des.BlockSize])
}

unPaddingData, err := pkcs5UnPad(dst, des.BlockSize)
if err != nil {
return "", err
}

return string(unPaddingData), nil
}

func pkcs7(data []byte, blockSize int) ([]byte, error) {
if blockSize <= 0 {
return nil, ErrInvalidBlockSize
}
if len(data) == 0 {
return nil, ErrInvalidPKCSData
}
n := blockSize - (len(data) % blockSize)
pd := make([]byte, len(data)+n)
copy(pd, data)
copy(pd[len(data):], bytes.Repeat([]byte{byte(n)}, n))
return pd, nil
}

func pkcs7UnPad(data []byte, blockSize int) ([]byte, error) {
if blockSize <= 0 {
return nil, ErrInvalidBlockSize
}
if len(data) == 0 {
return nil, ErrInvalidPKCSData
}
if len(data)%blockSize != 0 {
return nil, ErrInvalidPKCSPadding
}
c := data[len(data)-1]
n := int(c)
if n == 0 || n > len(data) {
return nil, ErrInvalidPKCSPadding
}
for i := 0; i < n; i++ {
if data[len(data)-i-1] != c {
return nil, ErrInvalidPKCSPadding
}
}
return data[:len(data)-n], nil
}

func pkcs5(data []byte, blockSize int) ([]byte, error) {
if blockSize <= 0 {
return nil, ErrInvalidBlockSize
}
if len(data) == 0 {
return nil, ErrInvalidPKCSData
}
pd := blockSize - (len(data) % blockSize)
return append(data, bytes.Repeat([]byte{byte(pd)}, pd)...), nil
}

func pkcs5UnPad(data []byte, blockSize int) ([]byte, error) {
if blockSize <= 0 {
return nil, ErrInvalidBlockSize
}
if len(data) == 0 {
return nil, ErrInvalidPKCSData
}
if len(data)%blockSize != 0 {
return nil, ErrInvalidPKCSPadding
}

c := data[len(data)-1]
n := int(c)
if n == 0 || n > len(data) {
return nil, ErrInvalidPKCSPadding
}
for i := 0; i < n; i++ {
if data[len(data)-i-1] != c {
return nil, ErrInvalidPKCSPadding
}
}
return data[:len(data)-n], nil
}

func executeHTTPDNSRequest(id, dn, alg string) (string, error) {
resp, err := http.Get(fmt.Sprintf("http://119.29.29.98/d?dn=%s&id=%s&alg=%s", dn, id, alg))
if err != nil {
return "", err
}

if resp.StatusCode != http.StatusOK {
return "", errors.New(fmt.Sprintf("请求HTTP DNS接口失败, 状态码: %d", resp.StatusCode))
}

defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

return string(data), nil
}

func scan(tip string, dest *string) {
fmt.Println(tip)
_, err := fmt.Scanln(dest)
if err != nil {
panic(err)
}
}

func aesEncryptAndDecryptTest() {
fmt.Println("AES加解密测试:")

var id, key, domain string
scan("请输入你的id:", &id)
scan("请输入你要请求的域名:", &domain)
scan("请输入你16字节的key:", &key)

encryptedData, err := AesEncrypt(domain, key)
if err != nil {
panic(err)
}
fmt.Println("加密后的域名:", encryptedData)
decryptedData, err := AesDecrypt(encryptedData, key)
if err != nil {
panic(err)
}
fmt.Println("解密后的域名:", decryptedData)

data, err := executeHTTPDNSRequest(id, encryptedData, "aes")
if err != nil {
panic(err)
}

decryptedIPAddress, err := AesDecrypt(data, key)
if err != nil {
panic(err)
}

fmt.Println("解密后的IP地址:", decryptedIPAddress)
}

func desEncryptAndDecryptTest() {
fmt.Println("DES加解密测试:")

var id, key, domain string
scan("请输入你的id:", &id)
scan("请输入你要请求的域名:", &domain)
scan("请输入你8字节的key:", &key)

encryptedData, err := DesEncrypt(domain, key)
if err != nil {
panic(err)
}
fmt.Println("加密后的域名:", encryptedData)
decryptedData, err := DesDecrypt(encryptedData, key)
if err != nil {
panic(err)
}
fmt.Println("解密后的域名:", decryptedData)

data, err := executeHTTPDNSRequest(id, encryptedData, "des")
if err != nil {
panic(err)
}

decryptedIPAddress, err := DesDecrypt(data, key)
if err != nil {
panic(err)
}

fmt.Println("解密后的IP地址:", decryptedIPAddress)
}

func main() {
aesEncryptAndDecryptTest()
desEncryptAndDecryptTest()
}

C++ 版本

安装 make、g++、以及 libcurl 和 libcrypto 库。
在 Centos 系统上执行以下命令:
yum install -y libcurl-devel openssl openssl-dev make gcc gcc-c++
创建test目录,进入test目录,复制以下文件到test目录,执行make,然后执行main可执行文件,根据提示输入获取响应 HTTPDNS 响应结果。
Makefile
all:
g++ -Wall -Werror -g -o main -lcrypto -lcurl main.cpp des.cpp aes.cpp hex.cpp

clean:
- rm -rf ./main
main.cpp
#include "aes.h"
#include "des.h"
#include <iostream>
#include <curl/curl.h>

size_t http_dns_response_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
size_t real_size = size * nmemb;
std::string *response = (std::string *)userdata;

response->append(ptr, real_size);
return real_size;
}

void send_http_dns_request(const std::string &encrypted_domain, const std::string &id,
const std::string &alg, std::string &response)
{
std::string url;
CURL *easy_handle;
CURLcode res;

easy_handle = curl_easy_init();

if (!easy_handle)
throw std::runtime_error("curl_easy_init() failed");

url.append("http://119.29.29.98/d?dn=")
.append(encrypted_domain)
.append("&id=")
.append(id)
.append("&alg=")
.append(alg);

res = curl_easy_setopt(easy_handle, CURLOPT_URL, url.c_str());
if (res != CURLE_OK)
{
curl_easy_cleanup(easy_handle);
throw std::runtime_error("failed to set url option");
}

res = curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, http_dns_response_callback);
if (res != CURLE_OK)
{
curl_easy_cleanup(easy_handle);
throw std::runtime_error("failed to set writedata option");
}

res = curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, &response);
if (res != CURLE_OK)
{
curl_easy_cleanup(easy_handle);
throw std::runtime_error("failed to set writedata option");
}

res = curl_easy_perform(easy_handle);
if (res != CURLE_OK)
{
curl_easy_cleanup(easy_handle);
throw std::runtime_error(std::string("curl_easy_perform() failed: ").append(curl_easy_strerror(res)));
}

curl_global_cleanup();
}

void get_user_input(const std::string &tip, std::string &id, std::string &domain,
std::string &key, const size_t key_length)
{
std::cout << tip << std::endl;
std::cout << "请输入你的id:" << std::endl;
std::cin >> id;
std::cout << "请输入需要解析的域名:" << std::endl;
std::cin >> domain;
std::cout << "请输入" << key_length << "个字节的key:" << std::endl;
std::cin >> key;

if (id.size() == 0)
throw std::runtime_error("请输入有效的id");

if (domain.size() == 0)
throw std::runtime_error("请输入有效的域名");

if (key.size() != key_length)
throw std::runtime_error(std::string("key长度必须为").append(std::to_string(key_length)).append("个字节"));
}

void aes_test()
{
std::string id, domain, key, response;

get_user_input("AES加解密", id, domain, key, 16);

std::string &&encrypted_data = aes_encrypt(domain, key);

send_http_dns_request(encrypted_data, id, "aes", response);

std::string &&decrypted_data = aes_decrypt(response, key);
std::cout << "响应结果: " << decrypted_data << std::endl;
}

void des_test()
{
std::string id, domain, key, response;

get_user_input("DES加解密", id, domain, key, 8);

std::string &&encrypted_data = des_encrypt(domain, key);

send_http_dns_request(encrypted_data, id, "des", response);

std::string &&decrypted_data = des_decrypt(response, key);
std::cout << "响应结果: " << decrypted_data << std::endl;
}

int main()
{
aes_test();
des_test();
}
aes.cpp
#include "aes.h"
#include "hex.h"
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <time.h>
#include <stdlib.h>
#include <stdexcept>

typedef unsigned char uchar;

const size_t IV_SIZE = 16;
const size_t KEY_SIZE = 16;
const size_t BLOCK_SIZE = 16;

static std::string generate_iv()
{
size_t i;
std::string iv;

srand(time(NULL));

for (i = 0; i < IV_SIZE; i++)
iv.push_back(rand() % 256);

return iv;
}

std::string aes_encrypt(const std::string &data, const std::string &key)
{
if (data.size() == 0)
throw std::runtime_error("data is empty");
else if (key.size() != KEY_SIZE)
throw std::runtime_error("key length must be 16");

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
std::string &&iv = generate_iv();

if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, (uchar *)key.c_str(), (uchar *)iv.c_str()))
{
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("EVP_EncryptInit_ex() failed");
}

EVP_CIPHER_CTX_set_key_length(ctx, KEY_SIZE);

uchar *output = new uchar[data.size() + 2 * BLOCK_SIZE];
int output_len = 0;

if (1 != EVP_EncryptUpdate(ctx, output, &output_len, (uchar *)data.c_str(), data.size()))
{
EVP_CIPHER_CTX_free(ctx);
delete[] output;
throw std::runtime_error("EVP_EncryptUpdate() failed");
}

uchar *pad_buf = output + output_len;
int pad_len;

if (1 != EVP_EncryptFinal_ex(ctx, pad_buf, &pad_len))
{
EVP_CIPHER_CTX_free(ctx);
delete[] output;
throw std::runtime_error("EVP_EncryptFinal_ex() failed");
}

std::string result;

result.append(iv);
result.append((char *)output, output_len + pad_len);

EVP_CIPHER_CTX_free(ctx);
delete[] output;

return encode(result);
}

std::string aes_decrypt(const std::string &data, const std::string &key)
{
if (data.size() == 0)
throw std::runtime_error("data is empty");
else if (key.size() != KEY_SIZE)
throw std::runtime_error("key length must be 16");

std::string &&decoded_data = decode(data);

if (decoded_data.size() <= IV_SIZE)
throw std::runtime_error("invalid data");

std::string &&iv = decoded_data.substr(0, IV_SIZE);
std::string &&encrypted_data = decoded_data.substr(IV_SIZE);

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, (uchar *)key.c_str(), (uchar *)iv.c_str()))
{
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("EVP_DecryptInit_ex() failed");
}

EVP_CIPHER_CTX_set_key_length(ctx, KEY_SIZE);

uchar *output = new uchar[data.size() + 2 * BLOCK_SIZE];
int output_len = 0;

if (1 != EVP_DecryptUpdate(ctx, output, &output_len, (uchar *)encrypted_data.c_str(), encrypted_data.size()))
{
EVP_CIPHER_CTX_free(ctx);
delete[] output;
throw std::runtime_error("EVP_DecryptUpdate() failed");
}

int pad_len = 0;
if (1 != EVP_DecryptFinal_ex(ctx, output + output_len, &pad_len))
{
EVP_CIPHER_CTX_free(ctx);
delete[] output;
throw std::runtime_error("EVP_DecryptFinal_ex() failed");
}

output_len += pad_len;
output[output_len] = 0;

std::string result = (char *)output;

EVP_CIPHER_CTX_free(ctx);
delete[] output;

return result;
}
aes.h
#ifndef AES_H
#define AES_H

#include <string>

std::string aes_encrypt(const std::string &data, const std::string &key);
std::string aes_decrypt(const std::string &data, const std::string &key);

#endif
des.cpp
#include "des.h"
#include "hex.h"
#include <openssl/evp.h>
#include <openssl/des.h>
#include <stdexcept>

typedef unsigned char uchar;

const size_t BLOCK_SIZE = 16;
const size_t KEY_SIZE = 8;

std::string des_encrypt(const std::string &data, const std::string &key)
{
if (data.size() == 0)
throw std::runtime_error("data is empty");
else if (key.size() != KEY_SIZE)
throw std::runtime_error("key length must be 8");

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

if (1 != EVP_EncryptInit_ex(ctx, EVP_des_ecb(), NULL, (uchar *)key.c_str(), NULL))
{
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("EVP_EncryptInit_ex() failed");
}

EVP_CIPHER_CTX_set_key_length(ctx, KEY_SIZE);

uchar *output = new uchar[data.size() + BLOCK_SIZE * 2];
int output_len = 0;

if (1 != EVP_EncryptUpdate(ctx, output, &output_len, (uchar *)data.c_str(), data.size()))
{
EVP_CIPHER_CTX_free(ctx);
delete[] output;
throw std::runtime_error("EVP_EncryptUpdate() failed");
}

int pad_len = 0;
if (1 != EVP_EncryptFinal_ex(ctx, output + output_len, &pad_len))
{
EVP_CIPHER_CTX_free(ctx);
delete[] output;
throw std::runtime_error("EVP_EncryptFinal_ex() failed");
}

output[output_len + pad_len] = 0;
std::string result = (char *)output;

EVP_CIPHER_CTX_free(ctx);
delete[] output;

return encode(result);
}

std::string des_decrypt(const std::string &data, const std::string &key)
{
if (data.size() == 0)
throw std::runtime_error("data is empty");
else if (key.size() != KEY_SIZE)
throw std::runtime_error("key length must be 8");

std::string &&decoded_data = decode(data);

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

if (1 != EVP_DecryptInit_ex(ctx, EVP_des_ecb(), NULL, (uchar *)key.c_str(), NULL))
{
EVP_CIPHER_CTX_free(ctx);
throw std::runtime_error("EVP_DecryptInit_ex() failed");
}

EVP_CIPHER_CTX_set_key_length(ctx, KEY_SIZE);

uchar *output = new uchar[data.size() + 2 * BLOCK_SIZE];
int output_len = 0;

if (1 != EVP_DecryptUpdate(ctx, output, &output_len, (uchar *)decoded_data.c_str(), decoded_data.size()))
{
EVP_CIPHER_CTX_free(ctx);
delete[] output;
throw std::runtime_error("EVP_DecryptUpdate() failed");
}

int pad_len = 0;
if (1 != EVP_DecryptFinal_ex(ctx, output + output_len, &pad_len))
{
EVP_CIPHER_CTX_free(ctx);
delete[] output;
throw std::runtime_error("EVP_DecryptFinal_ex() failed");
}

output_len += pad_len;
output[output_len] = 0;

std::string result = (char *)output;

EVP_CIPHER_CTX_free(ctx);
delete[] output;

return result;
}
des.h
#ifndef DES_H
#define DES_H

#include <string>

std::string des_encrypt(const std::string &data, const std::string &key);
std::string des_decrypt(const std::string &data, const std::string &key);

#endif
hex.h
#ifndef HEX_H
#define HEX_H

#include <string>

std::string encode(const std::string &data);
std::string decode(const std::string &data);

#endif
hex.cpp
#include "hex.h"
#include <stdexcept>
#include <iostream>
#include <map>

static const char HEX_STRING[] = "0123456789ABCDEF";
static std::map<char, int> HEX_TABLE = {
{'0', 0},
{'1', 1},
{'2', 2},
{'3', 3},
{'4', 4},
{'5', 5},
{'6', 6},
{'7', 7},
{'8', 8},
{'9', 9},
{'a', 10},
{'b', 11},
{'c', 12},
{'d', 13},
{'e', 14},
{'f', 15},
{'A', 10},
{'B', 11},
{'C', 12},
{'D', 13},
{'E', 14},
{'F', 15},
};

std::string encode(const std::string &data)
{
size_t i;
int high, low;
std::string res;

for (i = 0; i < data.size(); i++)
{
high = (data[i] >> 4) & 0xf;
low = data[i] & 0xf;
res.push_back(HEX_STRING[high]);
res.push_back(HEX_STRING[low]);
}

return res;
}

std::string decode(const std::string &data)
{
size_t i;
std::string res;

if (data.size() == 0)
return res;
else if (data.size() % 2 != 0)
throw std::runtime_error("invalid data length");

for (i = 0; i < data.size(); i += 2)
{
if (HEX_TABLE.find(data[i]) == HEX_TABLE.end() || HEX_TABLE.find(data[i + 1]) == HEX_TABLE.end())
throw std::runtime_error("invalid character");
res.push_back((HEX_TABLE[data[i]] << 4) | HEX_TABLE[data[i + 1]]);
}

return res;
}

JavaScript版本

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DES和AES加解密实践</title>
</head>
<body>
<h1>DES和AES加解密实践</h1>
</body>
<!-- 注意: 需要引入crypto-js库 -->
<script src="path-to/bower_components/crypto-js/crypto-js.js"></script>
<script>
class AESHelper {
constructor(key) {
if (typeof (key) !== "string" || key.length < 16) {
throw "[AES] failed to construct AESHelper, invalid key";
}
this.key = CryptoJS.enc.Utf8.parse(key);
}

Encrypt(data) {
if (typeof (data) !== "string" || data.length === 0) {
throw "[AES] failed to encrypt, invalid data";
}
const iv = CryptoJS.lib.WordArray.random(16);
const encryptedContent = CryptoJS.AES.encrypt(data, this.key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return iv.toString(CryptoJS.enc.Hex) + encryptedContent.ciphertext.toString(CryptoJS.enc.Hex);
}

Decrypt(data) {
if (typeof (data) !== "string" || data.length <= 32) {
throw "[AES] failed to decrypt, invalid data";
}

// 前32个字符是16个字节的iv值通过hex编码的
const iv = CryptoJS.enc.Hex.parse(data.substr(0, 32));
data = data.substr(32, data.length - 32);

const decryptionParam = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Hex.parse(data)
});

const decryptedContent = CryptoJS.AES.decrypt(decryptionParam, this.key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decryptedContent.toString(CryptoJS.enc.Utf8);
}
}

class DESHelper {
constructor(key) {
if (typeof (key) !== "string" || key.length < 8) {
throw "[DES] failed to construct DESHelper, invalid key";
}
this.key = CryptoJS.enc.Utf8.parse(key);
}

Encrypt(data) {
if (typeof (data) !== "string" || data.length === 0) {
throw "[DES] failed to encrypt, invalid data";
}
const encryptedContent = CryptoJS.DES.encrypt(data, this.key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encryptedContent.ciphertext.toString(CryptoJS.enc.Hex);
}

Decrypt(data) {
if (typeof (data) !== "string" || data.length == 0) {
throw "[DES] failed to decrypt, invalid data";
}

const decryptionParam = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Hex.parse(data)
});

const decryptedContent = CryptoJS.DES.decrypt(decryptionParam, this.key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return decryptedContent.toString(CryptoJS.enc.Utf8);
}
}

{
// 输入对应的aes key和域名
const aesHelper = new AESHelper("your aes key");
const encryptedData = aesHelper.Encrypt("your domain");
console.log('AES加密后的数据', encryptedData);
const decryptedData = aesHelper.Decrypt(encryptedData);
console.log('AES解密后的数据', decryptedData);
}

{
// 输入对应的des key和域名
const desHelper = new DESHelper("your des key");
const encryptedData = desHelper.Encrypt("your domain");
console.log('DES加密后的数据', encryptedData);
const decryptedData = desHelper.Decrypt(encryptedData);
console.log('DES解密后的数据', decryptedData);
}
</script>
</html>