View more

View more

View more

### Image of the Day Submit

IOTD | Top Screenshots

### The latest, straight to your Inbox.

Subscribe to GameDev.net Direct to receive the latest updates and exclusive content.

# PinWheel Encryption

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

20 replies to this topic

### #1LarryKing  Members

Posted 25 November 2013 - 09:47 PM

Edit: See the newest version in this post

New phases have been added: Turbulence and Avalanche.

--

Hello everybody, I'm looking for some feedback on an encryption technique/algorithm I've been working on for the past few days: PinWheel (PNWL) encryption.
Now, I've found that explaining how the technique works is a challenge in and of itself, so please bear with me – I've even included lots of pictures.
Before I get to how the algorithm works, here are some statistics:

• Operates on 256 Byte Blocks
• Makes heavy use of XOR
• “Spins” the data to achieve encryption
• Strength of encryption is exponentially proportional to the password length

Essentially PNWL works by splitting up 256 bytes of data into sized blocks. Sort of like this:

Thus, one block of 256 bytes (the main block) contains four blocks of 64 bytes; each of these blocks contains four blocks of 16 bytes, and similarly each of these blocks contain four blocks of 4 bytes.

To encrypt the data each block's content is quartered and then spun clockwise. As the quartered block spins, its content is internally XOR'd.

This hierarchy of spins is repeated for each character in the password. Furthermore, the magnitude of each spin is determined by the respective char.

The only exception to the “Spin” technique are the Block4's, which instead “roll.” The amount of roll is determined by a set of magic numbers:

MAGIC[4][4] = { { 1, 3, 5, 7}, { 1, 7, 2, 9}, { 2, 3, 5, 7}, { 1, 9, 9, 6} };

To encrypt:

For each character in the password:

• Roll Block4's Left
• Spin Bloc16's
• Spin Block64's
• Spin the Block256

To decrypt:

Reverse the password, and then for each character:

• Spin the Block256 in reverse
• Spin the Block64's in reverse
• Spin the Block16's in reverse
• Roll Block4's Right

Anyways enough talk; here's the code, which also attached (note: requires SSE3)

//Copyright (C) 2013 Laurence King
//
//Permission is hereby granted, free of charge, to any person obtaining a
//copy of this software and associated documentation files (the "Software"),
//to deal in the Software without restriction, including without limitation
//the rights to use, copy, modify, merge, publish, distribute, sublicense,
//and/or sell copies of the Software, and to permit persons to whom the
//Software is furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included
//in all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
//PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
//OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
//SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#pragma once

#include <intrin.h>

#ifdef _MSC_VER
#define ALIGN( n )	__declspec( align( n ) )
#else
#define ALIGN( n ) alignas( n )
#endif

