网络安全实验——安全通信软件safechat的设计

网络安全实验——安全通信软件safechat的设计

仅供参考,请勿直接抄袭,抄袭者后果自负。

仓库地址:

后端地址:https://github.com/yijunquan-afk/safechat-server

前端地址: https://github.com/yijunquan-afk/safechat-client

CosUpload.java中的COS设置,需要自己配

![image-20230317214314077](https://note-image-1307786938.cos.ap-
beijing.myqcloud.com/typora/%20image-20230317214314077.png)

1 设计要求

结合所学安全机制设计实现一个简单的安全通信软件,包含 机密性,消息认证 等基本功能。并考虑其中涉及的 密钥分配方式与机密性算法
等相关问题的解决.实现方法不限,使用机制不限。

要求:

1、 独立完成

2、 具有完整的流程设计,报文格式等相关分析。

3、 具备自圆其说的安全性设计思考

2 设计分工

3 设计原理

SHA-2

SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm
2)的缩写,一种密码散列函数算法标准,由美国国家安全局研发[3],由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

RSA

RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。RSA是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi
Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的。当时他们三人都在麻省理工学院工作。RSA
就是他们三人姓氏开头字母拼在一起组成的。

极大整数做因数分解 的难度决定了 RSA 算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA
算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用 RSA 加密的信息的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的 RSA
钥匙才可能被强力方式破解。到2020年为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被破解的。

WebSocket协议

WebSocket是双向的,在客户端-
服务器通信的场景中使用的全双工协议,与HTTP不同,它以ws://或wss://开头。它是一个有状态协议,这意味着客户端和服务器之间的连接将保持活动状态,直到被任何一方(客户端或服务器)终止。在通过客户端和服务器中的任何一方关闭连接之后,连接将从两端终止。

以客户端-服务器通信为例,每当启动客户端和服务器之间的连接时,客户端-
服务器进行握手随后创建一个新的连接,该连接将保持活动状态,直到被他们中的任何一方终止。建立连接并保持活动状态后,客户端和服务器将使用相同的连接通道进行通信,直到连接终止。

新建的连接被称为WebSocket。一旦通信链接建立和连接打开后,消息交换将以双向模式进行,客户端-
服务器之间的连接会持续存在。如果其中任何一方(客户端服务器)宕掉或主动关闭连接,则双方均将关闭连接。套接字的工作方式与HTTP的工作方式略有不同,状态代码101表示WebSocket中的交换协议。

![image-20230317210412500](https://img-
blog.csdnimg.cn/img_convert/c5a5cc7792bb24c23d3c4c9c7a8aaff1.png)

JWT

JWT就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密,签名等相关处理。

基于JWT认证

首先,前端通过Wb表单将自己的用戶名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。

2、后端核对用戶名和密码成功后,将用戶的id等其他信息作为JWT
Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同11.Zzz.xx的字符串。token
head.payload.signature

3、后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage.上,退出登录时前端删除保存的JWT即可。

4、前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)

5、后端检查JWT是否存在,如存在验证JWT的有效性。检查签名是否正确,检查Token是否过期,检查Token的接收方是否是自己(可选)

JWT结构

jwt生成的字符串包含有三部分

1、
jwt头信息部分header:标头通常由两部分组成:令牌的类型(即JWT所使用的签名算法,例如HMAC、SHA256或RSA。它会使用Base64编码组成JWT结构的第一部分。

2、
在效载荷Payload:令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用戶)和其他数据的声明。同样的,它会使用Ba$64编码组成JWT结构的第二部分

3、
签名哈希Signature:header和payload都是结果Base64编码过的,中间用.隔开,第三部分就是前面两部分合起来做签名,密钥绝对自己保管好,签名值同样做Base64编码拼接在JWT后面。(签名并编码)

AES

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),又称Rijndael加密法(荷兰语发音:
[ˈrɛindaːl],音似英文的“Rhine
doll”),是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS
PUB 197,并在2002年5月26日成为有效的标准。现在,高级加密标准已然成为对称密钥加密中最流行的算法之一。

严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中两者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度固定为128比特,密钥长度则可以是128,192或256比特;而Rijndael使用的密钥和区块长度均可以是128,192或256比特。加密过程中使用的密钥是由Rijndael密钥生成方案产生。

