-
Notifications
You must be signed in to change notification settings - Fork 13
Algorithm
THIS DOCUMENTATION IS OBSOLETE! WE ARE GOING TO WRITE AN UPDATED DOCUMENT SOON Hohha Dynamic XOR is a new symmetric encryption algorithm developed for Hohha Secure Instant Messaging Platform and opened to the public via dual licence MIT and GPL.
The essential logic of the algorithm is using the key as a "jump table" which is dynamically updated with every "jump" and to resist as much as possible to cryptro-analysis with "hidden" values. Our aim is to create maximum random output from "any" input. It may an all 0 file or all random distribution. It doesn't matter. In order to do this, we have :
- Salt: 8 bytes of random salt data
- KeyCheckSum(or KC): 4 bytes of key body crc
- Body: KeyBody bytes of key body
- M: Our moving pointer on the key body, which tells us where we are
- InOutBuf: Plaintext(or ciphertext for decryption)
- We must use those variables in order to:
- Create maximum random output to prevent detecting a pattern on ciphertext
- Hide the key body even if the attacker knows both the ciphertext and the plaintext
Method:
- Our first number to be XORed with the first plaintext byte completely depends on the random salt value
- Our starting point on the key body completely depends on the random salt value
- All subsequent ciphertext outputs depend on the starting values: Even attacker intercepts the ciphertext and plaintext, the data gathered will not be useful for subsequent encryptions. Because, they will use different salt data.
- To hide our key body elements We XOR at least five body elements with each other.
- We use a third dynamic variable initially set to Key CRC and XORed with 4 hidden key body elements and dynamically update it according to plaintext checksum
- We create two distinct uint32 variables from Salt data: Salt1 and Salt which are dynamically updated during jumps
- We create two another uint32 variable X and Y, by combining 8 body bytes, randomly chosen according to salt
- We update key body elements according to combination of these variables
- We update salt data according to key body elements
- Our jump start point and steps are hidden
- We use the previous XOR values obtained to XOR with the next XOR values(chaining)
Briefly, we start from a random position, we make random steps, we xor hidden body values and every time we encrypt a byte, we also change this unknown body values. And the result of the next encrypted ciphertext depends on the previous one. This is fundamentally the basic logic.
memcpy(Body,K+SP_BODY, BodyMask+1); p = InOutBuf; // We compute our start values as much randomly as possible upon salt(or nonce or iv) value which is transmitted with every data to be encrypted or decrypted Salt1 = ((uint32_t)(Salt[0]) | ((uint32_t)(Salt[1]) << 8) | ((uint32_t)(Salt[2]) << 16) | ((uint32_t)(Salt[3]) << 24)); Salt2 = ((uint32_t)(Salt[4]) | ((uint32_t)(Salt[5]) << 8) | ((uint32_t)(Salt[6]) << 16) | ((uint32_t)(Salt[7]) << 24));
X = ~(((uint32_t)(Body[Salt[3]&BodyMask]) | ((uint32_t)(Body[Salt[4]&BodyMask]) << 8) | ((uint32_t)(Body[Salt[0]&BodyMask]) << 16) | ((uint32_t)(Body[Salt[6]&BodyMask]) << 24))); Y = ~((uint32_t)(Body[Salt[7]&BodyMask]) | ((uint32_t)(Body[Salt[2]&BodyMask]) << 8) | ((uint32_t)(Body[Salt[1]&BodyMask]) << 16) | ((uint32_t)(Body[Salt[5]&BodyMask]) << 24)); V ^= (((uint32_t)(Body[(~Salt[5])&BodyMask]) | ((uint32_t)(Body[(~Salt[0])&BodyMask]) << 8) | ((uint32_t)(Body[(~Salt[2])&BodyMask]) << 16) | ((uint32_t)(Body[(~Salt[4])&BodyMask]) << 24))); // Our initial jump position in the key body depends on a random value M = X & BodyMask; //printf("X: %u Y: %u V: %u\n", X,Y,V); while (t--) { // First jump point Salt1 ^= (uint32_t)(Body[M]); Body[M] = (uint8_t)(Salt2^V); M = (M^Salt2) & BodyMask; ROL32_1(Salt2);
// Second jump point
Salt2 ^= (uint32_t)(Body[M]);
Body[M] = (uint8_t)(Salt1);
M = (M^V) & BodyMask;
ROR32_1(Salt1);
// Following jumps
for (tt=2; tt<GetNumJumps(K); tt++)
{
if (tt&1)
{
Salt2 ^= (uint32_t)(Body[M]);
//Body[M] = (uint8_t)(Salt1) ^ X;
M = (M^Salt1) & BodyMask;
ROR32_1(Salt1);
}
else {
Salt1 ^= (uint32_t)(Body[M]);
//Body[M] = (uint8_t)(Salt2);
M = (M^V) & BodyMask;
ROL32_1(Salt2);
}
}
Checksum = CRC32Table[*p ^ ((Checksum >> 24) & 0xff)] ^ (Checksum << 8);
*p ^= (uint8_t)(Salt1 ^ Salt2 ^ V ^ X ^ Y);
X ^= (uint32_t)((Body[Salt1 & BodyMask] & Body[Salt2 & BodyMask])); ROL32_1(X);
Y ^= (uint32_t)(Body[V & BodyMask]); ROR32_1(Y);
V ^= Checksum;ROL32_1(V);
p++;
} return Checksum ^ 0xffffffff; }
Briefly, to decypher a ciphertext, a cracker needs to find out the key, and, to find out the key, cracker needs to find out the plaintext and the random value which, is different for every encryption. The key is also dynamically updated according to plaintext and the random value and the jump path is chosen accoding to plaintext+encrypted text during encryption process; Probably not impossible, in theory, but in practice very difficult!
Brute force attack complexity for only the "initial state" of the encryptor or decryptor is:
2^( (Number of key body CRC bit: 32) + (Number of bits chosen from body according to key body CRC: 32)
(Number of bits for Salt: 64) + (Number of bits chosen from body according to Salt: 64)) =
2^(32 + 32 + 64 + 64) = 2^192
The overall brute force attack complexity is:
2^(Initial State brute force attack complexity + Possibilities for every byte encrypted * Number of characters in plaintext) =
2^(Initial State brute force attack complexity + Number of unknown bits from key body, used to encrypt every chars * Number of characters in plaintext) =
2^(192 + ((5*8) * Number of characters in plaintext) =
2^(192 + 40 * Number of characters in plaintext)
This is the "raw" algorithm. We don't use it in raw form.
The algorithm is designed from scratch to use a different "hidden 64 bit random Salt" for every plaintext to be encrypted. In addition, we have a plaintext crc to check the integrity of the encrypted packet we receive.
We have a "packet header" to transmit "hidden salt" and "plaintext crc" values with every encrypted packet.
Here are is the code to produce the whole packet:
// The number of random padding bytes before the salt value in header. We use those random numbers in order to better hide our packet salt value. // The minimum value is 1, maximum is 8. We use 4 as default in Hohha Messenger. #define HEADER_SALT_PADDING_SIZE 4 // Hohha communication header structure: // 1 byte AlignedLenSize which describes the size of the variable which describes the length of the plaintext body, in bytes // SALT_PADDING_SIZE byte dummy random byte(against known plaintext attacks) // 8 bytes packet salt value for encryption // 4 bytes -> Plaintext checksum // 1 byte Left padding(number of random characters) before the real plaintext or ciphertext // AlignedLenSize bytes for packet plaintext length(low byte first) #define ALIGN_TO(v,n) ((uint32_t)((v)+(n)-1) & (uint32_t)(0xffffffff - (n - 1))) typedef struct attribute((packed)) { // AlignedLenSize; // Highest 1 bit(&128) : 1->Packet is encrypted 0->Packet is NOT encrypted // Next highest 2 bits(&64 &32) : RESERVED FOR COMPRESSION TYPE! NOT IMPLEMENTED YET! // Low 3 bits(&7) : 1 --> Aligned length is between 0..255; 2-> 255..65535 3->0..2^24-1 4-> 65536..2^32-1 THIS VALUE IS NEVER ENCRYPTED uint8_t AlignedLenSize; uint8_t SaltProtectionPadding[HEADER_SALT_PADDING_SIZE]; // Random data to better protect random salt data uint8_t Salt[SALT_SIZE]; // Salt value unique for packet uint32_t PlaintextCRC; // Plaintext CRC value to check integrity of the packet LITTLE ENDIAN uint8_t Padding; // LeftPad + RightPad uint8_t AlignedLen[8]; // Plaintext or ciphertext aligned length. May be 1,2,3,4 or 8 bytes!!! 8 BYTES IS NOT IMPLEMENTED! } THohhaPacketHeader; //#define HHLEN sizeof(THohhaPacketHeader) static inline uint8_t GetAlignedLenSize(size_t AlignedLen) { if (AlignedLen < (1<<8)) return 1; if (AlignedLen < (1<<16)) return 2; if (AlignedLen < (1<<24)) return 3; if (AlignedLen <= 0xffffffff) return 4; return 8; } static inline size_t GetCommHeaderLenByAlignedLenSize(uint8_t AlignedLenSize) { return sizeof(THohhaPacketHeader) - 8 + AlignedLenSize; } static inline unsigned int GetCommHeaderLenByAlignedLen(unsigned int AlignedLen) { return GetCommHeaderLenByAlignedLenSize(GetAlignedLenSize(AlignedLen)); } static inline unsigned int GetCommHeaderLenByHeader(THohhaPacketHeader *Hdr) { return GetCommHeaderLenByAlignedLenSize(Hdr->AlignedLenSize & 7); } #define HOHHA_TOTAL_COMM_PACKET_SIZE(DataSize,DataAlignment) ((ALIGN_TO((DataSize)+1,DataAlignment)) + GetCommHeaderLenByAlignedLen((ALIGN_TO((DataSize)+1,DataAlignment)))) #define HOHHA_TOTAL_COMM_PACKET_SIZE_WITHOUT_ENCRYPTION(DataSize) ((DataSize) + GetCommHeaderLenByAlignedLen(DataSize)) // This function sets the exact ciphertext or plaintext length on the hohha communication header static inline void SetHeaderAlignedLenValue(THohhaPacketHeader *PacketHeader, size_t AlignedDataLen) { uint8_t AlignedLenSize = GetAlignedLenSize(AlignedDataLen);
PacketHeader->AlignedLenSize = AlignedLenSize; if (AlignedLenSize == 1) PacketHeader->AlignedLen[0] = (uint8_t)AlignedDataLen; else if (AlignedLenSize == 2) { PacketHeader->AlignedLen[0] = (uint8_t)(AlignedDataLen >> 8); PacketHeader->AlignedLen[1] = (uint8_t)(AlignedDataLen & 0xff); } else if (AlignedLenSize == 3) { PacketHeader->AlignedLen[0] = (uint8_t)((AlignedDataLen >> 16) & 0xff); PacketHeader->AlignedLen[1] = (uint8_t)((AlignedDataLen >> 8) & 0xff); PacketHeader->AlignedLen[2] = (uint8_t)(AlignedDataLen & 0xff); } else { PacketHeader->AlignedLen[0] = (uint8_t)((AlignedDataLen >> 24) & 0xff); PacketHeader->AlignedLen[1] = (uint8_t)((AlignedDataLen >> 16) & 0xff); PacketHeader->AlignedLen[2] = (uint8_t)((AlignedDataLen >> 8) & 0xff); PacketHeader->AlignedLen[3] = (uint8_t)(AlignedDataLen & 0xff); } } // This function gets the Aligned ciphertext or plaintext length from the hohha communication header static inline ssize_t GetHeaderAlignedLenValue(THohhaPacketHeader *PacketHeader) { uint8_t V = PacketHeader->AlignedLenSize & 7;
if (V == 1) return PacketHeader->AlignedLen[0];
if (V == 2) return ((size_t)(PacketHeader->AlignedLen[0]) << 8) | PacketHeader->AlignedLen[1];
if (V == 3) return ((size_t)(PacketHeader->AlignedLen[0]) << 16) | ((size_t)(PacketHeader->AlignedLen[1]) << 8) | PacketHeader->AlignedLen[2];
if (V == 4) return ((size_t)(PacketHeader->AlignedLen[0]) << 24) | ((size_t)(PacketHeader->AlignedLen[1]) << 16) | ((size_t)(PacketHeader->AlignedLen[2]) << 8) | PacketHeader->AlignedLen[3];
return -1; }
void CreateHohhaCommunicationPacket2(uint8_t *K, uint32_t KeyCheckSum, size_t InDataLen, uint8_t *InBuf, uint32_t DataAlignment, uint8_t *OutBuf) { // This function encrypts InBuf and creates a communication packet with a proper header // OutBuf must be already allocated and must be enough large to store (InDataLen-1 + HOHHA_PACKET_ALIGNMENT + HHLEN) bytes if (!OutBuf || !(DataAlignment == 8 || DataAlignment == 16 || DataAlignment == 32 || DataAlignment == 64)) { //printf("INVALID DATAALIGNMENT: %d\n", DataAlignment); return; }
uint8_t *OriginalSalt = K + SP_SALT_DATA; size_t AlignedDataLen = ALIGN_TO(InDataLen+1,DataAlignment); THOPEncryptorFnc EncryptorFnc = xorGetProperHOPEncryptorFnc(K); uint8_t AlignedLenSize = GetAlignedLenSize(AlignedDataLen); size_t HHLEN = GetCommHeaderLenByAlignedLenSize(AlignedLenSize); uint8_t RPad; ssize_t LPad;
THohhaPacketHeader *PacketHeader = (THohhaPacketHeader *)OutBuf; uint8_t *OBufStart = OutBuf + HHLEN; SetHeaderAlignedLenValue((THohhaPacketHeader *)OutBuf, AlignedDataLen); PacketHeader->Padding = (uint8_t)(AlignedDataLen-InDataLen); RPad = PacketHeader->Padding >> 1; LPad = PacketHeader->Padding - RPad;
// First, let's create a new salt value and its padding data, unique for this transmission and copy original salt data to a buffer GetRandomNumbers(SALT_SIZE+HEADER_SALT_PADDING_SIZE, (uint8_t *)&(PacketHeader->SaltProtectionPadding)); // Fill padding data if necessary if (LPad) { GetRandomNumbersForPadding(LPad, OBufStart); // Then, we put right padding characters if necessary if (RPad) GetRandomNumbersForPadding(RPad, OBufStart + LPad + InDataLen); }
// Now, let's copy our plaintext to new packet memcpy(OBufStart + LPad, InBuf, InDataLen);
// Now, let's encrypt our data PacketHeader->PlaintextCRC = htole32(EncryptorFnc(K, PacketHeader->Salt, KeyCheckSum, AlignedDataLen, OBufStart)); //printf("PacketHeader->PlaintextCRC: %u\n",PacketHeader->PlaintextCRC); // We encrypted our packet. Now, let's encrypt packet header with original salt and key. But we don't encrypt header's first byte(AlignedLenSize) EncryptorFnc(K, OriginalSalt, KeyCheckSum, HHLEN-(1+AlignedLenSize), OutBuf+1); // Set encrypted flag to TRUE *OutBuf |= 128; }
uint8_t *CreateHohhaCommunicationPacket(uint8_t *K, uint32_t KeyCheckSum, size_t InDataLen, uint8_t *InBuf, uint32_t DataAlignment) { // This function encrypts InBuf and creates a communication packet with a proper header // Allocates and returns encrypted packet data with size equal to HOHHA_TOTAL_COMM_PACKET_SIZE(DataSize) // If DoNotEncrypt is true, data will not be encrypted and copied into the packet as plaintext if (!(DataAlignment == 8 || DataAlignment == 16 || DataAlignment == 32 || DataAlignment == 64)) { //printf("INVALID DATAALIGNMENT: %d\n", DataAlignment); return NULL; }
uint8_t *OutBuf = malloc(HOHHA_TOTAL_COMM_PACKET_SIZE(InDataLen, DataAlignment));
if (OutBuf) CreateHohhaCommunicationPacket2(K, KeyCheckSum, InDataLen, InBuf, DataAlignment, OutBuf); return OutBuf; }
I believe this algorithm is the future of encryption. It may not be perfect. However, I believe, this "dynamic key" model is the right way for encryption security. This project is an open source projec, thus public property, and I believe we all can benefit greatly from it. By Open Sourcing this code, I hope to make it faster and stronger together.
Please feel free to test it and share your success or faux-pas: ikizir@gmail.com