Java手动实现完整的加密通信流程

默认的像哥斯拉这种通信中会把AES密钥明文发送到流量里,甚至有些硬编码到木马上,我脑海中一直有一种利用RAS实现对称加密密钥的安全分发,从而实现后门与客户端的完整加密通信的过程,来一起探讨下。

RAS加密

首先温故一下RSA相关的知识

RAS算法中

公钥(e,n)

e是公钥指数,n作为模数

公钥用来加密明文

私钥(d,n)

d是私钥指数

pq为两个互质的质数

欧拉函数

ϕ(n)=(p-1)*(q-1)

如果n是质数,则 φ(n)=n-1 。因为质数与小于它的每一个数,都构成互质关系。比如 5 与 1、2、3、4 都构成互质关系。

欧拉定理

x的φ(n)次方的值,模n的余数一定为1

扩展欧几里得算法

用于计算两个整数的最大公约数(GCD)的同时,还能找到一组整数解 xy,使得:

ax+by=gcd(a,b)

在 RSA 密钥生成中,扩展欧几里得算法用于求解私钥 d,其中 d 满足以下条件:

**d×e≡1 (mod ϕ(n))**

这表示de相乘mod**ϕ(n)**的值为1

欧几里得扩展算法求d逆元

假设c是a,b的一个公约数,则有:

a mod c=0, b mod c=0

⇒(a−b) mod c=0

⇒(a−kb) mod c=0

⇒(a mod b) mod c=0

这里的e和n是互质的,那么可以得到gcd(a,b)=gcd(b,a mod b)

static Integer[] exgcd(int a, int b)
{
    if (b == 0)
    {
        return new Integer[]{ 1, 1 };
    }
    Integer[] ret = exgcd(b, a % b);
    int t = ret[0];
    ret[0] = ret[1];
    ret[1] = t - (a / b * ret[1]);
    return ret;
}

假设我们有:

ax1+by1=gcd(a,b)

通过递归的方式,我们得到了:

bx2+(a mod b)y2=gcd(b,a mod b)

