20.5 OpenSSL socket RSA encrypted transmission

The RSA algorithm can also be used for encrypted transmission, but although this type of encryption algorithm is very secure, it is usually not used for large amounts of data transmission. This is because the encryption and decryption process of the RSA algorithm involves a large number of mathematical operations. Especially modular exponentiation (that is, calculating the modular exponentiation of large numbers), these operations are very time-consuming for computers.

Secondly, in the RSA algorithm, the length of the encrypted data cannot exceed the key length minus a certain padding length. Generally speaking, when the RSA key length is 1024 bits, the encryption length can be 128 bytes, and when the key length is 2048 bits , the encrypted length is 245 bytes; when the key length is 3072 bits, the encrypted length is 371 bytes. Therefore, if the length of the data that needs to be encrypted exceeds the allowed range of the key length, segmented encryption can be used. We can cut the packet into 128 characters each, so that we can transmit a large number of strings in a loop.

20.5.1 Encryption and decryption algorithm encapsulation

In the previous chapters, we used the command line to manually generate the key pair file. In fact, in OpenSSL we can use the function provided by SDK to automatically generate the corresponding key pair file. Encryption key pair file, in the following code, CreateRSAPEM is a function that generates a key pair. Pass a public key, private key, and data length to the function to get two RSA files.

#include <iostream>
#include <string>
#include <Windows.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>

extern "C"
{<!-- -->
#include <openssl/applink.c>
}

#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")

// Generate RSA public and private key files
BOOL CreateRSAPEM(char *PublicKey, char *PrivateKey, int KeySize)
{<!-- -->
  BIO* bpub, *bpri;

  //Create new key pair files separately
  bpub = BIO_new_file(PublicKey, "w");
  bpri = BIO_new_file(PrivateKey, "w");

  if (!bpub || !bpri)
  {<!-- -->
    return FALSE;
  }

  RSA* pRSA = 0;

  // Generate a key pair KeySize and specify the key pair length 1024
  pRSA = RSA_generate_key(KeySize, RSA_F4, NULL, NULL);
  if (pRSA != NULL)
  {<!-- -->
    // write the public key
    if (!PEM_write_bio_RSAPublicKey(bpub, pRSA))
    {<!-- -->
      return FALSE;
    }
    //Write the private key
    if (!PEM_write_bio_RSAPrivateKey(bpri, pRSA, NULL, NULL, 0, NULL, NULL))
    {<!-- -->
      return FALSE;
    }
  }
  if(bpub)
  {<!-- -->
    BIO_free(bpub);
  }
  if(bpri)
  {<!-- -->
    BIO_free(bpri);
  }
  if(pRSA)
  {<!-- -->
    RSA_free(pRSA);
  }
  return TRUE;
}

int main(int argc, char* argv[])
{<!-- -->
  // Generate public and private keys
  BOOL flag = CreateRSAPEM("public.rsa","private.rsa",1024);

  if (flag == TRUE)
  {<!-- -->
    printf("[*] Key pair generated\
");
  }

  system("pause");
  return 0;
}

After the code is run, two files, public.rsa public key and private.rsa private key, will be generated in the current directory, as shown in the figure below;

Next is the encapsulation implementation of the encryption and decryption functions. In order to better realize network transmission, the following are the four encapsulated functions, among which public_rsa_encrypt is used to encrypt strings using the public key, The private_rsa_decrypt function uses the private key to decrypt the string, private_rsa_encrypt uses the private key to encrypt, and public_rsa_decrypt uses the public key to decrypt. Readers can choose different ones according to their actual needs. encryption and decryption functions.

//Use public key encryption
BOOL public_rsa_encrypt(char* in, char* key_path, char* out)
{<!-- -->
  RSA* p_rsa;
  FILE* file;
  int rsa_len;
  if ((file = fopen(key_path, "r")) == NULL)
  {<!-- -->
    return FALSE;
  }
  if ((p_rsa = PEM_read_RSAPublicKey(file, NULL, NULL, NULL)) == NULL)
  {<!-- -->
    ERR_print_errors_fp(stdout);
    return FALSE;
  }
  rsa_len = RSA_size(p_rsa);
  if (RSA_public_encrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0)
  {<!-- -->
    return FALSE;
  }
  RSA_free(p_rsa);
  fclose(file);
  return TRUE;
}