大多数AES计算是在一个特别的有限域完成的。

AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。(Rijndael加密法因支持更大的区块,其矩阵的“列数(Row
number)”可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:

① AddRoundKey—矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。

② SubBytes—透过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。

③ ShiftRows—将矩阵中的每个横列进行循环式移位。


MixColumns—为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey取代。

4 整体设计方案

网络协议

本次设计中,我使用了HTTP协议处理一般的网络请求:如登录、注册、好友列表获取、个人信息获取、头像更新等功能。

而好友之间点对点的通信,为了持续快速地沟通,我是用WebSocket协议来处理信息发送请求。

客户端技术选型

客户端负责的是与用户进行交互,因此在实用之外还需要考虑到界面美观整洁,以给用户带来良好的使用体验。因此,前端选择使用 vue + AntDesign
组件库进行界面构建。另一方面,由于需要建立 WebSocket 连接,发送 WebSocket 请求,因此需要引入 WebSocket
相关功能的实现。这里使用的是 socket.io 这一 NodeJS 第三方模块。

![image-20230317210728995](https://img-
blog.csdnimg.cn/img_convert/ff4331223a8444114041c924a9fc9f65.png)

服务端技术选型

对于服务端,采用了 Java + SpringBoot 为大框架来进行服务端的开发。数据库采用的是经典的关系型数据库 MySql。同时为了建立
WebSocket 连接,处理 WebSocket 请求,选择了 socket.io 的一个 Java 移植版本 netty-socketio。netty-
socketio是一个开源的Socket.io服务器端的一个java的实现,它基于Netty框架,可用于服务端推送消息给客户端。

![image-20230317210745758](https://img-
blog.csdnimg.cn/img_convert/9401a5d5b70769906d7d2e10b4a1af13.png)

整体功能说明

本系统主要包含六个大的功能模块:登陆注册、用户信息获取、信息发送、好友列表显示、头像上传以及退出系统。
其中信息发送是本次课程设计最重要的部分,是安全通信的主要体现。

![image-20230317210825820](https://img-
blog.csdnimg.cn/img_convert/0e723298fb618725db60750b77034ef1.png)

5 安全加密部分代码说明

整体设计

![image-20230317210912033](https://img-
blog.csdnimg.cn/img_convert/ff636262dc443f47cb255b03ccefc831.png)

HTTP加密
Token产生
private static String sign(String userId,String password){
    Algorithm algorithm = Algorithm.HMAC256(password);
    String token = JWT.create()
            .withClaim(CLAIM_USERID_NAME,userId)
            .withExpiresAt(new Date(System.currentTimeMillis()+EXPIRED_TIME/2))
            .sign(algorithm);
    return token;
}

/**
* 生成一个登录token
* @param userId
* @param password
* @return
*/
public static String loginSign(String userId,String password){
    String token = sign(userId,password);
    cache.putToken(token,token);
    return token;
}

每次登录产生Token,并存储在前端的localStorage中,每次发送HTTP的POST和GET请求时加在HTTP
Header中的Authorization位。(解决XSS和XSRF问题)

Token认证

后端接收HTTP请求时需要认证Token。

如此做可以认证发送HTTP请求的用户身份,适用于所有HTTP请求

 /**
     * 验证客户端传来token是否有效
     * 验证逻辑顺序如下:
     * 1. token是否为空
     * 2. token中账号是否存在
     * 3. 根据token中账号从数据库中获取真实密码等用户信息,并验证用户信息是否有效
     */
    public static void verifyToken(String clientToken, stu.software.chatroom.common.CommonService commonService){
        if(!StringUtils.hasText(clientToken)){
            //token为空
            throw new RuntimeException("无登录令牌!");
        }

        //从客户端登录令牌中获取当前用户账号
        String userId = JWT.decode(clientToken).getClaim(CLAIM_USERID_NAME).asString();
        if(!StringUtils.hasText(userId)){
            //token中账号不存在
            throw new RuntimeException("登录令牌失效!");
        }

        //取出缓存中的登录令牌
        String cacheToken = cache.getToken(clientToken);
        if(!StringUtils.hasText(cacheToken)){
            //缓存中没有登录令牌
            throw new RuntimeException("登录令牌失效!");
        }

        User user = commonService.getUserById(userId);
        if(user==null){
            //用户不存在
            throw new RuntimeException("用户不存在!");
        }

        //验证Token有效性
        try{
            Algorithm algorithm = Algorithm.HMAC256(user.getU_pwd());
            JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim(CLAIM_USERID_NAME,userId).build();//构建验证器
            jwtVerifier.verify(cacheToken);
        }catch(TokenExpiredException e){
            //令牌过期,刷新令牌
            String newToken = sign(userId,user.getU_pwd());
            cache.putToken(clientToken,newToken);
        }catch(Exception e){
            e.printStackTrace();
            //令牌验证未通过
            throw new RuntimeException("令牌错误!请登录。");
        }
注册密码加密

使用SHA256加密注册时用户使用的密码,数据库中存的是密文,这样可防止数据库被攻击导致密码泄露。

![image-20230317211223024](https://note-image-1307786938.cos.ap-
beijing.myqcloud.com/typora/%20image-20230317211223024.png)

/***
* 利用Apache的工具类实现SHA-256加密
* @return str 加密后的报文
*/
public static String getSHA256Str(String str) {
    MessageDigest messageDigest;
    String encodeSir = str;
    try {
        messageDigest = MessageDigest.getInstance("SHA-256");
        byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
        encodeSir = Hex.encodeHexString(hash);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return encodeSir;
}

/**
* 通过该方法将密码加密(实际上并没有)
*/
public static String encodePwd(String u_pwd) {
    // 密码通过此方法解密并再加密
    return getSHA256Str(u_pwd);
}
登录密码加密

登录时,前端输入明文密码,使用SHA256加密该密码以后,再加数据发送到后端。后端根据该加密后的密码与数据库比对,从而验证用户身份。

此做法避免了前端请求数据被拦截导致密码泄露。

![image-20230317211402183](https://note-image-1307786938.cos.ap-
beijing.myqcloud.com/typora/%20image-20230317211402183.png)

import { sha256 } from 'js-sha256';

/**
 * 加密方法
 */
export function PASSWORD(str) {
    let encodedStr = str;
    encodedStr = sha256(encodedStr);
    return encodedStr;
}
const login = () => {
  post("/user/login", {
    u_name: u_name.value,
    u_pwd: PASSWORD(u_pwd.value),
  })
    .then((res) => {
      tip.success(res.message);
      let token = res.data;
      setLocalToken(token);
      router.push({ name: "Room", query: { usr: u_name.value } });
    })
    .catch((err) => {
      tip.error("账号密码错误!");
    });
};
密钥分配——使用Keytool

参考教程 https://blog.csdn.net/m0_59579040/article/details/124811147

keytool
是个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向别的用户/服务认证自己)或数据完整性以及认证服务。它还允许用户储存他们的通信对等者的公钥(以证书形式)。

在计算机网络上,OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。

通过如下步骤可以产生证书和公钥

keytool -genkeypair -storetype PKCS12 -alias yjq - -keyalg RSA -keysize 1024 -dname "CN=xxx, OU=xxx, O=xxx, L=xx, ST=xx, C=CN" -keystore D:\mygit\大三下笔记\网安课设\safechat-server\src\main\resources\keys-and-certs\yjq.keystore -keypass 123456 -storepass 123456 -validity 36500 -v  

产生二进制文件yjq.keystore,以上部分可由脚本生成。

经过KeyStore的相关操作生成公钥、证书和私钥

![image-20230317211728014](https://img-
blog.csdnimg.cn/img_convert/df79c064382964f9eb2fb6abd6878cb3.png)

当用户需要公钥和私钥时,只需要调用相关方法即可。

public static void genKeyPair(String name) throws Exception {  
    //以 PKCS12 规格,创建 KeyStore  
    KeyStore keyStore = KeyStore.getInstance("PKCS12");  
    path = "keys-and-certs/" + name + ".keystore";  
    //载入 jks 和该 jks 的密码 到 KeyStore 内  
    keyStore.load(new FileInputStream(new ClassPathResource("keys-and-certs/yjq.keystore").getFile()), "123456".toCharArray());  
  
    // 要获取 key,需要提供 KeyStore 的别名 和该 KeyStore 的密码  
    // 获取 keyStore 内所有别名 alias  
    Enumeration<String> aliases = keyStore.aliases();  
    String alias = null;  
    alias = aliases.nextElement();  
    char[] keyPassword = "123456".toCharArray();  
    keyPairString.clear();  
    //私钥  
    privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword);  
    keyPairString.put("PR", new String(Base64.getEncoder().encode(privateKey.getEncoded())));  
    //证书  
    Certificate certificate = keyStore.getCertificate(alias);  
    //公钥  
    publicKey = certificate.getPublicKey();  
    keyPairString.put("PU", new String(Base64.getEncoder().encode(publicKey.getEncoded())));  
  
} 
使用公钥加密保证消息认证和机密性

参考教程https://blog.csdn.net/m0_59579040/article/details/124811147.

A和B进行通信,首先使用A的私钥对报文M进行加密——数字签名;然后A用B的公钥对上述结果进行加密——保证了保密性。

B收到消息后,用B的私钥解密,再用A的公钥验证签名。

![image-20230317211920550](https://img-
blog.csdnimg.cn/img_convert/f0b6b6fae53c95b2a35cbaf4777fd53f.png)

这里我使用RSA作为加密算法、SHA1WithRSA作为签名算法,签名和加密的操作实现在类RSAUtils.java中。

签名
/** 
* 私钥签名 
* @param content 字符串 
* @param priKey 私钥 
* @return 
* @throws Exception 
*/  
public static byte[] sign(String content, PrivateKey priKey) throws Exception {  
    Signature signature = Signature.getInstance(SIGALG);  
    signature.initSign(priKey);  
    signature.update(content.getBytes());  
    return signature.sign();  
}  
  
/** 
* 公钥验证签名 
* @param content 字符串 
* @param sign 签名 
* @param pubKey 公钥 
* @return 身份是否真实 
* @throws Exception 
*/  
public static boolean verify(String content, byte[] sign, PublicKey pubKey) throws Exception {  
  
    Signature signature = Signature.getInstance(SIGALG);  
    signature.initVerify(pubKey);  
    signature.update(content.getBytes());  
    return signature.verify(sign);  
}  
加密解密
/** 
* RSA公钥加密 
* 
* @param content       加密字符串 
* @param publicKey 公钥 
* @return 密文 
* @throws Exception 加密过程中的异常信息 
*/  
public static String encrypt(String content, String publicKey) throws Exception {  
    //base64编码的公钥  
    byte[] decoded = Base64.getMimeDecoder().decode(publicKey);  
    RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(KEYALG).generatePublic(new X509EncodedKeySpec(decoded));  
    System.out.println(pubKey.getAlgorithm());  
    //RSA加密  
    Cipher cipher = Cipher.getInstance(KEYALG);  
    cipher.init(Cipher.ENCRYPT_MODE, pubKey);  
    String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(content.getBytes("UTF-8")));  
    return outStr;  
}  
  