namespace PinWheel
{
typedef				int int32;
typedef unsigned	int uint32;

// PNWL Magic constants
const uint32 MAGIC[4][4] = { { 1, 3, 5, 7}, { 1, 7, 2, 9}, { 2, 3, 5, 7}, { 1, 9, 9, 6} };

ALIGN(16)
struct Block16
{
union
{
uint32		Data[4];
__m128i		vData;
};

void Spin0 (void);
void Spin1 (void);
void Spin2 (void);
void Spin3 (void);

void rSpin0 (void);
void rSpin1 (void);
void rSpin2 (void);
void rSpin3 (void);
};

ALIGN(16)
struct Block64
{
union
{
uint32		_Data[16];
Block16		Blocks[4];
__m128i		vData [4];
};

void Spin0 (void);
void Spin1 (void);
void Spin2 (void);
void Spin3 (void);

void rSpin0 (void);
void rSpin1 (void);
void rSpin2 (void);
void rSpin3 (void);

};

ALIGN(16)
struct Block256
{
union
{
uint32		_Data[64];
__m128i		_vData[16];
Block16		_Block16[16];
Block64		Blocks[4];
};

void Spin0 (void);
void Spin1 (void);
void Spin2 (void);
void Spin3 (void);

void rSpin0 (void);
void rSpin1 (void);
void rSpin2 (void);
void rSpin3 (void);

void Forward(const char *);
void Reverse(const char *);
};

#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
#define ROTATE_RIGHT(x, n) (((x) >> (n)) | ((x) << (32-(n))))

void Block16::Spin0(void)
{
Data[3] ^= Data[0];
Data[2] ^= Data[0];
Data[1] ^= Data[0];
Data[0] = ~Data[0];
}
void Block16::Spin1(void)
{
Data[3] ^= Data[0];

vData = _mm_shuffle_epi32(vData, _MM_SHUFFLE(2, 1, 0, 3));
}
void Block16::Spin2(void)
{
Data[3] ^= Data[0];
Data[2] ^= Data[0];
Data[0] = ~Data[0];

vData = _mm_shuffle_epi32(vData, _MM_SHUFFLE(1, 0, 3, 2));
}
void Block16::Spin3(void)
{
Data[3] ^= Data[0];
Data[2] ^= Data[0];
Data[1] ^= Data[0];

vData = _mm_shuffle_epi32(vData, _MM_SHUFFLE(0, 3, 2, 1));
}
void Block16::rSpin0(void)
{
Data[0] = ~Data[0];
Data[1] ^= Data[0];
Data[2] ^= Data[0];
Data[3] ^= Data[0];
}
void Block16::rSpin1(void)
{
vData = _mm_shuffle_epi32(vData, _MM_SHUFFLE(0, 3, 2, 1));

Data[3] ^= Data[0];
}
void Block16::rSpin2(void)
{
vData = _mm_shuffle_epi32(vData, _MM_SHUFFLE(1, 0, 3, 2));

Data[0] = ~Data[0];
Data[2] ^= Data[0];
Data[3] ^= Data[0];
}
void Block16::rSpin3(void)
{
vData = _mm_shuffle_epi32(vData, _MM_SHUFFLE(2, 1, 0, 3));

Data[3] ^= Data[0];
Data[2] ^= Data[0];
Data[1] ^= Data[0];
}

void Block64::Spin0(void)
{
vData[3] = _mm_xor_si128(vData[0], vData[3]);
vData[2] = _mm_xor_si128(vData[0], vData[2]);
vData[1] = _mm_xor_si128(vData[0], vData[1]);
}
void Block64::Spin1(void)
{
__m128i val_a = vData[0];

vData[0] = _mm_xor_si128(vData[3], val_a);
vData[3] = vData[2]; // _mm_xor_si128(vData[2], val_a);
vData[2] = vData[1]; // _mm_xor_si128(vData[1], val_a);
vData[1] = val_a;
}
void Block64::Spin2(void)
{
__m128i val_ab = vData[0];

vData[0] = _mm_xor_si128(vData[2], val_ab);
vData[2] = val_ab;

val_ab = vData[1];

vData[1] = _mm_xor_si128(vData[3], val_ab);
vData[3] = val_ab;
}
void Block64::Spin3(void)
{
__m128i val_a = vData[0];

vData[0] = _mm_xor_si128(vData[1], val_a);
vData[1] = _mm_xor_si128(vData[2], val_a);
vData[2] = _mm_xor_si128(vData[3], val_a);
vData[3] = val_a;
}
void Block64::rSpin0(void)
{
vData[3] = _mm_xor_si128(vData[0], vData[3]);
vData[2] = _mm_xor_si128(vData[0], vData[2]);
vData[1] = _mm_xor_si128(vData[0], vData[1]);
}
void Block64::rSpin1(void)
{
__m128i val_a = vData[1];

vData[1] = vData[2];
vData[2] = vData[3];
vData[3] = _mm_xor_si128(vData[0], val_a);
vData[0] = val_a;
}
void Block64::rSpin2(void)
{
__m128i val_ab = vData[2];

vData[2] = _mm_xor_si128(vData[0], val_ab);
vData[0] = val_ab;

val_ab = vData[3];

vData[3] = _mm_xor_si128(vData[1], val_ab);
vData[1] = val_ab;
}
void Block64::rSpin3(void)
{
__m128i val_a = vData[3];

vData[3] = _mm_xor_si128(vData[2], val_a);
vData[2] = _mm_xor_si128(vData[1], val_a);
vData[1] = _mm_xor_si128(vData[0], val_a);
vData[0] = val_a;
}

void Block256::Spin0(void)
{
_vData[0x4] = _mm_xor_si128(_vData[0x0], _vData[0x4]);
_vData[0x8] = _mm_xor_si128(_vData[0x0], _vData[0x8]);
_vData[0xC] = _mm_xor_si128(_vData[0x0], _vData[0xC]);

_vData[0x5] = _mm_xor_si128(_vData[0x1], _vData[0x5]);
_vData[0x9] = _mm_xor_si128(_vData[0x1], _vData[0x9]);
_vData[0xD] = _mm_xor_si128(_vData[0x1], _vData[0xD]);

_vData[0x6] = _mm_xor_si128(_vData[0x2], _vData[0x6]);
_vData[0xA] = _mm_xor_si128(_vData[0x2], _vData[0xA]);
_vData[0xE] = _mm_xor_si128(_vData[0x2], _vData[0xE]);

_vData[0x7] = _mm_xor_si128(_vData[0x3], _vData[0x7]);
_vData[0xB] = _mm_xor_si128(_vData[0x3], _vData[0xB]);
_vData[0xF] = _mm_xor_si128(_vData[0x3], _vData[0xF]);
}
void Block256::Spin1(void)
{
__m128i val_ = _vData[0];

_vData[0x0] = _mm_xor_si128(val_, _vData[0xC]);
_vData[0xC] = _vData[0x8];
_vData[0x8] = _vData[0x4];
_vData[0x4] = val_;

val_ = _vData[1];

_vData[0x1] = _mm_xor_si128(val_, _vData[0xD]);
_vData[0xD] = _vData[0x9];
_vData[0x9] = _vData[0x5];
_vData[0x5] = val_;

val_ = _vData[2];

_vData[0x2] = _mm_xor_si128(val_, _vData[0xE]);
_vData[0xE] = _vData[0xA];
_vData[0xA] = _vData[0x6];
_vData[0x6] = val_;

val_ = _vData[3];

_vData[0x3] = _mm_xor_si128(val_, _vData[0xF]);
_vData[0xF] = _vData[0xB];
_vData[0xB] = _vData[0x7];
_vData[0x7] = val_;
}
void Block256::Spin2(void)
{
__m128i val_ = _vData[0];

_vData[0x0] = _mm_xor_si128(val_, _vData[0x8]);
_vData[0x8] = val_;

val_ = _vData[1];

_vData[0x1] = _mm_xor_si128(val_, _vData[0x9]);
_vData[0x9] = val_;

val_ = _vData[2];

_vData[0x2] = _mm_xor_si128(val_, _vData[0xA]);
_vData[0xA] = val_;

val_ = _vData[3];

_vData[0x3] = _mm_xor_si128(val_, _vData[0xB]);
_vData[0xB] = val_;

val_ = _vData[4];

_vData[0x4] = _mm_xor_si128(_vData[0x8], _vData[0xC]);
_vData[0xC] = val_;

val_ = _vData[5];

_vData[0x5] = _mm_xor_si128(_vData[0x9], _vData[0xD]);
_vData[0xD] = val_;

val_ = _vData[6];

_vData[0x6] = _mm_xor_si128(_vData[0xA], _vData[0xE]);
_vData[0xE] = val_;

val_ = _vData[7];

_vData[0x7] = _mm_xor_si128(_vData[0xB], _vData[0xF]);
_vData[0xF] = val_;

}
void Block256::Spin3(void)
{
__m128i val_ = _vData[0];

_vData[0x0] = _mm_xor_si128(val_, _vData[0x4]);
_vData[0x4] = _mm_xor_si128(val_, _vData[0x8]);
_vData[0x8] = _mm_xor_si128(val_, _vData[0xC]);
_vData[0xC] = val_;

val_ = _vData[1];

_vData[0x1] = _mm_xor_si128(val_, _vData[0x5]);
_vData[0x5] = _mm_xor_si128(val_, _vData[0x9]);
_vData[0x9] = _mm_xor_si128(val_, _vData[0xD]);
_vData[0xD] = val_;

val_ = _vData[2];

_vData[0x2] = _mm_xor_si128(val_, _vData[0x6]);
_vData[0x6] = _mm_xor_si128(val_, _vData[0xA]);
_vData[0xA] = _mm_xor_si128(val_, _vData[0xE]);
_vData[0xE] = val_;

val_ = _vData[3];

_vData[0x3] = _mm_xor_si128(val_, _vData[0x7]);
_vData[0x7] = _mm_xor_si128(val_, _vData[0xB]);
_vData[0xB] = _mm_xor_si128(val_, _vData[0xF]);
_vData[0xF] = val_;
}
void Block256::rSpin0(void)
{
_vData[0x4] = _mm_xor_si128(_vData[0x0], _vData[0x4]);
_vData[0x8] = _mm_xor_si128(_vData[0x0], _vData[0x8]);
_vData[0xC] = _mm_xor_si128(_vData[0x0], _vData[0xC]);

_vData[0x5] = _mm_xor_si128(_vData[0x1], _vData[0x5]);
_vData[0x9] = _mm_xor_si128(_vData[0x1], _vData[0x9]);
_vData[0xD] = _mm_xor_si128(_vData[0x1], _vData[0xD]);

_vData[0x6] = _mm_xor_si128(_vData[0x2], _vData[0x6]);
_vData[0xA] = _mm_xor_si128(_vData[0x2], _vData[0xA]);
_vData[0xE] = _mm_xor_si128(_vData[0x2], _vData[0xE]);

_vData[0x7] = _mm_xor_si128(_vData[0x3], _vData[0x7]);
_vData[0xB] = _mm_xor_si128(_vData[0x3], _vData[0xB]);
_vData[0xF] = _mm_xor_si128(_vData[0x3], _vData[0xF]);
}
void Block256::rSpin1(void)
{
__m128i val_ = _vData[4];

_vData[0x4] = _vData[0x8];
_vData[0x8] = _vData[0xC];
_vData[0xC] = _mm_xor_si128(val_, _vData[0x0]);
_vData[0x0] = val_;

val_ = _vData[5];

_vData[0x5] = _vData[0x9];
_vData[0x9] = _vData[0xD];
_vData[0xD] = _mm_xor_si128(val_, _vData[0x1]);
_vData[0x1] = val_;

val_ = _vData[6];

_vData[0x6] = _vData[0xA];
_vData[0xA] = _vData[0xE];
_vData[0xE] = _mm_xor_si128(val_, _vData[0x2]);
_vData[0x2] = val_;

val_ = _vData[7];

_vData[0x7] = _vData[0xB];
_vData[0xB] = _vData[0xF];
_vData[0xF] = _mm_xor_si128(val_, _vData[0x3]);
_vData[0x3] = val_;
}
void Block256::rSpin2(void)
{
__m128i val_ = _vData[8];

_vData[0x8] = _mm_xor_si128(val_, _vData[0x0]);
_vData[0x0] = val_;

val_ = _vData[9];

_vData[0x9] = _mm_xor_si128(val_, _vData[0x1]);
_vData[0x1] = val_;

val_ = _vData[0xA];

_vData[0xA] = _mm_xor_si128(val_, _vData[0x2]);
_vData[0x2] = val_;

val_ = _vData[0xB];

_vData[0xB] = _mm_xor_si128(val_, _vData[0x3]);
_vData[0x3] = val_;

val_ = _vData[0xC];

_vData[0xC] = _mm_xor_si128(_vData[0x0], _vData[0x4]);
_vData[0x4] = val_;

val_ = _vData[0xD];

_vData[0xD] = _mm_xor_si128(_vData[0x1], _vData[0x5]);
_vData[0x5] = val_;

val_ = _vData[0xE];

_vData[0xE] = _mm_xor_si128(_vData[0x2], _vData[0x6]);
_vData[0x6] = val_;

val_ = _vData[0xF];

_vData[0xF] = _mm_xor_si128(_vData[0x3], _vData[0x7]);
_vData[0x7] = val_;

}
void Block256::rSpin3(void)
{
__m128i val_ = _vData[0xC];

_vData[0xC] = _mm_xor_si128(val_, _vData[0x8]);
_vData[0x8] = _mm_xor_si128(val_, _vData[0x4]);
_vData[0x4] = _mm_xor_si128(val_, _vData[0x0]);
_vData[0x0] = val_;

val_ = _vData[0xD];

_vData[0xD] = _mm_xor_si128(val_, _vData[0x9]);
_vData[0x9] = _mm_xor_si128(val_, _vData[0x5]);
_vData[0x5] = _mm_xor_si128(val_, _vData[0x1]);
_vData[0x1] = val_;

val_ = _vData[0xE];

_vData[0xE] = _mm_xor_si128(val_, _vData[0xA]);
_vData[0xA] = _mm_xor_si128(val_, _vData[0x6]);
_vData[0x6] = _mm_xor_si128(val_, _vData[0x2]);
_vData[0x2] = val_;

val_ = _vData[0xF];

_vData[0xF] = _mm_xor_si128(val_, _vData[0xB]);
_vData[0xB] = _mm_xor_si128(val_, _vData[0x7]);
_vData[0x7] = _mm_xor_si128(val_, _vData[0x3]);
_vData[0x3] = val_;
}

void Block256::Forward(const char * key)
{
for(char c = *(key++); c != 0; c = *(key++))
{
uint32 amnt0 =	c & PNWL_MASK1;
uint32 amnt1 = (c & PNWL_MASK2 ) >> 2;
uint32 amnt2 = (c & PNWL_MASK3 ) >> 4;
uint32 amnt3 = (c & PNWL_MASK4 ) >> 6;

#pragma region BLOCK4

for(int i = 0; i < 64; i+=4)
{
_Data[i] =		ROTATE_LEFT( _Data[i] ,		MAGIC[amnt3][0] );
_Data[i + 1] =	ROTATE_LEFT( _Data[i + 1] ,	MAGIC[amnt3][1] );
_Data[i + 2] =	ROTATE_LEFT( _Data[i + 2] ,	MAGIC[amnt3][2] );
_Data[i + 3] =	ROTATE_LEFT( _Data[i + 3] ,	MAGIC[amnt3][3] );
}
#pragma endregion

#pragma region BLOCK16
switch (amnt0)
{
case 0:
for(int i = 0; i < 16; i++)
_Block16[i].Spin0();
break;
case 1:
for(int i = 0; i < 16; i++)
_Block16[i].Spin1();
break;
case 2:
for(int i = 0; i < 16; i++)
_Block16[i].Spin2();
break;
case 3:
for(int i = 0; i < 16; i++)
_Block16[i].Spin3();
break;
}
#pragma endregion

#pragma region BLOCK64
switch (amnt1)
{
case 0:
Blocks[0].Spin0();
Blocks[1].Spin0();
Blocks[2].Spin0();
Blocks[3].Spin0();
break;
case 1:
Blocks[0].Spin1();
Blocks[1].Spin1();
Blocks[2].Spin1();
Blocks[3].Spin1();
break;
case 2:
Blocks[0].Spin2();
Blocks[1].Spin2();
Blocks[2].Spin2();
Blocks[3].Spin2();
break;
case 3:
Blocks[0].Spin3();
Blocks[1].Spin3();
Blocks[2].Spin3();
Blocks[3].Spin3();
break;
}
#pragma endregion

#pragma region BLOCK256
switch (amnt2)
{
case 0:
Spin0();
break;
case 1:
Spin1();
break;
case 2:
Spin2();
break;
case 3:
Spin3();
break;
}
#pragma endregion

}

}

// Expects the key to already have been reversed
void Block256::Reverse(const char * rKey)
{
for(char c = *(rKey++); c != 0; c = *(rKey++))
{
uint32 amnt0 =	c & PNWL_MASK1;
uint32 amnt1 = (c & PNWL_MASK2 ) >> 2;
uint32 amnt2 = (c & PNWL_MASK3 ) >> 4;
uint32 amnt3 = (c & PNWL_MASK4 ) >> 6;

#pragma region BLOCK256
switch (amnt2)
{
case 0:
rSpin0();
break;
case 1:
rSpin1();
break;
case 2:
rSpin2();
break;
case 3:
rSpin3();
break;
}
#pragma endregion

#pragma region BLOCK64
switch (amnt1)
{
case 0:
Blocks[0].rSpin0();
Blocks[1].rSpin0();
Blocks[2].rSpin0();
Blocks[3].rSpin0();
break;
case 1:
Blocks[0].rSpin1();
Blocks[1].rSpin1();
Blocks[2].rSpin1();
Blocks[3].rSpin1();
break;
case 2:
Blocks[0].rSpin2();
Blocks[1].rSpin2();
Blocks[2].rSpin2();
Blocks[3].rSpin2();
break;
case 3:
Blocks[0].rSpin3();
Blocks[1].rSpin3();
Blocks[2].rSpin3();
Blocks[3].rSpin3();
break;
}
#pragma endregion

#pragma region BLOCK16
switch (amnt0)
{
case 0:
for(int i = 0; i < 16; i++)
_Block16[i].rSpin0();
break;
case 1:
for(int i = 0; i < 16; i++)
_Block16[i].rSpin1();
break;
case 2:
for(int i = 0; i < 16; i++)
_Block16[i].rSpin2();
break;
case 3:
for(int i = 0; i < 16; i++)
_Block16[i].rSpin3();
break;
}
#pragma endregion

#pragma region BLOCK4
for(int i = 0; i < 64; i+=4)
{
_Data[i] =		ROTATE_RIGHT( _Data[i] ,		MAGIC[amnt3][0] );
_Data[i + 1] =	ROTATE_RIGHT( _Data[i + 1] ,	MAGIC[amnt3][1] );
_Data[i + 2] =	ROTATE_RIGHT( _Data[i + 2] ,	MAGIC[amnt3][2] );
_Data[i + 3] =	ROTATE_RIGHT( _Data[i + 3] ,	MAGIC[amnt3][3] );
}
#pragma endregion

}
}
}