// Decrypt using private key
BOOL private_rsa_decrypt(char* in, char* key_path, char* out)
{<!-- -->
  RSA* p_rsa;
  FILE* file;
  int rsa_len;
  if ((file = fopen(key_path, "r")) == NULL)
  {<!-- -->
    return FALSE;
  }
  if ((p_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL)) == NULL)
  {<!-- -->
    ERR_print_errors_fp(stdout);
    return FALSE;
  }
  rsa_len = RSA_size(p_rsa);
  if (RSA_private_decrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0)
  {<!-- -->
    return FALSE;
  }
  RSA_free(p_rsa);
  fclose(file);
  return TRUE;
}

// Encrypt using private key
BOOL private_rsa_encrypt(char* in, char* key_path, char* out)
{<!-- -->
  RSA* p_rsa;
  FILE* file;
  int rsa_len;
  if ((file = fopen(key_path, "r")) == NULL)
  {<!-- -->
    return FALSE;
  }
  if ((p_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL)) == NULL)
  {<!-- -->
    ERR_print_errors_fp(stdout);
    return FALSE;
  }
  rsa_len = RSA_size(p_rsa);
  if (RSA_private_encrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0)
  {<!-- -->
    return FALSE;
  }
  RSA_free(p_rsa);
  fclose(file);
  return TRUE;
}

// Decrypt using public key
BOOL public_rsa_decrypt(char* in, char* key_path, char* out)
{<!-- -->
  RSA* p_rsa;
  FILE* file;
  int rsa_len;
  if ((file = fopen(key_path, "r")) == NULL)
  {<!-- -->
    return FALSE;
  }
  if ((p_rsa = PEM_read_RSAPublicKey(file, NULL, NULL, NULL)) == NULL)
  {<!-- -->
    ERR_print_errors_fp(stdout);
    return FALSE;
  }
  rsa_len = RSA_size(p_rsa);
  if (RSA_public_decrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0)
  {<!-- -->
    return FALSE;
  }
  RSA_free(p_rsa);
  fclose(file);
  return TRUE;
}

When we need to use public key encryption, we can call the public_rsa_encrypt function and pass in the pre-encrypted string, the public key path and the encrypted storage location. When we need to decrypt, call private_rsa_decrypt function implements the decryption operation of the encrypted string. The usage code is as follows;

int main(int argc, char* argv[])
{<!-- -->
  char text[128] = "hello lyshark";

  char public_key[] = "d://public.rsa";
  char encry[128] = {<!-- --> 0 };

  char private_key[] = "d://private.rsa";
  char decry[128] = {<!-- --> 0 };

  // Public key encryption
  if (public_rsa_encrypt(text, public_key, encry))
  {<!-- -->
    printf("[Public key encryption] Encryption length: %d \
", strlen((char*)encry));
  }

  //Private key decryption
  if (private_rsa_decrypt(encry, private_key, decry))
  {<!-- -->
    printf("[Private key decryption] Decryption length: %d \
", strlen((char*)decry));
  }

  printf("Decrypted data: %s \
", decry);

  system("pause");
  return 0;
}

Readers can compile and run the above code by themselves to see the encrypted and decrypted data output, as shown in the figure below;

Use this process in reverse, use the private key to encrypt the data, and use the public key to decrypt the data. The code is as follows;

