-
Notifications
You must be signed in to change notification settings - Fork 0
Encryption and Security
With SmallChat all PDUs are encrypted using SCEDA (SmallChat Encryption-Decryption Algorithm).
SCEDA requires a 16 bytes long key and a randomly generated non necessarily secret initialization vector (those two data are also needed while decrypting).
Even encrypting the exact same message twice, using the same key and the same initialization vector, the output is completely different (and that makes it even more safe against statistical attacks), but, of course, when the two messages are decrypted the original message is always returned.
It is impossible to only decrypt a part of the message and you can't get the key from an encrypted message and the original one.
If, for some reason, some part of the message changes during transport, it will not be possible to decrypt it (and therefore the broken PDU will be detected).
In order to work, SCEDA needs a cryptographic hash function, so ScedaDigest is used.
SCEDA (SmallChat Encryption-Decryption Algorithm) is the encryption algorithm designed by Valentino Giudice to be used in SmallChat.
SmallChat uses a 16 bytes long key and works on 16 bytes long blocks. It also uses a 8 bytes long initialization vector, which doesn't have to be a secret, but must be provided and shoud
Confusion and diffusion are not created using sobstitution boxes or magick constants, but using an hashing algorithm (which must have a 16 bytes long result).
Each block is encrypted using a different key, using simple bitwise EXOR. The key for each block is generated hashing the encryption key for the message, using, as salt, the clear content of the previous block and the encrypted content of the previous block (for the first block the initialization vector is used as a salt).
It is important to use both because:
- The encrypted content of the previous block is available to whoever can access the encrypted message.
- The message may repeat the same block multiple times and it is important not to also repeat the same key for each block.
Using both the clear and the encrypted versions of the previous blocks eliminates both the problems.
That ensures that if a bit of the message changes, it will not be possible to decrypt it and also that a small difference in the message will change all following blocks.
In order to make a small difference influence all the other blocks (not just the following ones), a simple strategy is applied: the message is encrypted once, reversed and then encrypted another time.
As for the padding, each byte of padding is random. As a result, encrypting the same message twice will produce totally different results (making statistical attacks impossible).
Of course, the length of the message has to be stored. It is stored into 7 bytes and that compels to add 9 extra bytes of (random) padding.
8 of those 9 bytes are actually used as initialization vector for the first encryption (which does not encrypt the lenght of the message and those other 9 bytes of padding).
When the message is encrypted the second time, the initialization vector provided by the user is used.
Here is a C implementation of SCEDA:
#include <stdlib.h>
#include <string.h>
#include <time.h>
void sceda_func(unsigned char *output, const unsigned char *original, int blockC, const unsigned char *key, const unsigned char *iv, int decrypting) {
unsigned char blockKey[49], hashedBlockKey[16], *pt2;
const unsigned char *pt1;
int i, j;
memcpy(blockKey, key, 16);
memcpy(blockKey + 16, iv, 8);
memcpy(blockKey + 24, iv, 8);
memcpy(blockKey + 32, iv, 8);
memcpy(blockKey + 40, iv, 8);
blockKey[48] = 0;
pt1 = original;
pt2 = output;
for(i = 0; i < blockC; i++) {
hash(hashedBlockKey, blockKey, 49); /* "hash" is the name of the hashing function. The parameters are, in order: output vector, input value and lenght of the input value (the result is always 16 bytes long). */
if(decrypting) {
memcpy(blockKey + 16, pt1, 16);
} else {
memcpy(blockKey + 32, pt1, 16);
}
for(j = 0; j < 16; j++) {
pt2[j] = pt1[j] ^ hashedBlockKey[j];
}
if(decrypting) {
memcpy(blockKey + 32, pt2, 16);
} else {
memcpy(blockKey + 16, pt2, 16);
}
blockKey[32]++;
pt1 += 16;
pt2 += 16;
}
}
/**
* Encrypts a message using SCEDA.
* @param output A pointer to the buffer to be written the output into.
* @param original A pointer to the binary representation of the message to be encrypted.
* @param length The length of the message to be encrypted.
* @param key A pointer to the key to be used to encrypted the message (it must be 16 bytes long).
* @param iv A pointer to the initialization vector to be used to encrypt the message (it must be 8 bytes long).
* @return The length of the encrypted message (which is also the output of {@code encrypted_length(length)}).
*/
int sceda_encrypt(unsigned char *output, const unsigned char *original, int length, const unsigned char *key, const unsigned char *iv) {
int retVal, i, temp;
unsigned char *pt, *originalCopy;
srand(time(NULL) + rand());
originalCopy = (unsigned char*)malloc(length);
memcpy(originalCopy, original, length);
retVal = ((length+15)/16) * 16;
temp = length;
pt = output + 6;
for(i = 0; i < 7; i++) {
*pt-- = temp % 256;
temp /= 256;
}
pt += 8;
memcpy(pt, originalCopy, length);
pt += length;
for(i = 0; i < retVal - length + 9; i++) {
*(pt++) = rand();
}
pt = output + 7;
sceda_func(pt, pt, (length+15) / 16, key, pt + retVal, 0);
retVal += 16;
for(i = 0; i < retVal/2; i++) {
temp = output[i];
output[i] = output[retVal-i-1];
output[retVal-i-1] = temp;
}
sceda_func(output, output, retVal / 16, key, iv, 0);
free(originalCopy);
return retVal;
}
/**
* Decrypts a message encrypted with SCEDA.
* @param output A pointer to the buffer to be written the output into.
* @param original A pointer to the binary message to be decrypted.
* @param length The length of the encrypted message.
* @param key A pointer to the key used to encrypt the message (it must be 16 bytes long).
* @param iv A pointer to the initialization vector used to encrypt the message (it must be 16 bytes long).
* @return The length of the decrypted message.
*/
int sceda_decrypt(unsigned char *output, const unsigned char *original, int length, const unsigned char *key, const unsigned char *iv) {
int retVal, i, temp;
unsigned char *pt, *buffer;
if(length % 16) {
return -1;
}
buffer = (unsigned char*)malloc(length);
sceda_func(buffer, original, length / 16, key, iv, 1);
for(i = 0; i < length/2; i++) {
temp = buffer[i];
buffer[i] = buffer[length-i-1];
buffer[length-i-1] = temp;
}
pt = buffer;
retVal = 0;
for(i = 0; i < 7; i++) {
retVal *= 256;
retVal += *pt++;
}
if((retVal < 0) || (retVal > length-16)) {
return -1;
}
sceda_func(buffer, buffer + 7, (retVal+15) / 16, key, buffer + ((retVal+15)/16) * 16 + 7, 1);
memcpy(output, buffer, retVal);
free(buffer);
return retVal;
}
ScedaDigest is the hashing function designed by Valentino Giudice to be used in SCEDA. Its result is 16 bytes long.
Other implementations of SCEDA were based on MD5 algorithm.
As in SCEDA, the idea is to generate a result that looks random just by doing maths on input data.
Here's a C implementation of ScedaDigest:
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#define T1(x) (((~((x) >> 16)) << 16) | ((x) & 0xFFFF))
#define T2(x) (~(T1(x)))
#define MV 8191
#define M(x, y) ((((x) * (y)) ^ (((x) + MV) * ((y) + MV))))
void sceda_digest_func(unsigned char *output, const unsigned char *original) {
unsigned char buffer[16];
unsigned int k, a, b, c, d, temp, tempA, i;
memcpy(buffer, original, 16);
for(i = 0; i < 4; i++) {
a = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
b = (buffer[4] << 24) + (buffer[5] << 16) + (buffer[6] << 8) + buffer[7];
c = (buffer[8] << 24) + (buffer[9] << 16) + (buffer[10] << 8) + buffer[11];
d = (buffer[12] << 24) + (buffer[13] << 16) + (buffer[14] << 8) + buffer[15];
k = b ^ c ^ d;
temp = a;
tempA = a;
a = M(T1(a), T2(b)) ^ k;
k = temp ^ a;
temp = b;
b = M(T1(b), T2(c)) ^ k;
k = temp ^ b;
temp = c;
c = M(T1(c), T2(d)) ^ k;
k = temp ^ c;
d = M(T1(d), T2(tempA)) ^ k;
buffer[1] = a >> 24;
buffer[2] = a >> 16;
buffer[3] = a >> 8;
buffer[4] = a;
buffer[5] = c >> 24;
buffer[6] = c >> 16;
buffer[7] = c >> 8;
buffer[8] = c;
buffer[9] = b >> 24;
buffer[10] = b >> 16;
buffer[11] = b >> 8;
buffer[12] = b;
buffer[13] = d >> 24;
buffer[14] = d >> 16;
buffer[15] = d >> 8;
buffer[0] = d;
}
memcpy(output, buffer, 16);
}
/**
* Hashes data using the ScedaDigest algorithm.
* @param output A pointer to the buffer to be written the output into.
* @param original A pointer to the binary representation of the message to be hashed (it must be 16 bytes long).
*/
void sceda_digest(unsigned char *output, const unsigned char *original, int length) {
unsigned char *buffer, temp1[16], temp2[16];
int blockC, i, j;
blockC = (length + 17) / 16;
buffer = (unsigned char*)malloc(blockC * 16);
memcpy(buffer, original, length);
buffer[length] = length / 256;
buffer[length + 1] = length % 256;
for(i = length + 2; i < blockC * 16; i++) {
buffer[i] = 170;
}
for(i = 0; i < blockC; i++) {
sceda_digest_func(buffer + 16 * i, buffer + 16 * i);
}
memcpy(output, buffer, 16);
for(i = 1; i < blockC; i++) {
memcpy(temp1, output, 8);
memcpy(temp1 + 8, buffer + 16 * i, 8);
memcpy(temp2, buffer + 16 * i + 8, 8);
memcpy(temp2 + 8, output + 8, 8);
sceda_digest_func(temp1, temp1);
sceda_digest_func(temp2, temp2);
for(j = 0; j < 16; j++) {
output[j] = temp1[j] ^ temp2[j];
}
}
free(buffer);
}
The constant called MV
shall be a Mersenne prime number in order to get good results. Possible values include 3, 7, 31, 127, 8191, 131071, 524287 and 2147483647.
Preimage resistance of ScedaDigest is not guaranteed (if it was, the P vs NP problem would be solved), but some other tests on other characteristics have shown similar results between ScedaDigest and MD5 (except for the speed: MD5 is faster).