学习记录(密码&socket)【2019.9.10-9.12】

9.10

有关个人博客的反思

密码加密

学习博客:
https://blog.csdn.net/zhouyan8603/article/details/80473083
如何正确对用户密码进行加密?
https://blog.csdn.net/zl1zl2zl3/article/details/85331976
七种加密算法及代码实现

哈希算法是一种单向函数。它把任意数量的数据转换为固定长度的“指纹”,而且这个过程无法逆转。它们有这样的特性:如果输入发生了一点改变,由此产生的哈希值会完全不同。
这个特性很适合用来存储密码。因为我们需要一种不可逆的算法来加密存储的密码,同时保证我们也能够验证用户登陆的密码是否正确。

在基于哈希加密的帐号系统中,用户注册和认证的大致流程如下。
1.用户创建自己的帐号。

2.密码经过哈希加密后存储在数据库中。密码一旦写入到磁盘,任何时候都不允许是明文形式

3.当用户试图登录时,系统从数据库取出已经加密的密码,和经过哈希加密的用户输入的密码进行对比

4.如果哈希值相同,用户将被授予访问权限。否则,告知用户他们输入的登陆凭据无效。
每当有人试图尝试登陆,就重复步骤3和4。
在步骤4中,永远不要告诉用户输错的究竟是用户名还是密码。就像通用的提示那样,始终显示:“无效的用户名或密码。”就行了。防止攻击者在不知道密码的情况下枚举出有效的用户名


保护密码的最好办法是使用加盐密码哈希( salted password hashing)。


加盐( Adding Salt)

hash("hello")   = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1
hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab
hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007

我们可以通过“随机化”哈希,当同一个密码哈希两次后,得到的哈希值是不一样的,从而避免了这种攻击。

我们可以通过在密码中加入一段随机字符串再进行哈希加密,这个被加的字符串称之为盐值。我们需要盐值来校验密码是否正确。通常和密码哈希值一同存储在帐号数据库中,或者作为哈希字符串的一部分

最常见的错误:多次哈希加密使用相同的盐值,或者盐值太短。
解决:
1**.用户创建帐号或者更改密码时,都应该用新的随机盐值进行加密**。
2.尽量使用和哈希函数输出的字符串等长的盐值

碰撞攻击是指存在一个和用户密码不同的字符串,却有相同的哈希值。


盐值应该使用加密的安全伪随机数生成器( Cryptographically Secure Pseudo-Random Number Generator,CSPRNG )产生。CSPRNG和普通的伪随机数生成器有很大不同,如“ C ”语言的rand()函数。顾名思义, CSPRNG 被设计成用于加密安全,这意味着它能提供高度随机、完全不可预测的随机数。我们不希望盐值能够被预测到,所以必须使用 CSPRNG 。

表列出了一些当前主流编程平台的 CSPRNG 方法。
在这里插入图片描述
java的SecureRandom生成随机数
学习参考博客:https://blog.csdn.net/weixin_35703883/article/details/81138425

(1)指定算法名称

          仅指定算法名称:
  SecureRandom random= SecureRandom.getInstance("SHA1PRNG");
//系统将确定环境中是否有所请求的算法实现,是否有多个,是否有首选实现。

         既指定了算法名称又指定了包提供程序:
  SecureRandom random= SecureRandom.getInstance("SHA1PRNG","RUN");
 
//系统将确定在所请求的包中是否有算法实现;如果没有,则抛出异常。


(2)获取SecureRandom对象后,生成随机数

Integer randNum = random.nextInt();//生成10位数的随机数
Integer randNum = random.nextInt(100);//生成0~99的随机数


存储密码的步骤:
1.使用 CSPRNG 生成足够长的随机盐值。
2.将盐值混入密码,并使用标准的密码哈希函数进行加密,如Argon2、 bcrypt 、 scrypt 或 PBKDF2 。
3.将盐值和对应的哈希值一起存入用户数据库。


校验密码的步骤:
1.从数据库检索出用户的盐值和对应的哈希值。
2.将盐值混入用户输入的密码,并且使用通用的哈希函数进行加密。
3.比较上一步的结果,是否和数据库存储的哈希值相同。如果它们相同,则表明密码是正确的;否则,该密码错误。

使密码更难破解:慢哈希函数.这类算法采取安全因子或迭代次数作为参数。此值决定哈希函数将会如何缓慢。【未深入了解】

可以使用的哈希算法:

  • 精心设计的密钥扩展算法如 PBKDF2 、bcrypt 和 scrypt 。
  • OpenWall的的 Portable PHP password hashing framework。
  • PBKDF2在PHP、C#、Java和Ruby的实现。
  • crypt 的安全版本。

不可使用:

  • 快速加密哈希函数,如 MD5 、SHA1、SHA256、SHA512、RipeMD、WHIRLPOOL、SHA3等。
  • crypt()的不安全版本。
  • 任何自己设计的加密算法。


密码重置

大多数网站向那些忘记密码的用户发送电子邮件来进行身份认证。要做到这一点,需要随机生成一个一次性使用的令牌( token ),直接关联到用户的帐号。然后将这个令牌混入一个重置密码的链接中,发送到用户的电子邮箱。当用户点击包含有效令牌的密码重置链接,就提示他们输入新密码。确保令牌只对一个帐号有效,以防攻击者从邮箱获取到令牌后用来重置其他用户的密码。

