项目介绍

网络安全期末大作业:通过课堂上讲解的对称加密和非对称加密算法,实现一个能够加密解密的邮箱客户端。

注意:本文不介绍算法,只讲解主要实现过程。


效果展示

生成密钥

加密邮件

解密


整体设计

用 Java 实现加密邮箱客户端,由于是网络安全作业,所以主要在于加解密算法的编写。这里我采用随机生成的字符串为 AES128 的密钥,加密密文,再用其他用户的公钥采用 RSA 加密 AES128 密 钥,最后封装成 EncryptMessage 对象,向其他用户发送密钥和密文。

流程图如下:

加密邮件算法设计

加密设计
  1. 采用随机生成的字符串作为 AES128 的密钥,加密密文。

  2. 再用其他用户的公钥采用 RSA 加密 AES128 密钥。

  3. 最后封装成 EncryptMessage 对象,向其他用户发送密钥和密文。

其他设计
  1. 字符集编码:Unicodee 支持所有字符编码。

  2. 输出格式:Base64 编码。

  3. EncryptMessage 对象:

    • 密钥:RSA 加密 AES 密钥。
    • 密文:AES 加密的密文。

加密邮件算法部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 获取公钥和密钥
*
* @return
*/
public static RSAUtils.SecretKey createRSAKeyPair()
{
return RSAUtils.createKeyPair();
}

/**
* 加密
*
* @param plaintext 明文
* @param publicKey 公钥
* @return
*/
public static EncryptMessage encrypt(String plaintext, String publicKey) throws UnsupportedEncodingException
{
// base64解码公钥
publicKey = new String(Base64.getDecoder().decode(publicKey));
// 生成随机AES128密钥
String secretKey = getRandomString(16);
// AES128加密明文
String cipherText = AESUtils.encrypt(plaintext, secretKey);
// RSA加密AES128密钥
String key = encryptSecretKey(secretKey, publicKey);
// base64编码邮件密文和密钥
cipherText = new String(Base64.getEncoder().encode(cipherText.getBytes()));
key = new String(Base64.getEncoder().encode(key.getBytes()));
return new EncryptMessage(key, cipherText);
}

/**
* 解密
*
* @param encryptMessage 加密邮件
* @param privateKey 私钥
* @return
*/
public static String decrypt(EncryptMessage encryptMessage, String privateKey) throws UnsupportedEncodingException
{
// base64解码私钥
privateKey = new String(Base64.getDecoder().decode(privateKey));
// 获取邮件密文和密钥
String cipherText = encryptMessage.getCipherText();
String key = encryptMessage.getKey();
// base64解码密文和密钥
cipherText = new String(Base64.getDecoder().decode(cipherText));
key = new String(Base64.getDecoder().decode(key));
// AES128密钥解密密文
String secretKey = decryptSecretKey(key, privateKey);
return AESUtils.decrypt(cipherText, secretKey);
}

AES 算法

  • 编码方式为 Unicode,支持所有字符编码;
  • 密钥:AES128;
  • 加密模式:ECB;
  • 填充方式:ZerosPadding。

实现步骤

加密步骤
  1. 明文、密钥按 Unicode 编码格式转字节数组;

  2. 填充明文为 16 字节的倍数,刚好为 16 字节的倍数也要填充;

  3. 明文、密钥数组转字节矩阵;

  4. 分块加密

    • 生成拓展密钥

    • 初始变化

    • 9 轮循环

    • 最后轮循环

  5. 密文字节矩阵转密文字节数组;

  6. 密文字节数组转密文字节字符串;

  7. 密文拼接。