And here is how you would encrypt some data:

PinWheel::Block256 * blocks = reinterpret_cast<PinWheel::Block256 *>(memblock);

for(int i = 0; i < blockcount; i++)
{
}


Now for some visual examples of PNWL encryption in action:
(For illustration purposes, these were created by encrypting the image portion of either 24bpp bitmaps or grayscale bitmaps)

Mona:

Spoiler

Simple Triangles:

Spoiler

Flower ( grayscale bitmap)

Spoiler

Where I can see improvement: PNWL was designed to make use of SIMD commands, however it can be done without them.

I don't have a processor that supports AVX2, but I predict a 30% boost if it was used, for example, on the Roll portion. Furthermore, multithreading could yield excellent returns

Attached is the source code for PNWL and a quick console app to test it out.

Thank you

#### Attached Files

Edited by LarryKing, 28 November 2013 - 12:12 PM.

### #2TheComet  Members

Posted 26 November 2013 - 10:29 AM

I'm no encryption expert, but I'd like to throw in my thoughts.

1) It would make sense to hash the password before using it to encrypt the data. This way, there will firstly be no significant "visual" differences between weak, medium and strong passwords, and secondly it won't be possible to directly extract the password if you had access to the normal and encrypted versions of the file.

2) I flew over the code, and it seems in order, though a little lacking in comments. The way to use it isn't self explanatory, perhaps it would make sense to wrap it into a class to provide a more comprehensible front end?