/** 
* RSA私钥解密 
* 
* @param content        加密字符串 
* @param privateKey 私钥 
* @return 明文 
* @throws Exception 解密过程中的异常信息 
*/  
public static String decrypt(String content, String privateKey) throws Exception {  
  
    //64位解码加密后的字符串  
    byte[] inputByte = Base64.getMimeDecoder().decode(content);  
    //        //base64编码的私钥  
    byte[] decoded = Base64.getMimeDecoder().decode(privateKey);  
    RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));  
    //RSA解密  
    Cipher cipher = Cipher.getInstance("RSA");  
    cipher.init(Cipher.DECRYPT_MODE, priKey);  
    String outStr = new String(cipher.doFinal(inputByte));  
    return outStr;  
}  
使用AES加密消息

因为公钥加密的消息认证比较费时间,所以当两个用户建立消息通信时由一方产生会话密钥,使用公钥加密来传送会话密钥并认证身份。身份认证完成后,使用该会话密钥加密消息,其中使用对称加密技术AES加密消息。

消息报文格式如下:

![image-20230317212104464](https://note-image-1307786938.cos.ap-
beijing.myqcloud.com/typora/%20image-20230317212104464.png)

1、 id:报文标识id;

2、 time:报文发送时间

3、 content:报文内容(加密)