int main(int argc, char* argv[])
{<!-- -->
  char text[128] = "hello lyshark";

  char public_key[] = "d://public.rsa";
  char encry[128] = {<!-- --> 0 };

  char private_key[] = "d://private.rsa";
  char decry[128] = {<!-- --> 0 };

  //Private key encryption
  if (private_rsa_encrypt(text, private_key, encry))
  {<!-- -->
    printf("[Private key encryption] Encryption length: %d \
", strlen((char*)encry));
  }

  // Public key decryption
  if (public_rsa_decrypt(encry, public_key, decry))
  {<!-- -->
    printf("[Public key decryption] Decryption length: %d \
", strlen((char*)decry));
  }

  printf("Decrypted data: %s \
", decry);

  system("pause");
  return 0;
}

Private key encryption and public key decryption, the output rendering is as shown below;

20.5.2 Encrypted transmission string

After having the above encryption and decryption function implementation process, we can then implement the encrypted transmission function for strings. Because we use a 1024 bit key, we can only transmit 128 characters. In order to transmit a large number of characters, the characters need to be divided into blocks. The CutSplit() function is used to cut the string every 100 characters. , and then use the public key to encrypt it in the client. After encryption, it is enough to transmit a batch of encrypted data in blocks until the complete string is sent.

#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>

extern "C"
{<!-- -->
#include <openssl/applink.c>
}

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")

// Use public key encryption
int public_rsa_encrypt(char* in, char* key_path, char* out)
{<!-- -->
  RSA* p_rsa;
  FILE* file;
  int rsa_len;
  if ((file = fopen(key_path, "r")) == NULL)
  {<!-- -->
    return 0;
  }
  if ((p_rsa = PEM_read_RSAPublicKey(file, NULL, NULL, NULL)) == NULL)
  {<!-- -->
    ERR_print_errors_fp(stdout);
    return 0;
  }
  rsa_len = RSA_size(p_rsa);
  if (RSA_public_encrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0)
  {<!-- -->
    return 0;
  }
  RSA_free(p_rsa);
  fclose(file);
  return 1;
}

// Implement cutting at the specified position of the string
char* Cut(char* buffer, int offset, int length)
{<!-- -->
  char Split[100] = {<!-- --> 0 };
  memset(Split, 0, 100);
  strncpy(Split, buffer + offset, length);
  return Split;
}

// Loop to cut string
int CutSplit(char* buf, char len, OUT char Split[][1024])
{<!-- -->
  int count = 0;

  //Cut the len size each time
  for (int x = 0; x < strlen(buf); x + = len)
  {<!-- -->
    char* ref = Cut(buf, x, len);
    strcpy(Split[count], ref);
    count + = 1;
  }
  return count;
}

int main(int argc, char* argv[])
{<!-- -->
  char buf[8192] = "The National Aeronautics and Space Administration is America.";

  WSADATA WSAData;
  
  //Initialize the socket library
  if (WSAStartup(MAKEWORD(2, 0), & amp;WSAData))
  {<!-- -->
    return 0;
  }

  // Create Socket socket
  SOCKET client_socket;
  client_socket = socket(AF_INET, SOCK_STREAM, 0);

  struct sockaddr_in ClientAddr;
  ClientAddr.sin_family = AF_INET;
  ClientAddr.sin_port = htons(9999);
  ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

  // Connect to the server
  if (connect(client_socket, (LPSOCKADDR) & amp;ClientAddr, sizeof(ClientAddr)) != SOCKET_ERROR)
  {<!-- -->
    char SplitArray[100][1024] = {<!-- --> 0 };

    //Cut the string, once every 100 characters
    int count = CutSplit(buf, 100, SplitArray);

    // Number of times to send packets
    std::cout << "Number of packets sent: " << count << std::endl;
    char send_count[1024] = {<!-- --> 0 };
    sprintf(send_count, "%d", count);
    send(client_socket, send_count, strlen(send_count), 0);

    // Send data packets in a loop
    for (int x = 0; x < count; x + + )
    {<!-- -->
      std::cout << "Original packet: " << SplitArray[x] << std::endl;

      char public_key[] = "d://public.rsa";
      char encry[1024] = {<!-- --> 0 };

      // Public key encryption
      if (public_rsa_encrypt(SplitArray[x], public_key, encry))
      {<!-- -->
        std::cout << "RSA encryption length: " << strlen((char*)encry) << std::endl;
      }

      //Send encrypted data packet
      send(client_socket, encry, 1024, 0);
      memset(buf, 0, sizeof(buf));
      memset(encry, 0, sizeof(encry));
    }
    closesocket(client_socket);
    WSACleanup();
  }

  system("pause");
  return 0;
}