3) I'm trying to see the point of doing it this way as opposed to just XORing everything with a hashed password (which would be faster because there are less operations to perform). I honestly don't see the benefit of spinwheeling everything.

In the case of consecutive data being equal, it's very easy to extract the hashed password from a simple XOR operation whereas your method obfuscates that a little, so maybe that's a plus? However, I feel replacing all of the spinning operations for a simple and fast compression algorithm such as RLE followed by XORing everything would yield higher security than spinwheeling it.

Edited by TheComet, 26 November 2013 - 10:29 AM.

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

### #3aregee  Members

Posted 26 November 2013 - 12:58 PM

I am not any expert in cryptography either, but I just want to toss in an immediate thought I have from looking at the resulted encryption from the original pictures.

If you can see patterns and average colours or other hints in the result, then you are giving away information that can indicate weaknesses in your algorithm.

For instance: intuitively it seems like you can sort of do an average on colour values and get the same average as you have on the original picture.  On the picture with geometric figures, you can see lots of lines.  While it is not obvious what it is supposed to be in the encrypted versions of the pictures, it gives away information that can help in determining your algorithm to attack your encryption.

I can tell from the white noise picture that you are dealing with a black and white picture for instance.

I might be wrong about my assumptions, but it does give me a starting point as to figuring out your algorithm.  To me it sounds like The Comet's suggestion is a better one, or even generate a random seed from your password, and xor a random sequence from that seed over the picture.  To decode, just generate the same random seed and run the same random values and you will have the original data.