4、 type:报文类型:会话密钥消息/公钥消息

5、 sender_name:发送者

6、 receiver_name:接收者

7、 sign:发送者签名。

加密过程如下:

public final class AESUtils{  
    private static final String ALGORITHM = "AES";  
    public static String genAesSecret(){  
        try {  
            KeyGenerator kg = KeyGenerator.getInstance("AES");  
            //下面调用方法的参数决定了生成密钥的长度,可以修改为128, 192或256  
            kg.init(256);  
            SecretKey sk = kg.generateKey();  
            byte[] b = sk.getEncoded();  
            String secret = Base64.encodeBase64String(b);  
            return secret;  
        }  
        catch (NoSuchAlgorithmException e) {  
            e.printStackTrace();  
            throw new RuntimeException("没有此算法");  
        }  
    }  
    /** 
     * 根据密钥对指定的明文plainText进行加密. 
     * 
     * @param plainBytes 明文 
     * @param keyBytes   密码 
     * @return 加密后的密文. 
     * @since 0.0.8 
     */  
    public static byte[] encrypt(byte[] plainBytes, byte[] keyBytes) {  
        try {  
            SecretKey secretKey = getSecretKey(keyBytes);  
            Cipher cipher = Cipher.getInstance(ALGORITHM);  
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);  
            return cipher.doFinal(plainBytes);  
        } catch (Exception e) {  
            throw new RuntimeException(e);  
        }  
    }  
  
    /** 
     * 根据密钥对指定的密文 cipherBytes 进行解密. 
     * 
     * @param cipherBytes 加密密文 
     * @param keyBytes    秘钥 
     * @return 解密后的明文. 
     * @since 0.0.8 
     */  
    public static byte[] decrypt(byte[] cipherBytes, byte[] keyBytes) {  
        try {  
            SecretKey secretKey = getSecretKey(keyBytes);  
  
            Cipher cipher = Cipher.getInstance(ALGORITHM);  
            cipher.init(Cipher.DECRYPT_MODE, secretKey);  
            return cipher.doFinal(cipherBytes);  
        } catch (Exception e) {  
            throw new RuntimeException(e);  
        }  
    }  
  
    /** 
     * 获取加密 key 
     * @param keySeed seed 
     * @return 结果 
     * @since 0.0.8 
     */  
    private static SecretKey getSecretKey(byte[] keySeed) {  
        try {  
            // 避免 linux 系统出现随机的问题  
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); 
            secureRandom.setSeed(keySeed);  
            KeyGenerator generator = KeyGenerator.getInstance("AES");  
            generator.init(secureRandom);  
            return generator.generateKey();  
        } catch (Exception e) {  
            throw new RuntimeException(e);  
        }  
    }  
}  
服务端加密