令牌必须在15分钟内使用,且一旦使用后就立即作废。当用户登录成功时(表明还记得自己的密码), 或者重新请求令牌时,使原令牌失效是一个好做法。如果令牌永不过期,那么它就可以一直用于入侵用户的账号。电子邮件(SMTP)是一个纯文本协议,网络上有很多恶意路由在截取邮件信息。在用户修改密码后,那些包含重置密码链接的邮件在很长时间内缺乏保护,因此,尽早使令牌尽快过期,来降低用户信息暴露给攻击者的风险

攻击者能够篡改令牌,因此不要把帐号信息和失效时间存储在其中。它们应该以不可猜测的二进制形式存在,并且只用来识别数据库中某条用户的记录。

不要通过电子邮件向用户发送新密码。记得在用户重置密码时随机生成一个新的盐值用来加密,不要重复使用已用于密码哈希加密的旧盐值。

可以通过给数据库连接设置两种权限,防止密码哈希在遭遇注入攻击时被篡改。一种权限用于创建用户,一种权限用于用户登陆。
“创建用户”的代码应该能够读写用户表;但“用户登陆”的代码应该只能够读取用户表而不能写入。

验证密码

以固定时间比较哈希值

使用固定的时间来比较哈希值可以防止攻击者在在线系统使用基于时间差的攻击,以此获取密码的哈希值,然后进行本地破解。

慢比较函数

private static boolean slowEquals(byte[] a, byte[] b)
     {
         int diff = a.length ^ b.length;
         for(int i = 0; i < a.length && i < b.length; i++)
             diff |= a[i] ^ b[i];
         return diff == 0;
     }


关于从数据库注销账户信息

mysql数据库外键的使用,随主表同时更新或删除
【Navicat for MYSQL 添加外键很容易
参考:https://www.cnblogs.com/chechen/p/8308701.html】

CREATE TABLE stu(
    sid INT PRIMARY KEY,
    NAME VARCHAR(50) NOT NULL
);

-- 添加外键约束方式一
CREATE TABLE score1(
    score DOUBLE,
    sid INT,
    CONSTRAINT fk_stu_score1_sid FOREIGN KEY(sid) REFERENCES stu(sid)
);
-- 添加外键约束方式二(若表已存在,可用这种)
CREATE TABLE score1(
    score DOUBLE,
    sid INT
);
ALTER TABLE score1 ADD CONSTRAINT fk_sid FOREIGN KEY(sid) REFERENCES stu(sid)


9.11&9.12

socket的学习

学习博客:
https://blog.csdn.net/dahuang1016/article/details/80608561
Socket的理解和应用
https://blog.csdn.net/qq_30764991/article/details/82114742
socket详解与实例介绍
https://blog.csdn.net/tyt1002/article/details/79380326
Socket通讯简单实例
http://how2j.cn/k/socket/socket-ip-port/399.html



socket详解与实例介绍

TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
特点: 面向连接的协议,数据传输必须要建立连接,所以在TCP中需要连接时间。 传输数据大小限制,一旦连接建立,双方可以按统一的格式传输大的数据。 一个可靠的协议,确保接收方完全正确地获取发送方所发送的全部数据。

UDP:UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。
特点: 每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。 UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。 UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。

利用(ip地址,协议,端口)就可以标识网络的进程.

socket()函数

int socket(int domain, int type, int protocol);

domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。

protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

当protocol为0时,会自动选择type类型对应的默认协议。

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

bind()函数

bind()函数把一个地址族中的特定地址赋给socket。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是: 

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};
ipv6对应的是: 
struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};

addrlen:对应的是地址的长度。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

listen()、connect()函数

如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。


connect函数的第一个参数即为客户端的socket描述字第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

accept()函数

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。
TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。
TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数的第一个参数为服务器的socket描述字,*第二个参数为指向struct sockaddr 的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。


read()、write()、close()等函数

读写推荐recvmsg()/sendmsg()函数

他人实例代码学习

在这里插入图片描述
socketServer服务端的sockerServer.java代码分别如下:

package com.demo.server;
 
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
 
public class SocketServer {
	public static void main(String[] args) throws Exception {
		// 创建服务接口
		ServerSocket socket = new ServerSocket(8888);
		// 接收请求
		Socket a = socket.accept();
		// 调用服务端的业务逻辑
		String result = new PersonService().palyGames();
		// 获得输出流
		OutputStream out = a.getOutputStream();
		// 发送数据
		out.write(result.getBytes());
		// 关闭资源
		out.close();// 关闭输出流
		a.close();// 关闭接收
		socket.close();// 关闭服务
	}
}

personServer的业务代码如下:

package com.demo.server;
 
public class PersonService {
	public String palyGames() {
		return"努力学习,尽量不要玩游戏,容易玩物丧志!加油!!";
	}
}

sockerClient客户消费端代码如下:

package com.demo.client;
 
 
 
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
 
 
public class SocketClient {
	public static void main(String[] args) throws Exception {
		// 创建socket
		Socket s = new Socket("127.0.0.1", 8888);
		// 获得输入流
		java.io.InputStream in = s.getInputStream();
		//读取字节流
		BufferedReader reader = new BufferedReader(new InputStreamReader(in));
		String line = null;
		while ((line = reader.readLine()) != null) {
			System.out.println(line);
		}
		in.close();
		s.close();
	}
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值