No need to say that all these suggestions are really weak encryption methods, since it is easy to break as long as you have the algorithm.  I have a feeling the same goes for your spinwheel algorithm too.

A small note to what "TheComet" said, that you could obfuscate a little bit to make things harder.  While that is true, obfuscating doesn't really do much to secure your data.  It is better to have a proper encryption scheme right from the start.  Read about congruences and products of massively large prime numbers to do proper encryption or use a library if you want really solid encryption.  It is advanced, but there are examples that are easy to follow how to do this with examples using small prime numbers.  The technique is the same as in "proper" encryption, but you won't achieve really good encryption unless you use massively large prime numbers.

Security in today's encryption algorithms lie in the fact that it is hard to find the factors from a composite number from two massively big prime numbers.  If you can find a way to factor such numbers quickly, you will render almost all encryption methods useless.

EDIT: Having a second look at your images, I see that my assumptions are not entirely right, but my point is still true:  the result should look entirely random, with no patterns or colour-similarities or other hints at all.  Just pure random data.

Edited by aregee, 26 November 2013 - 01:11 PM.

### #4ApochPiQ  Moderators

Posted 26 November 2013 - 01:52 PM

POPULAR

Your method shows far too much correlation between input and output data. Secondly, it exhibits painfully strong correlation between the password "strength" and the final encrypted data - which is a massive flaw if you want this used in general-purpose encryption.

As has been said, your output should utilize the full entropy space available to it - i.e. a black and white image, encrypted, should still come out as full color noise, where pixels can take any color channel value from 0-255 on all channels. You're basically tightly coupling your input entropy to output entropy which means that numerical analysis methods can break your method almost trivially given even moderate knowledge of how the encryption algorithm works.

Keep in mind that good encryption methods can publish every last detail of how they work with near-zero risk of anyone using that knowledge to enhance their ability to break said encryption.
Wielder of the Sacred Wands

### #5wack  Members

Posted 26 November 2013 - 02:38 PM

What the above posters are saying is true. The least you should expect from an encryption algorithm is that the output should be indistinguishable from random noise.

Of course, having the output indistinguishable from random noise is not a guarantee of a secure algorithm, but the reverse is a sure indicator that it is not a secure algorithm.

It's pretty much the most basic test you can do to an encryption algorithm, and this does not pass.

### #6LarryKing  Members

Posted 26 November 2013 - 05:19 PM

Excellent, I wasn't expecting this much of a response. This is great