结合RSA与AES的加密如下:

先用公钥加密RSA发送对称加密使用的会话密钥,然后再用会话密钥进行AES对称加密通信。

 // 监听客户端发送消息  
socketIOServer.addEventListener(Constants.EVENT_MESSAGE_TO_SERVER, String.class, (client, data, ackSender) -> {  
    String sender_name = getParamsByClient(client, "u_name");  
    ObjectMapper mapper = new ObjectMapper();  
    Message message = mapper.readValue(data, Message.class);  
    String receiver_name = message.getReceiver_name();  
  
    if (message.getType().equals(Constants.MASTER_MESSAGE)) {  
        //使用公钥加密传送会话密钥  
        if (AesKey.equals("")) {  
            log.info("用户" + sender_name + "生成会话密钥");  
            AesKey = AESUtils.genAesSecret();  
            message.setContent(AesKey);  
            log.info("用户" + sender_name + "使用用户" + sender_name + "的私钥对会话密钥进行签名");  
            String sign = new String(RSAUtils.sign(message.getContent(), RSAUtils.getPrivateKey()), "ISO-8859-1");  
            message.setSign(sign);  
            String result = RSAUtils.encrypt(message.getContent(), publicKeyStringMap.get(receiver_name));  
            log.info("使用用户" + receiver_name + "的公钥对会话密钥进行加密:" + result);  
            message.setContent(result);  
            sendMessageToFriend(message.getReceiver_name(), message);  
        } else {  
            return;  
        }  
    } else {  
        //使用会话密钥发送消息  
        byte[] bytes = AESUtils.encrypt(message.getContent().getBytes(), AesKey.getBytes());  
        String encrypt = new String(bytes, "ISO-8859-1");  
        log.info("用户" + sender_name + "使用会话密钥加密消息");  
        message.setContent(encrypt);  
        sendMessageToFriend(message.getReceiver_name(), message);  
    }    
});  
//  
//GBK,  GB2312,UTF-8等一些编码方式为多字节或者可变长编码,原来的字节数组就被改变了,再转回原来的byte[]数组就会发生错误了。  
//ISO-8859-1通常叫做Latin-1,Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符,其中 0~127的字符与ASCII码相同,  
// 它是单字节的编码方式,在来回切换时不会出现错误。  
  