解密步骤
  1. 密钥按 Unicode 编码格式转字节数组;
  2. 密文 16 进制字符串转字节数组;
  3. 密文、密钥数组转字节矩阵;
  4. 分块解密
    • 生成拓展密钥;
    • 初始变化;
    • 9 轮循环;
    • 最后轮循环;
  5. 明文字节矩阵转明文字节数组;
  6. 明文字节数组按 Unicode 编码方式转字符串;
  7. 明文拼接。

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* 列混合的域乘
*
* @param fixMatrix 固定矩阵
* @param content
* @return
*/
private static byte mixMultiply(byte fixMatrix, byte content)
{
byte mulContent = 0;
if (fixMatrix == 0x01)
{
return content;
}
else if (fixMatrix == 0x02)
{
mulContent = GF(content, fixMatrix);
}
else if (fixMatrix == 0x03)
{
mulContent = (byte) (GF(content, (byte) 0x02) ^ content);
}
else if (fixMatrix == 0x09)
{
mulContent = (byte) (GF(GF(GF(content, (byte) 0x02), (byte) 0x02), (byte) 0x02) ^ content);
}
else if (fixMatrix == 0x0B)
{
mulContent = (byte) ((GF(content, (byte) 0x02) ^ GF(GF(GF(content, (byte) 0x02), (byte) 0x02), (byte) 0x02)) ^ content);
}
else if (fixMatrix == 0x0D)
{
mulContent = (byte) ((GF(GF(content, (byte) 0x02), (byte) 0x02) ^ GF(GF(GF(content, (byte) 0x02), (byte) 0x02), (byte) 0x02)) ^ content);
}
else if (fixMatrix == 0x0E)
{
mulContent = (byte) (GF(content, (byte) 0x02) ^ GF(GF(content, (byte) 0x02), (byte) 0x02) ^ GF(GF(GF(content, (byte) 0x02), (byte) 0x02), (byte) 0x02));
}

return mulContent;
}

/**
* 乘法操作
*
* @param content
* @param fixMatrix 固定矩阵
* @return
*/
private static byte GF(byte content, byte fixMatrix)
{
byte mulContent = 0;

if ((content & 0x80) == 0x80)
{
mulContent = (byte) ((byte) (content << 1) ^ 0x1B);
}
else
{
mulContent = (byte) (content << 1);
}

return mulContent;
}

RSA 算法

  • 本文 RSA 算法的主要实现是取自 CSDN 社区的 foDask Jhonson RSA算法原理及实现(Java) 。

  • RSA 生成的公钥 PublicKey(e, n) 和密钥 PrivateKey(d, n) 的类型为 BigInteger;

  • 公钥和密钥格式为两个超大整数且用 “.” 分割,最后再用 Base64 编码。

  • 素数长度:1024 bit;

  • 素数的准确率 1-(2 ^ (-accuracy)):128;

  • 公钥指数 e 的取值:根据PKCS#1的建议,公钥指数e是可以选取较小的素数3或65537(=2^16+1)。这里取 65537。

实现步骤

  1. 选择一对不相等且足够大的质数p, q;
  2. 计算 p,q 的乘积 n;
  3. 计算 n 的欧拉函数 φ(n) = (p - 1) * (q - 1);
  4. 选择一个与 φ(n) 互质的整数 e(1 < e < φ(n));
  5. 计算出 e 对于 φ(n) 的模反元素 d;
  6. 计算出的 d 不能是负数,如果是负数,d = d + φ(n)。使d为正数;
  7. 返回计算出的密钥。

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 扩展欧几里得法求逆元 -- 求e的逆元d
* d * e - y * φ(n) = 1 (e和φ(n)互质)
* d * e - y * φ(n) = gcd(e, φ(n))
* d * e - y * φ(n) = gcd(φ(n), e % φ(n))
* d * e - y * φ(n) = φ(n) * d` - y` * [e % φ(n)] (若当前为最后一个式子, 则 e % φ(n) = 0, 开始往回带值)
* d * e - y * φ(n) = φ(n) * d` - y` * [e - e / φ(n) * φ(n)]
* d * e - y * φ(n) = - y` * e + [d` + e / φ(n) * y`] * φ(n)
* d = -y` 和 y = d` - e / φ(n) * y`
* 由于 d 不能为负数,需要用d=d+φ(n),使它成为正数
*
* @param e e
* @param PHI_n φ(n)
* @return bigIntegers(最小公因数, d, y)
*/
private static BigInteger[] exGCD(BigInteger e, BigInteger PHI_n)
{
// φ(n) = 0 -> (1, 1, 0) 满足 d * e - y * φ(n) = 1
if (PHI_n.signum() == 0)
{
return new BigInteger[]{e, new BigInteger("1"), new BigInteger("0")};
}
else
{
BigInteger[] bigIntegers = exGCD(PHI_n, e.mod(PHI_n));
// y = d` - e / φ(n) * y`
BigInteger y = bigIntegers[1].subtract(e.divide(PHI_n).multiply(bigIntegers[2]));
return new BigInteger[]{bigIntegers[0], bigIntegers[2], y};
}
}


📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!

🔗参考文献:

🌐 Dask Jhonson. RSA算法原理及实现(Java). [EB/OL]. (2020-05-02). [2022-12-04]. https://blog.csdn.net/qq_41115702/article/details/105884973