OK, so the way I see it, here are the main concerns:

2. Why bother spinning the data?
3. What are these patterns I see?
4. Black and white!
5. Correlation between password strength and quality.

So, in relative order:

1 : Yes! hashing the password would be a good idea. I could generate a large number ( lets call it X) of bytes from a password and then use this as the "key".

The question is how large should the hash be? It clearly should be larger than 12 bytes, 24 bytes maybe?

The number of possible combinations from spinning the data appears to be 256n where n is the number of characters in the key. Thus 25624 yeilds 6.27 x 1057 possible combinations. If we assume that a computer can evaluate 1 billion combinations per second, an attempt to try every single combination of 24 bytes, or brute force a password, could still take up to 1041 years.

2 : You guessed correctly when you said "to obfuscate the data." By performing these spins, a byte, or a variant there of, can end up literately anywhere within the block! On a smaller scale, a bit can end up anywhere within the block as-well, because remember we're rolling too! With 256 bytes, there are 2048 bits. There are, by definition, 2048! ways to organize 2048 bits, and by spinning and rolling we can achieve any of these combinations. In other-words, by spinning and rolling we set the maximum number of possibilities to 2048! or around 105895 possibilities.

The other reason to spin the blocks is to create multiple solutions, which sounds counter intuitive!

Just because you find a solution to a block, doesn't mean that it'll work for any other block.

So for example lets say that we have 256 bytes, where the first byte is 0 and the rest are 1. Next we have another 256 bytes where the last byte is 1 and the rest are 0. Finally we encrypt both of the data with the same password, say "boat", Now because we spun the data, there is a massive number of ways to spin the data in reverse to get the byte '1' back into the first slot, one of these ways is, obviously, to follow the pattern generated by the password "boat", however other pass-codes may work like say "hamburger" The trick is that even though the pass-code "hamburger" successfully decrypts the first set of 256 bytes, it WILL not decrypt the second set! Only the passcode "boat" will do that.

Furthermore, even if you had the original as well as the encrypted version of a file, you would still need to try MANY ( up to  25624 ) combinations of passcodes to find the one that works for every block and crack the password.

3 : Ah yes the patterns in the triangles... maybe I should have been more specific in my captions.

Of course there will be patterns, this is a worse case scenario test. The data in that image is LARGELY repetitive, in fact each row of 256 pixels, the width of the image, is usually close to identical to the row above it. Encrypting blocks that only have a 1 byte difference between one and another will unfortunately yield somewhat similar results. This effect is compounded by the fact that the change between rows is fairly regular. If I reduce the size of the image to 246 x 246 pixels and then encrypt with an 8 char password, we get this:

Which while not perfect, you can still somewhat see the triangles, looks more like noise.

To spice things up, and prevent these types of patterns, the amount of roll could be varied. Part of the issue is that most ASCII pass-codes limit the amount of roll as the high bit is usually 0.

4 : This one is kind of silly. It's black and white because it's a black and white bitmap! - Remember I only encrypted the image/data portion of each bitmap. No amount of encryption is going to change the fact that in a grayscale bitmap, each pixel is determined by one and only byte and must be somewhere between black and white.

Here's what it would have looked like, had it not been grayscale, but 24bpp (COLOR) with an 8 char password :

5 : The correlation between password length and quality was actually intentional. I figured that longer passwords yielding better encryption was ideal. After reading some of the feedback I've gotten, I now know this to be false. By hashing the passcodes ( See # 1 ) we can remove this correlation.

Sorry if my explanations haven't been the best.

Again, thank you everybody who has spoken thus far. Any other ideas and comments would be appreciated.

Edited by LarryKing, 26 November 2013 - 05:29 PM.

### #7Bacterius  Members

Posted 26 November 2013 - 07:02 PM

For those of us under Linux, this is how you build:

1. remove Windows.h includes

2. replace "intrin.h" with "emmintrin.h"

3. Fix the alignment stuff by changing it to __attribute__((align)) and moving them after the "struct" definition, i.e:

#define ALIGN( n ) __attribute__((align(n)))

struct Block16
{
// ...
} ALIGN(16); // <-- here


I encrypted the all-zeroes file and unfortunately the algorithm only produced 0xFF's and 0x00's in regular patterns. Your algorithm is certainly fast but that is enough to show that it is no good at all as an encryption algorithm, as it does not meet the security properties of one. Don't take it badly though, one famous cryptographer said once that "it is easy to create a cipher that you yourself cannot break". Also, just to make sure: it's really fun to design ciphers, but please never use them for real stuff outside the lab. It's not worth it.

Of course there will be patterns, this is a worse case scenario test. The data in that image is LARGELY repetitive, in fact each row of 256 pixels, the width of the image, is usually close to identical to the row above it. Encrypting blocks that only have a 1 byte difference between one and another will unfortunately yield somewhat similar results. This effect is compounded by the fact that the change between rows is fairly regular. If I reduce the size of the image to 246 x 246 pixels and then encrypt with an 8 char password, we get this:

That's not good enough, sadly. For any input, the output should look completely like random noise, and for no two different inputs should the outputs be correlated (over any possible key). A strong encryption algorithm uses the principles of diffusion and confusion to destroy all structure in the input (while still making it possible to reverse with the right key). Take a look at SPN's (substitution permutation networks) to see how this can be achieved effectively, you could borrow some of this.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

### #8TheComet  Members

Posted 27 November 2013 - 03:59 AM

Sorry to go a little off topic here, but isn't there a flaw in the way 7zip encrypts its data?

