默认的像哥斯拉这种通信中会把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)的同时,还能找到一组整数解 x
和 y
,使得:
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)
因此,我们可以得到新的 x
和 y
值:
- 新的
x
是原来的y2
。 - 新的
y
是x2 - (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));
}
}
加密通信思维图
这种通信流程可以用于冰蝎、哥斯拉等实现加密的通信流程,在流量端也无法直接获取到私钥,没法解密通信内容。
这里主要是分享实现流程和思路,可以微调操作的地方还是很多,大家可以发挥想象。