// 监听客户端接收消息  
socketIOServer.addEventListener("receive_triger", String.class, (client, data, ackSender) -> {   
    ObjectMapper mapper = new ObjectMapper();  
    Message message = mapper.readValue(data, Message.class);  
    String sender_name = message.getSender_name();  
    String receiver_name = message.getReceiver_name();  
    if (message.getType().equals(Constants.MASTER_MESSAGE)) {  
        log.info("收到来自" + sender_name + "发送给" + message.getReceiver_name() + "的消息: " + message.getContent());  
        String result = RSAUtils.decrypt(message.getContent(), RSAUtils.getKeyPair().get("PR"));  
        log.info("用户" + receiver_name + "使用用户" + receiver_name + "的私钥对消息进行解密:");  
        message.setContent(result);  
        log.info("用户" + receiver_name + "使用用户" + sender_name + "的公钥对消息进行验证签名");  
        Boolean sign = (RSAUtils.verify(message.getContent(), message.getSign().getBytes("ISO-8859-1"), publicKeyMap.get(sender_name)));  
        if (sign) {  
            log.info("签名验证成功!身份无误");  
        } else {  
            throw new Exception("签名错误!");  
        }  
        receiveMessageFromFriend(message.getReceiver_name(), message);  
    } else {  
        log.info("收到来自" + sender_name + "发送给" + message.getReceiver_name() + "的消息: " + message.getContent());  
        String text = new String(AESUtils.decrypt(message.getContent().getBytes("ISO-8859-1"), AesKey.getBytes()), "UTF-8");  
        log.info("用户" + receiver_name + "使用会话密钥进行解密");  
        message.setContent(text);  
        receiveMessageFromFriend(message.getReceiver_name(), message);  
    }  
});

6 演示

登录

![image-20230317212322357](https://img-
blog.csdnimg.cn/img_convert/1030cb49d98196e03a7a96db90d00fe3.png)

进入主页面

可以看到好友列表

![image-20230317212354873](https://img-
blog.csdnimg.cn/img_convert/98d2ed7a1a34864bbff8904a9dc2b703.png)

同时获取本地密钥库中的公私钥并将其加入公钥库

![image-20230317212408120](https://img-
blog.csdnimg.cn/img_convert/da92f58a6c2efea05bf3b2717159f585.png)

选择好友进行私聊

选择好友进行私聊,进入聊天界面。

![image-20230317212431510](https://img-
blog.csdnimg.cn/img_convert/b13a039b886964bb0c44e2ffec915c68.png)

![image-20230317212436916](https://img-
blog.csdnimg.cn/img_convert/6e0e046ab002e36be6243bac873a02d3.png)

发送消息

在输入框中输入消息,点击发送,接收者和发送者的聊天框都会出现相应的消息。此消息是经过后端AES对称加密解密得到的。

![image-20230317212509053](https://img-
blog.csdnimg.cn/img_convert/f4931d907ed8e40bfade68ef8550be98.png)

接下来我将给各位同学划分一张学习计划表!

学习计划

那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:

阶段一:初级网络安全工程师

接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。

综合薪资区间6k~15k

1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)

那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?

阶段二:中级or高级网络安全工程师(看自己能力)

综合薪资区间15k~30k

7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。

零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;

Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完

用Python编写漏洞的exp,然后写一个简单的网络爬虫

PHP基本语法学习并书写一个简单的博客系统

熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)

了解Bootstrap的布局或者CSS。

阶段三:顶级网络安全工程师

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

学习资料分享

当然,只给予计划不给予学习资料的行为无异于耍流氓,这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值