If the program knows the password is incorrect before decrypting and decompressing the data, then it means the hashed password is easily accessible - perhaps in the file header? Shouldn't the data be decrypted and decompressed regardless if the password is correct or not, the only difference being an incorrect password will produce a huge pile of garbage? Can someone explain this?

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

### #9Bacterius  Members

Posted 27 November 2013 - 04:06 AM

If the program knows the password is incorrect before decrypting and decompressing the data, then it means the hashed password is easily accessible - perhaps in the file header? Shouldn't the data be decrypted and decompressed regardless if the password is correct or not, the only difference being an incorrect password will produce a huge pile of garbage? Can someone explain this?

Sure, the hashed password is available. But you can't use it for decryption. Basically it goes like this:



+----------------------------------------------+              +-------------------------------+
|                                              | ONE WAY ONLY |                               |
| Plain password (typed by the user)           | +----------> | Encryption key                |
|                                              |              |                               |
+----------------------------------------------+              +-------------------------------+
+
|
| ONE WAY ONLY
|
|
v
+----------------------------------------------+
|                                              |
| Hashed password (useful only for comparison) |
|                                              |
+----------------------------------------------+


The two are unrelated and you can't deduce one from the other. So the hashed password is used to find out if you typed the right password, and then when you did, the program can calculate the encryption key and decrypt the program. That way it can warn you if you typed the wrong password without decrypting garbage, without any security issues. So technically the hashed password isn't "needed", it's a quality of life thing to guarantee that you don't end up decrypting garbage, and also lets you check the key *without* decrypting anything (which is relevant in some cases).

You can also do more advanced stuff with this such as making sure the file was not compromised (modified surreptitiously) etc.. basically it's a useful technique, and does not indicate a security flaw

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

### #10LarryKing  Members

Posted 27 November 2013 - 03:40 PM

So, I've made a few changes that seem to fix the two biggest issues:

I've introduced a "Turbulence" phase to combat patterns,

and I've also added an "Avalanche" phase to increase sensitivity:

The results:

A difference of a single bit will result in dramatic variation.

Small test: A 256 byte file was created with all bytes = 0xFF and a second, similar file was created where all bytes = 0xFF except for the last byte which = 0xFE

Here are the visualizations of the two files: (Note they are 16 x 16 pixels and are quite difficult to see on a white background)

File A :

File B :

Both files were encrypted using the same passcode:

Result A :

Result B :

Both results appear as noise; however each is different.

And finally we have the "worst case scenario" triangle image/data test:

Please note that the vertical lines are the result of blocks of data being EXACTLY the same in the original data.

As in one row of pixels being exactly the same as another:

Note: Each row of pixels in the solid green block is exactly the same, as is the case wherever else there are vertical lines; encrypting identical datas returns identical results.

In other words, please don't point out the "clear pattern of vertical lines"

Now, adding both the turbulence and avalanche phases to the algorithm results in a ~ 40% increase in computation time.

The new code is attached ( PNWL.h )

Also, I'm still looking for a good passcode hashing function.

If you feel like testing the new algorithm, use a longer password, or better yet, to emulate a hashcode, generate a 24 byte null terminated string  randomly and use that.

Again thank you.

Edit: Forgot to attach the new code XD

#### Attached Files

Edited by LarryKing, 27 November 2013 - 04:04 PM.

### #11Vortez  Members

Posted 28 November 2013 - 04:34 AM

encrypting identical datas returns identical results.

Well, i know you asked not to point it out, buuut... i think that's the biggest flaw in your algorithm. Encrypting identical data should NOT return identical value...

Anyway, i guess that code is just for your personal usage so, who care. No one is probably gonna try to break your code, unless you ask them to.

Imo that's good enough for personal use.

My first encryptor was showing a similar flaw (you could see parts of a bitmap image in the noisy background), then i just created another algorithm, which isn't perfect, but at least give no clue as in your image, everything always look 100% noisy, but even then, ppl started pointing flaws about it. (You definitely should read this post)

For me, i guess that just good enough for my need, althrough i never used it yet for anything, it was more for learning than anything else.

For the fun of it, i encrypted your image using my algo. discussed above, and it gave me this. That's what you should aim.

PS: dont take me too seriously, im no encryption expert...

Edited by Vortez, 28 November 2013 - 05:41 AM.

### #12wintertime  Members

Posted 28 November 2013 - 07:08 AM

By performing these spins, a byte, or a variant there of, can end up literately anywhere within the block! On a smaller scale, a bit can end up anywhere within the block as-well, because remember we're rolling too!

There is the problem, it is not enough if the set of all passwords allows any bit to appear anywhere. You would need to make sure that for a single password the bits are shuffled around enough that they can be anywhere, but as can be seen from your pictures thats not happening. I would think that is because of an inherent flaw of your pinwheel thing, that moves the bits in a very predictable way that mostly depends on the source data and not much on the password.

And you know, xor obfuscation is the first thing everyone tries, but cause of its property of revealing a source bit by double xor-ing it with the same bit it will never make a good encryption in that way you use it, even if your method is a bit more elaborate than usually seen.

### #13TheComet  Members

Posted 28 November 2013 - 07:15 AM

its property of revealing a source bit by double xor-ing it with the same bit it will never make a good encryption in that way you use it

How does it reveal a source bit if you only have the XORd bit?

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

### #14wintertime  Members

Posted 28 November 2013 - 08:21 AM

(a xor x) xor x == a

### #15LarryKing  Members

Posted 28 November 2013 - 08:22 AM

encrypting identical datas returns identical results.

Well, i know you asked not to point it out, buuut... i think that's the biggest flaw in your algorithm. Encrypting identical data should NOT return identical value...

