3.1.5 套接字
套接字(Socket)是应用层和TCP/IP协议族之间的软件抽象层,它以一组应用编程接口(API)的形式将复杂的网络通信协议隐藏在简单的函数调用背后,使应用程序具备基于特定通信协议访问网络资源的能力。
TCP/IP提供了三种类型的套接字:
- 流式套接字(SOCK_STREAM)
- TCP协议
- 有连接,无丢失,无差错,无重复,无乱序
- 数据报套接字(SOCK_DGRAM)
- UDP协议
- 无连接,有丢失,有差错,有重复,有乱序
- 原始套接字(SOCK_RAW)
- IP或ICMP等较低层协议
- 实现自定义网络协议
- 对数据报文做较低层的控制
3.1.6 基本通信函数
Linux系统通过套接字的概念来进行网络编程。应用程序通过调用socket和其它几个函数可以获得被称为套接字的文件描述符。程序的设计者可以将其当做普通文件描述符来操作,象读写磁盘文件一样收发网络中的数据,实现网络中计算机之间的通信。这充分体现了Linux系统设备无关性的优点。如下图所示:
graph TB
subgraph File Programming
subgraph User Mode
fapp(File Application)
fd((File
Descriptor))
end
subgraph Kernel Mode
fs(File System)
disc{DISC}
end
fapp---fd
fd---fs
fs---disc
end
subgraph Network Programming
subgraph User Mode
napp(Network Application)
sockfd((Socket
Descriptor))
end
subgraph Kernel Mode
nps(Network Protocol Stack)
nic{NIC}
end
napp---sockfd
sockfd---nps
nps---nic
end
1. socket函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
该函数用于创建实现网络或本机通信的套接字,并返回该套接字的文件描述符,失败返回-1,并设置errno变量。如下图所示:
graph LR
subgraph Host A
socket_a((Socket))
end
参数说明:
- domain:通信所使用的协议族
- PF_UNIX:本机进程间的通信
- PF_INET:网络主机间的通信
- PF_PACKET:直接通过数据链路层或物理层实现网络通信
- type:套接字类型
- SOCK_STREAM:流式套接字,传输层使用TCP协议
- SOCK_DGRAM:数据报文式套接字,传输层使用UDP协议
- SOCK_RAW:原始套接字,跨越传输层实现网络通信
- protocol:通信协议
- 通常情况下,对于给定协议族(domain参数)只有单一协议支持特定套接字类型(type参数),此参数取0即可
2. bind函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数用于将套接字与指定端口绑定。成功返回0,失败返回-1,并设置errno变量。如下图所示:
graph LR
subgraph Host A
socket_a((Socket))
port_a((Port))
socket_a--bind---port_a
end
参数说明:
- sockfd:套接字文件描述符
- addr:描述绑定端口的地址结构
- addrlen:addr参数所指向结构的字节数
sockaddr是一个与协议无关的通用地址结构,相当于基类:
#include <sys/socket.h>
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
针对互联网通信,实际使用的是它的一个子类sockaddr_in:
#include <netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
其中in_addr结构中只有一个字段:
#include <netinet/in.h>
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
3. listen函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
该函数用于将套接字设置为侦听状态。成功返回0,失败返回-1,并设置errno变量。如下图所示:
graph LR
subgraph Host A
listening_socket_a((Listening
Socket))
port_a((Port))
listening_socket_a--bind---port_a
end
参数说明:
- sockdfd:套接字文件描述符
- backlog:未决连接队列最大长度
4. accept函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
该函数用于通过侦听套接字接受对方主机的连接请求。成功返回连接套接字,失败返回-1,并设置errno变量。如下图所示:
graph LR
host_b(Host b)
subgraph Host A
listening_socket_a((Listening
Socket))
connected_socket_a((Connected
Socket))
port_a((Port))
listening_socket_a--bind---port_a
connected_socket_a---port_a
listening_socket_a-.accept-.->connected_socket_a
end
port_a--connect---host_b
参数说明:
- sockfd:侦听套接字文件描述符
- addr:描述对方端口的地址结构
- addrlen:addr参数所指向结构的字节数
5. connect函数
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数用于向对方主机发起连接请求。成功返回0,失败返回-1,并设置errno变量。如下图所示:
graph LR
subgraph Host A
listening_socket_a((Listening
Socket))
connected_socket_a((Connected
Socket))
port_a((Port))
listening_socket_a--bind---port_a
connected_socket_a---port_a
listening_socket_a-.accept-.->connected_socket_a
end
subgraph Host B
port_b((Port))
socket_b((Socket))
port_b---socket_b
end
port_a--connect---port_b
参数说明:
- sockfd:套接字文件描述符
- addr:描述对方端口的地址结构
- addrlen:addr参数所指向结构的字节数
6. write函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
该函数用于通过已连接套接字向对方主机发送数据。成功返回实际发送的字节数,失败返回-1,并设置errno变量。如下图所示:
graph LR
subgraph Host A
listening_socket_a((Listening
Socket))
connected_socket_a((Connected
Socket))
port_a((Port))
data_a((Data))
listening_socket_a--bind---port_a
connected_socket_a-->port_a
listening_socket_a-.accept-.->connected_socket_a
data_a--write-->connected_socket_a
end
subgraph Host B
port_b((Port))
socket_b((Socket))
port_b-->socket_b
end
port_a--connect-->port_b
参数说明:
- fd:已建立连接的套接字文件描述符
- buf:发送数据缓冲区
- count:期望发送的字节数
7. read函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
该函数用于通过已连接套接字从对方主机接收数据。成功返回实际接收的字节数,失败返回-1,并设置errno变量。如下图所示:
graph LR
subgraph Host A
listening_socket_a((Listening
Socket))
connected_socket_a((Connected
Socket))
port_a((Port))
data_a((Data))
listening_socket_a--bind---port_a
connected_socket_a-->port_a
listening_socket_a-.accept-.->connected_socket_a
data_a--write-->connected_socket_a
end
subgraph Host B
port_b((Port))
socket_b((Socket))
data_b((Data))
port_b-->socket_b
socket_b--read-->data_b
end
port_a--connect-->port_b
参数说明:
- fd:已建立连接的套接字文件描述符
- buf:接收数据缓冲区
- count:期望接收的字节数
8. send函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
该函数用于通过已连接套接字向对方主机发送数据。成功返回实际发送的字节数,失败返回-1,并设置errno变量。如下图所示:
graph LR
subgraph Host A
listening_socket_a((Listening
Socket))
connected_socket_a((Connected
Socket))
port_a((Port))
data_a((Data))
listening_socket_a--bind---port_a
connected_socket_a-->port_a
listening_socket_a-.accept-.->connected_socket_a
data_a--send-->connected_socket_a
end
subgraph Host B
port_b((Port))
socket_b((Socket))
port_b-->socket_b
end
port_a--connect-->port_b
参数说明:
- sockfd:已建立连接的套接字文件描述符
- buf:发送数据缓冲区
- len:期望发送的字节数
- flags:发送标志,取0等价于write函数
9. recv函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
该函数用于通过已连接套接字从对方主机接收数据。成功返回实际接收的字节数,失败返回-1,并设置errno变量。如下图所示:
graph LR
subgraph Host A
listening_socket_a((Listening
Socket))
connected_socket_a((Connected
Socket))
port_a((Port))
data_a((Data))
listening_socket_a--bind---port_a
connected_socket_a-->port_a
listening_socket_a-.accept-.->connected_socket_a
data_a--send-->connected_socket_a
end
subgraph Host B
port_b((Port))
socket_b((Socket))
data_b((Data))
port_b-->socket_b
socket_b--recv-->data_b
end
port_a--connect-->port_b
参数说明:
- sockfd:已建立连接的套接字文件描述符
- buf:接收数据缓冲区
- len:期望接收的字节数
- flags:接收标志,取0等价于read函数
10. close函数
#include <unistd.h>
int close(int fd);
该函数用于关闭套接字及与之相关的网络连接。成功返回0,失败返回-1,并设置errno变量。
参数说明:
3.2 实训案例
3.2.1 基于DES加密的TCP聊天
在了解DES算法原理的基础上,编程实现对字符串的DES加密、解密操作。
在了解Linux操作系统中TCP套接字工作原理的基础上,编程实现简单的TCP通信。简化编程细节,不要求实现一对多通信。
将以上两部分结合到一起,编程实现对通信内容做DES加解密的TCP聊天程序:
- 通信双方事先约定密钥
- 发送方通过该密钥加密
- 接收方通过该密钥解密
- 网络上传输的都是密文
3.2.2 程序清单
1. 声明Des类
#pragma once
#include <stdint.h>
class Des {
public:
Des(void);
uint64_t generateKey(void) const;
int encrypt(uint64_t key, const void* mbuf, size_t mlen,
void* cbuf, size_t* clen) const;
int decrypt(uint64_t key, const void* cbuf, size_t clen,
void* mbuf, size_t* mlen) const;
private:
int verifyKey(uint64_t K0) const;
void PC1(uint64_t K0, uint32_t* C, uint32_t* D) const;
void LS(int iKey, uint32_t* C, uint32_t* D) const;
uint64_t PC2(uint32_t C, uint32_t D) const;
void generateRoundKeys(uint64_t K0, uint64_t* roundKeys) const;
uint64_t E(uint32_t R) const;
uint64_t X(uint64_t R, uint64_t K) const;
uint32_t S(uint64_t R) const;
uint32_t P(uint32_t R) const;
uint32_t F(uint32_t R, uint64_t K) const;
void IP(uint64_t M, uint32_t* L, uint32_t* R) const;
void R16(const uint64_t* roundKeys, uint32_t* L, uint32_t* R) const;
uint64_t IIP(uint32_t L, uint32_t R) const;
uint64_t M2C(const uint64_t* roundKeys, uint64_t M) const;
uint64_t C2M(const uint64_t* roundKeys, uint64_t C) const;
static const uint8_t pc1[2][28];
static const uint8_t ls[16];
static const uint8_t pc2[48];
static const uint8_t ip[64];
static const uint8_t e[48];
static const uint8_t s[8][64];
static const uint8_t p[32];
static const uint8_t iip[64];
uint32_t bm32[32];
uint64_t bm64[64];
};
2. 实现Des类
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include "des.h"
const uint8_t Des::pc1[2][28] = {
{
57, 49, 41, 33, 25, 17, 9, 1,
58, 50, 42, 34, 26, 18, 10, 2,
59, 51, 43, 35, 27, 19, 11, 3,
60, 52, 44, 36},
{
63, 55, 47, 39, 31, 23, 15, 7,
62, 54, 46, 38, 30, 22, 14, 6,
61, 53, 45, 37, 29, 21, 13, 5,
28, 20, 12, 4}};
const uint8_t Des::ls[16] = {
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};
const uint8_t Des::pc2[48] = {
14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32};
const uint8_t Des::ip[64] = {
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7};
const uint8_t Des::e[48] = {
32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1};
const uint8_t Des::s[8][64] = {
{
0xe, 0x0, 0x4, 0xf, 0xd, 0x7, 0x1, 0x4,
0x2, 0xe, 0xf, 0x2, 0xb, 0xd, 0x8, 0x1,
0x3, 0xa, 0xa, 0x6, 0x6, 0xc, 0xc, 0xb,
0x5, 0x9, 0x9, 0x5, 0x0, 0x3, 0x7, 0x8,
0x4, 0xf, 0x1, 0xc, 0xe, 0x8, 0x8, 0x2,
0xd, 0x4, 0x6, 0x9, 0x2, 0x1, 0xb, 0x7,
0xf, 0x5, 0xc, 0xb, 0x9, 0x3, 0x7, 0xe,
0x3, 0xa, 0xa, 0x0, 0x5, 0x6, 0x0, 0xd},
{
0xf, 0x3, 0x1, 0xd, 0x8, 0x4, 0xe, 0x7,
0x6, 0xf, 0xb, 0x2, 0x3, 0x8, 0x4, 0xf,
0x9, 0xc, 0x7, 0x0, 0x2, 0x1, 0xd, 0xa,
0xc, 0x6, 0x0, 0x9, 0x5, 0xb, 0xa, 0x5,
0x0, 0xd, 0xe, 0x8, 0x7, 0xa, 0xb, 0x1,
0xa, 0x3, 0x4, 0xf, 0xd, 0x4, 0x1, 0x2,
0x5, 0xb, 0x8, 0x6, 0xc, 0x7, 0x6, 0xc,
0x9, 0x0, 0x3, 0x5, 0x2, 0xe, 0xf, 0x9},
{
0xa, 0xd, 0x0, 0x7, 0x9, 0x0, 0xe, 0x9,
0x6, 0x3, 0x3, 0x4, 0xf, 0x6, 0x5, 0xa,
0x1, 0x2, 0xd, 0x8, 0xc, 0x5, 0x7, 0xe,
0xb, 0xc, 0x4, 0xb, 0x2, 0xf, 0x8, 0x1,
0xd, 0x1, 0x6, 0xa, 0x4, 0xd, 0x9, 0x0,
0x8, 0x6, 0xf, 0x9, 0x3, 0x8, 0x0, 0x7,
0xb, 0x4, 0x1, 0xf, 0x2, 0xe, 0xc, 0x3,
0x5, 0xb, 0xa, 0x5, 0xe, 0x2, 0x7, 0xc},
{
0x7, 0xd, 0xd, 0x8, 0xe, 0xb, 0x3, 0x5,
0x0, 0x6, 0x6, 0xf, 0x9, 0x0, 0xa, 0x3,
0x1, 0x4, 0x2, 0x7, 0x8, 0x2, 0x5, 0xc,
0xb, 0x1, 0xc, 0xa, 0x4, 0xe, 0xf, 0x9,
0xa, 0x3, 0x6, 0xf, 0x9, 0x0, 0x0, 0x6,
0xc, 0xa, 0xb, 0xa, 0x7, 0xd, 0xd, 0x8,
0xf, 0x9, 0x1, 0x4, 0x3, 0x5, 0xe, 0xb,
0x5, 0xc, 0x2, 0x7, 0x8, 0x2, 0x4, 0xe},
{
0x2, 0xe, 0xc, 0xb, 0x4, 0x2, 0x1, 0xc,
0x7, 0x4, 0xa, 0x7, 0xb, 0xd, 0x6, 0x1,
0x8, 0x5, 0x5, 0x0, 0x3, 0xf, 0xf, 0xa,
0xd, 0x3, 0x0, 0x9, 0xe, 0x8, 0x9, 0x6,
0x4, 0xb, 0x2, 0x8, 0x1, 0xc, 0xb, 0x7,
0xa, 0x1, 0xd, 0xe, 0x7, 0x2, 0x8, 0xd,
0xf, 0x6, 0x9, 0xf, 0xc, 0x0, 0x5, 0x9,
0x6, 0xa, 0x3, 0x4, 0x0, 0x5, 0xe, 0x3},
{
0xc, 0xa, 0x1, 0xf, 0xa, 0x4, 0xf, 0x2,
0x9, 0x7, 0x2, 0xc, 0x6, 0x9, 0x8, 0x5,
0x0, 0x6, 0xd, 0x1, 0x3, 0xd, 0x4, 0xe,
0xe, 0x0, 0x7, 0xb, 0x5, 0x3, 0xb, 0x8,
0x9, 0x4, 0xe, 0x3, 0xf, 0x2, 0x5, 0xc,
0x2, 0x9, 0x8, 0x5, 0xc, 0xf, 0x3, 0xa,
0x7, 0xb, 0x0, 0xe, 0x4, 0x1, 0xa, 0x7,
0x1, 0x6, 0xd, 0x0, 0xb, 0x8, 0x6, 0xd},
{
0x4, 0xd, 0xb, 0x0, 0x2, 0xb, 0xe, 0x7,
0xf, 0x4, 0x0, 0x9, 0x8, 0x1, 0xd, 0xa,
0x3, 0xe, 0xc, 0x3, 0x9, 0x5, 0x7, 0xc,
0x5, 0x2, 0xa, 0xf, 0x6, 0x8, 0x1, 0x6,
0x1, 0x6, 0x4, 0xb, 0xb, 0xd, 0xd, 0x8,
0xc, 0x1, 0x3, 0x4, 0x7, 0xa, 0xe, 0x7,
0xa, 0x9, 0xf, 0x5, 0x6, 0x0, 0x8, 0xf,
0x0, 0xe, 0x5, 0x2, 0x9, 0x3, 0x2, 0xc},
{
0xd, 0x1, 0x2, 0xf, 0x8, 0xd, 0x4, 0x8,
0x6, 0xa, 0xf, 0x3, 0xb, 0x7, 0x1, 0x4,
0xa, 0xc, 0x9, 0x5, 0x3, 0x6, 0xe, 0xb,
0x5, 0x0, 0x0, 0xe, 0xc, 0x9, 0x7, 0x2,
0x7, 0x2, 0xb, 0x1, 0x4, 0xe, 0x1, 0x7,
0x9, 0x4, 0xc, 0xa, 0xe, 0x8, 0x2, 0xd,
0x0, 0xf, 0x6, 0xc, 0xa, 0x9, 0xd, 0x0,
0xf, 0x3, 0x3, 0x5, 0x5, 0x6, 0x8, 0xb}};
const uint8_t Des::p[32] = {
16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25};
const uint8_t Des::iip[64] = {
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25};
Des::Des(void) {
bm32[31] = 1;
for (int i = 30; i >= 0; --i)
bm32[i] = bm32[i+1] << 1;
bm64[63] = 1;
for (int i = 62; i >= 0; --i)
bm64[i] = bm64[i+1] << 1;
}
uint64_t Des::generateKey(void) const {
srand (time (NULL));
uint64_t K0 = 0;
int checksum = 0;
for (int i = 0; i < 64; ++i)
if ((i + 1) % 8) {
if (rand () & 1) {
K0 |= bm64[i];
++checksum;
}
}
else {
if (! checksum & 1)
K0 |= bm64[i];
checksum = 0;
}
return K0;
}
int Des::encrypt(uint64_t key, const void* mbuf, size_t mlen,
void* cbuf, size_t* clen) const {
size_t nlen = (mlen / 8 + (mlen % 8 != 0)) * 8;
if (*clen < nlen) {
*clen = nlen;
return -1;
}
*clen = nlen;
if (verifyKey(key) == -1)
return -2;
uint64_t roundKeys[16];
generateRoundKeys(key, roundKeys);
const uint64_t* M = (const uint64_t*)mbuf;
uint64_t* C = (uint64_t*)cbuf;
while (mlen) {
if (mlen >= 8)
*C = M2C(roundKeys, *M);
else {
uint8_t tail[8] = {0};
memcpy(tail, M, mlen);
tail[7] = 8 - mlen;
*C = M2C(roundKeys, *(uint64_t*)tail);
break;
}
++C;
++M;
mlen -= 8;
}
return 0;
}
int Des::decrypt(uint64_t key, const void* cbuf, size_t clen,
void* mbuf, size_t* mlen) const {
if (*mlen < clen) {
*mlen = clen;
return -1;
}
*mlen = clen;
if (verifyKey(key) == -1)
return -2;
uint64_t roundKeys[16];
generateRoundKeys(key, roundKeys);
const uint64_t* C = (const uint64_t*)cbuf;
uint64_t* M = (uint64_t*)mbuf;
while (clen) {
*M = C2M(roundKeys, *C);
if (clen == 8) {
uint8_t tail[8];
memcpy(tail, M, 8);
if (tail[7] < 8) {
size_t i;
for (i = 8 - tail[7]; i < 7 && ! tail[i]; ++i);
if (i == 7)
*mlen -= tail[7];
}
}
++M;
++C;
clen -= 8;
}
return 0;
}
int Des::verifyKey(uint64_t K0) const {
int checksum = 0;
for (int i = 0; i < 64; ++i) {
if (K0 & bm64[i])
++checksum;
if ((i + 1) % 8 == 0) {
if (! checksum & 1)
return -1;
checksum = 0;
}
}
return 0;
}
void Des::PC1(uint64_t K0, uint32_t* C, uint32_t* D) const {
*C = *D = 0;
for (int i = 0; i < 28; ++i) {
if (K0 & bm64[pc1[0][i]-1])
*C |= bm32[i];
if (K0 & bm64[pc1[1][i]-1])
*D |= bm32[i];
}
}
void Des::LS(int iKey, uint32_t* C, uint32_t* D) const {
uint8_t N = ls[iKey];
uint32_t lm = 0;
for (int i = 0; i < N; ++i)
lm |= bm32[i];
*C = *C << N | (*C & lm) >> (28 - N);
*D = *D << N | (*D & lm) >> (28 - N);
}
uint64_t Des::PC2(uint32_t C, uint32_t D) const {
uint64_t CD = C;
CD = (CD << 28 | D) << 4;
uint64_t K = 0;
for (int i = 0; i < 48; ++i)
if (CD & bm64[pc2[i]-1])
K |= bm64[i];
return K;
}
void Des::generateRoundKeys(uint64_t K0, uint64_t* roundKeys) const {
uint32_t C, D;
PC1(K0, &C, &D);
for (int iKey = 0; iKey < 16; ++iKey) {
LS(iKey, &C, &D);
roundKeys[iKey] = PC2(C, D);
}
}
uint64_t Des::E(uint32_t R) const {
uint64_t _R = 0;
for (int i = 0; i < 48; ++i)
if (R & bm32[e[i]-1])
_R |= bm64[i];
return _R;
}
uint64_t Des::X(uint64_t R, uint64_t K) const {
return R ^ K;
}
uint32_t Des::S(uint64_t R) const {
uint64_t gm = 63;
uint32_t _R = 0;
for (int i = 0; i < 8; ++i)
_R |= s[i][(R & gm << (64 - (i + 1) * 6)) >> (64 - (i + 1) * 6)]
<< (32 - (i + 1) * 4);
return _R;
}
uint32_t Des::P(uint32_t R) const {
uint32_t _R = 0;
for (int i = 0; i < 32; ++i)
if (R & bm32[p[i]-1])
_R |= bm32[i];
return _R;
}
uint32_t Des::F(uint32_t R, uint64_t K) const {
return P(S(X(E(R), K)));
}
void Des::IP(uint64_t M, uint32_t* L, uint32_t* R) const {
uint64_t _M = 0;
for (int i = 0; i < 64; ++i)
if (M & bm64[ip[i]-1])
_M |= bm64[i];
*L = _M >> 32;
*R = _M;
}
void Des::R16(const uint64_t* roundKeys, uint32_t* L, uint32_t* R) const {
for (int i = 0; i < 16; ++i) {
if (i < 15) {
uint32_t _L = *L;
*L = *R;
*R = _L ^ F(*R, roundKeys[i]);
}
else
*L = *L ^ F(*R, roundKeys[i]);
}
}
uint64_t Des::IIP(uint32_t L, uint32_t R) const {
uint64_t LR = L;
LR = LR << 32 | R;
uint64_t C = 0;
for (int i = 0; i < 64; ++i)
if (LR & bm64[iip[i]-1])
C |= bm64[i];
return C;
}
uint64_t Des::M2C(const uint64_t* roundKeys, uint64_t M) const {
uint32_t L, R;
IP(M, &L, &R);
R16(roundKeys, &L, &R);
return IIP(L, R);
}
uint64_t Des::C2M(const uint64_t* roundKeys, uint64_t C) const {
uint64_t inverseKeys[16];
for (int i = 0; i < 16; ++i)
inverseKeys[i] = roundKeys[15-i];
uint32_t L, R;
IP(C, &L, &R);
R16(inverseKeys, &L, &R);
return IIP(L, R);
}
3. 测试Des类
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <iomanip>
using namespace std;
#include "des.h"
int main(void) {
Des des;
uint64_t key = des.generateKey();
cout << hex << setfill('0') << setw(16) << key << endl;
char mbuf[1024] = "Give back to Ceasar what is "
"Ceasar's and to God what is God's";
uint8_t cbuf[1024];
size_t mlen = strlen(mbuf), clen;
clen = 8;
cout << dec << des.encrypt(key, mbuf, mlen, cbuf, &clen) << endl;
cout << clen << endl;
cout << des.encrypt(key, mbuf, mlen, cbuf, &clen) << endl;
cout << hex;
for (size_t i = 0; i < clen; ++i)
cout << setw(2) << (unsigned int)cbuf[i];
cout << endl;
mlen = 8;
cout << dec << des.decrypt(key, cbuf, clen, mbuf, &mlen) << endl;
cout << mlen << endl;
cout << des.decrypt(key, cbuf, clen, mbuf, &mlen) << endl;
cout << mlen << endl;
mbuf[mlen] = '\0';
cout << mbuf << endl;
return EXIT_SUCCESS;
}
4. 测试Des类构建脚本
PROJ = des_test
OBJS = des_test.o des.o
CXX = g++
LINK = g++
RM = rm -rf
CFLAGS = -c -g -Wall -I.
$(PROJ): $(OBJS)
$(LINK) $^ -o $@
.cpp.o:
$(CXX) $(CFLAGS) $^
clean:
$(RM) $(PROJ) $(OBJS)
5. 声明SecChat类
#pragma once
#include <string>
using namespace std;
#include "des.h"
class SecChat {
public:
SecChat(const char* port, const char* ip = "");
int accept(void);
int connect(void);
int chat(void) const;
protected:
int send(void) const;
int recv(void) const;
int sockfd;
const Des des;
uint64_t key;
private:
static void sigTerm (int sigNum);
virtual int sendKey(void);
virtual int recvKey(void);
static const size_t MAX_MESSAGE;
const string port;
const string ip;
};
6. 实现SecChat类
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
using namespace std;
#include "secchat.h"
const size_t SecChat::MAX_MESSAGE = 1024;
SecChat::SecChat(const char* port,
const char* ip ) : port(port), ip(ip) {
signal (SIGTERM, sigTerm);
}
int SecChat::accept(void) {
int sockls = socket(PF_INET, SOCK_STREAM, 0);
if (sockls == -1) {
perror("socket");
return -1;
}
int on = 1;
if (setsockopt(sockls, SOL_SOCKET, SO_REUSEADDR, &on,
sizeof(on)) == -1) {
perror("setsockopt");
close(sockls);
return -1;
}
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
bzero(&addr, addrlen);
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(port.c_str()));
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockls, (struct sockaddr*)&addr, addrlen) == -1) {
perror ("bind");
close(sockls);
return -1;
}
if (listen(sockls, 1024) == -1) {
perror("listen");
close(sockls);
return -1;
}
if ((sockfd = ::accept(sockls, (struct sockaddr*)&addr,
&addrlen)) == -1) {
perror("accept");
close(sockls);
return -1;
}
if (sendKey() == -1){
close(sockfd);
close(sockls);
return -1;
}
close(sockls);
return 0;
}
int SecChat::connect(void) {
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return -1;
}
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
bzero(&addr, addrlen);
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(port.c_str()));
addr.sin_addr.s_addr = inet_addr(ip.c_str());
if (::connect(sockfd, (struct sockaddr*)&addr, addrlen) == -1) {
perror("connect");
close(sockfd);
return -1;
}
if (recvKey() == -1) {
close(sockfd);
return -1;
}
return 0;
}
int SecChat::chat(void) const {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
close(sockfd);
return -1;
}
if (pid) {
for (;;)
if (send() == -1) {
close(sockfd);
kill(pid, SIGTERM);
return -1;
}
}
else {
for (;;)
if (recv() == -1) {
close(sockfd);
kill(getppid(), SIGTERM);
return -1;
}
}
close(sockfd);
return 0;
}
int SecChat::send(void) const {
char mbuf[MAX_MESSAGE+1];
fgets (mbuf, sizeof(mbuf), stdin);
ssize_t mlen = strlen(mbuf);
if (mbuf[mlen-1] == '\n')
mbuf[--mlen] = '\0';
if (! strcmp(mbuf, "!"))
return -1;
uint8_t cbuf[MAX_MESSAGE];
size_t clen = sizeof(cbuf);
if (des.encrypt(key, mbuf, mlen, cbuf, &clen) < 0) {
cerr << "Unable to encrypt content" << endl;
return -1;
}
if (::send(sockfd, cbuf, clen, 0) != (ssize_t)clen) {
perror("send");
return -1;
}
return 0;
}
int SecChat::recv(void) const {
uint8_t cbuf[MAX_MESSAGE];
ssize_t clen = ::recv(sockfd, cbuf, sizeof(cbuf), 0);
if (clen == -1) {
perror("recv");
return -1;
}
if (! clen) {
cerr << "recv: " << "Connection break" << endl;
return -1;
}
char mbuf[MAX_MESSAGE+1];
size_t mlen = sizeof(mbuf) - 1;
if (des.decrypt(key, cbuf, clen, mbuf, &mlen) < 0) {
cerr << "Unable to decrypt content" << endl;
return -1;
}
mbuf[mlen] = '\0';
cout << "> " << mbuf << endl;
return 0;
}
void SecChat::sigTerm (int sigNum) {
exit(EXIT_SUCCESS);
}
int SecChat::sendKey(void) {
key = des.generateKey();
if (::send(sockfd, &key, sizeof(key), 0) != sizeof(key)) {
perror("send");
return -1;
}
return 0;
}
int SecChat::recvKey(void) {
ssize_t len = ::recv(sockfd, &key, sizeof(key), 0);
if (len == -1) {
perror("recv");
return -1;
}
if (! len) {
cerr << "recv: " << "Connection break" << endl;
return -1;
}
return 0;
}
7. 测试SecChat类
#include <stdlib.h>
#include <string.h>
#include <iostream>
using namespace std;
#include "secchat.h"
int main(int argc, char* argv[]) {
if (argc < 3)
goto escape;
if (! strcmp(argv[1], "-s")) {
SecChat server(argv[2]);
if (server.accept() == -1)
return EXIT_FAILURE;
if (server.chat() == -1)
return EXIT_FAILURE;
}
else if (! strcmp(argv[1], "-c")) {
if (argc < 4)
goto escape;
SecChat client(argv[3], argv[2]);
if (client.connect() == -1)
return EXIT_FAILURE;
if (client.chat() == -1)
return EXIT_FAILURE;
}
else
goto escape;
return EXIT_SUCCESS;
escape:
cerr << "Usage: " << argv[0] << " -s <port>" << endl;
cerr << "Usage: " << argv[0] << " -c <ip> <port>" << endl;
return EXIT_FAILURE;
}
8. 测试SecChat类构建脚本
PROJ = secchat_test
OBJS = secchat_test.o secchat.o des.o
CXX = g++
LINK = g++
RM = rm -rf
CFLAGS = -c -g -Wall -I.
$(PROJ): $(OBJS)
$(LINK) $^ -o $@
.cpp.o:
$(CXX) $(CFLAGS) $^
clean:
$(RM) $(PROJ) $(OBJS)
3.3 扩展提高
3.3.1 DES算法的安全性
1. DES算法的安全性缺陷
DES算法的安全性缺陷主要集中在以下三个方面:
- 64位加密分组仅占8个字节,对于数据传输而言实在太小
- 64位初始密钥(实际56位,另8位用于奇偶校验)过于短小,16轮子密钥由递推产生,这都为针对密钥的暴力破解提供了可能
- 不能对抗差分和线性密码分析
2. 多重DES算法
多重DES算法,即用多个不同的密钥连续多次对一组明文进行加密。其中最常用的是三重DES算法:
- 先用64位(含8个奇偶校验位)密钥K1对明文进行加密
- 再用64位(含8个奇偶校验位)密钥K2对上一步加密的结果进行加密
- 最后用密钥K1对上一步加密的结果再加一次密
如下图所示:
graph LR
k1{K1}
m((M))
des1(DES)
k2{K2}
c1((C1))
des2(DES)
k3{K1}
c2((C2))
des3(DES)
c3((C3))
k1-->des1
m-->des1
des1-->c1
k2-->des2
c1-->des2
des2-->c2
k3-->des3
c2-->des3
des3-->c3
三重DES算法的密钥(K1K2)总长度为128位(含16个奇偶校验位),在可以预见的未来被认为是合适的、安全的,截至目前还没有找到针对此方案的攻击方法。但是三重DES算法的耗时是普通DES算法的三倍,时间开销比较大。
3. 密钥管理
密钥管理的两大核心任务:
- 设置密钥更新周期
- 密钥长度
- 被保护信息的敏感程度
- 系统环境的安全状况
- 必要的安全冗余
- 归档废弃密钥备用
3.3.2 AES算法
由于DES及其改进算法的安全强度已经越来越难以满足新的安全需求,尤其是在对抗20世纪末出现的差分和线性密码分析方法时,更加显得不堪一击。美国政府从1997年开始公开征集新的数据加密标准(Advanced Encryption Standard, AES)以取代DES算法。经过三轮筛选,最后选中比利时密码学家Joan Daemen和Vincent Rijmen提出的密码算法Rijndael作为AES正式取代DES。
1. AES算法描述
AES算法的简单描述如下:
- 将16字节(128位)明文每4字节一列排成方阵
- 将16字节(128位)初始密钥每4字节一行排成方阵
- 将初始密钥扩展为11个子密钥方阵
- 迭代11轮,每轮使用一个子密钥
- 按行重新组合成16字节(128位)密文
也可以将字节置换、行移位和列混淆,合并为4张加密置换表,用查表的方式实现每一轮迭代。
2. AES算法与DES算法的比较
AES算法与DES算法的时间比较,如下表所示:
算法 |
加密(秒) |
解密(秒) |
DES |
18 |
18 |
AES |
6 |
10 |
AES算法与DES算法的效率比较,如下表所示:
算法 |
加密(M位/秒) |
解密(M位/秒) |
DES |
1.676 |
1.676 |
AES |
5.027 |
3.016 |
两种算法相比:
- 相比DES算法使用64位分组,AES算法使用128位分组,加密解密效率更高
- 相比DES算法使用64位密钥,AES算法使用128位密钥,暴力破解难度更大
send/recv函数提供了与write/read函数类似的功能,不同之处在于前者增加了第四个参数flags作为发送/接收标志:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
flags可以是0或以下标志的组合:
- MSG_DONTROUTE:仅用于send函数,意在告诉IP协议,目的主机就在本地网络中,不必查找路由表,通常用在网络诊断或路由程序中
- MSG_OOB:收发带外(Out Of Band, OOB)数据。带外数据是TCP传输中独立且享有更高优先级的数据通道,它们可以独立于普通数据传送给用户
- MSG_PEEK:仅用于recv函数,从系统缓冲区中读取内容,但并不从系统缓冲区中清除该内容
- MSG_WAITALL:仅用于recv函数,等所有信息都到达时才返回。使用该标志的recv函数会一直阻塞,直到接收完期望接收的字节数或者发生错误。这时recv函数的返回值会有三种情况:
- 接收到期望接收的字节数,返回值等于其第三个参数len
- 等待过程中对方关闭了连接,即读到文件结尾,返回值小于len甚至可能是0
- 发生错误,返回-1,同时设置errno变量
当flags为0时,send/recv函数与write/read函数完全等价。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
TCP连接是全双工的,可以同时读写一个流式套接字。对该套接字调用close函数,会将其读写通道同时关闭,而如果调用shutdown函数,则可通过其how参数指定不同的关闭方式:
- 0:只关闭读通道,该套接字不可读取,但仍可继续写入
- 1:只关闭写通道,该套接字不可写入,但仍可继续读取
- 2:读写通道都关,等价于close函数
达内集团◇C++教研部◇闵卫