As for the server code implementation part, it needs to be consistent with the client. The client receives as many times as the server sends. First, the server receives the number of data packets it needs to receive, and uses this as a loop. Conditional use, accept data packets through an uninterrupted loop, and call private_rsa_decrypt to complete the decryption of the data packets, and finally splice the data packets into recv_message_all and output the complete packet.

#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>

extern "C"
{<!-- -->
#include <openssl/applink.c>
}

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")

// Decrypt using private key
int private_rsa_decrypt(char* in, char* key_path, char* out)
{<!-- -->
  RSA* p_rsa;
  FILE* file;
  int rsa_len;
  if ((file = fopen(key_path, "r")) == NULL)
  {<!-- -->
    return 0;
  }
  if ((p_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL)) == NULL)
  {<!-- -->
    ERR_print_errors_fp(stdout);
    return 0;
  }
  rsa_len = RSA_size(p_rsa);
  if (RSA_private_decrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0)
  {<!-- -->
    return 0;
  }
  RSA_free(p_rsa);
  fclose(file);
  return 1;
}

int main(int argc, char* argv[])
{<!-- -->
  WSADATA WSAData;

  //Initialize the socket library
  if (WSAStartup(MAKEWORD(2, 0), & amp;WSAData))
  {<!-- -->
    return 0;
  }

  // Create Socket socket
  SOCKET server_socket;
  server_socket = socket(AF_INET, SOCK_STREAM, 0);

  struct sockaddr_in ServerAddr;
  ServerAddr.sin_family = AF_INET;
  ServerAddr.sin_port = htons(9999);
  ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

  // Bind socket
  bind(server_socket, (LPSOCKADDR) & amp;ServerAddr, sizeof(ServerAddr));
  listen(server_socket, 10);

  SOCKET message_socket;

  //Receive and splice data
  char recv_message_all[8109] = {<!-- --> 0 };

  if ((message_socket = accept(server_socket, (LPSOCKADDR)0, (int*)0)) != INVALID_SOCKET)
  {<!-- -->
    //Receive the number of times you need to get
    char recv_count[1024] = {<!-- --> 0 };
    recv(message_socket, recv_count, 1024, 0);
    std::cout << "Number of packets received: " << recv_count << std::endl;

    for (int x = 0; x < atoi(recv_count); x + + )
    {<!-- -->
      //Receive encrypted data packets
      char buf[1024] = {<!-- --> 0 };
      recv(message_socket, buf, 1024, 0);

      //Private key decryption
      char private_key[] = "d://private.rsa";
      char decry[1024] = {<!-- --> 0 };

      // Call the decryption function
      if (private_rsa_decrypt(buf, private_key, decry))
      {<!-- -->
        std::cout << "RSA decryption length: " << strlen((char*)decry) << std::endl;
      }

      std::cout << "RSA decrypted packet: " << decry << std::endl;

      // Combine data packets
      strCut(recv_message_all, decry);
      memset(buf, 0, sizeof(buf));
      memset(decry, 0, sizeof(decry));
    }
    closesocket(message_socket);

    // Output the final data packet
    std::cout << std::endl;
    std::cout << "Combined packet: " << recv_message_all << std::endl;
  }
  closesocket(server_socket);
  WSACleanup();

  system("pause");
  return 0;
}

Readers can fill in the length of the buf string to be sent in the client by themselves. After filling, run the server first, and then run the client. At this time, the data packet will be encrypted and transmitted, decrypted and processed at the opposite end. The output is as shown in the figure below;

syntaxbug.com © 2021 All Rights Reserved.