因为 a mod b = a - (a // b) * b//为整除,我们可以将其代入上式:

bx2+(a−(a//b)∗b)y2=gcd(a,b)

展开并整理得到:

a**y2**+b**(x2−(a//b)∗y2)**=gcd(a,b)

因此,我们可以得到新的 xy 值:

  • 新的 x 是原来的 y2
  • 新的 yx2 - (a // b) * y2

故得到公式代码

RAS加密中明文长度

RAS加密过程涉及对大整数进行模幂运算,所以明文的长度不能超过模数n的byte长度。

RAS密钥长度和padding方式决定了明文的最大长度。

假设使用的 RSA 密钥长度为 2048 位(256 字节),我们可以计算明文的最大长度:

  • 使用 PKCS#1 填充
    • 最大明文长度 = 密钥长度(字节数) - 填充字节数
    • 对于 2048 位密钥,最大明文长度 = 256 - 11 = 245 字节
  • 使用 OAEP 填充
    • 最大明文长度 = 密钥长度(字节数) - 填充字节数
    • 对于 2048 位密钥,最大明文长度 = 256 - 42 = 214 字节

RAS加密的主要作用就是用来分发一个加密后的对称加密密钥,而不是加密整个消息。

字符串格式,一位就是一个字节,如果加密明文为int型,那么一个字节

就是8位 2^8=1024

最小值 [-2^8,2^8-1]也就是[-1024,1023],所以一个字节差不多就是4位十进制数字大小范围,不同字节计算出来int可占位也是不同的

4个字节差不多就是最大10位的十进制数

而对于字符串来说 一个字符也就是一个byte

package RAS;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;

class RsaKeySize {
    public static void main(String[] args) {
        // 给定的 n、e 和 d 值
        BigInteger n = new BigInteger("123456789");
        BigInteger e = new BigInteger("65537");
        BigInteger d = new BigInteger("1959087718915866815402369");

        // 计算 n 的位数
        int bitLength = n.bitLength();
        // 计算字节数
        int byteLength = (bitLength + 7) / 8; // 进位计算 保证字节位数是大于等于被8整除位的

        System.out.println("Bit length of n: " + bitLength);
        System.out.println("Byte length of n: " + byteLength);


        String asciiString = "Hello123!";
        String utf8String = "你好!";

        try {
            byte[] asciiBytes = asciiString.getBytes("UTF-8");
            byte[] utf8Bytes = utf8String.getBytes("UTF-8");
            System.out.println("ASCII string bytes: " + asciiBytes.length); // 输出 9
            System.out.println("UTF-8 string bytes: " + utf8Bytes.length);  // 输出 15
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }
    }
}

实现RAS加密

public class RasCrypt {
    private static final SecureRandom random = new SecureRandom();
    private static final BigInteger e = BigInteger.valueOf(65537);

    static {
        random.setSeed(new Date().getTime());
    }

    // 获取一个质数
    public static BigInteger getPrime(int bitLength) {
        BigInteger p;
        while (!(p = BigInteger.probablePrime(bitLength, random)).isProbablePrime(100)) {
            continue;
        }
        return p;
    }

    // 生成rsa三个参数
    public static BigInteger[] genRsaKey() {
        BigInteger p, q, n, φ, d, e = BigInteger.valueOf(65537);
        p = getPrime(200);
        q = getPrime(200);
        n = p.multiply(q);
        //φ=(p-1)*(q-1)
        φ = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
        d = extGcd(e, φ)[0];
        BigInteger[] result = new BigInteger[]{n, e, d};
        if (d.compareTo(BigInteger.ONE) < 0 || !p.gcd(q).equals(BigInteger.ONE)) {
            return genRsaKey();
        }

/*        System.out.println(p);
        System.out.println(q);*/
        return result;
    }

    // rsa加密
    public static byte[] rsaEncrype(byte[] m, BigInteger n, BigInteger e) {
        if (e == null) {
            e = BigInteger.valueOf(65537);
        }

        return new BigInteger(m).modPow(e, n).toByteArray();
    }

    // rsa解密
    public static byte[] rsaDecrype(byte[] c, BigInteger n, BigInteger d) {
        return new BigInteger(c).modPow(d, n).toByteArray();
    }

    // 扩展欧几里得算法,求私钥d
    public static BigInteger[] extGcd(BigInteger a, BigInteger b) {
        BigInteger[] result = null;
        if (b.equals(BigInteger.ZERO)) {
            result = new BigInteger[]{BigInteger.ONE, BigInteger.ZERO};
            return result;
        }
        result = extGcd(b, a.mod(b));
        BigInteger x = result[1];
        BigInteger y = result[0].subtract(a.divide(b).multiply(x));
        result = new BigInteger[]{x, y};
        return result;
    }

    public static void main(String[] args) {

  BigInteger p  = getPrime(5);
        BigInteger[] result =genRsaKey();

        BigInteger[] keys = genRsaKey();
        BigInteger n =  keys[0];
        BigInteger privateKey =  keys[2];
        System.out.println("n,e,d:"+ Arrays.toString(keys));
        String key ="";
        key = padTo16Bytes(key);
        String originalMessage = key;
        byte[] encryptedMessage = rsaEncrype(originalMessage.getBytes(), n, e);
        byte[] decryptedMessage = rsaDecrype(encryptedMessage, n, privateKey);


        String base64Encoded = Base64.getEncoder().encodeToString(encryptedMessage);
        // 输出Base64编码的字符串
        System.out.println("Base64 Encoding Ciphertext: " + base64Encoded);
        System.out.println("Private Key is :"+Base64.getEncoder().encodeToString(originalMessage.getBytes(StandardCharsets.UTF_8)));
        System.out.println("Ciphertext Byte Array:"+new String(encryptedMessage));
        if (new String(decryptedMessage).equals(originalMessage)) {
            System.out.println("AES key is:"+new String(decryptedMessage));
            System.out.println("Public and private keys match!");

        } else {
            System.out.println("Public and private keys do not match.");
        }

    }
}

AES加密

利用RAS来分发对称密钥,不过密钥长度有要求,需要满足标准的16、24、32字节,也就是128位、192位和256位

对于加密的明文有区块填充要求,可以采用多种填充方式

Cipher c = Cipher.getInstance(aesMode);

aesMode="AES/OFB/ISO10126Padding";表示AES加密采用OFB加密模式,采用随机填充

常见的加密模式:

  • ECB(Electronic Codebook):最简单的模式,每个块独立加密。
  • CBC(Cipher Block Chaining):将前一个密文块与当前块异或后再加密。
  • CFB(Cipher Feedback):类似于 CBC,但更适合流加密。
  • OFB(Output Feedback):将加密器的输出作为下一个块的输入。
  • GCM(Galois/Counter Mode):一种支持认证的加密模式,确保数据完整性。

填充方式:

  • PKCS5Padding:将缺少的字节填充为与块大小相同的数值。
  • ISO10126Padding:随机填充,并用最后一个字节指明填充的字节数。
  • NoPadding:表示不使用填充。通常,分组加密算法要求输入的数据长度是加密块大小的整数倍(AES 的块大小是 16 字节)。如果输入的数据不足以填满最后一个块,通常会使用填充(比如 PKCS5Padding)。NoPadding 表示你必须自行确保数据是正确大小,否则会导致异常。

这里随机生成密钥key16位或者32位,iv为随机生成

public class aesEncrypt {
    private static final String aesMode = "AES/OFB/ISO10126Padding";
    private static final SecureRandom random = new SecureRandom();
    static {
        random.setSeed(new Date().getTime());
    }

    // 随机生成16字节的IV
    private static byte[] generateIV() {
        byte[] iv = new byte[16]; // AES的IV是16字节
        random.nextBytes(iv);
        return iv;
    }
    /**
     * hex解码为byte
     *
     * @param data
     * @return
     */
    public static byte[] hex2b(String data) {
        byte[] byteArray = new BigInteger(data, 36)
                .toByteArray();
        if (byteArray[0] == 0) {
            byte[] output = new byte[byteArray.length - 1];
            System.arraycopy(
                    byteArray, 1, output,
                    0, output.length);
            return output;
        }
        return byteArray;
    }

    /**
     * byte编码为hex
     *
     * @param data
     * @return
     */
    public static String b2hex(byte[] data){
        return new BigInteger(1, data).toString(36).toLowerCase();
    }

    /**
     * 字节转为字符串
     *
     * @param data
     * @return
     */
    public static String b2s(byte[] data) {
        try {
            return new String(data, "utf-8");
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 字符串转为字节
     *
     * @param data
     * @return
     */
    public static byte[] s2b(String data) {
        try {
            return data.getBytes("utf-8");
        } catch (Exception e) {
            return new byte[]{};
        }
    }

    /**
     * aes加密
     *
     * @param s
     * @param k
     * @return
     */
    public static byte[] aesEncrypt(byte[] s, String k, byte[] iv) {
        try {
            Cipher c = Cipher.getInstance(aesMode);
            c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k.getBytes(), "AES"), new IvParameterSpec(iv));
            return c.doFinal(s);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * aes解密
     *
     * @param s
     * @param k
     * @return
     */
    public static byte[] aesDecrypt(byte[] s, String k, byte[] iv) {
        try {
            Cipher c = Cipher.getInstance(aesMode);
            c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(k.getBytes(), "AES"), new IvParameterSpec(iv));
            return c.doFinal(s);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // 填充或截断字符串以使其为16字节
    private static String padTo16Bytes(String input) {
        StringBuilder paddedInput = new StringBuilder(input);
        int padding_le = 16;
        if (input.length()>=16 && input.length()<=32 )
        {
            padding_le =32;
        }
        else if(input.length()>32)
            return input.substring(0,32);
        while (paddedInput.length() < padding_le) {
            paddedInput.append((char)(random.nextInt(94)+32)); // 补全为16、32字节
        }


        return paddedInput.toString();
    }

    public static void main(String[] args) {
        String key ="";
        key = padTo16Bytes(key);
        System.out.println("padding Key:"+key);
        String se = "test shellcode \\xfc\\xe8\\x89\\x00..........";

        byte[] test =s2b(se);
        byte[] shellcode={0x68,0x65,0x6c,0x6c,0x6f,0x77,0x6f,0x72,0x64};
        byte[]  iv = generateIV();
        byte[] mi = aesEncrypt(test,key,iv);
        byte[] test_1= aesDecrypt(mi,key,iv);

        System.out.println(new String(test_1));

    }
}

加密通信思维图

这种通信流程可以用于冰蝎、哥斯拉等实现加密的通信流程,在流量端也无法直接获取到私钥,没法解密通信内容。

这里主要是分享实现流程和思路,可以微调操作的地方还是很多,大家可以发挥想象。

2 条评论
某人
表情
可输入 255