Anyway, i guess that code is just for your personal usage so, who care. No one is probably gonna try to break your code, unless you ask them to.

Imo that's good enough for personal use.

My first encryptor was showing a similar flaw (you could see parts of a bitmap image in the noisy background), then i just created another algorithm, which isn't perfect, but at least give no clue as in your image, everything always look 100% noisy, but even then, ppl started pointing flaws about it. (You definitely should read this post)

For me, i guess that just good enough for my need, althrough i never used it yet for anything, it was more for learning than anything else.

For the fun of it, i encrypted your image using my algo. discussed above, and it gave me this. That's what you should aim.

PS: dont take me too seriously, im no encryption expert...

What I'm trying to say is that for a set of inputs - the passcode being X and the 256 byte block being  Y - the algorithm should satisfy the requirements of a "function" : for a given set of inputs the function should return a specific output

F(x, y) == z always

If two blocks are identical, Y1 == Y2, and they are being encrypted with the same pass code, then F(X, Y1) == F(X, Y2) == some value z

Our ability to decrypt the data, given the correct passcode, relies on this fact.

Now if I wanted two identical blocks encrypted using the same passcode to not return equivalent values, I would need to add another "variable" to the function.

So If I added the block's number as variable W so,

F(w, x, y) == z

then two identical blocks, being encrypted with the same pass code would not give identical results.

I tried this and the results were promising :

However, I decided that the algorithm shouldn't care or know which block it was dealing with, as an "attacker" could easily later determine this "variable" and use it to weaken the integrity of the system

I'd rather have only two inputs X and Y that can't be determined by looking at the encrypted data, than

three variable W, X and Y, where one of the variables could be determined.

By performing these spins, a byte, or a variant there of, can end up literately anywhere within the block! On a smaller scale, a bit can end up anywhere within the block as-well, because remember we're rolling too!

There is the problem, it is not enough if the set of all passwords allows any bit to appear anywhere. You would need to make sure that for a single password the bits are shuffled around enough that they can be anywhere, but as can be seen from your pictures thats not happening. I would think that is because of an inherent flaw of your pinwheel thing, that moves the bits in a very predictable way that mostly depends on the source data and not much on the password.

And you know, xor obfuscation is the first thing everyone tries, but cause of its property of revealing a source bit by double xor-ing it with the same bit it will never make a good encryption in that way you use it, even if your method is a bit more elaborate than usually seen.

A bit can take multiple paths to end up in the same spot. By taking these alternative paths though, the other 2047 bits also take alternative paths.

The movement, or spinning if you will, is determined only by the passcode, and there is only one combination of spins, only one passcode, that will decrypt all of the blocks.

Even if you know that the data was "spun", you don't know which path it took.

I imagine this path on a massive tree where each branch has 256 child branches, and each of those child branches has 256 child branches and so on.

The number of unique combination for a given password length n is 256n where hopefully n is some large number like 24

Also, there is more than XOR'ing going on, there is twisting, turning, and in the newer version, turbulence ( noise ) being added to the mix.

To decrypt, each byte must not only be XOR'd with every byte it had been, which would have to determined some how, but it must do so in the correct order as other operations such as rolling and the turbulence take place. In other words, you must follow your exact path up the tree to retrieve the original data.

Yes, this is only a pet-project, so it's not likely anyone other than myself will ever use it.

Still, I'm happy to see other people's ideas.

Edited by LarryKing, 28 November 2013 - 08:26 AM.

### #16TheComet  Members

Posted 28 November 2013 - 08:30 AM

(a xor x) xor x == a

Yeah but you don't have "a", you only have the encrypted data...

Edited by TheComet, 28 November 2013 - 08:30 AM.

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

### #17wintertime  Members

Posted 28 November 2013 - 08:55 AM

If a is a point in the above picture with the triangles, then there is a high chance its rotated and xored twice (or any multiple of 2 times) with the same color from one of the big same colored areas, then you have a as "encrypted" data although its actually not. (There are a few other things happening and then it results in those stripes.)

### #18TheComet  Members

Posted 28 November 2013 - 09:24 AM

Ah, I see your point now. Thanks for clearing that up!
"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

### #19LarryKing  Members

Posted 28 November 2013 - 12:10 PM

If a is a point in the above picture with the triangles, then there is a high chance its rotated and xored twice (or any multiple of 2 times) with the same color from one of the big same colored areas, then you have a as "encrypted" data although its actually not. (There are a few other things happening and then it results in those stripes.)

Impossible, each block is processed independently.

I think I wasn't clear in my description of what a block is.

A block is 256 bytes; the picture divided into 9 areas, does not show 9 blocks, but rather 768.

Maybe this will help with my explanation of what a "block" is

*Pretend I was able to draw 768 lines ( which each represent a block )

Sorry if I've been a bit hard to understand, my mind is focused on Thanksgiving

But, in the case where data is divided as such, and each block is independently encrypted, does this make sense?

### #20aregee  Members

Posted 29 November 2013 - 09:35 PM

(a xor x) xor x == a

Yeah but you don't have "a", you only have the encrypted data...

This situation is not so far fetched, if you just XOR password with data:

Source data:            010203040506000000000000000001020304050
Result: (XOR-encrypted) 71637077726a726470617373776f73667365727
Similarities:           7 6 7 7 7 6 726470617373776f7 6 7 6 7 7
Oops, password in clear text:       r d p a s s w o


So if you are going to use an encryption algorithm based on XOR, you need proper "random" data to XOR with.  Anything else will be exposed.