Here is my code(I am including -lsodium when compiling:
#include <sodium.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
// Preprocessor directives
#define KEY_SIZE 32
#define NONCE_SIZE 8
#define INPUT_SIZE 128
bool is_valid_email(const char *email) {
const char *at_ptr = strchr(email, '@');
if (at_ptr == NULL || at_ptr == email || at_ptr[1] == '\0') {
return false;
}
const char *dot_ptr = strrchr(at_ptr, '.');
if (dot_ptr == NULL || dot_ptr == (at_ptr + 1) || dot_ptr[1] == '\0') {
return false;
}
return true;
}
bool is_valid_password(const char *password) {
int len = strlen(password);
if (len < 12) {
return false;
}
bool has_digit = false;
bool has_upper = false;
bool has_special = false;
for (int i = 0; i < len; ++i) {
if (isdigit(password[i])) {
has_digit = true;
} else if (isupper(password[i])) {
has_upper = true;
} else if (strchr("!?(),", password[i]) != NULL) {
has_special = true;
}
}
return has_digit && has_upper && has_special;
}
bool is_valid_ipv4(const char *ipv4) {
int octets[4];
char extra;
if (sscanf(ipv4, "%d.%d.%d.%d%c", &octets[0], &octets[1], &octets[2], &octets[3], &extra) != 4) {
return false;
}
for (int i = 0; i < 4; ++i) {
if (octets[i] < 0 || octets[i] > 255) {
return false;
}
}
return true;
}
void encrypt(const char *input, unsigned char *ciphertext, unsigned char *key, unsigned char *nonce, size_t input_len) {
// Generate random key and nonce
randombytes_buf(key, KEY_SIZE);
randombytes_buf(nonce, NONCE_SIZE);
// Encrypt the input fields
crypto_stream_salsa20_xor(ciphertext, (const unsigned char *)input, input_len, nonce, key);
}
void decrypt(unsigned char *plaintext, unsigned char *ciphertext, size_t len, unsigned char *key, unsigned char *nonce) {
// Decrypt the data
crypto_stream_salsa20_xor(plaintext, ciphertext, len, nonce, key);
}
int main() {
// Initialize libsodium
if (sodium_init() < 0) {
printf("The necessary libraries are not available, exiting\n");
return 1;
}
// Prompt user for mode selection (encrypt/decrypt)
char mode[INPUT_SIZE];
printf("Enter mode (encrypt/decrypt): ");
fgets(mode, INPUT_SIZE, stdin);
// If user chooses to encrypt
if (strncmp(mode, "encrypt\n", INPUT_SIZE) == 0) {
char field1[INPUT_SIZE];
char field2[INPUT_SIZE];
char field3[INPUT_SIZE];
// Prompt user for input fields and validate them
do {
printf("Enter email: ");
fgets(field1, INPUT_SIZE, stdin);
field1[strcspn(field1, "\n")] = 0;
} while (!is_valid_email(field1));
do {
printf("Enter password (min 12 characters, at least one uppercase, one digit, and one special character !?(),): ");
fgets(field2, INPUT_SIZE, stdin);
field2[strcspn(field2, "\n")] = 0;
} while (!is_valid_password(field2));
do {
printf("Enter IPv4 address: ");
fgets(field3, INPUT_SIZE, stdin);
field3[strcspn(field3, "\n")] = 0;
} while (!is_valid_ipv4(field3));
// Encrypt the input fields
unsigned char key1[KEY_SIZE], key2[KEY_SIZE], key3[KEY_SIZE];
unsigned char nonce1[NONCE_SIZE], nonce2[NONCE_SIZE], nonce3[NONCE_SIZE];
unsigned char encrypted1[INPUT_SIZE];
unsigned char encrypted2[INPUT_SIZE];
unsigned char encrypted3[INPUT_SIZE];
encrypt(field1, encrypted1, key1, nonce1, strlen(field1));
encrypt(field2, encrypted2, key2, nonce2, strlen(field2));
encrypt(field3, encrypted3, key3, nonce3, strlen(field3));
// Write the encrypted data, key, and nonce to a file
FILE *file = fopen("encrypted_data.bin", "wb");
if (file == NULL) {
printf("Error: Unable to open file\n");
return 1;
}
fwrite(key1, 1, KEY_SIZE, file);
fwrite(nonce1, 1, NONCE_SIZE, file);
fwrite(key2, 1, KEY_SIZE, file);
fwrite(nonce2, 1, NONCE_SIZE, file);
fwrite(key3, 1, KEY_SIZE, file);
fwrite(nonce3, 1, NONCE_SIZE, file);
fclose(file);
printf("Encryption successful. Data saved to encrypted_data.bin\n");
}
else if (strncmp(mode, "decrypt\n", INPUT_SIZE) == 0) {
// Read encrypted data, key, and nonce from the file
FILE *file = fopen("encrypted_data.bin", "rb");
if (file == NULL) {
printf("Error: Unable to open file\n");
return 1;
}
unsigned char encrypted1[INPUT_SIZE];
unsigned char encrypted2[INPUT_SIZE];
unsigned char encrypted3[INPUT_SIZE];
unsigned char key1[KEY_SIZE];
unsigned char nonce1[NONCE_SIZE];
unsigned char key2[KEY_SIZE];
unsigned char nonce2[NONCE_SIZE];
unsigned char key3[KEY_SIZE];
unsigned char nonce3[NONCE_SIZE];
fread(encrypted1, 1, INPUT_SIZE, file);
fread(encrypted2, 1, INPUT_SIZE, file);
fread(encrypted3, 1, INPUT_SIZE, file);
fread(key1, 1, KEY_SIZE, file);
fread(nonce1, 1, NONCE_SIZE, file);
fread(key2, 1, KEY_SIZE, file);
fread(nonce2, 1, NONCE_SIZE, file);
fread(key3, 1, KEY_SIZE, file);
fread(nonce3, 1, NONCE_SIZE, file);
fclose(file);
// Decrypt the data
unsigned char decrypted1[INPUT_SIZE];
unsigned char decrypted2[INPUT_SIZE];
unsigned char decrypted3[INPUT_SIZE];
decrypt(decrypted1, encrypted1, INPUT_SIZE, key1, nonce1);
decrypt(decrypted2, encrypted2, INPUT_SIZE, key2, nonce2);
decrypt(decrypted3, encrypted3, INPUT_SIZE, key3, nonce3);
// Display the decrypted data
printf("Decrypted email: %s\n", decrypted1);
printf("Decrypted password: %s\n", decrypted2);
printf("Decrypted IPv4 address: %s\n", decrypted3);
}
else {
printf("Invalid choice. Please enter 'encrypt' or 'decrypt'.\n");
}
return 0;
}
and the outputted return:
Decrypted email: ��������AN����L��ϑ)��,��j��?О\>7�Y�n���c���"�H�T��C�:�:���0�2q\`%h��\]"WRz��& Ҟ���FUC}���dc-��5��:\>�4{���Kh�:.���iu���ŷ*�-\[
Decrypted password: h�:.���iu���ŷ*�-\[
Decrypted IPv4 address: ϊgo��x�r%�kg�3Y�AYԡ����!�5
���C�l\~}\<\]uL����H͈bڧ��#P�2 �F��lu�����5 �
���1K|%�\*E����ҫ�1|�k6�H ���s\~x\_�
�L�x�
I tried to encrypt and decrypt, however the decrypt is not working as intended.
You have several issues resulting in Undefined Behavior in your code.
encrypted1
, encrypted2
or encrypted3
to your data file "encrypted_data.bin"
,decrypted1
, decrypted2
or decrypted3
contained before being encrypted,Another stumbling block you encounter in your code is mixing the order of parameters between encrypt()
and decrypt()
and the order they appear in crypto_stream_salsa20_xor()
. While legal to pass parameters in any order you like, failing to follow a consistent order -- such as that given by the function declaration, e.g.
int crypto_stream_salsa20_xor(unsigned char *c, const unsigned char *m,
unsigned long long mlen, const unsigned char *n,
const unsigned char *k);
is a recipe for confusion. Pick and order and be consistent, see, e.g. Salsa20
In order to recover your inputs field1
, field2
and field3
from your encrypted file, you need to know how many characters they contain. One simple way to make that available is to write the length out (as a size_t
or int
or unsigned
) immediately before your write INPUT_SIZE
bytes of the encrypted text to the file, e.g.
fwrite (&len1, 1, sizeof len1, file);
fwrite (encrypted1, 1, INPUT_SIZE, file);
fwrite (&len2, 1, sizeof len2, file);
fwrite (encrypted2, 1, INPUT_SIZE, file);
fwrite (&len3, 1, sizeof len3, file);
fwrite (encrypted3, 1, INPUT_SIZE, file);
fwrite (key1, 1, KEY_SIZE, file);
fwrite (nonce1, 1, NONCE_SIZE, file);
fwrite (key2, 1, KEY_SIZE, file);
fwrite (nonce2, 1, NONCE_SIZE, file);
fwrite (key3, 1, KEY_SIZE, file);
fwrite (nonce3, 1, NONCE_SIZE, file);
(that may let you write only the needed bytes to the file depending on how the salsa20 cipher works)
Read the data back in in the same order you wrote it out, e.g.
fread (&len1, 1, sizeof len1, file);
fread (encrypted1, 1, INPUT_SIZE, file);
fread (&len2, 1, sizeof len2, file);
fread (encrypted2, 1, INPUT_SIZE, file);
fread (&len3, 1, sizeof len3, file);
fread (encrypted3, 1, INPUT_SIZE, file);
fread (key1, 1, KEY_SIZE, file);
fread (nonce1, 1, NONCE_SIZE, file);
fread (key2, 1, KEY_SIZE, file);
fread (nonce2, 1, NONCE_SIZE, file);
fread (key3, 1, KEY_SIZE, file);
fread (nonce3, 1, NONCE_SIZE, file);
After you decrypt, the information contained in decryptedX
are not nul-terminated C-strings, it is just an array of bytes. You need to limit the output of each to len1
, len2
, len3
, respectively. You can do that by adding the Precision modifier to the "%s"
format specifier, e.g. "%.*s"
and then casing len1
, len2
, len3
to (int)
to provide the precision, e.g.
// Display the decrypted data
printf ("Decrypted email: %.*s\n", (int)len1, decrypted1);
printf ("Decrypted password: %.*s\n", (int)len2, decrypted2);
printf ("Decrypted IPv4 address: %.*s\n", (int)len3, decrypted3);
Example Use/Output
Putting it all together, you recover the data you have written to file, e.g.
Encrypting and writing the encrypted data to encrypted_data.bin
:
$ ./bin/sodium-encrypt
Enter mode (encrypt/decrypt): encrypt
Enter email: foo@bar.com
Enter password (min 12 characters, at least one uppercase, one digit, and one special character !?(),): aaaaaaA1234!
Enter IPv4 address: 44.55.66.77
Encryption successful. Data saved to encrypted_data.bin
Decrypting and outputting the information
$ ./bin/sodium-encrypt
Enter mode (encrypt/decrypt): decrypt
Decrypted email: foo@bar.com
Decrypted password: aaaaaaA1234!
Decrypted IPv4 address: 44.55.66.77
For completeness, the updated code is shown below. You should add additional validations to each output to ensure the output succeeds before presuming so. You should check the return of fclose()
after the write of the encrypted data to catch any errors that occur after the final write, e.g. stream errors or errors flushing the data to disk.... There are many further improvements you can make.
Also note you should not make comparisons of strings with "\n"
dangling off the end. It is valid for the user to end input by generating a manual EOF
which would not result in a '\n'
at the end of your input. Go ahead and trim all input with strcspn()
like you do in other places so you can make a valid comparison of the input alone -- without the newline.
Finally, you should use the assignment suppression operator with the "%c"
specifier, e.g. "%*c"
in sscanf (ipv4, "%d.%d.%d.%d%*c", ...
and remove extra
from the pointers listed at the end so your match count is always 4
when a valid IPv4 address is entered.
#include <sodium.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
// Preprocessor directives
#define KEY_SIZE 32
#define NONCE_SIZE 8
#define INPUT_SIZE 128
bool is_valid_email (const char *email)
{
const char *at_ptr = strchr (email, '@');
if (at_ptr == NULL || at_ptr == email || at_ptr[1] == '\0') {
return false;
}
const char *dot_ptr = strrchr (at_ptr, '.');
if (dot_ptr == NULL || dot_ptr == (at_ptr + 1) || dot_ptr[1] == '\0') {
return false;
}
return true;
}
bool is_valid_password (const char *password)
{
int len = strlen (password);
if (len < 12) {
return false;
}
bool has_digit = false;
bool has_upper = false;
bool has_special = false;
for (int i = 0; i < len; ++i) {
if (isdigit (password[i])) {
has_digit = true;
}
else if (isupper (password[i])) {
has_upper = true;
}
else if (strchr ("!?().,", password[i]) != NULL) {
has_special = true;
}
}
return has_digit && has_upper && has_special;
}
bool is_valid_ipv4 (const char *ipv4)
{
int octets[4];
if (sscanf (ipv4,
"%d.%d.%d.%d%*c",
&octets[0], &octets[1], &octets[2], &octets[3]) != 4) {
return false;
}
for (int i = 0; i < 4; ++i) {
if (octets[i] < 0 || octets[i] > 255) {
return false;
}
}
return true;
}
void encrypt (unsigned char *ciphertext, const char *input,
unsigned char *key, unsigned char *nonce, size_t input_len)
{
// Generate random key and nonce
randombytes_buf (key, KEY_SIZE);
randombytes_buf (nonce, NONCE_SIZE);
// Encrypt the input fields
crypto_stream_salsa20_xor (ciphertext, (const unsigned char *) input,
input_len, nonce, key);
}
void decrypt (unsigned char *plaintext, unsigned char *ciphertext,
size_t len, unsigned char *key, unsigned char *nonce)
{
// Decrypt the data
crypto_stream_salsa20_xor (plaintext, ciphertext, len, nonce, key);
}
int main ()
{
size_t len1 = 0, len2 = 0, len3 = 0;
// Initialize libsodium
if (sodium_init () < 0) {
printf ("The necessary libraries are not available, exiting\n");
return 1;
}
// Prompt user for mode selection (encrypt/decrypt)
char mode[INPUT_SIZE];
fputs ("Enter mode (encrypt/decrypt): ", stdout);
if (!fgets (mode, INPUT_SIZE, stdin)) {
fputs ("error: input failed fgets-mode\n", stderr);
return 1;
}
mode[strcspn(mode, "\n")] = 0;
// If user chooses to encrypt
if (strcmp (mode, "encrypt") == 0) {
char field1[INPUT_SIZE] = "";
char field2[INPUT_SIZE] = "";
char field3[INPUT_SIZE] = "";
// Prompt user for input fields and validate them
do {
fputs ("Enter email: ", stdout);
if (!fgets (field1, INPUT_SIZE, stdin)) {
fputs ("error: failed input - fgets-field1\n", stderr);
return 1;
}
field1[(len1 = strcspn (field1, "\n"))] = 0;
} while (!is_valid_email (field1));
do {
fputs ( "Enter password (min 12 characters, "
"at least one uppercase, one digit, and "
"one special character !?(),): ", stdout);
if (!fgets (field2, INPUT_SIZE, stdin)) {
fputs ("error: failed input - fgets-field2\n", stderr);
return 1;
}
field2[(len2 = strcspn (field2, "\n"))] = 0;
} while (!is_valid_password (field2));
do {
fputs ("Enter IPv4 address: ", stdout);
if (!fgets (field3, INPUT_SIZE, stdin)) {
fputs ("error: failed input - fgets-field3\n", stderr);
return 1;
}
field3[(len3 = strcspn (field3, "\n"))] = 0;
} while (!is_valid_ipv4 (field3));
// Encrypt the input fields
unsigned char key1[KEY_SIZE] = "",
key2[KEY_SIZE] = "",
key3[KEY_SIZE] = "",
nonce1[NONCE_SIZE] = "",
nonce2[NONCE_SIZE] = "",
nonce3[NONCE_SIZE] = "",
encrypted1[INPUT_SIZE] = "",
encrypted2[INPUT_SIZE] = "",
encrypted3[INPUT_SIZE] = "";
encrypt (encrypted1, field1, key1, nonce1, len1);
encrypt (encrypted2, field2, key2, nonce2, len2);
encrypt (encrypted3, field3, key3, nonce3, len3);
// Write the encrypted data, key, and nonce to a file
FILE *file = fopen ("encrypted_data.bin", "wb");
if (file == NULL) {
printf ("Error: Unable to open file\n");
return 1;
}
fwrite (&len1, 1, sizeof len1, file);
fwrite (encrypted1, 1, INPUT_SIZE, file);
fwrite (&len2, 1, sizeof len2, file);
fwrite (encrypted2, 1, INPUT_SIZE, file);
fwrite (&len3, 1, sizeof len3, file);
fwrite (encrypted3, 1, INPUT_SIZE, file);
fwrite (key1, 1, KEY_SIZE, file);
fwrite (nonce1, 1, NONCE_SIZE, file);
fwrite (key2, 1, KEY_SIZE, file);
fwrite (nonce2, 1, NONCE_SIZE, file);
fwrite (key3, 1, KEY_SIZE, file);
fwrite (nonce3, 1, NONCE_SIZE, file);
fclose (file);
printf ("Encryption successful. Data saved to encrypted_data.bin\n");
}
else if (strncmp (mode, "decrypt", INPUT_SIZE) == 0) {
// Read encrypted data, key, and nonce from the file
FILE *file = fopen ("encrypted_data.bin", "rb");
if (file == NULL) {
printf ("Error: Unable to open file\n");
return 1;
}
unsigned char encrypted1[INPUT_SIZE] = "",
encrypted2[INPUT_SIZE] = "",
encrypted3[INPUT_SIZE] = "",
key1[KEY_SIZE] = "",
nonce1[NONCE_SIZE] = "",
key2[KEY_SIZE] = "",
nonce2[NONCE_SIZE] = "",
key3[KEY_SIZE] = "",
nonce3[NONCE_SIZE] = "";
fread (&len1, 1, sizeof len1, file);
fread (encrypted1, 1, INPUT_SIZE, file);
fread (&len2, 1, sizeof len2, file);
fread (encrypted2, 1, INPUT_SIZE, file);
fread (&len3, 1, sizeof len3, file);
fread (encrypted3, 1, INPUT_SIZE, file);
fread (key1, 1, KEY_SIZE, file);
fread (nonce1, 1, NONCE_SIZE, file);
fread (key2, 1, KEY_SIZE, file);
fread (nonce2, 1, NONCE_SIZE, file);
fread (key3, 1, KEY_SIZE, file);
fread (nonce3, 1, NONCE_SIZE, file);
fclose (file);
// Decrypt the data
unsigned char decrypted1[INPUT_SIZE] = "",
decrypted2[INPUT_SIZE] = "",
decrypted3[INPUT_SIZE] = "";
decrypt (decrypted1, encrypted1, INPUT_SIZE, key1, nonce1);
decrypt (decrypted2, encrypted2, INPUT_SIZE, key2, nonce2);
decrypt (decrypted3, encrypted3, INPUT_SIZE, key3, nonce3);
// Display the decrypted data
printf ("Decrypted email: %.*s\n", (int)len1, decrypted1);
printf ("Decrypted password: %.*s\n", (int)len2, decrypted2);
printf ("Decrypted IPv4 address: %.*s\n", (int)len3, decrypted3);
}
else {
printf ("Invalid choice. Please enter 'encrypt' or 'decrypt'.\n");
}
return 0;
}
Let me know if you have questions.